Updating a layout.pack in d3.js
Asked Answered
S

3

10

I am trying to wrap my mind around d3's pack layout (http://bl.ocks.org/4063530).

I have the basic layout working but I would like to update it with new data. i.e. collect new data, bind it to the current layout.pack and update accordingly (update/exit/enter).

My attempts are here (http://jsfiddle.net/emepyc/n4xk8/14/):

var bPack = function(vis) {
    var pack = d3.layout.pack()
    .size([400,400])
    .value(function(d) {return d.time});

    var node = vis.data([data])
    .selectAll("g.node")
    .data(pack.nodes)
    .enter()
    .append("g")
    .attr("class", function(d) { return d.children ? "node" : "leaf node"; })
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

node.append("circle")
    .attr("r", function(d) { return d.r });

    node.filter(function(d) { return !d.children; }).append("text")
    .attr("text-anchor", "middle")
    .attr("dy", ".3em")
    .text(function(d) { return d.analysis_id });

    bPack.update = function(new_data) {
        console.log("UPDATE");

        node
        .data([new_data])
        .selectAll("g.node")
        .data(pack.nodes);

        node
        .transition()
        .duration(1000)
        .attr("class", function(d) { return d.children ? "node" : "leaf node" })
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" });

        node.selectAll("circle")
        .data(new_data)
        .transition()
    .duration(1000)
    .attr("r", function(d) { return d.r; });

    };

Specific questions...

How do I bind the data? (since the data is not complex structure and not an array of data)

How can new nodes/leafs be added to the layout? And old ones removed?

Pointers to a working example would be greatly appreciated.

Sycee answered 10/2, 2013 at 2:40 Comment(3)
I know there's been a while since you asked the question, but could you perhaps check my solution out? I'm curious if its the right answer for your dilemmas. And maybe others are interested how your question is resolved, if its resolved... :)Wilmer
Yes, the answer is to let the pack layout do all the work for you. This was not obvious when I started working on this, but I found the solution working in another example (updating a tree layout). Thanks for the working example.Sycee
Glad that you managed to resolve the issue. Its quite often the case that the final solution is simple, but not obvious when you first tackle the problem. I like those cases a lot.Wilmer
W
10

Working example is here.

Basically, there is code for initial load, where all circles, tooltips, etc. are created and positioned in initial places. As well, the layout (pack) is created.

Than, on each button press, new data is loaded into pack, and the pack is recalculated. That crucial code is here:

Here you bind (load) now data into pack layout: (in my example its random data, of course you'll have your data from json or code or similar):

pack.value(function(d) { return 1 +
             Math.floor(Math.random()*501); });

Here the new layout is calculated:

pack.nodes(data);

After that, elements are transitioned to new positions, and its attributes are changed as you determine.

I just want to stress that I don't use enter/update/exit pattern or transformations (that you might see in others solutions), since I believe this introduces unnecessary complexity for examples like this.

Here are some pics with transition in action:

Start:

start

Transition:

transition

End:

end

Wilmer answered 29/12, 2013 at 12:21 Comment(3)
I'm struggling with the complexity you mentioned in your caveat about not using the enter() / exit() methods. I'm trying to get this to work and wondering if you could explain a bit about the nuances of the added complexity. Very much appreciate your example here!Canonry
Do you know if this is possible in D3 v4? I don't see the same functionality.Carnival
@gwg I have no idea unfortunately. Perhaps I would try something, but honestly I don't have time right now and in near future. I know there are differences wrt circle pack D3 v3 vs D3 v4. According to Murphy Law, this does not work and is not possible in D3 v4, and it will never be possible in any future D3 version ;) . But I will let you know if I find out something related and useful.Wilmer
O
4

I had the same problem recently, and came across the General Update Pattern tutorials as well. These did not serve my purpose. I had a few hundred DOM elements in a graph (ForceLayout), and I was receiving REST data back with properties for each individual node. Refreshing by rebinding data led to reconstruction of the entire graph, as you said in response to mg1075's suggestion. It tooks minutes to finish updating the DOM in my case.

I ended up assign unique ids to elements that need updating later, and I cherry picked them with JQuery. My entire graph setup uses D3, but then my updates don't. This feels bad, but it works just fine. Instead of taking minutes from destroying and recreating most of my DOM, it takes something like 3 seconds (leaving out timing for REST calls). I don't see a reason that something like property updates could not be made possible in D3.

Perhaps if Mike Bostock added a remove() or proper subselection function to the enter() selection, we could follow a pure D3 pattern to do updates. While figuring this out I was trying to bind a subset of data, the data with the new properties added, and then subselecting to get at elements that need updating, but it didn't work, due to the limited and specific nature of the enter() selection.

Obey answered 5/9, 2013 at 17:7 Comment(1)
I think Mike should just add a transparent, easy way to transition between different data in circles packing graph. I have a graph which I need to do a drill down (double-clicking a circle "dives" down) and I only get 1 level at a time from the server, so every double-click I request from the server that level's data and I want to have nicely animated drill-down to it.Westmoreland
C
3

Of relevance, if you have not already reviewed:
http://bl.ocks.org/3808218 - General Update Pattern, I
http://bl.ocks.org/3808221 - General Update Pattern, II
http://bl.ocks.org/3808234 - General Update Pattern, III

This sample fiddle has no transitions, but here is at least one approach for updating the data.

http://jsfiddle.net/jmKH6/

//  VISUALIZATION
var svg = d3.select("#kk")
    .append("svg")
    .attr("width", 500)
    .attr("height", 600)
    .attr("class", "pack"); 

var g = svg.append("g")
    .attr("transform", "translate(2,2)");

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

function update(data) {

    var nodeStringLenth = d3.selectAll("g.node").toString().length; 
    if ( nodeStringLenth > 0) {
        d3.selectAll("g.node")
            .remove();
    }

    var node = g.data([data]).selectAll("g.node")
            .data(pack.nodes);

        node.enter()
          .append("g")
            .attr("class", function(d) { return d.children ? "node" : "leaf node"; })
            .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

        node.append("circle")
            .attr("r", function(d) { return d.r });

        node.filter(function(d) { return !d.children; }).append("text")
            .attr("text-anchor", "middle")
            .attr("dy", ".3em")
            .text(function(d) { return d.analysis_id });

       node
            .exit()
            .remove();
}


var myData = [data1, data2, data3];
update(data1); 
setInterval(function() {
    update( myData[Math.floor(Math.random() * myData.length)] );  // https://mcmap.net/q/45447/-getting-a-random-value-from-a-javascript-array?lq=1
}, 1500);
Cauchy answered 10/2, 2013 at 8:34 Comment(1)
Thanks for the comment. What you are doing here is to remove all the nodes and build new ones on every update, not what I want. I am interested binding the data and use a transition (or maybe I should just alter the nodes "in place" with the new values/nodes without binding?).Sycee

© 2022 - 2024 — McMap. All rights reserved.