You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 12:30:03