Search code examples
javascriptjsond3.jsupdatingcircle-pack

how to update data form file json using d3.js (zoomable circle pack)


I'm working on a real-time visualization of incoming data stored in a json file. I use D3 for the visualization. This is the chart I use: http://mbostock.github.io/d3/talk/20111116/pack-hierarchy.html

enter image description here

This is all the code page:

<body onload="visualize()">

<h2>
    <input type="button" value="Get new data"
        onclick='ajaxSyncRequest("get-current-time")' /> <br /> <br />
    Message from server :: <span id="message"></span>
</h2>

<script type="text/javascript">

function ajaxSyncRequest(reqURL) {
    //Creating a new XMLHttpRequest object
    var xmlhttp;
    if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest(); //for IE7+, Firefox, Chrome, Opera, Safari
    } else {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); //for IE6, IE5
    }
    //Create a asynchronous GET request
    xmlhttp.open("GET", reqURL, false);
    xmlhttp.send(null);

    //Execution blocked till server send the response
    if (xmlhttp.readyState == 4) {
        if (xmlhttp.status == 200) {
            document.getElementById("message").innerHTML = xmlhttp.responseText;
            //alert(xmlhttp.responseText);
            update();

        } else {
            alert('Something is wrong !!');
        }
    }
}
</script>



<script type="text/javascript" src="d3/d3.js"></script>
<script type="text/javascript" src="d3/d3.layout.js"></script>
<script type="text/javascript">
    function visualize() {

        var w = 1280, h = 800, r = 720, x = d3.scale.linear().range(
                [ 0, r ]), y = d3.scale.linear().range([ 0, r ]), node, root;

        var pack = d3.layout.pack().size([ r, r ]).value(function(d) {
            return d.size;
        })

        var vis = d3.select("body").insert("svg:svg", "h2")
                .attr("width", w).attr("height", h).append("svg:g").attr(
                        "transform",
                        "translate(" + (w - r) / 2 + "," + (h - r) / 2
                                + ")");

        d3.json("flare.json", function(data) {
            node = root = data;

            var nodes = pack.nodes(root);

            vis.selectAll("circle").data(nodes).enter()
                    .append("svg:circle").attr("class", function(d) {
                        return d.children ? "parent" : "child";
                    }).attr("cx", function(d) {
                        return d.x;
                    }).attr("cy", function(d) {
                        return d.y;
                    }).attr("r", function(d) {
                        return d.r;
                    }).on("click", function(d) {
                        return zoom(node == d ? root : d);
                    });

            vis.selectAll("text").data(nodes).enter().append("svg:text")
                    .attr("class", function(d) {
                        return d.children ? "parent" : "child";
                    }).attr("x", function(d) {
                        return d.x;
                    }).attr("y", function(d) {
                        return d.y;
                    }).attr("dy", ".35em").attr("text-anchor", "middle")
                    .style("opacity", function(d) {
                        return d.r > 20 ? 1 : 0;
                    }).text(function(d) {
                        return d.name;
                    });

            d3.select(window).on("click", function() {
                zoom(root);
            });
        });

        function zoom(d, i) {
            var k = r / d.r / 2;
            x.domain([ d.x - d.r, d.x + d.r ]);
            y.domain([ d.y - d.r, d.y + d.r ]);

            var t = vis.transition().duration(d3.event.altKey ? 7500 : 750);

            t.selectAll("circle").attr("cx", function(d) {
                return x(d.x);
            }).attr("cy", function(d) {
                return y(d.y);
            }).attr("r", function(d) {
                return k * d.r;
            });

            t.selectAll("text").attr("x", function(d) {
                return x(d.x);
            }).attr("y", function(d) {
                return y(d.y);
            }).style("opacity", function(d) {
                return k * d.r > 20 ? 1 : 0;
            });

            node = d;
            d3.event.stopPropagation();
        }
    }
</script>

