heliotrope heliotrope - 6 months ago 33
AngularJS Question

svg rendering order of lines and shapes when using d3

My understanding of svg is that elements that are created first are painted first, and subsequent elements are painted on the top of the previous elements(painter's algorithm).

I'm making a tree-like visualization, made up of groups, each of which is a company. Each group has 5 elements. First, there is straight-line for a timeline with circles on each end, comprising the start and end of the company.

simple company timeline

Then, there is a vertical line that attaches to another circle on the timeline of the company that acquired it.

All of this works within the

<g>
tag in d3(the text is on top of the circles, which are on top of the lines).

However, this behavior does not hold across groups. The text may be in front of its own acquisition line, but behind the lines of others.

Example:

enter image description here

Here, the text stays in front of the path in its group, but gets behind the lines of other groups, even though text should be the last part added to the entire svg.

Heres the relevant code:

function start(root) {
var duration = d3.event && d3.event.altKey ? 5000: 500;

var nodes = tree.nodes(root);
var node = sv.selectAll("g.node")
.data(nodes, function(d) {return d.id || (d.id = ++i); });

node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) {
if(d.depth === 0){
d.xy = [0, centerline + d.position];
}
else{
d.xy = [(2016-parseInt(d.acquisition_date))*30, centerline + d.position];
}
//don't need this anymore
return "scale(" + 1 + "," + 1 + ")"; });

function lines(node){
node.append("svg:line")
.attr("x1", function(d){
return d.xy[0];
})
.attr("y1", function(d){
return centerline + d.position;
})
.attr("x2",function(d){
return (2016 - parseInt(d.founded_date))*30;
})
.attr("y2", function(d){
return centerline + d.position;
})
.attr("timeline", function(d){
return d.name;
})
.style("stroke", function(k){
if(k.depth === 0) {
return "black";
}
return "#ccc";
})
.style("stroke-width", "2.5");

node.append("svg:path")
.filter(function(d){
if(d.parent){
d.acquiz = [((2016)- parseInt(d.acquisition_date))*30, centerline + d.parent.position];
}
return d.hasOwnProperty('parent');
})
.attr("acquired_by", function(d){
return d.parent.name;
})
.attr("acquired", function(d){
return d.name;
})
.attr("d", function(d){
return "M" + d.xy[0] +"," + d.xy[1] + " L" + d.acquiz[0] + "," +d.acquiz[1];
})
.attr("stroke", "#ccc")
.attr("stroke-width", 2)
.style("fill", "none");
}

function circles(node){
node.append("svg:circle")
.attr("cx", function(d){
return (2016 - parseInt(d.founded_date))*30;
})
.attr("cy", function(d){
return centerline + d.position;
})
.attr("r", 4.5)
.style("fill", function(d){ return d.children ? "white"
: "white"; });

node.append("svg:circle")
.attr("cx", function(d){
if(d.acquisition_date){
return (2016 - parseInt(d.acquisition_date))*30;
}
else{
return 0;
}
})
.attr("cy", function(d){
return centerline + d.position;
})
.attr("r", 4.5)
.style("fill", function(d) { return d.children ? "white"
: "white"; });

node.append("svg:circle")
.filter(function(d){
return d.hasOwnProperty('parent');
})
.attr("cx", function(d){
return ((2016 - parseInt(d.acquisition_date))*30);
})
.attr("name", function(d){
return d.name;
})
.attr("cy", function(d){
return centerline + d.parent.position;
})
.attr("r", 5.5)
.attr("acq", function(d){return d.name;})
.style("fill", "lightsteelblue");
}

function text(node){
node.append("svg:a")
.attr("ng-attr-xlink:href", function(d){
return "http://xxxxxxxxxxxx.edu/company/" + encodeURIComponent(d.name);
})
.append("svg:text")
.attr("x", function(d){
return d.refined[0]; })
.attr("dy", "1.5em")
.attr("y", function(d){
return d.refined[1];
})
.style("fill-opacity", 1)
.attr("text-anchor", function(d) { return d.children || d.children ? "middle" : "middle"; })
.text(function(d) {
return d.name;
});
}

lines(node);
circles(node);
text(node);


Don't think this makes a difference, but all of this code is inside an angular directive.

Answer

The order of what is in the foreground and what is in the background does not depend on when elements are inserted via d3.js (aka the creation of the SVG DOM element and the addition of it to the SVG document via d3.js' API), but depends on the order in the resulting SVG.

That said, the call of text(node) causes the text elements to be appended in the various g elements that have been added to the SVG already which determines the drawing order.

What I would try instead is to append all text elements at the end of the SVG document. Would result in something like that:

... <g class="node">...</g> <g class="node">...</g> <g class="node">...</g> <g class="text-overlays"> <text>1</text <text>2</text ... </g> ...

Comments