使用 d3.join 时是否可以不对元素重新排序?

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

d3 中,我们可以更改选择中元素的顺序,例如使用 raise.

然而,当我们重新绑定数据并使用join时,这个顺序被丢弃了。

当我们使用“旧方法”绑定数据时,不会发生这种情况,使用 entermerge

请参阅下面的 fiddle,您可以在其中单击圆圈(例如蓝色圆圈)将其置于最前面。当您单击“重绘”时,使用 join 时圆圈会恢复到原来的 z 顺序,但使用 entermerge 时则不会.

我能否实现圆圈保持其 z 顺序并仍然使用 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>

join 在合并 enter- 和 update-selection 后执行隐式 order,请参阅 https://github.com/d3/d3-selection/blob/91245ee124ec4dd491e498ecbdc9679d75332b49/src/selection/join.js#L14

你的例子中数据绑定后的选择顺序,即使文档顺序改变了,依然是红蓝绿。因此,使用 join.

将圆圈重新排序为原始顺序

您可以通过更改反映文档顺序更改的数据绑定来解决这个问题。我在这里通过将单击圆的数据移动到数据数组的末尾来做到这一点。

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>