Is it possible to not reorder elements when using d3.join?
Asked Answered
S

1

6

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>
Serapis answered 18/2, 2022 at 19:41 Comment(0)
B
3

join does an implicit order after merging the enter- and update-selection, see https://github.com/d3/d3-selection/blob/91245ee124ec4dd491e498ecbdc9679d75332b49/src/selection/join.js#L14.

The selection order after the data binding in your example is still red, blue, green even if the document order is changed. So the circles are reordered to the original order using join.

You can get around that by changing the data binding reflecting the change in the document order. I did that here, by moving the datum of the clicked circle to the end of the data array.

let 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++
  d3.select('#svg2')
    .selectAll('circle')
    .data(data, d => d.id)
    .join(enter => enter
      .append('circle')
      .on('click', function() {
        const circle = d3.select(this).raise();
        data.push(data.splice(data.indexOf(circle.datum()), 1)[0]);
      })
    )
    .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()
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="svg2" />
    </div>
  </body>

</html>
Bogoch answered 18/2, 2022 at 22:10 Comment(1)
Thank you, appreciate the highlighting of the respective source code line. I really do not want to change the data array order but only the document order. My app implements the command pattern, and the do and undo operations get unnecessarily complex if I change the data array order. I wrote an issue on d3's GitHub page to ask whether a join without implicit order call might be introduced.Serapis

© 2022 - 2024 — McMap. All rights reserved.