D3 force layout collapsing issue - javascript

I have this D3 instance. I'm trying nodes to collapse and hide its childrens when click but I'm getting "Uncaught NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node." when I click one.
Since the error happens on d3.js I'm having a big trouble debugging it.
Here's the code
window.onload=function(){
var width = $(window).width();
height = $(window).height();
var force = d3.layout.force()
.gravity(.2)
.charge(-500)
.size([width, height])
.linkDistance(250);
var svg = d3.select("body").append("svg:svg")
.attr("width", width)
.attr("height", height);
var root = getData();
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
nodes.forEach(function(d, i) {
d.x = width/2 + i;
d.y = height/2 + 100 * d.depth;
});
root.fixed = true;
root.x = width / 2;
root.y = height / 2;
force.nodes(nodes)
.links(links)
.start();
var link = svg.selectAll("line")
.data(links)
.enter()
.insert("svg:line")
.attr("class", "link");
var gnodes = svg.selectAll("circle.node")
.data(nodes)
.enter()
.append('g')
.classed('gnode', true);
var node = gnodes.append("circle")
.attr("class", "node")
.attr('data-name', function(d) { return d.name; })
.attr("r", 25)
.style("fill", '#FFFF44' )
.call(force.drag)
.on("click", click);;
var labels = gnodes.append("text")
.attr("text-anchor", "middle")
.attr('class', 'etiqueta')
.text( function(d) { return d.name; } );
force.on("tick", function(e) {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
/*
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
*/
gnodes.attr("transform", function(d) {
return 'translate(' + [d.x, d.y] + ')';
});
});
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
function update() {
console.log('updating');
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll("line.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links.
link.enter().insert("svg:line", ".node")
.attr("class", "link")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll("circle.node")
.data(nodes, function(d) { return d.id; })
.style("fill", color);
node.transition()
.attr("r", function(d) { return d.children ? 4.5 : Math.sqrt(d.size) / 10; });
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.children ? 4.5 : Math.sqrt(d.size) / 10; })
.style("fill", color)
.call(force.drag)
.on("click", click);
// Exit any old nodes.
node.exit().remove();
}
function flatten(root) {
var nodes = [];
function recurse(node, depth) {
if (node.children) {
node.children.forEach(function(child) {
recurse(child, depth + 1);
});
}
node.depth = depth;
nodes.push(node);
}
recurse(root, 1);
return nodes;
}
function getData() {
return {
"name": "flare",
"children": [{
"name": "analytics",
"children": [{
"name": "cluster",
"children": [{
"name": "AgglomerativeCluster",
"size": 3938
}, {
"name": "CommunityStructure",
"size": 3812
}, {
"name": "HierarchicalCluster",
"size": 6714
}, {
"name": "MergeEdge",
"size": 743
}]
}, {
"name": "graph",
"children": [{
"name": "BetweennessCentrality",
"size": 3534
}, {
"name": "LinkDistance",
"size": 5731
}, {
"name": "MaxFlowMinCut",
"size": 7840
}, {
"name": "ShortestPaths",
"size": 5914
}, {
"name": "SpanningTree",
"size": 3416
}]
}, {
"name": "optimization",
"children": [{
"name": "AspectRatioBanker",
"size": 7074
}]
}]
}, {
"name": "animate",
"children": [{
"name": "interpolate",
"children": [{
"name": "ArrayInterpolator",
"size": 1983
}, {
"name": "ColorInterpolator",
"size": 2047
}, {
"name": "DateInterpolator",
"size": 1375
}, {
"name": "Interpolator",
"size": 8746
}, {
"name": "MatrixInterpolator",
"size": 2202
}, {
"name": "NumberInterpolator",
"size": 1382
}, {
"name": "ObjectInterpolator",
"size": 1629
}, {
"name": "PointInterpolator",
"size": 1675
}, {
"name": "RectangleInterpolator",
"size": 2042
}]
}, {
"name": "ISchedulable",
"size": 1041
}, {
"name": "Parallel",
"size": 5176
}, {
"name": "Pause",
"size": 449
}, {
"name": "Scheduler",
"size": 5593
}, {
"name": "Sequence",
"size": 5534
}, {
"name": "Transition",
"size": 9201
}, {
"name": "Transitioner",
"size": 19975
}, {
"name": "TransitionEvent",
"size": 1116
}, {
"name": "Tween",
"size": 6006
}]
}, {
"name": "data",
"children": [{
"name": "converters",
"children": [{
"name": "Converters",
"size": 721
}, {
"name": "DelimitedTextConverter",
"size": 4294
}, {
"name": "GraphMLConverter",
"size": 9800
}, {
"name": "IDataConverter",
"size": 1314
}, {
"name": "JSONConverter",
"size": 2220
}]
}, {
"name": "DataField",
"size": 1759
}, {
"name": "DataSchema",
"size": 2165
}, {
"name": "DataSet",
"size": 586
}, {
"name": "DataSource",
"size": 3331
}, {
"name": "DataTable",
"size": 772
}, {
"name": "DataUtil",
"size": 3322
}]
}]
};
}
}

Related

Paging for D3 tree child nodes

