0

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;
}
Georgi Nikolov
  • 113
  • 3
  • 11
  • can you also provide a specimen of the data you are getting from `fileToRead` – Cyril Cherian Mar 30 '16 at 13:27
  • It also looks like you aren't calling the `update()` function, which is likely causing (potentially related) issues. – JSBob Mar 30 '16 at 14:09
  • @Cyril I have updated the post with a sample of data and the CSS for the website – Georgi Nikolov Mar 30 '16 at 14:14
  • @JSBob should i call the update() function after I exit() the svg or after I have enter() the data? Because I use the attr("class", "update") for the selected items in the svg – Georgi Nikolov Mar 30 '16 at 14:14
  • @GeorgiNikolov the way I did it when dealing with charts was `enter()` -> `update()` -> `exit()`. I'm not particularly sure if the exact order matters, but that's what worked for me. That was based off [this](http://bl.ocks.org/d3noob/8375092) example. – JSBob Mar 30 '16 at 14:19

1 Answers1

2

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

Cyril Cherian
  • 32,177
  • 7
  • 46
  • 55
  • The names of the children nodes will be unique for each parent, but two different parent nodes can have the same type of children. I will try to add unique ID for each node and see how that goes. – Georgi Nikolov Mar 30 '16 at 14:52
  • So after some work fiddling with unique IDs I managed to get it to work partially. I still encounter problems when updating that the borders or highlights (style attr defined via d3 or SCC) are not redrawn. Would you know what the reason for that is? – Georgi Nikolov Mar 31 '16 at 09:09
  • yes you are updating the attributes along with the append it will not reflect the style changes. To do that you need to do the update like this `circle. 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"; })` after new data is set read this https://medium.com/@c_behrens/enter-update-exit-6cafc6014c36#.hlvpvxbfy specially the **wrapping it up section** – Cyril Cherian Mar 31 '16 at 09:18
  • I tried to adapt the code but I don''t think it has to do with the style("fill") attribute but with the "class" one. It seems as if the old data objects loose the class assigned to them and only new elements get the right style from the CSS – Georgi Nikolov Mar 31 '16 at 11:32
  • 1
    Ok I found where the problem is, its because after selecting all circles or texts i do ("class", "update") which doesn't exist, if i remove that line they borders and shadows stay. – Georgi Nikolov Mar 31 '16 at 11:40