0

My understanding of d3 is currently rather limited and I am currently fiddling with a dendrogram example of D3.js. It can be found on several places: on this interactive version for example.

When I implement it, all goes fine until you try to update a property like the circle diameter of the nodes. If I do (using an interactive framework like AngularJS which watches the parameter change): the nodes change in size. So no problem yet. However if I then click one of the nodes, the size is reset to the initialization size instead of the new one.

when a node gets clicked it follow the click function:

 var nodeEnter = node.enter().append("g")
                        .attr("class", "dendrogramnode2")
                        .on("click", click);

The click function calls an update function.

        function click(d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else {
                d.children = d._children;
                d._children = null;
            }
            update(d);
        }

Which then updates the dendrogram and opens or closes the necessary nodes.

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root),
      links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * 80; });

  // Update the nodes…
  var node = svg.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      //.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
      .on("click", click);

  nodeEnter.append("circle")
      .attr("r", 1e-6)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeEnter.append("text")
      .attr("x", 10)
      .attr("dy", ".35em")
      .attr("text-anchor", "start")
      //.attr("transform", function(d) { return d.x < 180 ? "translate(0)" : "rotate(180)translate(-" + (d.name.length * 8.5)  + ")"; })
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })

  nodeUpdate.select("circle")
      .attr("r", 4.5)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeUpdate.select("text")
      .style("fill-opacity", 1)
      .attr("transform", function(d) { return d.x < 180 ? "translate(0)" : "rotate(180)translate(-" + (d.name.length + 50)  + ")"; });

  // TODO: appropriate transform
  var nodeExit = node.exit().transition()
      .duration(duration)
      //.attr("transform", function(d) { return "diagonal(" + source.y + "," + source.x + ")"; })
      .remove();

  nodeExit.select("circle")
      .attr("r", 1e-6);

  nodeExit.select("text")
      .style("fill-opacity", 1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0};
        return diagonal({source: o, target: o});
      });

  // Transition links to their new position.
  link.transition()
      .duration(duration)
      .attr("d", diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
      .duration(duration)
      .attr("d", function(d) {
        var o = {x: source.x, y: source.y};
        return diagonal({source: o, target: o});
      })
      .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

This update function is thus called:

  1. at initialization --> no problem (example: circle r = 5)

  2. when a parameter get updated --> no problem, the parameter updates. (example: circle r = 10)

  3. when you click a node --> problematic--> graph takes initial parameters.(example: circle r = 5)

All of this probably has a lot to do with scoping and how javascript handles databinding but I don't know how to do this properly. I either need to be able to access the new properties instead of the old ones.

Is there a way to either

  1. adapt the code so the new parameter is taken instead of the old ones?

  2. bind the parameters to the svg group as an object (probably not as efficient? ) so I can manually reach for it from whatever scope using the d3 selectors?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Sleenee
  • 594
  • 1
  • 8
  • 21
  • It would be more D3-like to make the radius part of the data bound to the circles. This should also fix the bug. – Lars Kotthoff Jun 05 '14 at 17:06
  • Very good remark Lars. However in that case adapting a parameter which does not need to change per data-point but is more like an overall property would: A) take more space in the DOM and B) when changing it, you would need to loop through the data in order alter it for ever data-point. So I thought there would be a more efficient way for "overhead" properties? – Sleenee Jun 06 '14 at 06:56
  • 1
    I don't think that A is significant as it's just a single number. I don't see how B would be a problem either, as you could simply set and use the property instead of using a number in the code you currently have. – Lars Kotthoff Jun 06 '14 at 16:13
  • A becomes significant if you put a $watch on your data in angularjs. As for B, yes that is what I did, thank you for the answer :). – Sleenee Jun 12 '14 at 12:59

1 Answers1

0

I have set a property to the DOM element for those attributes which don't differ per datum.

            function dograph(p){
                //paramater object is passed and it's name is "p"
                d3.select(elementid).selectAll("svg")[0][0]["p"] = p
                //at some point update function gets called as part of an event and p is no longer available. 

                //someSvgElement
                        .on("mouseover"){update())}

                function update(source) {
                    var p = d3.select(elementid).selectAll("svg")[0][0]["p"];
                    //stuff happens
                }
            }

might not be the perfect solution but it works for me.

Sleenee
  • 594
  • 1
  • 8
  • 21