I have been trying to implemented a nested bubble chart for a project of mine where I have to display what happens in different clients in a network. I used the example showed at: https://bl.ocks.org/mbostock/7607535
Here is the code I am using:
/// <reference path="../bower_components/dt-d3/d3.d.ts" />
var margin = 20, diameter = 960;
var color = d3.scale.linear()
.domain([-1, 4])
.range(["#ff8080", "#b30000"])
.interpolate(d3.interpolateHcl);
var pack = d3.layout.pack()
.padding(2)
.size([diameter - margin, diameter - margin])
.value(function (d) { return d.size; });
var svg = d3.select("body").select("#parent").select("#svg1").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var svg2 = d3.select("body").select("#parent").select("#svg2").append("svg")
.attr("width", 100)
.attr("height", 100)
.append("g")
.attr("transform", "translate(" + margin + "," + margin + ")");
function draw(fileToRead){
console.log(fileToRead);
d3.json(fileToRead, function (error, root) {
if (error)
throw error;
var focus = root, nodes = pack.nodes(root), view;
var circle = svg.selectAll("circle").data(nodes);
circle.attr("class","update");
circle.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 ? d.children.length > 3 ? color(d.depth) : "white" : d.size > 0.02 ? "#660000" : "#003366"; })
.style("fill", function (d) { return d.children ? "white" : d.size > 0.02 ? "#660000" : "#003366"; })
.on("click", function (d) { if (focus !== d)
zoom(d), d3.event.stopPropagation(); });
circle.exit().remove();
var text = svg.selectAll("text").data(nodes);
text.attr("class","update");
text.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"; })
.style("fill", "black")
.text(function (d) { return d.name; });
text.exit().remove();
var node = svg.selectAll("circle,text");
d3.select("body")
.style("background", color(-1))
.on("click", function () { zoom(root); });
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus;
focus = d;
if (focus.depth == 2) {
circle2.style("fill", "white");
} else {
circle2.style("fill", "black");
}
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; })
.each("start", function (d) { if (d.parent === focus)
this.style.display = "inline"; })
.each("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; });
}
});
}
d3.select(self.frameElement).style("height", diameter + "px");
The problem I am encountering is when I continuously stream new data to the UI. Everytime I call up "Draw" with a full json file with all the data I have in the database. I have read that the enter(), exit(), remove() are responsible for updating the DOM elements, but it seems I am missing something. When the draw() function is called it seems to draw bubbles above existing ones and stuff like circle border color or text border color disappears. I am wondering if I am using the enter() and exit() commands in the right way. I would like to have the draw() function recieve the json file and only update info in the bubble graph that is new and no DOM elements exist for it, but it seems it just recreates all the DOM elements again.
Thank you in advance, Georgi
PS: I have two svg's becase I am trying to split the screen in two with the left side being occupied by the bubble graph and the right side with extra information shown when we zoom it at an internal bubble
EDIT1: Here is a sample of the data I pass to the draw() function:
{"name": "",
"children":
[{"name": "10.0.0.64", "children":
[{"name": "unidentified", "children":
[{"name": "datasource", "size": 0.002551020408163265}, {"name": "datasource", "size": 0.0031746031746031746}, {"name": "datasource", "size": 0.0017123779389460522}, {"name": "average", "size": 0.008550406895439414}, {"name": "datasource", "size": 0.019020266320109332}]},
{"name": "www.quefaire.be", "children":
[{"name": "average", "size": 0.002270490544565449}, {"name": "datasource", "size": 0.023243328100470965}]}, {"name": "tnsinternet.be", "children": [{"name": "average", "size": 0.002199967070440535}, {"name": "datasource", "size": 0.022009167303284966}]},
{"name": "hy.sachinese.com", "children":
[{"name": "average", "size": 0.0022121416372369493}, {"name": "datasource", "size": 0.022222222222222223}]}, {"name": "pagesdor.be", "children": [{"name": "average", "size": 0.0030411250824668935}, {"name": "datasource", "size": 0.03672943251374624}]},
{"name": "r.254a.com", "children":
[{"name": "average", "size": 0.0018942905665264935}, {"name": "datasource", "size": 0.015873015873015872}]},
{"name": "i.ctnsnet.com", "children":
[{"name": "average", "size": 0.0018942905665264935}, {"name": "datasource", "size": 0.015873015873015872}]},
{"name": "link.carrefour.eu", "children":
[{"name": "average", "size": 0.0019793245801319357}, {"name": "datasource", "size": 0.017857142857142856}]},
{"name": "goudengids.be", "children":
[{"name": "average", "size": 0.00457041828122788}, {"name": "datasource", "size": 0.06349206349206349}]}, {"name": "www.inmemoriam.be", "children": [{"name": "average", "size": 0.0018443560093702978}, {"name": "datasource", "size": 0.014707876206037973}]},
{"name": "www1.gfk-wi.com", "children":
[{"name": "datasource", "size": 0.011986645572622365}, {"name": "average", "size": 0.0017277318393667718}]}, {"name": "bootstrapcdn.com", "children": [{"name": "average", "size": 0.008550406895439414}, {"name": "datasource", "size": 0.13314186424076532}]},
{"name": "maxcdn.bootstrapcdn.com", "children":
[{"name": "datasource", "size": 0.031746031746031744}, {"name": "average", "size": 0.0027563593243117796}]}]}]}
Here is also my CSS:
.svg-container {
display: flex;
position: relative;
width: 100%;
padding-bottom: 100%; /* aspect ratio */
vertical-align: top;
overflow: hidden;
}
.svg-content-responsive {
display: inline-block;
position: absolute;
top: 10px;
left: 0;
}
.container {
float: left;
}
.node {
cursor: pointer;
stroke: darkgrey;
}
.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;
}
First of all ensure that all the nodes have unique names or must have some id to uniquely define a node. Use that unique key as shown below to identify the data uniquely. In you dataset my solution will not work reason is that few nodes have non unique names like dataset etc.
The below solution will work only if there is a unique name.
Instead of
var circle = svg.selectAll("circle").data(nodes)
;
do this
var circle = svg.selectAll("circle").data(nodes, function(d){return d.name});
and instead of this
var text = svg.selectAll("text").data(nodes);
do this
var text = svg.selectAll("text").data(nodes, function(d){return d.name});
the exit()
function is not able to work properly because of the above.
Read about the key function here