Search code examples
javascriptd3.jscircle-pack

d3 updating data from GUI (circle pack layout)


As far as I see update(),enter(),exit() are used to update the view from the data. Is there also a way in the other direction, updating the data from the GUI?

e.g. click on the B-circle, click remove -> will be removed from GUI, but is still present in the data.. by clicking refresh, it appears again:

http://jsfiddle.net/stefanf/6Qm2z/33/

'var nodesToRemove = pack.nodes(data);
    var sel = vis.selectAll("circle").data(nodesToRemove, function(d){
      return d.id;
    });
    sel.remove();'

p.s. I now I could do it by first updating data with javascript, however my question is if there is a appropriate d3-way..


Solution

  • I've done this working fiddle http://jsfiddle.net/6Qm2z/36/

    var dataGlobal =  {  
    "name": "Area",
        "id": 1,
        "size": 1,
      "children": [
        {
          "name": "F",
          "id": 2,
        "size": 1,
          "children": [
            {     "name": "C",
             "id": "3",
                  "size": 1
             },
                {
                  "name": "D",
                     "id": 4,
        "size": 1,
                   "children": [
                    {     "name": "A",
                     "id": 5,
    
                      "size": 0.5
                     },
                    {     "name": "B",
                      "id": 6,
                      "size": 1
                     }
                       ]
                },
               {
                  "name": "H",
                   "id": 7,
                  "size": 1
                }
              ]
              }
          ]
    };
    
    var w = 680,
        h = 600,
        r = 420,
        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 + ")");
    node = root = dataGlobal;
    
    var refreshGraph = function(newArray) {
        var nodes;
        if(newArray != null){
           nodes=newArray;
        }
        else{
            nodes = pack.nodes(root);    
        }
    
              var sel = vis.selectAll("circle")
                  .data(nodes, function(d) { return d.id; });
                sel
                  .enter().append("svg:circle");
                sel
                  .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("dblclick", function(d) { return zoom(node == d ? root : d); })
                  .on("click", function () {
                        d3.select(".selected").classed("selected", false);
                        d3.select(this).classed("selected", true);
                    });
                sel.exit().remove();
              sel = vis.selectAll("text")
                  .data(nodes,  function(d) { return d.name; });
                sel
                .enter().append("svg:text");
                sel
                  .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; });
                sel.exit().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();
    }
    
    d3.select('#refreshBtn').on("click", function(d) {         
        updatedRefresh(dataGlobal);
    });
    
    d3.select('#addBtn').on("click", function(d) {         
        obj = {
                  "name": document.getElementById('textBtn').value,
                   "id": document.getElementById('idBtn').value,
                  "size": 1
         }
         d3.select(".selected").datum().children.push(obj)
         refreshGraph()
    });
    
    d3.select("#deleteBtn").on("click", function() {
        d3.select(".selected").each(function(data){
            var nodesToRemove = pack.nodes(data);
    
            var sel = vis.selectAll("circle").data(nodesToRemove, function(d){
              return d.id;
            });
            sel.remove();
            vis.selectAll("text").data(nodesToRemove, function(d){
              return d.name;
            })
            .remove();
    
            dataGlobal.children = prune(dataGlobal.children, data.id);
        });
    
    });
    
    refreshGraph();
    
    function prune(array, label) {
        for (var i = 0; i < array.length; ++i) {
            var obj = array[i];
            if (obj.id === label) {
                // splice out 1 element starting at position i
                array.splice(i, 1);
                return array;
            }
            if (obj.children) {
                if (prune(obj.children, label)) {
                    if (obj.children.length === 0) {
                        // delete children property when empty
                        delete obj.children;
    
                        // or, to delete this parent altogether
                        // as a result of it having no more children
                        // do this instead
                        array.splice(i, 1);
                    }
                    return array;
                }
            }
        }
    }
    
    
    function updatedRefresh(newArray) {
       debugger;
        var nodes;
        if(newArray != null){
           nodes=pack.nodes(newArray);
        }
        else{
            nodes = pack.nodes(root);    
        }
    
              var sel = vis.selectAll("circle")
                  .data(nodes, function(d) { return d.id; });
                sel
                  .enter().append("svg:circle");
                sel
                  .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("dblclick", function(d) { return zoom(node == d ? root : d); })
                  .on("click", function () {
                        d3.select(".selected").classed("selected", false);
                        d3.select(this).classed("selected", true);
                    });
                sel.exit().remove();
              sel = vis.selectAll("text")
                  .data(nodes,  function(d) { return d.name; });
                sel
                .enter().append("svg:text");
                sel
                  .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; });
                sel.exit().remove();
    
              d3.select(window).on("click", function() { zoom(root); });
    }
    

    every time that you delete one item I update dataGlobal array with the prune function.

    Look at the updatedRefresh function to update with an array input