4

I'm following the General Update Pattern but having an issue with regards to layering.

Using a circle-pack layout, I pack the new data, update, enter and exit the circle elements. However, when new elements enter, they overlap the updated circles.

Data key function is based on element name:

.data(nodes, function(d, i) { return d.name; });

So my circle pack has a spot for the updated circle (of the correct location and size) but it's hidden behind its newly entered parent circle.

selecting the hidden node

node not visible in circle-pack

Is there a way to send these updated nodes to the front or redraw them over the entered circles?

--UPDATE-- As suggested by the person who closed this issue, I've tried implementing the linked to solution using moveToFront.

I added the following code in my update section (which didn't change anything) and then tried adding it after the enter and exit code, which also didn't make any difference.

.each("end", function(d){ d3.select(this).moveToFront(); });


d3.selection.prototype.moveToFront = function() {
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};

For clarity, this is what the selection and update looks like:

// Load data into svg, join new data with old elements, if any.
var nodes = pack.nodes(postData);
node = root = postData;

groupNodes = svg.selectAll("g")
  .data(nodes, function(d, i) { return d.name; });

// Update and transition existing elements
groupNodes.select("circle")
  .transition()
  .duration(duration)
  .attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
  .attr('r', function(d) { return d.r; })
  .each("end", function(d){ d3.select(this).moveToFront(); });

This moveToFront code does not make a difference to my output, and the updated circles remain behind the entered selection circles.

To summarize: the issue seems to be caused by a hierarchy layout (circle-packing) which expects the circles to be drawn in the order of the data's hierarchy. The d3 update pattern (using enter, update and exit selections) causes selected update elements to remain in the svg when the hierarchy is re-drawn, and the new layers are drawn over it. The parents of those nodes are already correctly set, so parentNode.appendChild doesn't do anything in this case, because it's not the cause of the issue.

Here is a fiddle to demonstrate my issue. I've tried putting the moveToFront code in various places, with no visible difference. When you hit the "Change Data" button, it'll redraw the circles, but any circles whose names overlap between the two data sets are not nested properly in the circle-pack. Children of "Group A" are hidden behind one of the parent circles. You can verify the nodes are there via Inspect Element.

Another pic from the updated fiddle: hidden nodes

punkrockpolly
  • 9,270
  • 6
  • 36
  • 37
  • @lars-kotthoff the solution you linked to doesn't work. Do you have any other suggestions? – punkrockpolly Jul 27 '15 at 21:58
  • Hmm ok, I've reopened. Could you provide a complete example that demonstrates the problem please? – Lars Kotthoff Jul 27 '15 at 22:10
  • @lars-kotthoff I posted a jsfiddle to demonstrate the problem. Is there a way to move these hidden nodes to the proper layer in the hierarchy? – punkrockpolly Jul 28 '15 at 14:58
  • I don't really see what's going on when half of the circles are not visible. I've changed the stroke [here](https://jsfiddle.net/1f8Lqn8o/2/) and that looks fine to me -- all the circles appear to be in the proper order. – Lars Kotthoff Jul 28 '15 at 16:07
  • @lars-kotthoff I added another pic from your fiddle, to show an example of a "hidden" node. The ones that aren't being colored properly are also an example of nodes from the "update" selection that aren't being properly styled, but thats a different issue to solve. – punkrockpolly Jul 28 '15 at 16:21

1 Answers1

2

D3 provides a way to reorder elements based on the data bound to them with the .sort() function. In your case, the condition to check is the .depth attribute of the elements -- "deeper" elements should appear in front:

svg.selectAll("g")
  .sort(function (a, b) {
    if (a.depth < b.depth) return -1;
    else return 1;
  });

Complete demo here.

Lars Kotthoff
  • 107,425
  • 16
  • 204
  • 204