I am trying to implement paging in d3 tree. Can anyone please help me with the same.
Please find below my code with screenshots of current and expected output.
The data_tree is the input data variable. This gets read by script below and produces hierarchy tree as showed in screenshot
data_tree = {
"Type": "Root",
"id": 0,
"name": "ERM",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 4,
"name": "RG",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 5,
"name": "WCR F",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Stem",
"id": 50,
"name": "WCR P",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Leaf",
"id": 8,
"name": "IBQA SD",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 9,
"name": "WCR EMS",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 28,
"name": "WCR DD & RGEC",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 29,
"name": "PMM",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 30,
"name": "PRRM",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 31,
"name": "WCR Rep",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 32,
"name": "RR",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 33,
"name": "CO",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 34,
"name": "LD",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 35,
"name": "ESRM",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 36,
"name": "TPCR",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 37,
"name": "InterA",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 38,
"name": "DR",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 39,
"name": "MAR",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 40,
"name": "BSLM",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 51,
"name": "CCR Meas",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 52,
"name": "WLP",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 53,
"name": "TTS Pro",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 54,
"name": "CI",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 55,
"name": "LL",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 56,
"name": "SP",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 57,
"name": "CC",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 58,
"name": "CRE",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 59,
"name": "Sec",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
},
{
"Type": "Leaf",
"id": 60,
"name": "SS",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
}
]
}]
},
{
"Type": "Stem",
"id": 10,
"name": "GCMP",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Leaf",
"id": 11,
"name": "WCR CMS",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
}]
},
{
"Type": "Stem",
"id": 12,
"name": "REAVP",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 13,
"name": "CREAVS",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Leaf",
"id": 14,
"name": "CREAVP",
"ParentDocType": "WholesaleCreditRisk",
"children": [
]
}]
}]
}
]
}]
};
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var root =JSON.parse(data_tree);
var i = 0,
duration = 750,
rectW = 150,
rectH = 50;
//var tree = d3.layout.tree().nodeSize([70, 40]);
var nodeWidth = 150;
var nodeHeight = 50;
var horizontalSeparationBetweenNodes = 16;
var verticalSeparationBetweenNodes = 128;
var tree = d3.layout.tree()
.nodeSize([nodeWidth + horizontalSeparationBetweenNodes, nodeHeight + verticalSeparationBetweenNodes])
.separation(function(a, b) {
return a.parent == b.parent ? 1 : 1.25;
});
/*var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x + rectW / 2, d.y + rectH / 6];
});*/
var diagonal = d3.svg.diagonal()
.target(function(d) {
var o = d.target;
o.y = o.y - 50
return o;
})
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH];
});
var svg = d3.select("#body").append("svg").attr("width", document.getElementById("body").style.width).attr("height", document.getElementById("body").style.height)
.call(zm = d3.behavior.zoom().scaleExtent([0.5, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 650 + "," + 20 + ")scale(0.7)");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([650, 20]);
root.x0 = 0;
root.y0 = height / 2;
d3.select("#myCheckbox").on("change", enableLink);
enableLink(root);
d3.select("#body").style("height", "800px");
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
function enableLink(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 * 125;
});
// 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", "blue")
.attr("rx", 4)
.attr("ry", 4)
.attr("stroke-width", 2)
.style("fill", function(d) {
if (d.ParentDocType == "EnterpriseWide") return "darkblue";
if (d.ParentDocType == "WholesaleCreditRisk") return "blue";
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
}).call(wrap, rectW - 10);
nodeEnter
.append("a")
.attr("xlink:href", function(d) {
return d.url;
});
// 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", "blue")
.attr("stroke-width", 2)
.style("fill", function(d) {
if (d.ParentDocType == "EnterpriseWide") return "darkblue";
if (d.ParentDocType == "WholesaleCreditRisk") return "blue";
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", "blue")
.attr("stroke-width", 2);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
d.y = d.y + 100;
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 + 100
};
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 + 100;
});
}
// 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;
}
enableLink(d);
}
//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 + ")");
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
fill: white;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked+.slider {
background-color: #2196F3;
}
input:focus+.slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.box {
float: left;
height: 20px;
width: 20px;
margin-bottom: 15px;
border: 1px solid black;
}
.darkblue {
background-color: darkblue;
}
.blue {
background-color: blue;
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.6.0/d3.min.js"></script>
</head>
<body>
<div id="body" style="border: 1px black solid; width:100%; height:600px;"></div>
</body>
Current output
Expected output
var root = {
"Type": "Root",
"id": 0,
"name": "ERM",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 4,
"name": "RG",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 5,
"name": "WCR F",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Stem",
"id": 50,
"name": "WCR P",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Leaf",
"id": 8,
"name": "IBQA SD",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 9,
"name": "WCR EMS",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 28,
"name": "WCR DD & RGEC",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 29,
"name": "PMM",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 30,
"name": "PRRM",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 31,
"name": "WCR Rep",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 32,
"name": "RR",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 33,
"name": "CO",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 34,
"name": "LD",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 35,
"name": "ESRM",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 36,
"name": "TPCR",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 37,
"name": "InterA",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 38,
"name": "DR",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 39,
"name": "MAR",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 40,
"name": "BSLM",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 51,
"name": "CCR Meas",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 52,
"name": "WLP",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 53,
"name": "TTS Pro",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 54,
"name": "CI",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 55,
"name": "LL",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 56,
"name": "SP",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 57,
"name": "CC",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 58,
"name": "CRE",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 59,
"name": "Sec",
"ParentDocType": "WholesaleCreditRisk",
"children": []
},
{
"Type": "Leaf",
"id": 60,
"name": "SS",
"ParentDocType": "WholesaleCreditRisk",
"children": []
}
]
}]
},
{
"Type": "Stem",
"id": 10,
"name": "GCMP",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Leaf",
"id": 11,
"name": "WCR CMS",
"ParentDocType": "WholesaleCreditRisk",
"children": []
}]
},
{
"Type": "Stem",
"id": 12,
"name": "REAVP",
"ParentDocType": "EnterpriseWide",
"children": [{
"Type": "Stem",
"id": 13,
"name": "CREAVS",
"ParentDocType": "WholesaleCreditRisk",
"children": [{
"Type": "Leaf",
"id": 14,
"name": "CREAVP",
"ParentDocType": "WholesaleCreditRisk",
"children": []
}]
}]
}
]
}]
};
function pageNodes(d) {
if (d.children) {
d.children.forEach(c => pageNodes(c));
if (d.children.length > pageNodes.maxNode) {
d.pages = {}
const count = pageNodes.maxNode - 1;
const l = Math.ceil(d.children.length / count);
for (let i = 0; i < l; i++) {
let startRange = i * count;
let endRange = i * count + count;
d.pages[i] = d.children.slice(startRange, endRange);
pageNodes.addNode(d.pages[i], "More...", {
__next: i != (l - 1) ? i + 1 : 0,
});
}
d.children = d.pages[0];
}
}
}
pageNodes.maxNode = 5;
pageNodes.addNode = function(children, name, more) {
let node = Object.assign({
Type: "Leaf",
id: children[children.length - 1].id + 10000,
ParentDocType: children[children.length - 1].ParentDocType,
name: name,
}, more);
children.push(node);
};
root.children.forEach(c => pageNodes(c));
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = window.innerWidth - margin.right - margin.left,
height = window.innerHeight - margin.top - margin.bottom;
var i = 0,
duration = 750,
rectW = 150,
rectH = 50;
// var tree = d3.layout.tree().nodeSize([70, 40]);
var nodeWidth = 150;
var nodeHeight = 50;
var horizontalSeparationBetweenNodes = 16;
var verticalSeparationBetweenNodes = 128;
var tree = d3.layout.tree()
.nodeSize([nodeWidth + horizontalSeparationBetweenNodes, nodeHeight + verticalSeparationBetweenNodes])
.separation(function(a, b) {
return a.parent == b.parent ? 1 : 1.25;
});
/*var diagonal = d3.svg.diagonal()
.projection(function (d) {
return [d.x + rectW / 2, d.y + rectH / 6];
});*/
var diagonal = d3.svg.diagonal()
.target(function(d) {
var o = d.target;
o.y = o.y - 50
return o;
})
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH];
});
var svg = d3.select("#body").append("svg").attr("width", document.getElementById("body").style.width).attr("height", document.getElementById("body").style.height)
.call(zm = d3.behavior.zoom().scaleExtent([0.5, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 650 + "," + 20 + ")scale(0.7)");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([650, 20]);
root.x0 = 0;
root.y0 = height / 2;
d3.select("#myCheckbox").on("change", enableLink);
enableLink(root);
d3.select("#body").style("height", "800px");
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
function enableLink(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 * 125;
});
// 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", "blue")
.attr("rx", 4)
.attr("ry", 4)
.attr("stroke-width", 2)
.style("fill", function(d) {
if (d.ParentDocType == "EnterpriseWide") return "darkblue";
if (d.ParentDocType == "WholesaleCreditRisk") return "blue";
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
}).call(wrap, rectW - 10);
nodeEnter
.append("a")
.attr("xlink:href", function(d) {
return d.url;
});
// 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", "blue")
.attr("stroke-width", 2)
.style("fill", function(d) {
if (d.ParentDocType == "EnterpriseWide") return "darkblue";
if (d.ParentDocType == "WholesaleCreditRisk") return "blue";
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", "blue")
.attr("stroke-width", 2);
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
d.y = d.y + 100;
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 + 100
};
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 + 100;
});
}
// Toggle children on click.
function click(d) {
if (d.hasOwnProperty('__next')) {
d.parent.children = d.parent.pages[d.__next];
} else if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
enableLink(d);
}
//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 + ")");
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
fill: white;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
body {
overflow: hidden;
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked+.slider {
background-color: #2196F3;
}
input:focus+.slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.box {
float: left;
height: 20px;
width: 20px;
margin-bottom: 15px;
border: 1px solid black;
}
.darkblue {
background-color: darkblue;
}
.blue {
background-color: blue;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id="body" style="border: 1px black solid; width:100%; height:600px;"></div>

How to draw arrows between circles produced by CirclePacking d3.js?

I am using the Zoomable CirclePacking layout provided by d3.js. Each of my circles(at all levels) is uniquely identifiable.
I need to write a generic function that can draw svg arrows(with a text label) from any circle at any level to any other circle. How do I go about doing this?
Here is an example code snippet which connects nodes inside each circle with its parent circle on click.
var svg = d3.select("svg"),
margin = 20,
diameter = +svg.attr("width"),
g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
svg.append('svg:defs').append('svg:marker')
.attr('id', 'head')
.attr('orient', 'auto')
.attr('markerWidth', '2')
.attr('markerHeight', '4')
.attr('refX', '0.1')
.attr('refY', '2')
.append('marker:polygon').attr('points', '0,0 0,4 2,2').attr('fill', 'red');
var color = d3.scaleLinear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.pack()
.size([diameter - margin, diameter - margin])
.padding(2);
var root = {
"name": "flare",
"children": [{
"name": "analytics",
"children": [{
"name": "cluster",
"children": [{
"name": "AgglomerativeCluster",
"size": 3938
}, {
"name": "CommunityStructure",
"size": 3812
}]
}, {
"name": "graph",
"children": [{
"name": "BetweennessCentrality",
"size": 3534
}]
}, {
"name": "optimization",
"children": [{
"name": "AspectRatioBanker",
"size": 7074
}]
}]
}, {
"name": "animate",
"children": [{
"name": "Easing",
"size": 17010
}, {
"name": "FunctionSequence",
"size": 5842
}, {
"name": "interpolate",
"children": [{
"name": "ArrayInterpolator",
"size": 1983
}, {
"name": "ColorInterpolator",
"size": 2047
}]
}, {
"name": "Parallel",
"size": 5176
}]
}, {
"name": "data",
"children": [{
"name": "converters",
"children": [{
"name": "Converters",
"size": 721
}]
}, {
"name": "DataField",
"size": 1759
}]
}, {
"name": "display",
"children": [{
"name": "DirtySprite",
"size": 8833
}, {
"name": "LineSprite",
"size": 1732
}]
}, {
"name": "flex",
"children": [{
"name": "FlareVis",
"size": 4116
}]
}, {
"name": "physics",
"children": [{
"name": "DragForce",
"size": 1082
}, {
"name": "GravityForce",
"size": 1336
}]
}, {
"name": "query",
"children": [{
"name": "AggregateExpression",
"size": 1616
}, {
"name": "And",
"size": 1027
}, {
"name": "methods",
"children": [{
"name": "add",
"size": 593
}]
}, {
"name": "Minimum",
"size": 843
}]
}, {
"name": "util",
"children": [{
"name": "Arrays",
"size": 8258
}, {
"name": "Colors",
"size": 10001
}, {
"name": "heap",
"children": [{
"name": "FibonacciHeap",
"size": 9354
}, {
"name": "HeapNode",
"size": 1233
}]
}]
}, {
"name": "vis",
"children": [{
"name": "axis",
"children": [{
"name": "Axes",
"size": 1302
}, {
"name": "Axis",
"size": 24593
}]
}, {
"name": "controls",
"children": [{
"name": "AnchorControl",
"size": 2138
}, {
"name": "ClickControl",
"size": 3824
}]
}, {
"name": "data",
"children": [{
"name": "Data",
"size": 20544
}, {
"name": "render",
"root": true,
"children": [{
"name": "ArrowType",
"size": 698
}, {
"name": "EdgeRenderer",
"size": 5569
}]
}]
}, {
"name": "events",
"children": [{
"name": "DataEvent",
"size": 2313
}, {
"name": "SelectionEvent",
"size": 1880
}]
}, {
"name": "legend",
"children": [{
"name": "Legend",
"size": 20859
}, {
"name": "LegendItem",
"size": 4614
}]
}, {
"name": "operator",
"children": [{
"name": "distortion",
"children": [{
"name": "BifocalDistortion",
"size": 4461
}, {
"name": "Distortion",
"size": 6314
}]
}, {
"name": "encoder",
"children": [{
"name": "ColorEncoder",
"size": 3179
}, {
"name": "Encoder",
"size": 4060
}]
}, {
"name": "filter",
"children": [{
"name": "FisheyeTreeFilter",
"size": 5219
}, {
"name": "GraphDistanceFilter",
"size": 3165
}]
}, {
"name": "IOperator",
"size": 1286
}, {
"name": "label",
"children": [{
"name": "Labeler",
"size": 9956
}, {
"name": "RadialLabeler",
"size": 3899
}]
}, {
"name": "layout",
"children": [{
"name": "AxisLayout",
"size": 6725
}, {
"name": "BundledEdgeRouter",
"size": 3727
}]
}, {
"name": "Operator",
"size": 2490
}, {
"name": "OperatorList",
"size": 5248
}]
}, {
"name": "Visualization",
"size": 16540
}]
}]
};
root = d3.hierarchy(root)
.sum(function(d) {
return d.size;
})
.sort(function(a, b) {
return b.value - a.value;
});
var focus = root,
nodes = pack(root).descendants(),
view;
var circle = g.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function(d) {
return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root";
})
.style("fill", function(d) {
return d.children ? color(d.depth) : null;
})
.on("click", function(d) {
if (focus !== d) zoom(d), d3.event.stopPropagation();
});
var text = g.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) {
return d.parent === root ? 1 : 0;
})
.style("display", function(d) {
return d.parent === root ? "inline" : "none";
})
.text(function(d) {
return d.data.name;
});
var node = g.selectAll("circle,text");
svg
.style("background", color(-1))
.on("click", function() {
zoom(root);
});
zoomTo([root.x, root.y, root.r * 2 + margin]);
var activeNode = root;
function zoom(d) {
var focus0 = focus;
focus = d;
activeNode = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) {
zoomTo(i(t));
};
});
transition.selectAll("text")
.filter(function(d) {
return d.parent === focus || this.style.display === "inline";
})
.style("fill-opacity", function(d) {
return d.parent === focus ? 1 : 0;
})
.on("start", function(d) {
if (d.parent === focus) this.style.display = "inline";
})
.on("end", function(d) {
if (d.parent !== focus) this.style.display = "none";
});
}
function zoomTo(v) {
var k = diameter / v[2];
view = v;
node.attr("transform", function(d) {
return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
});
circle.attr("r", function(d) {
return d.r * k;
});
if (activeNode) {
g.selectAll("path").remove();
g.selectAll("path").data(activeNode.children).enter().append("svg:path")
.attr('d', function(d) {
var x = (d.x - v[0]) * k;
var y = (d.y - v[1]) * k;
var fX = (activeNode.x - v[0]) * k;
var fY = (activeNode.y - activeNode.r - v[1]) * k;
return 'M ' + fX + ' ' + -fY + ' Q ' + (parseInt(fX) + 20) + ' ' + y / 2 + ' ' + x + ' ' + y
})
.attr("style", function(d) {
return "stroke:#4169E1;stroke-width:4;fill:none;";
}).attr('marker-end', 'url(#head)');
}
}
.node {
cursor: pointer;
}
.node:hover {
stroke: #000;
stroke-width: 1.5px;
}
.node--leaf {
fill: white;
}
.label {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-anchor: middle;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
.label,
.node--root,
.node--leaf {
pointer-events: none;
}
<svg width="300" height="300"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>

d3 zoom function issues in v4

I am facing issues with the zoom function in D3 while using v4. It throws up an error saying that zoom.translate is not defined. I am mostly using the following code from this answer d3 focus on node on click, which worked perfectly for v3. However, as I was having issues with v3 as it has restrictions with data where the source and nodes are in the form of strings(instead of indices) D3 JSON file with source and index as strings rather than indices, I switched to v4.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height")
active = d3.select(null);
var zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json("graph.json", function(error, graph) {
if (error) throw error;
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", clicked);
node.append("title")
.text(function(d) { return d.id; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
});
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function clicked(d){
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
var bbox = active.node().getBBox(),
bounds = [[bbox.x, bbox.y],[bbox.x + bbox.width, bbox.y + bbox.height]];
var dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = Math.max(1, Math.min(8, 0.9 / Math.max(dx / width, dy / height))),
translate = [width / 2 - scale * x, height / 2 - scale * y];
svg.transition()
.duration(750)
.call(zoom.translate(translate).scale(scale).event);
}
function reset() {
active.classed("active", false);
active = d3.select(null);
svg.transition()
.duration(750)
.call(zoom.translate([0, 0]).scale(1).event);
}
function zoomed() {
console.log(d3.event)
g.style("stroke-width", 1.5 / d3.event.scale + "px");
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
</script>
I changed d3.behaviour.zoom() to d3.zoom() and even changed
.call(zoom.translate(translate).scale(scale).event);
to
.call(d3.zoom().on("zoom", function () {
svg.attr("transform", d3.event.transform)
}));
which throws up a strange error, error: unknown type: wheel
What would be the best way to go about overcoming this situation?
In d3 version 4 the correct way to do this is:
function clicked(d) {
if (active.node() === this){
active.classed("active", false);
return reset();
}
active = d3.select(this).classed("active", true);
svg.transition()
.duration(750)
.call(zoom.transform,
d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(8)
.translate(-(+active.attr('cx')), -(+active.attr('cy')))
);
}
Where your zoom handler is:
function zoomed() {
g.attr("transform", d3.event.transform);
}
Note, I simplified the transform calculation from my previous answer. The bounds calculations there were not really necessary.
Full code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #aaa;
}
.nodes circle {
pointer-events: all;
stroke: none;
stroke-width: 40px;
}
.active {
fill: yellow;
}
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var zoom = d3.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed);
var g = svg.append("g");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var graph = {
"nodes": [{
"id": "Myriel",
"group": 1
}, {
"id": "Napoleon",
"group": 1
}, {
"id": "Mlle.Baptistine",
"group": 1
}, {
"id": "Mme.Magloire",
"group": 1
}, {
"id": "CountessdeLo",
"group": 1
}, {
"id": "Geborand",
"group": 1
}, {
"id": "Champtercier",
"group": 1
}, {
"id": "Cravatte",
"group": 1
}, {
"id": "Count",
"group": 1
}, {
"id": "OldMan",
"group": 1
}, {
"id": "Labarre",
"group": 2
}, {
"id": "Valjean",
"group": 2
}, {
"id": "Marguerite",
"group": 3
}, {
"id": "Mme.deR",
"group": 2
}, {
"id": "Isabeau",
"group": 2
}, {
"id": "Gervais",
"group": 2
}, {
"id": "Tholomyes",
"group": 3
}, {
"id": "Listolier",
"group": 3
}, {
"id": "Fameuil",
"group": 3
}, {
"id": "Blacheville",
"group": 3
}, {
"id": "Favourite",
"group": 3
}, {
"id": "Dahlia",
"group": 3
}, {
"id": "Zephine",
"group": 3
}, {
"id": "Fantine",
"group": 3
}, {
"id": "Mme.Thenardier",
"group": 4
}, {
"id": "Thenardier",
"group": 4
}, {
"id": "Cosette",
"group": 5
}, {
"id": "Javert",
"group": 4
}, {
"id": "Fauchelevent",
"group": 0
}, {
"id": "Bamatabois",
"group": 2
}, {
"id": "Perpetue",
"group": 3
}, {
"id": "Simplice",
"group": 2
}, {
"id": "Scaufflaire",
"group": 2
}, {
"id": "Woman1",
"group": 2
}, {
"id": "Judge",
"group": 2
}, {
"id": "Champmathieu",
"group": 2
}, {
"id": "Brevet",
"group": 2
}, {
"id": "Chenildieu",
"group": 2
}, {
"id": "Cochepaille",
"group": 2
}, {
"id": "Pontmercy",
"group": 4
}, {
"id": "Boulatruelle",
"group": 6
}, {
"id": "Eponine",
"group": 4
}, {
"id": "Anzelma",
"group": 4
}, {
"id": "Woman2",
"group": 5
}, {
"id": "MotherInnocent",
"group": 0
}, {
"id": "Gribier",
"group": 0
}, {
"id": "Jondrette",
"group": 7
}, {
"id": "Mme.Burgon",
"group": 7
}, {
"id": "Gavroche",
"group": 8
}, {
"id": "Gillenormand",
"group": 5
}, {
"id": "Magnon",
"group": 5
}, {
"id": "Mlle.Gillenormand",
"group": 5
}, {
"id": "Mme.Pontmercy",
"group": 5
}, {
"id": "Mlle.Vaubois",
"group": 5
}, {
"id": "Lt.Gillenormand",
"group": 5
}, {
"id": "Marius",
"group": 8
}, {
"id": "BaronessT",
"group": 5
}, {
"id": "Mabeuf",
"group": 8
}, {
"id": "Enjolras",
"group": 8
}, {
"id": "Combeferre",
"group": 8
}, {
"id": "Prouvaire",
"group": 8
}, {
"id": "Feuilly",
"group": 8
}, {
"id": "Courfeyrac",
"group": 8
}, {
"id": "Bahorel",
"group": 8
}, {
"id": "Bossuet",
"group": 8
}, {
"id": "Joly",
"group": 8
}, {
"id": "Grantaire",
"group": 8
}, {
"id": "MotherPlutarch",
"group": 9
}, {
"id": "Gueulemer",
"group": 4
}, {
"id": "Babet",
"group": 4
}, {
"id": "Claquesous",
"group": 4
}, {
"id": "Montparnasse",
"group": 4
}, {
"id": "Toussaint",
"group": 5
}, {
"id": "Child1",
"group": 10
}, {
"id": "Child2",
"group": 10
}, {
"id": "Brujon",
"group": 4
}, {
"id": "Mme.Hucheloup",
"group": 8
}],
"links": [{
"source": "Napoleon",
"target": "Myriel",
"value": 1
}, {
"source": "Mlle.Baptistine",
"target": "Myriel",
"value": 8
}, {
"source": "Mme.Magloire",
"target": "Myriel",
"value": 10
}]
}
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line");
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 2.5)
.on('click', clicked);
node.append("title")
.text(function(d) {
return d.id;
});
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
var active = d3.select(null);
function clicked(d) {
if (active.node() === this){
active.classed("active", false);
return reset();
}
active = d3.select(this).classed("active", true);
svg.transition()
.duration(750)
.call(zoom.transform,
d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(8)
.translate(-(+active.attr('cx')), -(+active.attr('cy')))
);
}
function reset() {
svg.transition()
.duration(750)
.call(zoom.transform,
d3.zoomIdentity
.translate(0, 0)
.scale(1)
);
}
function zoomed() {
g.attr("transform", d3.event.transform);
}
</script>

d3.js: How to change a nodes' representation in a force-layout graph

I am trying to expand this force layout example by changing a nodes' shape from circle to rectangle when it is clicked. So I don't want to change any data but just want to replace the corresponding SVG element.
One of my approaches looked like this:
node.on("click", function() {
this.remove();
svg.selectAll(".node").data(graph.nodes).enter().append("rect")
.attr("class", "node")
.attr("width", 5).attr("height", 5)
.style("fill", function(d) { return color(d.group); });
});
So I removed the SVG element from the DOM and rebound the data, adding a rectangle for the now missing node.
Unfortunately this does not work (the force layout does not set any properties on the new element) and I have no idea if this a reasonable approach at all.
Any ideas how to do this properly?
Try this way.
node.on("click", function(d) {
var size = d.weight * 2 + 12;
d3.select(this).select("circle").remove();
d3.select(this).append("rect")
.attr("x", -(size / 2))
.attr("y", -(size / 2))
.attr("height", size)
.attr("width", size)
.style("fill", function(d) {
return color(1 / d.rating);
});
});
Working code snippet -
var graph = {
"nodes": [{
"name": "1",
"rating": 90,
"id": 2951
}, {
"name": "2",
"rating": 80,
"id": 654654
}, {
"name": "3",
"rating": 80,
"id": 6546544
}, {
"name": "4",
"rating": 1,
"id": 68987978
}, {
"name": "5",
"rating": 1,
"id": 9878933
}, {
"name": "6",
"rating": 1,
"id": 6161
}, {
"name": "7",
"rating": 1,
"id": 64654
}, {
"name": "8",
"rating": 20,
"id": 354654
}, {
"name": "9",
"rating": 50,
"id": 8494
}, {
"name": "10",
"rating": 1,
"id": 6846874
}, {
"name": "11",
"rating": 1,
"id": 5487
}, {
"name": "12",
"rating": 80,
"id": "parfum_kenzo"
}, {
"name": "13",
"rating": 1,
"id": 65465465
}, {
"name": "14",
"rating": 90,
"id": "jungle_de_kenzo"
}, {
"name": "15",
"rating": 20,
"id": 313514
}, {
"name": "16",
"rating": 40,
"id": 36543614
}, {
"name": "17",
"rating": 100,
"id": "Yann_YA645"
}, {
"name": "18",
"rating": 1,
"id": 97413
}, {
"name": "19",
"rating": 1,
"id": 97414
}, {
"name": "20",
"rating": 100,
"id": 976431231
}, {
"name": "21",
"rating": 1,
"id": 9416
}, {
"name": "22",
"rating": 1,
"id": 998949
}, {
"name": "23",
"rating": 100,
"id": 984941
}, {
"name": "24",
"rating": 100,
"id": "99843"
}, {
"name": "25",
"rating": 1,
"id": 94915
}, {
"name": "26",
"rating": 1,
"id": 913134
}, {
"name": "27",
"rating": 1,
"id": 9134371
}],
"links": [{
"source": 6,
"target": 5,
"value": 6,
"label": "publishedOn"
}, {
"source": 8,
"target": 5,
"value": 6,
"label": "publishedOn"
}, {
"source": 7,
"target": 1,
"value": 4,
"label": "containsKeyword"
}, {
"source": 8,
"target": 10,
"value": 3,
"label": "containsKeyword"
}, {
"source": 7,
"target": 14,
"value": 4,
"label": "publishedBy"
}, {
"source": 8,
"target": 15,
"value": 6,
"label": "publishedBy"
}, {
"source": 9,
"target": 1,
"value": 6,
"label": "depicts"
}, {
"source": 10,
"target": 1,
"value": 6,
"label": "depicts"
}, {
"source": 16,
"target": 1,
"value": 6,
"label": "manageWebsite"
}, {
"source": 16,
"target": 2,
"value": 5,
"label": "manageWebsite"
}, {
"source": 16,
"target": 3,
"value": 6,
"label": "manageWebsite"
}, {
"source": 16,
"target": 4,
"value": 6,
"label": "manageWebsite"
}, {
"source": 19,
"target": 18,
"value": 2,
"label": "postedOn"
}, {
"source": 18,
"target": 1,
"value": 6,
"label": "childOf"
}, {
"source": 17,
"target": 19,
"value": 8,
"label": "describes"
}, {
"source": 18,
"target": 11,
"value": 6,
"label": "containsKeyword"
}, {
"source": 17,
"target": 13,
"value": 3,
"label": "containsKeyword"
}, {
"source": 20,
"target": 13,
"value": 3,
"label": "containsKeyword"
}, {
"source": 20,
"target": 21,
"value": 3,
"label": "postedOn"
}, {
"source": 22,
"target": 20,
"value": 3,
"label": "postedOn"
}, {
"source": 23,
"target": 21,
"value": 3,
"label": "manageWebsite"
}, {
"source": 23,
"target": 24,
"value": 3,
"label": "manageWebsite"
}, {
"source": 23,
"target": 25,
"value": 3,
"label": "manageWebsite"
}, {
"source": 23,
"target": 26,
"value": 3,
"label": "manageWebsite"
}]
}
var margin = {
top: -5,
right: -5,
bottom: -5,
left: -5
};
var width = 500 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-200)
.linkDistance(50)
.size([width + margin.left + margin.right, height + margin.top + margin.bottom]);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var drag = d3.behavior.drag()
.origin(function(d) {
return d;
})
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
var svg = d3.select("#map").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
//d3.json('http://blt909.free.fr/wd/map2.json', function(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.call(drag);
node.append("circle")
.attr("r", function(d) {
return d.weight * 2 + 12;
})
.style("fill", function(d) {
return color(1 / d.rating);
});
force.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index];
}
node.on("click", function(d) {
var size = d.weight * 2 + 12;
d3.select(this).select("circle").remove();
d3.select(this).append("rect")
.attr("x", -(size / 2))
.attr("y", -(size / 2))
.attr("height", size)
.attr("width", size)
.style("fill", function(d) {
return color(1 / d.rating);
});
});
function dottype(d) {
d.x = +d.x;
d.y = +d.y;
return d;
}
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
force.start();
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.node-active {
stroke: #555;
stroke-width: 1.5px;
}
.link {
stroke: #555;
stroke-opacity: .3;
}
.link-active {
stroke-opacity: 1;
}
.overlay {
fill: none;
pointer-events: all;
}
#map {
border: 2px #555 dashed;
width: 500px;
height: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body>
<div id="map"></div>
</body>
You've updated the dom elements, but not the array used by the force layout algorithm, so do this as the last line in the on click function:
graph.nodes = svg.selectAll(".node");
(You might also find without a data key function that random nodes get changed to rectangles rather than the one you clicked)

