In d3
, we may change the order of elements in a selection, for example by using raise
.
Yet, when we rebind the data and use join
, this order is discarded.
This does not happen when we use "the old way" of binding data, using enter
and merge
.
See following fiddle where you can click a circle (for example the blue one) to bring it to front. When you click "redraw", the circles go back to their original z-ordering when using join
, but not when using enter
and merge
.
Can I achive that the circles keep their z-ordering and still use join
?
const data = [{
id: 1,
v: 10,
c: 'red'
}, {
id: 2,
v: 30,
c: 'blue'
}, {
id: 3,
v: 60,
c: 'green'
}]
let nDrawCall = 0
function redraw() {
nDrawCall++
//svg1 with old enter-merge pattern that works
const circles = d3.select('#svg1')
.selectAll('circle')
.data(data, d => d.id)
circles
.enter()
.append('circle')
.on('click', function() {
d3.select(this).raise()
})
.merge(circles)
.attr('cx', d => d.v * nDrawCall)
.attr('cy', d => d.v)
.attr('r', d => d.v)
.attr('fill', d => d.c)
//svg2 with new join pattern that sadly reorders
d3.select('#svg2')
.selectAll('circle')
.data(data, d => d.id)
.join(enter => enter
.append('circle')
.on('click', function() {
d3.select(this).raise()
})
)
.attr('cx', d => d.v * nDrawCall)
.attr('cy', d => d.v)
.attr('r', d => d.v)
.attr('fill', d => d.c)
}
function reset() {
nDrawCall = 0
redraw()
}
redraw()
/*
while (true) {
iter++
console.log(iter)
sleepFor(500)
}
*/
svg {
height: 100px;
width: 100%;
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
</head>
<body>
<button onclick="redraw()">
Redraw
</button>
<button onclick="reset()">
Reset
</button>
<div>
<svg id="svg1" />
<svg id="svg2" />
</div>
</body>
</html>
do
andundo
operations get unnecessarily complex if I change the data array order. I wrote an issue on d3's GitHub page to ask whether ajoin
without implicitorder
call might be introduced. – Serapis