Question

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top