I am trying to run this demo shared by Igor from this post: Add custom html to nodes in d3 js tree. I've made some changes to reflect the new version of D3 below, but I get the above error. I can't find where the error is happening and was wondering if someone could help me figure it out :)
var treedata = {
"name": "PublisherNameLongName",
"id": "id1",
"type": "type0",
"addable": false,
"editable": false,
"removable": false,
"enableble": false,
"children": [{
"name": "Landing A",
"id": "id2",
"type": "type1",
"addable": true,
"editable": true,
"removable": true,
"enablable": true,
"enable": false,
"children": [{
"name": "Account 1",
"id": "id3",
"type": "type2",
"children": [{
"name": "tracking link 1",
"id": "id4",
"type": "type3",
"enablable": true,
"enable": true
}, {
"name": "tracking link 2",
"id": "id5",
"type": "type3",
"enablable": true,
"enable": true
}, {
"name": "tracking link 3",
"id": "id6",
"type": "type3",
"enablable": true,
"enable": false
}]
}, {
"name": "Account 2",
"id": "id7",
"type": "type2"
}, {
"name": "Account 3",
"id": "id9",
"type": "type2"
}]
}, {
"name": "Landing B",
"id": "id8",
"type": "type1",
"children": [{
"name": "Account 4",
"id": "id10",
"type": "type2"
}, {
"name": "Account 5",
"id": "id11",
"type": "type2"
}, {
"name": "Account 6",
"id": "id12",
"type": "type2"
}]
}, {
"name": "Landing C",
"id": "id13",
"type": "type1",
"children": [{
"name": "Subtopic 7",
"id": "id14",
"type": "type3"
}, {
"name": "Subtopic 8",
"id": "id15",
"type": "type3"
}, {
"name": "Subtopic 9",
"id": "id16",
"type": "type3"
}]
}]
}
var m = [20, 20, 20, 20],
w = 800 - m[1] - m[3],
h = 600 - m[0] - m[2],
i = 0,
r = 800,
x = d3.scaleLinear().domain([0, w]).range([0, w]),
y = d3.scaleLinear().domain([0, h]).range([0, h]),
root;
var vis = d3.select("#network")
.append("div").classed("svg-container", true)
.append("svg:svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 600 600")
.classed("svg-content-responsive", true)
.append("svg:g")
//.attr("pointer-events", "all")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")")
//.call(d3.behavior.zoom().scaleExtent([1,8]).on("zoom",zoom));
//.call(d3.behavior.zoom().x(x).y(y).scaleExtent([1, 8]).on("zoom", zoom))
;
vis.append("rect")
.attr("class", "svg-content-responsive")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.attr("opacity", 0)
// draws edge
var diagonal = d3.linkVertical()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
root = treedata;
root.x0 = h / 2;
root.y0 = 0;
// open or collaspe children of selected node
function toggleAll(d) {
if (d.children) {
d.children.forEach(toggleAll);
toggle(d);
}
};
console.log(root)
// initialize the display to show a few nodes.
root.children.forEach(toggleAll);
//toggle(root.children[1]);
//toggle(root.children[9]);
update(root);
function update(source) {
// how long animations last
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// Compute the new tree layout.
var nodes = hierarchy(root).children
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes...
var node = vis.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("svg:g")
.attr("class", "node")
.attr("id", function(d) {
return "node-" + d.id;
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", function(d) {
toggle(d);
update(d);
// if (d['info']) {
// playvid(d['info']);
// }
});
nodeEnter.append("svg:circle")
.attr("r", 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
var nodeText = nodeEnter
.append("svg:foreignObject")
//.attr("y", function(d) { return d.children || d._children ? -10 : 10; })
//.attr("dx", ".35em")
//.attr("x", function(d) {
// return d.children || d._children ? -10 : 10;
//})
.attr("y", -30)
.attr("x", -5)
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.style("fill-opacity", 1e-6)
.attr('width', 300)
.attr('height', 100)
.append('xhtml:div')
.attr("class", function(d) {
return "node-label" + " node-" + d.type
})
.classed("disabled", function(d) {
return d.enable !== undefined && !d.enable;
});
//Enable node button
nodeText.filter(function(d) {
return d.enablable;
})
.append("input", ".")
.attr("type", "checkbox")
.property("checked", function(d) {
return d.enable;
})
.on("change", toggleEnable, true)
.on("click", stopPropogation, true);
//Node label
nodeText.append("span")
.attr("class", "node-text")
.text(function(d) {
return d.name;
});
//Edit node button
nodeText.filter(function(d) {
return d.editable;
})
.append("a")
.attr("class", "node-edit")
.on("click", onEditNode, true)
.append("i")
.attr("class", "fa fa-pencil");
//Add node button
nodeText.filter(function(d) {
return d.addable;
})
.append("a")
.attr("class", "node-add")
.on("click", onAddNode, true)
.append("i")
.attr("class", "fa fa-plus");
//Remove node button
nodeText.filter(function(d) {
return d.removable;
})
.append("a")
.attr("class", "node-remove")
.on("click", onRemoveNode, true)
.append("i")
.attr("class", "fa fa-times");
// 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", 4.5)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting ndoes 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 = vis.selectAll("path.link")
.data(hierarchy(root).links(), function(d) {
return d.target.id;
});
// Enter any new links at hte parent's previous position
link.enter().insert("svg:path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.transition()
.duration(duration)
.attr("d", diagonal);
// 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
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
// zoom in / out
function zoom(d) {
//vis.attr("transform", "transl3ate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
var nodes = vis.selectAll("g.node");
nodes.attr("transform", transform);
// Update the links...
var link = vis.selectAll("path.link");
link.attr("d", translate);
// Enter any new links at hte parent's previous position
//link.attr("d", function(d) {
// var o = {x: d.x0, y: d.y0};
// return diagonal({source: o, target: o});
// });
}
function transform(d) {
return "translate(" + x(d.y) + "," + y(d.x) + ")";
}
function translate(d) {
var sourceX = x(d.target.parent.y);
var sourceY = y(d.target.parent.x);
var targetX = x(d.target.y);
var targetY = (sourceX + targetX) / 2;
var linkTargetY = y(d.target.x0);
var result = "M" + sourceX + "," + sourceY + " C" + targetX + "," + sourceY + " " + targetY + "," + y(d.target.x0) + " " + targetX + "," + linkTargetY + "";
//console.log(result);
return result;
}
function onEditNode(d) {
var length = 9;
var id = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, length);
addChildNode(d.id, {
"name": "new child node",
"id": id,
"type": "type2"
});
stopPropogation();
}
function onAddNode(d) {
var length = 9;
var id = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, length);
addChildNode(d.id, {
"name": "new child node",
"id": id,
"type": "type2"
});
stopPropogation();
}
function onRemoveNode(d) {
var index = d.parent.children.indexOf(d);
if (index > -1) {
d.parent.children.splice(index, 1);
}
update(d.parent);
stopPropogation();
}
function addChildNode(parentId, newNode) {
var node = d3.select('#' + 'node-' + parentId);
var nodeData = node.datum();
if (nodeData.children === undefined && nodeData._children === undefined) {
nodeData.children = [newNode];
} else if (nodeData._children != null) {
nodeData._children.push(newNode);
toggle(nodeData);
} else if (nodeData.children != null) {
nodeData.children.push(newNode);
}
update(node);
stopPropogation();
}
function toggleEnable(d) {
d.enable = !d.enable;
var node = d3.select('#' + 'node-' + d.id + " .node-label")
.classed("disabled", !d.enable);
stopPropogation();
}
function stopPropogation() {
d3.event.stopPropagation();
}
body {
background-color: #fff;
}
.node circle {
cursor: pointer;
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node-label {
font-size: 12px;
padding: 3px 5px;
display: inline-block;
word-wrap: break-word;
max-width: 160px;
background: #d0dee7;
border-radius: 5px;
}
.node a:hover {
cursor: pointer;
}
.node a {
font-size: 10px;
margin-left: 5px
}
a.node-remove {
color: red;
}
input+.node-text {
margin-left: 5px;
}
.node-label.node-type1 {
background: coral;
}
.node-label.node-type2 {
background: lightblue;
}
.node-label.node-type3 {
background: yellow;
}
.node-label.disabled {
background: #e9e9e9;
color: #838383
}
.node text {
font-size: 11px;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
<script src="https://d3js.org/d3.v5.js"></script>
<div id="tree">
</div>
The problem was the diagonal function; I replaced it as well as other outdated chunks of code with code from this demo, https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd.
Related
I am looking for a way to add a button to a link connecting nodes in a TreeLayout in D3.
On click of this button I have to add another rhombus/rect node below it. The below image is just a UX visualization. The example could be a simple treelayout in D3.
Example D3 Treelayout:
var jsonData = getData();
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,
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 = jsonData;
root.x0 = height / 2;
root.y0 = 0;
update(root);
//svg.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)
.attr('stroke', function(d) {
return d.color ? d.color : 'blue';
})
.style("fill", function(d) {
return d._children ? "#ccc" : "#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);
// 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", 10)
.style("fill", function(d) {
var collapseColor = d.color ? d.color : '#ccc';
return d._children ? collapseColor : "#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);
}
function getData() {
return {
"name": "js",
"parent": "null",
"color": "green",
"children": [{
"name": "frameworks",
"parent": "js",
"color": "red",
"children": [{
"name": "Angular",
"parent": "frameworks",
"color": "red",
"size": 75
},
{
"name": "Backbone",
"parent": "frameworks",
"color": "red",
"size": 15
},
{
"name": "Ember",
"parent": "frameworks",
"color": "red",
"size": 5
},
{
"name": "Aurelia",
"parent": "frameworks",
"color": "red",
"size": 5
}
]
},
{
"name": "libraries",
"parent": "js",
"color": "blue",
"children": [{
"name": "jQuery",
"parent": "libraries",
"size": 70
},
{
"name": "YUI",
"parent": "libraries",
"size": 30
},
{
"name": "Dojo",
"parent": "libraries",
"size": 10
},
{
"name": "Prototype",
"parent": "libraries",
"size": 15
},
{
"name": "MooTools",
"parent": "libraries",
"size": 5
},
{
"name": "ExtJS",
"parent": "libraries",
"size": 5
}
]
}
]
};
};
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
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>
<div class="tree-diagram"></div>
When a user clicks ( or hover ? ) on any of the links a '+' button should be displayed. I came up with the SVG path to draw a + .
<svg height="400" width="450">
<path d= "M0 6 H12 M6 0 V12" stroke="blue" stroke-width="1.5" />
</svg>
But I am stuck on what needs to be done for converting this into a button and appending it to the link and making it clickable.
I added the button to your example, without making it do anything yet. Just, as soon as you hover on a link, the button is shown in the middle of that link, and when you click it, it logs to the console.
Now, there are probably a few things you'll want in the future:
Increase the hitbox of the link to make it easier to use;
Know which link was clicked, either by writing to a global variable clickedLink, or by re-assigning on('click', ...) inside the on('mouseenter') of the link;
Change the data object by adding a new node and calling update either on the entire data set (easy, but computationally expensive) or on the source node and its children.
var jsonData = getData();
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,
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 + ")");
// 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');
root = jsonData;
root.x0 = height / 2;
root.y0 = 0;
update(root);
//svg.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)
.attr('stroke', function(d) {
return d.color ? d.color : 'blue';
})
.style("fill", function(d) {
return d._children ? "#ccc" : "#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);
// 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", 10)
.style("fill", function(d) {
var collapseColor = d.color ? d.color : '#ccc';
return d._children ? collapseColor : "#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
});
})
// New part
.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 getData() {
return {
"name": "js",
"parent": "null",
"color": "green",
"children": [{
"name": "frameworks",
"parent": "js",
"color": "red",
"children": [{
"name": "Angular",
"parent": "frameworks",
"color": "red",
"size": 75
},
{
"name": "Backbone",
"parent": "frameworks",
"color": "red",
"size": 15
},
{
"name": "Ember",
"parent": "frameworks",
"color": "red",
"size": 5
},
{
"name": "Aurelia",
"parent": "frameworks",
"color": "red",
"size": 5
}
]
},
{
"name": "libraries",
"parent": "js",
"color": "blue",
"children": [{
"name": "jQuery",
"parent": "libraries",
"size": 70
},
{
"name": "YUI",
"parent": "libraries",
"size": 30
},
{
"name": "Dojo",
"parent": "libraries",
"size": 10
},
{
"name": "Prototype",
"parent": "libraries",
"size": 15
},
{
"name": "MooTools",
"parent": "libraries",
"size": 5
},
{
"name": "ExtJS",
"parent": "libraries",
"size": 5
}
]
}
]
};
};
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
/* New part */
.button > path {
stroke: blue;
stroke-width: 1.5;
}
.button > rect {
fill: #ddd;
stroke: grey;
stroke-width: 1px;
}
.hide {
opacity: 0 !important;
pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>
I have a d3.js tree with descendant nodes receiving their level 2 ancestor's node colour. This is working from level 2 to level 3, but stops working at level 4 and on.
Relevant code:
var colourScale = d3.scale.ordinal()
.domain(["MD","Professional", "Leader", "Advocate", "Clinician", "Educator", "Scholar"])
.range(["#6695c8", "#cd3838","#d48440", "#a8ba5f", "#63b7c0", "#c97eb2", "#ccc136"]);
and
nodeUpdate.select("circle")
.attr("r", 10)
.attr("fill-opacity","0.7")
.attr("stroke-opacity","1")
.style("fill", function(d) {
return d.depth === 2 ? colourScale(d.parent.name) : colourScale(d.name);
})
.style("stroke", function(d) {
return d.depth === 2 ? colourScale(d.parent.name) : colourScale(d.name);
});
and
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke-width", function(d) {
return 1;
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.attr("opacity","0.3")
.style("stroke", function(d) {
return d.target.depth === 2 ? colourScale(d.target.parent.name) : colourScale(d.target.name);
});
How can I set ALL descendants of a parent to the same colour (nodes and links)?
Fiddle
Use a recursive function to go up the hierarchy until you find a node with the correct depth. For instance:
function findParent(datum) {
if (datum.depth < 2) {
return datum.name
} else {
return findParent(datum.parent)
}
}
Or even shorter with a ternary:
function findParent(datum) {
return datum.depth < 2 ? datum.name : findParent(datum.parent);
};
Then, you can pass it to the colour scale:
.style("fill", function(d) {
return colourScale(findParent(d));
})
Here is the updated code:
var treeData = [{
"name": "MD",
"children": [{
"name": "Professional",
"children": [{
"name": "Third A",
"children": [{
"name": "Fourth A",
"children": [{
"name": "Fifth A"
}, {
"name": "Fifth B"
}, {
"name": "Fifth C"
}, {
"name": "Fifth D"
}]
}, {
"name": "Fourth B"
}, {
"name": "Fourth C"
}, {
"name": "Fourth D"
}]
}, {
"name": "Third B"
}]
}, {
"name": "Leader",
"children": [{
"name": "Third C"
}, {
"name": "Third D"
}]
}, {
"name": "Advocate",
"children": [{
"name": "Third E"
}, {
"name": "Third F"
}]
}, {
"name": "Clinician",
"children": [{
"name": "Third G"
}, {
"name": "Third H"
}, ]
}, ]
}];
var colourScale = d3.scale.ordinal()
.domain(["MD", "Professional", "Leader", "Advocate", "Clinician"])
.range(["#6695c8", "#cd3838", "#d48440", "#a8ba5f", "#63b7c0"]);
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1200 - margin.right - margin.left,
height = 650 - 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");
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
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 * 200;
});
// 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 ? "#C0C0C0" : "#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);
// 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", 10)
.attr("fill-opacity", "0.7")
.attr("stroke-opacity", "1")
.style("fill", function(d) {
return colourScale(findParent(d));
})
.style("stroke", function(d) {
return colourScale(findParent(d));
});
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("stroke-width", function(d) {
return 1;
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.attr("opacity", "0.3")
.style("stroke", function(d) {
return colourScale(findParentLinks(d));
});
// 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 findParent(datum) {
if (datum.depth < 2) {
return datum.name
} else {
return findParent(datum.parent)
}
}
function findParentLinks(datum) {
if (datum.target.depth < 2) {
return datum.target.name
} else {
return findParent(datum.target.parent)
}
}
// 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: #C0C0C0;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #C0C0C0;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
Tree chart image:-TreeChart
I have plotted a d3.js tree chart, the chart size is bigger then the window size but i want to download the entire tree chart as PNG image and then zoom it and see each node. When I try to download as PNG the entire picture is not showing and certain nodes are not visible in the downloaded image. I have tried to resize and re position within the window size but as the size of chart gets bigger it is difficult to resize. I want to download the whole tree chart. As The size of tree chart getting bigger the image downloaded does not show full image. I have try using the SVG height and width and it is of no use. I have also used to try and get the g element in the SVG and its size but still not getting the desired image.
Scripts Used:-
1. d3.v5.min.js
2. saveSvgAsPng
Tree Data Used:-
{
"node-v": "A||23",
"name": "A",
"children": [
{
"node-v": "B||23.3",
"name": "B",
"children": [
{
"node-v": "C||673803786739669840",
"name": "C",
"children": [
{
"node-v": "D||-376559623913149976",
"name": "D",
"parent": null,
"type": "APPLICATION SERVER",
"key": "-376559623913149976"
}
],
"parent": null,
"type": "APPLICATION",
"key": "673803786739669840"
},
{
"node-v": "E||-1381480713933961904",
"name": "E",
"children": [
{
"node-v": "F||-376589310727107096",
"name": "F",
"parent": null,
"type": "APPLICATION SERVER",
"key": "-376589310727107096"
}
],
"parent": null,
"type": "APPLICATION",
"key": "-1381480713933961904"
}
],
"parent": null,
"type": "BUSINESS",
"key": "23.3"
},
{
"node-v": "G||23.1",
"name": "G",
"children": [
{
"node-v": "H||673803786739669840",
"name": "H",
"children": [
{
"node-v": "I||-376559623913149976",
"name": "I",
"parent": null,
"type": "APPLICATION SERVER",
"key": "-376559623913149976"
}
],
"parent": null,
"type": "APPLICATION",
"key": "673803786739669840"
},
{
"node-v": "J||-1381480713933961904",
"name": "J",
"children": [
{
"node-v": "K||-376589310727107096",
"name": "K",
"parent": null,
"type": "APPLICATION SERVER",
"key": "-376589310727107096"
}
],
"parent": null,
"type": "APPLICATION",
"key": "-1381480713933961904"
},
{
"node-v": "L||23.1.1",
"name": "L",
"parent": null,
"type": "BUSINESS",
"key": "23.1.1"
},
{
"node-v": "M||23.1.2",
"name": "M",
"children": [
{
"node-v": "N||23.1.2.1",
"name": "N",
"parent": null,
"type": "BUSINESS",
"key": "23.1.2.1"
}
],
"parent": null,
"type": "BUSINESS",
"key": "23.1.2"
}
],
"parent": null,
"type": "BUSINESS",
"key": "23.1"
}
],
"type": "BUSINESS",
"key": "23"}
Below is the code used:-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
<style>
.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;
}
text {
display: inline-block;
position: relative;
padding: 4px;
}
image {
display: inline-block;
position: relative;
padding: 4px;
}
</style>
</head>
<body>
<div id="dracula"></div>
<!-- load the d3.js library -->
<script src="d3.v5.min.js"></script>
<script src="saveSvgAsPng"></script>
<script>
var treeData = treeMapData[0];
// Set the dimensions and margins of the diagram
var margin = {
top: 20,
right: 90,
bottom: 30,
left: 150
},
width = 1360 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("#dracula").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 + ")");
d3.select("#dracula").select("svg").call(d3.zoom().scaleExtent([0.8, 3]).on("zoom", zoom));
//d3.select("#dracula").select("svg").select("g").attr("class", "drawarea");
var i = 0,
duration = 50,
root;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function(d) {
return d.children;
});
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
//root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function zoom() {
svg.attr("transform", d3.event.transform);
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 380
});
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new modes 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);
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function(d) {
return d.children || d._children ? -13 : 37;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.data.name;
});
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 10)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll('path.link')
.data(links, function(d) {
return d.id;
});
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d) {
var o = {
x: source.x0,
y: source.y0
}
return diagonal(o, o)
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function(d) {
return diagonal(d, d.parent)
});
// Remove any exiting links
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function(d) {
var o = {
x: source.x,
y: source.y
}
return diagonal(o, o)
})
.remove();
// Store the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
return "M" + s.y + "," + s.x + "C" + (s.y + d.y) / 2 + "," + s.x + " " + (s.y + d.y) / 2 + "," + d.x + " " + d.y + "," + d.x;
}
// 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);
}
}
d3.select("saveButton")
.on("click", svgToPng);
function svgToPng() {
saveSvgAsPng(d3.select('svg').select('g').node(), 'myDrawing.png', {
scale: 4,
backgroundColor: "#FFFFFF"
});
}
</script>
</body>
<div>
<button id="saveButton"; style="position: relative; z-index: 2; margin-top: -592px; float: right; margin-right: 146px;">
Export my D3 visualization to PNG</button>
</div>
</html>
I'm trying to pass a python string into the following template (tring to get the same html file as in : http://bl.ocks.org/d3noob/8375092:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tree Example</title>
<style>
.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;
}
</style>
</head>
<body>
<h1>{{json_string }}<h1/>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var treeData = [
{
{{json_string }}
}
];
// ************** 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,
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 ? -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);
// 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", 10)
.style("fill", function(d) { return d._children ? "green" : "#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);
}
</script>
</body>
</html>
Using the following View:
from django.shortcuts import render
import json
def tree(request):
python_object = {
"name": "Top Level",
"parent": "null",
"children": [
{
"name": "Level yosh: 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"
}
]}
json_string = json.dumps(python_object)
return render(request, 'ctreeProject/tree diagram.html' , {'json_string': json_string})
When I open the "view source" I noticed that the python string was changes into:
var treeData = [
{
{"children": [{"children": [{"parent": "Level 2: A", "name": "Son of A"}, {"parent": "Level 2: A", "name": "Daughter of A"}], "parent": "Top Level", "name": "Level yosh: A"}, {"parent": "Top Level", "name": "Level 2: B"}], "parent": "null", "name": "Top Level"}
}
];
while it should have been:
"name": "Top Level",
"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"
}
]
Any idea why the Python string is being changed?
Django escapes it. If you are sure about your json content you can use
var treeData = [
{
{{ json_string|safe }}
}
];
I am trying to compose a D3 pie component in each node of a tree.
I am able to build separately the tree and one pie, but I couldn't figure out how to compose them.
Basically, I have the following json data:
window.json = {
"health": [{
"value": 60
}, {
"value": 10
}, {
"value": 30
}],
"color": orange,
"children": [{
"health": [{
"value": 60
}, {
"value": 20
}, {
"value": 20
}],
"color": green
}, {
"health": [{
"value": 40
}, {
"value": 30
}, {
"value": 30
}],
"color": orange
}]
};
It represents the tree. Each node contains data for a pie: it's the "health" properties.
I've build the tree here: http://jsfiddle.net/4srt30pj/4/
I can build a single pie: http://jsfiddle.net/4srt30pj/5/
But I can't see how to mix them together, so that each node shows a pie. I've tried to create a function that draws a pie component:
function drawPie(selection, node) {
selection.data(node, function(d, i) {
console.log(node);
console.log(d);
console.log(i);
return pie(d.health);
})
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function (d, i) {
return color(i);
});
}
Then call it for each tree nodes:
drawPie(vis.selectAll("g.node"), nodes);
(the code is there: http://jsfiddle.net/4srt30pj/6/ )
But it doesn't show the pies.
Is it possible to achieve this composition?
You are close. Try:
function drawPie(d) {
d3.select(this)
.selectAll('path')
.data(pie(d.health))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(i);
});
}
nodeEnter.each(drawPie);
Full working sample:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
path.link {
fill: none;
stroke-width: 5px;
}
svg text {
font-family: Roboto, Arial;
}
.selected {
display: none;
}
</style>
</head>
<body>
<script>
var red = "#f5696d";
var green = "#40bc96";
var orange = "#fabd57";
window.json = {
"health": [{
"value": 60
}, {
"value": 10
}, {
"value": 30
}],
"color": orange,
"children": [{
"health": [{
"value": 60
}, {
"value": 20
}, {
"value": 20
}],
"color": green
}, {
"health": [{
"value": 40
}, {
"value": 30
}, {
"value": 30
}],
"color": orange
}]
};
var w = 100;
var h = 60;
var i = 0;
var root;
var tree = d3.layout.tree()
.nodeSize([w + 10, h + 20])
.separation(function(a, b) {
return (a.parent == b.parent ? 1 : 1.5);
});
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x, d.y];
});
var vis = d3.select("body").append("svg:svg")
.attr("width", 500)
.attr("height", 500)
.append("svg:g")
.attr("transform", "translate(" + 250 + "," + 30 + ")");
root = window.json;
root.x0 = 0;
root.y0 = 0;
function toggleAll(d) {
if (d.children) {
d.children.forEach(toggleAll);
toggle(d);
}
}
var arc = d3.svg.arc()
.outerRadius(30)
.innerRadius(0);
var pie = d3.layout.pie()
.value(function(d) {
return d.value;
})
.sort(null);
var color = d3.scale.ordinal()
.range(['#40bc96', '#fabd57', '#f5696d']);
function drawPie(d) {
d3.select(this)
.selectAll('path')
.data(pie(d.health))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(i);
});
}
update(root);
function update(source) {
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse();
// Update the nodes…
var node = vis.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("svg:g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
});
nodeEnter
.each(drawPie);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
// Update the links…
var link = vis.selectAll("path.link")
.data(tree.links(nodes), function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("svg:path", "g")
.attr("class", "link")
.style("stroke-opacity", 0.4)
.style("stroke", function(d) {
return d.target.color;
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.transition()
.duration(duration)
.attr("d", diagonal);
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
</script>
</body>
</html>