<script type="text/javascript">
    function update() {

        var w = 1280, h = 800, r = 720, x = d3.scale.linear().range(
                [ 0, r ]), y = d3.scale.linear().range([ 0, r ]), node, root;

        var pack = d3.layout.pack().size([ r, r ]).value(function(d) {
            return d.size;
        })

        var vis = d3.select("svg").attr("width", w).attr("height", h)
                .append("svg:g").attr(
                        "transform",
                        "translate(" + (w - r) / 2 + "," + (h - r) / 2
                                + ")");

        d3.json("flare.json", function(data) {

            node = root = data;

            var nodes = pack.nodes(root);
            // DATA JOIN
            // Join new data with old elements, if any.
            var newG = vis.selectAll("circle").data(nodes);

            // UPDATE
            // Update old elements as needed.
            newG.attr("class", "update").transition();

            // ENTER
            // Create new elements as needed.
            newG.enter().append("svg:circle").attr("class", function(d) {
                return d.children ? "parent" : "child";
            }).attr("cx", function(d) {
                return d.x;
            }).attr("cy", function(d) {
                return d.y;
            }).attr("r", function(d) {
                return d.r;
            }).on("click", function(d) {
                return zoom(node == d ? root : d);
            });

            newG.enter().append("svg:text")
            .attr("class", function(d) {
                return d.children ? "parent" : "child";
            }).attr("x", function(d) {
                return d.x;
            }).attr("y", function(d) {
                return d.y;
            }).attr("dy", ".35em").attr("text-anchor", "middle")
            .style("opacity", function(d) {
                return d.r > 20 ? 1 : 0;
            }).text(function(d) {
                return d.name;
            });

            // EXIT
            // Remove old elements as needed.
            newG.exit().attr("class", "exit").transition().remove();

            d3.select(window).on("click", function() {
                zoom(root);
            });
        });

        function zoom(d, i) {
            var k = r / d.r / 2;
            x.domain([ d.x - d.r, d.x + d.r ]);
            y.domain([ d.y - d.r, d.y + d.r ]);

            var t = vis.transition().duration(d3.event.altKey ? 7500 : 750);

            t.selectAll("circle").attr("cx", function(d) {
                return x(d.x);
            }).attr("cy", function(d) {
                return y(d.y);
            }).attr("r", function(d) {
                return k * d.r;
            });

            t.selectAll("text").attr("x", function(d) {
                return x(d.x);
            }).attr("y", function(d) {
                return y(d.y);
            }).style("opacity", function(d) {
                return k * d.r > 20 ? 1 : 0;
            });

            node = d;
            d3.event.stopPropagation();
        }
    }
</script>

I wanted to dynamically update the chart from the new file that I get from the server. But with this function it draw a new chart on the old. I have tried different solution to update the chart but none of them worked. How I have to modify the code to get the dynamically update?


Solution

  • The following changes are necessary to make this work. First, you need to select the existing SVG element (and its descendant g) as vis in the update function:

    var vis = d3.select("svg > g");
    

    Then, you need to compute and handle enter, update and exit selections for circles and text separately:

    var newG = vis.selectAll("circle").data(nodes);
    
    newG.enter().append("svg:circle");
    newG.exit().remove();
    newG.attr("class", function(d) {
        return d.children ? "parent" : "child";
      }).attr("cx", function(d) {
        return d.x;
      }).attr("cy", function(d) {
        return d.y;
      }).attr("r", function(d) {
        return d.r;
      }).on("click", function(d) {
        return zoom(node == d ? root : d);
      });
    
    var texts = vis.selectAll("text").data(nodes);
    
    texts.enter().append("svg:text");
    texts.exit().remove();
    texts.append("svg:text")
      .attr("class", function(d) {
        return d.children ? "parent" : "child";
      }).attr("x", function(d) {
        return d.x;
      }).attr("y", function(d) {
        return d.y;
      }).attr("dy", ".35em")
      .attr("text-anchor", "middle")
      .style("opacity", function(d) {
        return d.r > 20 ? 1 : 0;
      }).text(function(d) {
        return d.name;
      }); 
    

    You could also merge your functions as they both do almost the same thing and there's a lot of redundant code. But the above should be sufficient to make it work.