How to align embedded external svg file to the viewbox - javascript

I managed to insert an external svg file as "svg/xml", to my canvas. When I click the "download file" button, the file is loaded and is displayed, but I am not able to align it correctly to be inside the view box. It displays at the lower left corner outside the view box. I am not able to figure out the alignment issue. I want to display the "title" centered at the top before the root node.
var treeData = [{
"name": "Top Level",
"parent": "null",
"remark": "yes",
"children": [{
"name": "Level 2: A",
"parent": "Top Level",
"remark": "yes",
"children": [{
"name": "Son of A",
"parent": "Level 2: A",
"remark": "null"
},
{
"name": "Daughter of A",
"parent": "Level 2: A",
"remark": "null"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level",
"remark": "null"
}
]
}];
//*************************************************************************************
// 1. Create the button
var button = document.createElement("button");
button.innerHTML = "download file";
//*************************************************************************************
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
rectW = 100,
rectH = 30,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
//swap x and y for vertical
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
// .projection(function(d) { return [d.x, d.y]; });
});
var svgBgImg = document.createElementNS('http://www.w3.org/2000/svg', 'image');
svgBgImg.setAttributeNS(null, 'height', '100%');
svgBgImg.setAttributeNS(null, 'width', '100%');
svgBgImg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'http://localhost/images/background-F4EED7.svg');
svgBgImg.setAttributeNS(null, 'x', '0');
svgBgImg.setAttributeNS(null, 'y', '0');
svgBgImg.setAttributeNS(null, 'visibility', 'visible');
//var gElement = document.createElement('svg:g');
var gElement = document.createElementNS(d3.ns.prefix.svg, 'g');
gElement.setAttribute("id", "fg");
console.log(gElement);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append(function() {
return gElement;
}) //The argument to .append() has to be a function, you can't just pass element to it.
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
//swap x and y for vertical
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
//nodeEnter.append("circle")
// .attr("r", 1e-6)
.attr("width", rectW)
.attr("height", rectH)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Filter to put tooltip only on nodes that have information associated with it
var nodesWithInfo = nodeEnter.filter(function(d) {
return (d.remark != 'null')
})
nodesWithInfo
.append("circle")
.attr("export-ignore", true)
.attr("cx", 96)
.attr("cy", 7)
.attr("r", 10);
nodesWithInfo
.append("text")
.attr("export-ignore", true)
.attr("id", "infoText")
.attr("x", 96)
.attr("y", 7)
.attr("dy", ".30em")
.style("font-style", "italic")
.style("font-family", "serif")
.style("font-weight", "bold")
.text("i");
// Transition nodes to their new position.
//swap x and y for vertical layout
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
//nodeUpdate.select("circle")
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.style("stroke", "black")
// .attr("r", 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
//vertical switch x and y positions
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
//nodeExit.select("circle")
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.style("stroke", "black");
// .attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke", "#ccc")
.attr("fill", "none")
.attr("stroke-width", "2px")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
//*************************************************************************************
// 2. Append button somewhere
var body = document.getElementsByTagName("body")[0];
body.appendChild(button)
.setAttribute("transform", "translate(" + 0 + "," + 0 + ")");
//*************************************************************************************
function svgDataURL(svg) {
var svgAsXML = (new XMLSerializer).serializeToString(svg);
var dataURL = "data:image/svg+xml," + encodeURIComponent(svgAsXML);
return dataURL;
}
button.onclick = function() {
var groupElement = document.getElementById('fg');
const bb = groupElement.getBBox();
//load the title image
var titlePosX = bb.width / 6 + bb.x + 25; //added offeset of 25 to align to center as much as possible
var imageWidth = bb.width / 4;
var imageHeight = imageWidth * .33;
var newVboxH = bb.height + imageHeight + 10;
console.log('newVboxH ', newVboxH);
console.log('titlePosX ' + titlePosX + ' imageHeight ', +imageHeight + ' imageWidth ' + imageWidth);
groupElement.setAttribute("transform", "translate(0," + (imageHeight + 10) + ")scale(.995,.995)"); //scale down by .05% to fit nicely
svg.attr('viewBox', ' ' + bb.x + ' ' + bb.y + ' ' + bb.width + ' ' + newVboxH);
svg.attr("width", "100%");
svg.attr("height", "100%");
svg.attr("preserveAspectRatio", "xMidYMid meet");
//how to put the border around the cloned SVG element
var borderRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
borderRect.setAttribute("id", "bRect");
borderRect.setAttribute("width", bb.width);
borderRect.setAttribute("height", newVboxH);
borderRect.setAttribute("x", bb.x);
borderRect.setAttribute("y", bb.y);
borderRect.setAttribute("style", "fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9");
d3.xml("https://upload.wikimedia.org/wikipedia/commons/8/8c/SVG_logo_h.svg").mimeType("image/svg+xml").get(function(error, xml) {
if (error) throw error;
xml.documentElement.setAttribute("transform", "translate(0,0)scale(.995,.995)");
xml.documentElement.setAttribute("transform", "translate(0," + (imageHeight + 10) + ")scale(.995,.995)"); //scale down by .05% to fit nicely
xml.documentElement.setAttribute('width', imageWidth);
groupElement.appendChild(xml.documentElement);
groupElement.appendChild(borderRect);
// bring back to original position
groupElement.setAttribute("transform", 'translate(' + margin.left + ',' + margin.top + ')scale(.7)');
});
};
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1px;
}
.node text {
font: 12px sans-serif;
}
.link {
/*
fill: none;
stroke: #ccc;
stroke-width: 2px;*/
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
</head>

Related

how to append a circle with a label to indicate information to rectangle node and remove the circle before download

I have a vertical D3JS tree where the nodes are rectangles. I would like to append a circle with the italic letter "i" to the top right corner of the rectangle node to indicate that the node has additional information. I managed to append the italic letter "i" only, however, I am not able to put it in a circle and still keep the node clickable. Also I use cloning for downloading the tree as a SVG file and would like to remove this appended circle and the letter "i" from the cloned group element before downloading and printing.
var treeData = [{
"name": "Top Level",
"parent": "null",
"remark": "yes",
"children": [{
"name": "Level 2: A",
"parent": "Top Level",
"remark": "yes",
"children": [{
"name": "Son of A",
"parent": "Level 2: A",
"remark": "null"
},
{
"name": "Daughter of A",
"parent": "Level 2: A",
"remark": "null"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level",
"remark": "null"
}
]
}];
//*************************************************************************************
// 1. Create the button
var button = document.createElement("button");
button.innerHTML = "download file";
//*************************************************************************************
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
rectW = 100,
rectH = 30,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
//swap x and y for vertical
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
// .projection(function(d) { return [d.x, d.y]; });
});
//var gElement = document.createElement('svg:g');
var gElement = document.createElementNS(d3.ns.prefix.svg, 'g');
gElement.setAttribute("id", "fg");
console.log(gElement);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append(function() {
return gElement;
}) //The argument to .append() has to be a function, you can't just pass element to it.
// .append("svg:g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
//swap x and y for vertical
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
//nodeEnter.append("circle")
// .attr("r", 1e-6)
.attr("width", rectW)
.attr("height", rectH)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// IAH 20/01/2019 Filter to put tooltip only on nodes that have information associated with it
nodeEnter.filter(function(d) {
return (d.remark != 'null')
})
.append("text")
.attr("id", "infoText")
.attr("x", 96)
.attr("y", 7)
.attr("dy", ".30em")
.style("font-style", "italic")
.style("font-family", "serif")
.style("font-weight", "bold")
.text("i");
// Transition nodes to their new position.
//swap x and y for vertical layout
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
//nodeUpdate.select("circle")
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.style("stroke", "black")
// .attr("r", 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
//vertical switch x and y positions
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
//nodeExit.select("circle")
nodeExit.select("rect")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke", "#ccc")
.attr("fill", "none")
.attr("stroke-width", "2px")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
//*************************************************************************************
// 2. Append button somewhere
var body = document.getElementsByTagName("body")[0];
body.appendChild(button)
.setAttribute("transform", "translate(" + 0 + "," + 0 + ")");
//*************************************************************************************
function svgDataURL(svg) {
var svgAsXML = (new XMLSerializer).serializeToString(svg);
var dataURL = "data:image/svg+xml," + encodeURIComponent(svgAsXML);
return dataURL;
}
button.onclick = function() {
var svgElement = document.querySelector('svg');
var svgWH = svgElement.getBBox();
var canvasWidth = svgWH.width;
var canvasHeight = svgWH.height;
var groupElement = document.getElementById('fg');
groupElement.setAttribute("transform", "translate(0,0)");
const bb = groupElement.getBBox();
// clone the svg to avoid destroying it while appending to the svg namespace
let clonedGroupElement = groupElement.cloneNode(true);
var svgContent = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgContent.setAttribute('viewBox', ' ' + bb.x + ' ' + bb.y + ' ' + bb.width + ' ' + bb.height);
svgContent.setAttribute("width", "100%");
svgContent.setAttribute("height", "100%");
svgContent.setAttribute("preserveAspectRatio", "xMidYMid meet");
// try to remove the information icon before downloading
var rmText = document.getElementById('infoText');
console.log(rmText);
svgContent.appendChild(clonedGroupElement); // use the cloned nodes
var dl = document.createElement('a');
document.body.appendChild(dl);
dl.setAttribute("href", svgDataURL(svgContent)); // function svgDataURL expects a node
dl.setAttribute("download", "test.svg");
dl.click();
dl.remove();
svgContent.removeChild(clonedGroupElement);
};
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
/*
fill: none;
stroke: #ccc;
stroke-width: 2px;*/
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
I created a dummy attribute ignore-export and was able to use querySelectorAll() to select all elements with that attribute and remove them. Note that the node.remove() function is not supported in IE, but there is a polyfill available at MDN.
I didn't have any problems with the nodes not responding when I drew the circle, but if you experience it, let me know.
var treeData = [{
"name": "Top Level",
"parent": "null",
"remark": "yes",
"children": [{
"name": "Level 2: A",
"parent": "Top Level",
"remark": "yes",
"children": [{
"name": "Son of A",
"parent": "Level 2: A",
"remark": "null"
},
{
"name": "Daughter of A",
"parent": "Level 2: A",
"remark": "null"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level",
"remark": "null"
}
]
}];
//*************************************************************************************
// 1. Create the button
var button = document.createElement("button");
button.innerHTML = "download file";
//*************************************************************************************
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
rectW = 100,
rectH = 30,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
//swap x and y for vertical
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
// .projection(function(d) { return [d.x, d.y]; });
});
//var gElement = document.createElement('svg:g');
var gElement = document.createElementNS(d3.ns.prefix.svg, 'g');
gElement.setAttribute("id", "fg");
console.log(gElement);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append(function() {
return gElement;
}) //The argument to .append() has to be a function, you can't just pass element to it.
// .append("svg:g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
//swap x and y for vertical
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
//nodeEnter.append("circle")
// .attr("r", 1e-6)
.attr("width", rectW)
.attr("height", rectH)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// IAH 20/01/2019 Filter to put tooltip only on nodes that have information associated with it
var nodesWithInfo = nodeEnter.filter(function(d) {
return (d.remark != 'null')
});
nodesWithInfo
.append("circle")
.attr("export-ignore", true)
.attr("cx", 96)
.attr("cy", 7)
.attr("r", 10);
nodesWithInfo
.append("text")
.attr("export-ignore", true)
.attr("id", "infoText")
.attr("x", 96)
.attr("y", 7)
.attr("dy", ".30em")
.style("font-style", "italic")
.style("font-family", "serif")
.style("font-weight", "bold")
.text("i");
// Transition nodes to their new position.
//swap x and y for vertical layout
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
//nodeUpdate.select("circle")
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.style("stroke", "black")
// .attr("r", 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
//vertical switch x and y positions
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
//nodeExit.select("circle")
nodeExit.select("rect")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke", "#ccc")
.attr("fill", "none")
.attr("stroke-width", "2px")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
//*************************************************************************************
// 2. Append button somewhere
var body = document.getElementsByTagName("body")[0];
body.appendChild(button)
.setAttribute("transform", "translate(" + 0 + "," + 0 + ")");
//*************************************************************************************
function svgDataURL(svg) {
var svgAsXML = (new XMLSerializer).serializeToString(svg);
var dataURL = "data:image/svg+xml," + encodeURIComponent(svgAsXML);
return dataURL;
}
button.onclick = function() {
var svgElement = document.querySelector('svg');
var svgWH = svgElement.getBBox();
var canvasWidth = svgWH.width;
var canvasHeight = svgWH.height;
var groupElement = document.getElementById('fg');
groupElement.setAttribute("transform", "translate(0,0)");
const bb = groupElement.getBBox();
// clone the svg to avoid destroying it while appending to the svg namespace
let clonedGroupElement = groupElement.cloneNode(true);
var svgContent = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgContent.setAttribute('viewBox', ' ' + bb.x + ' ' + bb.y + ' ' + bb.width + ' ' + bb.height);
svgContent.setAttribute("width", "100%");
svgContent.setAttribute("height", "100%");
svgContent.setAttribute("preserveAspectRatio", "xMidYMid meet");
// try to remove the information icon before downloading
clonedGroupElement.querySelectorAll('[export-ignore]').forEach(function(node) {
node.remove();
});
svgContent.appendChild(clonedGroupElement); // use the cloned nodes
var dl = document.createElement('a');
document.body.appendChild(dl);
dl.setAttribute("href", svgDataURL(svgContent)); // function svgDataURL expects a node
dl.setAttribute("download", "test.svg");
dl.click();
dl.remove();
svgContent.removeChild(clonedGroupElement);
};
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
text-anchor: middle;
}
.link {
/*
fill: none;
stroke: #ccc;
stroke-width: 2px;*/
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

How to add title, background, and border as svg external images to a cloned element of d3js tree to be downloaded

I have a d3js tree which I am able to download, I want to add a title, Arabesque background and Arabesque border from external .svg files for a better visual effect.
I have 3 external files: title_3.png (later I will use an svg equivalent), background-F4EED7.svg, and border_1.svg. I need to center the title to the middle of the graph and allow the height of the title to be resized according to the length and depth of the generated tree.
What would be the best way of achieving this?
var treeData = [{
"name": "Top Level",
"parent": "null",
"remark": "yes",
"children": [{
"name": "Level 2: A",
"parent": "Top Level",
"remark": "yes",
"children": [{
"name": "Son of A",
"parent": "Level 2: A",
"remark": "null"
},
{
"name": "Daughter of A",
"parent": "Level 2: A",
"remark": "null"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level",
"remark": "null"
}
]
}];
//*************************************************************************************
// 1. Create the button
var button = document.createElement("button");
button.innerHTML = "download file";
//*************************************************************************************
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
rectW = 100,
rectH = 30,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
//swap x and y for vertical
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var gElement = document.createElementNS(d3.ns.prefix.svg, 'g');
gElement.setAttribute("id", "fg");
console.log(gElement);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append(function() {
return gElement;
}) //The argument to .append() has to be a function, you can't just pass element to it.
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
//swap x and y for vertical
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
//nodeEnter.append("circle")
// .attr("r", 1e-6)
.attr("width", rectW)
.attr("height", rectH)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Filter to put tooltip only on nodes that have information associated with it
var nodesWithInfo = nodeEnter.filter(function(d) {
return (d.remark != 'null')
})
nodesWithInfo
.append("circle")
.attr("export-ignore", true)
.attr("cx", 96)
.attr("cy", 7)
.attr("r", 10);
nodesWithInfo
.append("text")
.attr("export-ignore", true)
.attr("id", "infoText")
.attr("x", 96)
.attr("y", 7)
.attr("dy", ".30em")
.style("font-style", "italic")
.style("font-family", "serif")
.style("font-weight", "bold")
.text("i");
// Transition nodes to their new position.
//swap x and y for vertical layout
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
//nodeUpdate.select("circle")
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.style("stroke", "black")
// .attr("r", 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
//vertical switch x and y positions
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.style("stroke", "black");
// .attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke", "#ccc")
.attr("fill", "none")
.attr("stroke-width", "2px")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
//*************************************************************************************
// 2. Append button somewhere
var body = document.getElementsByTagName("body")[0];
body.appendChild(button)
.setAttribute("transform", "translate(" + 0 + "," + 0 + ")");
//*************************************************************************************
function svgDataURL(svg) {
var svgAsXML = (new XMLSerializer).serializeToString(svg);
var dataURL = "data:image/svg+xml," + encodeURIComponent(svgAsXML);
return dataURL;
}
button.onclick = function() {
var svgElement = document.querySelector('svg');
var svgWH = svgElement.getBBox();
var canvasWidth = svgWH.width;
var canvasHeight = svgWH.height;
var groupElement = document.getElementById('fg');
groupElement.setAttribute("transform", "translate(0,0)");
const bb = groupElement.getBBox();
//Title of the tree
var svgimg = document.createElementNS('http://www.w3.org/2000/svg', 'image');
svgimg.setAttributeNS(null, 'height', "30");
svgimg.setAttributeNS(null, 'width', bb.width / 2);
svgimg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', 'http://localhost/images/Title3.png');
svgimg.setAttributeNS(null, 'x', '-' + rectW / 2);
svgimg.setAttributeNS(null, 'y', '-' + rectH / 2);
svgimg.setAttributeNS(null, 'visibility', 'visible');
//Background SVG pattern
svgBgImg.style.backgroundImage = "url('http://localhost/images/background-F4EED7.svg')";
// clone the svg to avoid destroying it while appending to the svg namespace
let clonedGroupElement = groupElement.cloneNode(true);
var svgContent = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgContent.setAttribute('viewBox', ' ' + bb.x + ' ' + bb.y + ' ' + bb.width + ' ' + bb.height);
svgContent.setAttribute("width", "100%");
svgContent.setAttribute("height", "100%");
svgContent.setAttribute("preserveAspectRatio", "xMidYMid meet");
// try to remove the information icon before downloading
clonedGroupElement.querySelectorAll('[export-ignore]').forEach(function(node) {
node.remove();
});
clonedGroupElement.style.borderImageSource = "url('http://localhost/images/border_1.svg')";
clonedGroupElement.append(svgimg);
clonedGroupElement.style.borderImageSource = "url('http://localhost/images/border_1.svg')";
svgContent.appendChild(clonedGroupElement); // use the cloned nodes
var dl = document.createElement('a');
document.body.appendChild(dl);
dl.setAttribute("href", svgDataURL(svgContent)); // function svgDataURL expects a node
dl.setAttribute("download", "test.svg");
dl.click();
dl.remove();
svgContent.removeChild(clonedGroupElement);
};
<style>.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1px;
}
.node text {
font: 12px sans-serif;
}
.link {
/*
fill: none;
stroke: #ccc;
stroke-width: 2px;*/
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.4/d3.min.js"></script>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
</head>
<body>

D3.js draw diamonds and connecting links in a decision tree

I am working on the below layout ( decision tree) using D3 where I need to draw diamond shapes for the nodes that are "decisions" in a flow chart and rest of the nodes are actions ( rectangles).
Logically, all the nodes that have children are diamond shapes. Below is a UX Visualisation.
I've come up with a jsfiddle for the top to bottom D3 chart here : https://jsfiddle.net/p6vrmnu0/3/
But all the svg elements are currently rectangles and all the lines are now connected by "curved" links, I would like svgs to be diamonds for "decision" nodes and the links to be similar to the above image originating from the diamond's two corners and ending at at the top of the rhombus if the next is decision or top of rectangle if the next is an action.
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var emptyDecisionBox = {
"name": "newDecision",
"id": "newId",
"value": "notSure",
"condition": "true",
};
var selectedNode;
var root = {
"name": "Root",
"children": [{
"name": "analytics",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "true",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4"
}],
},
{
"name": "division",
"type": "action",
"value": "a-b",
"children": [],
}
]
};
var i = 0,
duration = 750,
rectW = 60,
rectH = 30;
var tree = d3.layout.tree().nodeSize([70, 40]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
var svg = d3.select("body").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
// new part
var plusButton = svg
.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("CLICKED");
});
plusButton
.append('rect')
.attr('transform', 'translate(-8, -8)') // center the button inside the `g`
.attr('width', 16)
.attr('height', 16)
.attr('rx', 2);
plusButton
.append('path')
.attr('d', 'M-6 0 H6 M0 -6 V6');
var rectangleShape = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
removeAllButtonElements();
})
rectangleShape
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImage = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
addElement(selectedNode);
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImage
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
var rectangleShapeFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("rectangle clicked");
removeAllButtonElements();
})
rectangleShapeFalse
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImageFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImageFalse
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
function removeAllButtonElements() {
plusButton.classed('hide', true);
diamondImage.classed('hide', true);
rectangleShape.classed('hide', true);
diamondImageFalse.classed('hide', true);
rectangleShapeFalse.classed('hide', true);
}
// new part ends.
root.x0 = 0;
root.y0 = height / 2;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
/* root.children.forEach(collapse); */
update(root);
d3.select("#body").style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
/* nodeEnter.append('path').attr("d", d3.svg.symbol().type( function(d) { return "circle" }) );
*/
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.on('mouseenter', function(d, i) {
// Use the native SVG interface to get the bounding box to
// calculate the center of the path
var bbox = this.getBBox();
var x = bbox.x + bbox.width / 2,
y = bbox.y + bbox.height / 2;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
})
.on('mouseleave', function(d, i) {
plusButton
.classed('hide', true);
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
/* function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
} */
function click(d) {
console.log(d);
selectedNode = d;
/* if(d.children && d.children.length < 1){
return;
} */
var x = d.x;
var y = d.y + 40;
/* plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
plusButton
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false); */
var m = d.x + 50;
var h = d.y + 20;
diamondImage
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x + 60;
var h = d.y - 10;
rectangleShape
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y + 20;
diamondImageFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y - 10;
rectangleShapeFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
function addElement(d) {
console.log(d);
d.children = [];
d.children.push(emptyDecisionBox);
update(root);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
.button>path {
stroke: blue;
stroke-width: 1.5;
}
.button>rect {
fill: #ddd;
stroke: grey;
stroke-width: 1px;
}
.hide {
display: none;
opacity: 0 !important;
pointer-events: none;
}
.link:hover {
cursor: pointer;
stroke-width: 8px;
}
.scale {
/* transform: scale(0.4); */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>
For the line shape, I recommend to just write a shape function yourself. I wrote one that goes horizontal, then makes a small curve, then goes down. It doesn't take the width of the shape into account, but that shouldn't matter, since it's behind the box after all.
For the box shapes, I'd do something similar. Instead of using rects, use a path, and write the shape of the path using an if statement. This is always a very valuable resource.
Just to get you started:
function drawDiamond(centroid) {
// Start at the top
var result = 'M' + centroid.x + ',' + (centroid.y - rectH / 2);
// Go right
result += 'L' + (centroid.x + rectW / 2) + ',' + centroid.y;
// Bottom
result += 'L' + centroid.x + ',' + (centroid.y + rectH / 2);
// Left
result += 'L' + (centroid.x - rectW / 2) + ',' + centroid.y;
// Close the shape
result += 'Z';
return result;
}
function drawRect(centroid) {
// Start at the top left
var result = 'M' + (centroid.x - rectW / 2) + ',' + (centroid.y - rectH / 2);
// Go right
result += 'h' + rectW;
// Go down
result += 'v' + rectH;
// Left
result += 'h-' + rectW;
// Close the shape
result += 'Z';
return result;
}
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var emptyDecisionBox = {
"name": "newDecision",
"id": "newId",
"value": "notSure",
"condition": "true",
};
var selectedNode;
var root = {
"name": "Root",
"children": [{
"name": "analytics",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "true",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4"
}],
},
{
"name": "division",
"type": "action",
"value": "a-b",
"children": [],
}
]
};
var i = 0,
duration = 750,
rectW = 60,
rectH = 30;
var tree = d3.layout.tree().nodeSize([70, 40]);
var linkFunc = function(d) {
var source = {
x: d.source.x,
y: d.source.y + (rectH / 2)
};
var target = {
x: d.target.x + (rectW / 2),
y: d.target.y
};
// This is where the line bends
var inflection = {
x: target.x,
y: source.y
};
var radius = 5;
var result = "M" + source.x + ',' + source.y;
if (source.x < target.x) {
// Child is to the right of the parent
result += ' H' + (inflection.x - radius);
} else {
result += ' H' + (inflection.x + radius);
}
// Curve the line at the bend slightly
result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);
result += 'V' + target.y;
return result;
}
var svg = d3.select("body").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
// new part
var plusButton = svg
.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("CLICKED");
});
plusButton
.append('rect')
.attr('transform', 'translate(-8, -8)') // center the button inside the `g`
.attr('width', 16)
.attr('height', 16)
.attr('rx', 2);
plusButton
.append('path')
.attr('d', 'M-6 0 H6 M0 -6 V6');
var rectangleShape = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
removeAllButtonElements();
})
rectangleShape
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImage = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
addElement(selectedNode);
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImage
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
var rectangleShapeFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("rectangle clicked");
removeAllButtonElements();
})
rectangleShapeFalse
.append('rect')
.attr('width', 40)
.attr('height', 20)
.style('fill', 'orange');
var diamondImageFalse = svg.append('g')
.classed('button', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
diamondImageFalse
.append('path')
.attr('d', 'M 20 0 40 20 20 40 0 20 Z')
.style("fill", 'orange');
function removeAllButtonElements() {
plusButton.classed('hide', true);
diamondImage.classed('hide', true);
rectangleShape.classed('hide', true);
diamondImageFalse.classed('hide', true);
rectangleShapeFalse.classed('hide', true);
}
// new part ends.
root.x0 = 0;
root.y0 = height / 2;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
/* root.children.forEach(collapse); */
update(root);
d3.select("#body").style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
/* nodeEnter.append('path').attr("d", d3.svg.symbol().type( function(d) { return "circle" }) );
*/
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodeUpdate.select("rect")
.attr("width", rectW)
.attr("height", rectH)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", rectW)
.attr("height", rectH)
//.attr("width", bbox.getBBox().width)""
//.attr("height", bbox.getBBox().height)
.attr("stroke", "black")
.attr("stroke-width", 1);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("d", linkFunc)
.on('mouseenter', function(d, i) {
// Use the native SVG interface to get the bounding box to
// calculate the center of the path
var bbox = this.getBBox();
var x = bbox.x + bbox.width / 2,
y = bbox.y + bbox.height / 2;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
})
.on('mouseleave', function(d, i) {
plusButton
.classed('hide', true);
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", linkFunc);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", linkFunc)
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function click(d) {
console.log(d);
selectedNode = d;
var x = d.x;
var y = d.y + 40;
var m = d.x + 50;
var h = d.y + 20;
diamondImage
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x + 60;
var h = d.y - 10;
rectangleShape
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y + 20;
diamondImageFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 40;
var h = d.y - 10;
rectangleShapeFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
}
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
function addElement(d) {
console.log(d);
d.children = [];
d.children.push(emptyDecisionBox);
update(root);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
.button>path {
stroke: blue;
stroke-width: 1.5;
}
.button>rect {
fill: #ddd;
stroke: grey;
stroke-width: 1px;
}
.hide {
display: none;
opacity: 0 !important;
pointer-events: none;
}
.link:hover {
cursor: pointer;
stroke-width: 8px;
}
.scale {
/* transform: scale(0.4); */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>
Now, there are some problems with your code in general.
You set the attributes of items you're going to delete, or items that have already been set. Remember that every node that is on the page has already been entered once, so when you set their width/height/stroke, there is no reason to ever set it again, unless it changes. Similarly, there is no reason to use nodeUpdate.select('rect') and then set all the values all over again, just remove that code, it does nothing and leads to clutter.
You also don't select by class, but by tag. That will give you problems as soon as you start with paths. Use classes instead!

How can I include add, edit and delete button to d3 tree layout nodes?

I have generated a tree structure using d3.js which looks like this
But I want to include add,edit and delete button so that tree can be managed dynamically.
Here is one more picture of what I want to include.
Code looks like this:
var treeData =data;
console.log(treeData)
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [/*width -*/ d.y + 30, d.x + 25]; });
var svg = d3.select("#tree").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = width-(d.depth * 180); });
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; });
/* nodeEnter.append("circle")
/!*nodeEnter.append("new-div")*!/
.attr("r", 20)
.style("fill", "#fff");*/
nodeEnter.append("rect")
.attr("width", 100)
.attr("height", 40)
.attr("stroke", "black")
.attr("stroke-width", 1)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
/* nodeEnter.append("text")
.attr("y", function(d) {
return d.children || d._children ? -18 : 18; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; })
.style("fill-opacity", 1);*/
nodeEnter.append("text")
.attr("x", 100 / 2)
.attr("y", 40 / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.name;
});
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
}
In html file I just have
<div id="tree">
</div>
I found a fiddle online and edited it to show you a basic version of what you want : http://jsfiddle.net/reko91/JnNwu/729/
Here is the main code :
var nodeWidth = 300, nodeHeight = 100;
var buttonWidth = nodeWidth/3;
//container
nodeEnter.append("rect")
.attr('class','buttonContainer')
.attr("width", nodeWidth)
.attr("height", nodeHeight)
.style("fill", 'lightsteelblue')
.attr('transform', 'translate(0,'+(-nodeHeight/2)+')');
//buttonAdd
nodeEnter.append("rect")
.attr('class','addButton')
.attr("width", buttonWidth)
.attr("height", nodeHeight/1.5)
.style("fill",'white')
.attr('transform', 'translate(' + nodeHeight/6 + ','+(-nodeHeight/2 + nodeHeight/6)+')')
.on('click', function(d){
console.log('addButton')
})
;
//buttonDelete
nodeEnter.append("rect")
.attr('class','deleteButton')
.attr("width", buttonWidth)
.attr("height", nodeHeight/1.5)
.style("fill",'red')
.attr('transform', 'translate(' + (nodeWidth - buttonWidth- nodeHeight/6 )+ ','+(-nodeHeight/2 + nodeHeight/6)+')')
.on('click', function(d){
console.log('deleteButton')
})
;
What I have done here is, for every node I have appended a container which holds both the add and delete button (you can easily add the edit). On click, they console log their corresponding functions. So the add logs addButton and so on.
Now to implement the proper functionality of each button :)
var json =
{
"name": "Base",
"children": [
{
"name": "Type A",
"children": [
{
"name": "Section 1",
"children": [
{"name": "Child 1"},
{"name": "Child 2"},
{"name": "Child 3"}
]
},
{
"name": "Section 2",
"children": [
{"name": "Child 1"},
{"name": "Child 2"},
{"name": "Child 3"}
]
}
]
},
{
"name": "Type B",
"children": [
{
"name": "Section 1",
"children": [
{"name": "Child 1"},
{"name": "Child 2"},
{"name": "Child 3"}
]
},
{
"name": "Section 2",
"children": [
{"name": "Child 1"},
{"name": "Child 2"},
{"name": "Child 3"}
]
}
]
}
]
};
var width = 700;
var height = 650;
var maxLabel = 150;
var duration = 500;
var radius = 5;
var i = 0;
var root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + maxLabel + ",0)");
root = json;
root.x0 = height / 2;
root.y0 = 0;
root.children.forEach(collapse);
function update(source)
{
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse();
var links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * maxLabel; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d){
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d){ return "translate(" + source.y0 + "," + source.x0 + ")"; })
//.on("click", click);
var nodeWidth = 300, nodeHeight = 100;
var buttonWidth = nodeWidth/3;
//container
nodeEnter.append("rect")
.attr('class','buttonContainer')
.attr("width", nodeWidth)
.attr("height", nodeHeight)
.style("fill", 'lightsteelblue')
.attr('transform', 'translate(0,'+(-nodeHeight/2)+')');
//buttonAdd
nodeEnter.append("rect")
.attr('class','addButton')
.attr("width", buttonWidth)
.attr("height", nodeHeight/1.5)
.style("fill",'white')
.attr('transform', 'translate(' + nodeHeight/6 + ','+(-nodeHeight/2 + nodeHeight/6)+')')
.on('click', function(d){
alert('Add Button : ' + d.name)
console.log('addButton')
})
;
//buttonDelete
nodeEnter.append("rect")
.attr('class','deleteButton')
.attr("width", buttonWidth)
.attr("height", nodeHeight/1.5)
.style("fill",'red')
.attr('transform', 'translate(' + (nodeWidth - buttonWidth- nodeHeight/6 )+ ','+(-nodeHeight/2 + nodeHeight/6)+')')
.on('click', function(d){
alert('Delete Button : ' + d.name)
console.log('deleteButton')
})
;
nodeEnter.append("text")
.attr("x", function(d){
var spacing = computeRadius(d) + 5;
return d.children || d._children ? -spacing : spacing;
})
.attr("dy", "3")
.attr("text-anchor", function(d){ return d.children || d._children ? "end" : "start"; })
.text(function(d){ return d.name; })
.style("fill-opacity", 0);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", function(d){ return computeRadius(d); })
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text").style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle").attr("r", 0);
nodeExit.select("text").style("fill-opacity", 0);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d){ return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d){
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d){
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
}
function computeRadius(d)
{
if(d.children || d._children) return radius + (radius * nbEndNodes(d) / 10);
else return radius;
}
function nbEndNodes(n)
{
nb = 0;
if(n.children){
n.children.forEach(function(c){
nb += nbEndNodes(c);
});
}
else if(n._children){
n._children.forEach(function(c){
nb += nbEndNodes(c);
});
}
else nb++;
return nb;
}
function click(d)
{
if (d.children){
d._children = d.children;
d.children = null;
}
else{
d.children = d._children;
d._children = null;
}
update(d);
}
function collapse(d){
if (d.children){
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
update(root);
html{
font: 10px sans-serif;
}
svg{
border: 1px solid silver;
}
.node{
cursor: pointer;
}
.node circle{
stroke: steelblue;
stroke-width: 1.5px;
}
.link{
fill: none;
stroke: lightgray;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id=tree></div>

D3.js Decision Tree - text wraping, bounding box, # of load circles

I'm trying to create a decision tree in D3.
Problems:
- The text goes off the right side, can/how do I set a bounding box to stop that?
The text in the circle isn't wrapping. I am willing to make circles bigger to fit the text, but the wrapping is still a problem.
How do I get only the first 3 circles to show on load?
var treeData = [
{
"name": "TK question here that's long",
"content":"Question here long",
"parent": "null",
"children": [
{
"name": "Level 2: A",
"parent": "Top Level",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A"
},
{
"name": "Daughter of A",
"parent": "Level 2: A"
}
]
},
{
"name": "Level 2: B",
"parent": "Top Level"
}
]
}
];
// ************** Generate the tree diagram *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 860 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -33 : 13; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
nodeEnter.append("text")
.attr("x", 0)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.content; });
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 30)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
</head>
<body>
</body>
</html>
for the wrapping of the text mike bostock covered that in this example: text wrapping in d3 and for the showing of only certain nodes, you should include a filter after the data to show only certain tipes of data (i.e. the nodes that have either no parents or one parent and son (so the first and the middle ones)
you can check this post.. Wrapping text labels also in the foreignObject you can add x and y based on radius of the circle.
var nodeEnter = node.enter().append(\"g\")
.attr(\"class\", \"node\")
.attr(\"transform\", function(d) {
d.radius = (d.x - 90); // add some data to use in ForeignObject
return \"translate(\" + source.y0 + \",\" + source.x0 + \")\";
})
.on(\"click\", click);

Categories

Resources