我使用具有滑动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;
答案 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并用每个刻度重绘轴。