Reputation: 1290
I have a json dataset which I dynamically visualize in a similar way as this example using the d3.js
library.
However, I would like the bars that change their position within the chart to a lower position to be located "behind" the ones that are at higher positions. For this I would like to set up a z-index based on the data (bars with low values have lower z-index and thus would be located behind the ones with a larger value). Unfortunately, SVG uses document order as a z-index, which is why I would need to reorder the bars (the chartRow
variable in the example) inside the document each time the chart changes.
Is there any way to achieve this goal?
Upvotes: 1
Views: 896
Reputation: 38151
Reorder Elements in the DOM
To reorder elements in the DOM with d3 you can use selection.sort and selection.order:
// Sort & then order:
selection.sort(function(a,b) {
return a.property - b.property;
})
.order();
Selection.sort sorts a selection based on the bound data, a
and b
each represent a datum bound to a single element. The resulting behavior is essentially the same as Array.sort, but rather than using an array, you are sorting elements in a selection using their bound data. The content of the function provided to selection.sort (the compare function) determines sorting order:
The compare function, which defaults to ascending, is passed two elements’ data a and b to compare. It should return either a negative, positive, or zero value. If negative, then a should be before b; if positive, then a should be after b; otherwise, a and b are considered equal and the order is arbitrary. (docs)
Once we have a sorted selection we can apply selection.order()
, which re-inserts each element into the DOM based on the selection's order. Remember that elements drawn first can be covered by elements drawn later - first in the selection means potentially underneath everything.
So if we had something like this, where overlapping bars are drawn sequentially based on the order of a data array:
var bars = [
{value: 100},
{value: 50},
{value: 200},
{value: 70},
{value: 40},
{value: 120}
];
var height = 200;
var width = 500;
var svg = d3.select("body")
.append("svg")
.attr("height",height)
.attr("width",width);
var bars = svg.selectAll("rect")
.data(bars)
.enter()
.append("rect")
.attr("width", 45)
.attr("height", function(d) { return d.value; })
.attr("y", function(d) { return height-d.value; })
.attr("x", function(d,i) { return i * 30 + 50; })
.attr("fill", function(d,i){ return d3.schemeCategory10[i]; })
.attr("stroke","black")
.attr("stroke-width",1);
// Order the bars:
bars.sort(function(a,b) {
return b.value - a.value;
}).order();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
We can pull up the small bars, say, for greater visibility with a sort and order:
var bars = [
{value: 100},
{value: 50},
{value: 200},
{value: 70},
{value: 40},
{value: 120}
];
var height = 200;
var width = 500;
var svg = d3.select("body")
.append("svg")
.attr("height",height)
.attr("width",width);
var bars = svg.selectAll("rect")
.data(bars)
.enter()
.append("rect")
.attr("width", 45)
.attr("height", function(d) { return d.value; })
.attr("y", function(d) { return height-d.value; })
.attr("x", function(d,i) { return i * 30 + 50; })
.attr("fill", function(d,i){ return d3.schemeCategory10[i]; })
.attr("stroke","black")
.attr("stroke-width",1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Reposition based on order
As you can see above, this doesn't reset the x or y attribute of each bar to reflect the ordering on the z (as in your example). As in my basic example above, we can reset the x values based on i (this can be done by rescaling too, but is enough to be a separate question and answer):
var bars = [
{value: 100},
{value: 50},
{value: 200},
{value: 70},
{value: 40},
{value: 120}
];
var height = 200;
var width = 500;
var svg = d3.select("body")
.append("svg")
.attr("height",height)
.attr("width",width);
var bars = svg.selectAll("rect")
.data(bars)
.enter()
.append("rect")
.attr("width", 45)
.attr("height", function(d) { return d.value; })
.attr("y", function(d) { return height-d.value; })
.attr("x", function(d,i) { return i * 30 + 50; })
.attr("fill", function(d,i){ return d3.schemeCategory10[i]; })
.attr("stroke","black")
.attr("stroke-width",1)
bars = bars.sort(function(a,b) {
return a.value - b.value;
}).order();
bars.transition()
.attr("x", function(d,i) { return i * 30 + 50; })
.delay(500)
.duration(2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
See also: This question and answer which has a lot of similarities to the example you have: sorting and ordering are used while transitioning both length of bars and bar order.
Upvotes: 3