为什么在d3中没有从DOM中删除轴标签?

时间:2017-01-16 05:04:31

标签: d3.js

我使用具有滑动x轴的d3实现图表。 Demo

我注意到刻度线的数量(即轴标签的数量)不断增长,这意味着从图表中滑出的标签不会从DOM中删除。

为什么旧标签会留在DOM中,我该如何解决?



const timeWindow = 10000;
const transitionDuration = 3000;

const xScaleDomain = (now = new Date()) =>
  [now - timeWindow, now];

const totalWidth = 500;
const totalHeight = 200;
const margin = {
  top: 30,
  right: 50,
  bottom: 30,
  left: 50
};
const width = totalWidth - margin.left - margin.right;
const height = totalHeight - margin.top - margin.bottom;

const svg = d3.select('.chart')
  .append('svg')
    .attr('width', totalWidth)
    .attr('height', totalHeight)
  .append('g')
    .attr('transform', `translate(${margin.left}, ${margin.top})`)

svg
  .append('rect')
    .attr('width', width)
    .attr('height', height);

// Add x axis
const xScale = d3.scaleTime()
  .domain(xScaleDomain(new Date() - transitionDuration))
  .range([0, width]);

const xAxis = d3.axisBottom(xScale);

const xAxisSelection = svg
  .append('g')
    .attr('transform', `translate(0, ${height})`)
  .call(xAxis);

// Animate
const animate = () => {
  console.log(d3.selectAll('.tick').size()); // DOM keeps growing!!!

  xScale.domain(xScaleDomain());

  xAxisSelection
    .transition()
    .duration(transitionDuration)
    .ease(d3.easeLinear)
    .call(xAxis)
    .on('end', animate);
};

animate();

svg {
  margin: 30px;
  background-color: #ccc;
}

rect {
  fill: #fff;
  outline: 1px dashed #ddd;
}

<script src="https://unpkg.com/d3@4.4.1/build/d3.js"></script>
<div class="chart"></div>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:2)

分析

轴组件实际上将尝试以移除不再可见的刻度线。检查源代码会显示line

tickExit.remove();

调试到此行显示正确计算出口选择,即所有现有节点都包含在tickExit中。但是节点不会按预期被删除,因为您在它们上运行了一个活动的转换。 documentation有它:

  

# 过渡删除()<>

     

对于每个选定元素,removes转换结束时的元素,只要该元素没有其他活动或挂起转换。如果元素具有其他活动或挂起的转换,则不执行任何操作。

解决方法

一个 - admittely hacky - 解决方法可以利用D3淡化不再可见的刻度线的方式。但这并不是很好,因为它依赖于D3的内部工作,并且如果这种行为被改变,将来可能会破坏。

由于selection.remove()不是那么胆小,它可以用来处理删除,而不是使用transition.remove()。就个人而言,我会在animate()函数中使用以下几行:

d3.selectAll(".tick")
  .filter(function() {
    return +d3.select(this).attr("opacity") === 1e-6;
  })
  .remove();

因为轴组件最终会将所有不可见的刻度淡化为不透明度1e-6,这可以用来丢弃这些元素。但请注意,滴答计数首先会达到起始值以外的某个值,因为转换到最终不透明度需要一些时间才能完成。但是,超额滴答计数很小,可以安全地忽略。

查看以下工作演示。在此示例中,滴答计数将从最初的10增加到19,然后保持此值。

const timeWindow = 10000;
const transitionDuration = 3000;

const xScaleDomain = (now = new Date()) =>
  [now - timeWindow, now];

const totalWidth = 500;
const totalHeight = 200;
const margin = {
  top: 30,
  right: 50,
  bottom: 30,
  left: 50
};
const width = totalWidth - margin.left - margin.right;
const height = totalHeight - margin.top - margin.bottom;

const svg = d3.select('.chart')
  .append('svg')
    .attr('width', totalWidth)
    .attr('height', totalHeight)
  .append('g')
    .attr('transform', `translate(${margin.left}, ${margin.top})`)

svg
  .append('rect')
    .attr('width', width)
    .attr('height', height);

// Add x axis
const xScale = d3.scaleTime()
  .domain(xScaleDomain(new Date() - transitionDuration))
  .range([0, width]);

const xAxis = d3.axisBottom(xScale);

const xAxisSelection = svg
  .append('g')
    .attr('transform', `translate(0, ${height})`)
  .call(xAxis);

// Animate
const animate = () => {
  console.log(d3.selectAll('.tick').size()); // DOM keeps growing!!!
  d3.selectAll(".tick")
    .filter(function() {
      return +d3.select(this).attr("opacity") === 1e-6;
    })
    .remove();

  xScale.domain(xScaleDomain());

  xAxisSelection
    .transition()
    .duration(transitionDuration)
    .ease(d3.easeLinear)
    .call(xAxis)
    .on('end', animate);
};

animate();
svg {
  margin: 30px;
  background-color: #ccc;
}

rect {
  fill: #fff;
  outline: 1px dashed #ddd;
}
<script src="https://d3js.org/d3.v4.js"></script>
<div class="chart"></div>

以下任何内容都是我对OP为d3轴模块发布#23 "Axis labels are not removed from the DOM"的评论所做的,其中包含一些非常好的观点。

Mike Bostock的comment提供了对同一元素上的同时转换的更深入的了解,这最终会阻止删除刻度:

  

问题在于,当调度父G元素的 end 事件时,轴尚未删除旧标记。 transition.remove删除了刻度线,它会在刻度元素上侦听end event。 G元素的 end 事件在tick元素的 end 事件之前被调度,因此你开始一个新的转换,在轴有一个之前中断旧转换有机会删除旧的蜱虫。

@curran在comment中找到真正的宝石,他建议使用setTimeout(animate)。这是非常好的,据我所知,这个问题唯一非侵入性,非hacky解决方案!通过将animate函数推送到事件循环的末尾,这将推迟创建下一个转换,直到实际转换有可能在自身之后进行清理。

而且,为了结束这个理论讨论,对你的实际问题最好的结论似乎是迈克博斯托克的:

  

如果你想要一个实时轴,你可能不想要过渡。相反,使用d3.timer并用每个刻度重绘轴。