Position several sets of circles on same SVG with D3.JS

Small circles with different radius grouped in bigger circles
Here is what I have so far - one circle: http://jsfiddle.net/dmitrychuba/hqy6q6qv/
(function () {
var rand = function (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
var width = 400,
height = 400,
root = {
"name": "A",
"size": 12323,
"children": [{
"name": "B",
"size": 3938
}, {
"name": "C",
"size": 3812
}, {
"name": "D",
"size": 6714
}, {
"name": "E",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
},
]
};
var force = d3.layout.force()
.linkDistance(function (d) {
return 100;
}) // link distance between the nodes
.charge(-200) // charge that repel nodes from each other
.gravity(0.1)
.size([width, height])
.on("tick", tick);
var svg = d3.select("#main-container").append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
//Fix the position of the nodes and doesnt allow them to move out of the screen
flatten(root); //to set ids
update();
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force.nodes(nodes)
.links(links)
.start();
// Update nodes.
node = node.data(nodes, function (d) {
return d.id;
});
node.exit().remove();
var nodeEnter = node.enter().append("g")
.attr("class", "node");
//Adjusting the node sizes according to the children
nodeEnter.append("circle")
.style("display", function (d) {
return d.children ? "none" : "";
})
.attr("stroke", 'black')
.attr("r", function (d) {
var r = rand(6, 18);
return d._children ? d.size ? 14 : 18 : d.children ? 24 : r;
});
nodeEnter.append("text")
.attr("dy", ".35em")
.text(function (d) {
return d.name;
}).style("display", function (d) {
return d.children ? "none" : "";
})
.style("font-size", function (d) {
return 10;
});
node.select("circle")
.style("fill", color);
}
function tick() {
link.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function color(d) {
return "#fd8d3c";
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [],
i = 0;
function recurse(node) {
if (node.children) node.children.forEach(recurse);
if (!node.id) node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
setInterval(function () {
force.alpha(.1);
}, 100);
})();
In order to make several circles I just do 'for' loop here: http://jsfiddle.net/dmitrychuba/hm72c74a/, but this way I have several SVG elements, which is not very good as I can't get them position like on image above(closer to each other). And also I will need few smaller solid circles plus future ability to zoom-in/out.
So my question is: is there a way to have multiple sets of circles on same SVG and with few solid circles and with ability to zoom-in/out?
Thanks
Dmitry
It is better to put all circle groups and individual circles inside a group element rather than putting them as separate SVG. Try as shown below.
var rand = function(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
var width = 400,
height = 400,
root = {
"name": "A",
"size": 12323,
"children": [{
"name": "B",
"size": 3938
}, {
"name": "C",
"size": 3812
}, {
"name": "D",
"size": 6714
}, {
"name": "E",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
}, {
"name": "D1",
"size": 6714
}, {
"name": "E1",
"size": 743
}, {
"name": "B1",
"size": 3938
}, {
"name": "C1",
"size": 3812
},
]
};
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var color = d3.scale.category10();
var svg = d3.select("#main-container").append("svg")
.attr("width", width * 3)
.attr("height", height * 2)
.call(zoom);
var mainContainer = svg.append("g");
function zoomed() {
mainContainer.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
var positions = [
[0, 0],
[width, 0],
[width * 2, 0],
[width - 200, height],
[width * 2 - 200, height]
];
function addCircle(x, y) {
mainContainer.append("circle")
.style("fill", "#3D91B2")
.attr("cx", x)
.attr("cy", y)
.attr("r", 50);
}
addCircle(width, height - 70);
addCircle(width * 2, height - 70);
for (var i = 0; i < 5; i++)
(function() {
var force = d3.layout.force()
.linkDistance(function(d) {
return 100;
}) // link distance between the nodes
.charge(-200) // charge that repel nodes from each other
.gravity(0.1)
.size([width, height])
.on("tick", tick);
var gContainer = mainContainer.append("g")
.attr("transform", "translate(" + positions[i][0] + "," + positions[i][1] + ")");
var link = gContainer.selectAll(".link"),
node = gContainer.selectAll(".node");
//Fix the position of the nodes and doesnt allow them to move out of the screen
flatten(root); //to set ids
update(i);
function update(colorIdx) {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force.nodes(nodes)
.links(links)
.start();
// Update nodes.
node = node.data(nodes, function(d) {
return d.id;
});
node.exit().remove();
var nodeEnter = node.enter().append("g")
.attr("class", "node");
//Adjusting the node sizes according to the children
nodeEnter.append("circle")
.style("display", function(d) {
return d.children ? "none" : "";
})
.attr("stroke", 'black')
.attr("r", function(d) {
var r = rand(6, 18);
return d._children ? d.size ? 14 : 18 : d.children ? 24 : r;
});
nodeEnter.append("text")
.attr("dy", ".35em")
.text(function(d) {
return d.name;
}).style("display", function(d) {
return d.children ? "none" : "";
})
.style("font-size", function(d) {
return 10;
});
node.select("circle")
.style("fill", color(colorIdx));
}
function tick() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [],
i = 0;
function recurse(node) {
if (node.children) node.children.forEach(recurse);
if (!node.id) node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="main-container"></div>

Categories