如何在Vue中搭建交互式D3柱状图结构并解决重复创建group问题
问题排查结果
- 核心原因是所有DOM容器元素都在每次
genChart执行时重复创建:当前代码逻辑里,每次调用genChart都会执行svg.append("g")、g.append("g")创建根容器、x轴、y轴元素,这些静态容器不需要每次更新都新建,只需要初始化时创建一次即可。 - 次要问题:图表数据源写死在
genChart内部,没有使用父组件传入的reps属性,外部数据变更无法同步到图表。 - 优化点:现有逻辑把新柱子入场的初始属性(height:0、y在底部)和merge后的更新逻辑合并,导致已有柱子更新时也会重复执行从0开始的动画。
修复后代码
<script> import * as d3 from 'd3' export default { name: "StatisticsUI", props:["reps"], data(){ return { svg: null, chartGroup: null, xAxisGroup: null, yAxisGroup: null, margin: {left: 50, right:50, top:50, bottom:50}, innerWidth: 0, innerHeight: 0 } }, mounted(){ this.setOptions() // 初始化只执行一次:创建所有静态容器元素 this.initChart() // 首次渲染数据 this.genChart() }, updated(){ // 数据更新时只重绘视图,不新建容器 this.genChart() }, methods:{ initChart(){ this.svg = d3.select("svg") const width = +this.svg.attr("width") const height = +this.svg.attr("height") this.innerWidth = width - this.margin.left - this.margin.right this.innerHeight = height - this.margin.top - this.margin.bottom // 根容器只创建一次 this.chartGroup = this.svg.append("g") .attr("transform", `translate(${this.margin.left}, ${this.margin.top})`); // 轴容器只创建一次 this.yAxisGroup = this.chartGroup.append("g").attr("class", "y-axis") this.xAxisGroup = this.chartGroup.append("g").attr("class", "x-axis") .attr("transform", `translate(0, ${this.innerHeight})`) }, genChart(){ // 替换为传入的props数据源 const data = this.reps const xValue = d => d[0] const yValue = d => d[1] const xScale = d3.scaleBand().domain(data.map(xValue)).range([0, this.innerWidth]).padding(0.2); const yScale = d3.scaleLinear().domain([0, d3.max(data, yValue)]).range([this.innerHeight, 0]); // 更新轴数据,不需要重建轴容器 this.yAxisGroup.call(d3.axisLeft(yScale)); this.xAxisGroup.call(d3.axisBottom(xScale)) // 柱子的更新逻辑,第二个参数为唯一标识key,避免元素复用错误 let rect = this.chartGroup.selectAll("rect").data(data, d => xValue(d)); // 删除多余柱子 rect.exit().remove() // 新增柱子的入场逻辑 const newRect = rect.enter() .append("rect") .attr("fill", "#69b3a2") .attr("x", (d) => xScale(xValue(d))) .attr("width", xScale.bandwidth()) // 新柱子初始状态 .attr("height", 0) .attr("y", this.innerHeight) // 合并新老柱子,执行更新动画 newRect.merge(rect) .transition() .duration(1000) .delay((d, i) => i * 50) .ease(d3.easeBounce) .attr("x", (d) => xScale(xValue(d))) .attr("width", xScale.bandwidth()) .attr("y", (d) => yScale(yValue(d))) .attr("height", function (d) { return this.innerHeight - yScale(yValue(d)); }.bind(this)); } } } </script>
内容的提问来源于stack exchange,提问作者Cap.Auh3b
相关产品推荐
相关产品推荐

