Vue结合D3实现柱状图更新时exit().remove()无法删除rect元素问题
问题根因与修复方案
核心问题
你代码的根本问题出在模板插值中直接调用chart()初始化方法:Vue的插值表达式会在每次组件重渲染时重复执行,相当于每次响应式数据变化都会重新创建一份完整的图表实例堆叠在同一个SVG容器中,你调用exit().remove()删除的只是当前最新实例绑定的柱状图元素,之前多次初始化生成的旧元素不在当前选择集范围内,自然无法被删除。
同时存在以下辅助问题:
- 选择柱状图元素时没有加类名限定,存在误选其他SVG矩形元素的可能
- 每次执行
chart()都会重复向SVG中添加坐标轴容器、重复绑定下拉框/复选框事件,造成内存泄漏和逻辑冲突 - 年份筛选逻辑写为
d.year <= year,如果是要筛选指定单年份数据,这个逻辑会带出往年所有数据,导致数据量只增不减,exit选择集本来就为空
修复代码
模板部分
<template> <div class="billabilityPlot" v-if="$store.state.monthlyBillability.items?.length > 0"> <div class="chart"> <b>Dynamic bar-chart example.</b> <svg id="chart" width="960" height="400"></svg> 选择年份: <select id="year"></select> <input type="checkbox" id="sort"> 切换排序 </div> </div> </template>
脚本部分
<script> import * as d3 from 'd3' export default { data() { return { chartUpdate: null // 存储update方法引用 } }, mounted() { // 监听Vuex数据加载完成,仅初始化一次图表 const unwatch = this.$watch( () => this.$store.state.monthlyBillability.items, (data) => { if (data?.length > 0) { this.initChart(data) unwatch() // 初始化完成后取消监听,避免重复执行 } }, { immediate: true } ) }, methods: { initChart(billabilityData) { const width = 960 const height = 400 const months = [...new Set(billabilityData.map(d => d.month))], years = [...new Set(billabilityData.map(d => d.year))] // 初始化年份下拉框 d3.select("div.chart").select("#year").selectAll("option") .data(years) .enter().append("option") .text(d => d) // 初始化图表参数 const svg = d3.select("div.chart").select("#chart"); const margin = {top: 25, bottom: 25, left: 25, right: 10}; const w = +svg.attr("width") - margin.left - margin.right; const h = +svg.attr("height") - margin.top - margin.bottom; const x = d3.scaleBand() .range([margin.left, w - margin.right]) .padding(0.1) .paddingOuter(0.2) const y = d3.scaleLinear() .range([h, margin.top]) const xAxis = g => g .attr("transform", "translate(0," + (h) + ")") .call(d3.axisBottom(x).tickSizeOuter(0)) const yAxis = g => g .attr("transform", "translate(" + margin.left + ",0)") .call(d3.axisLeft(y)) // 坐标轴容器仅添加一次 svg.append("g").attr("class", "x-axis") svg.append("g").attr("class", "y-axis") // 定义更新方法 const update = (year, speed) => { // 修正为单年份筛选,不需要的话改回<=即可 const data = billabilityData.filter(d => d.year == year) y.domain([0, d3.max(data, d => d.billability)]).nice(); svg.selectAll(".y-axis").transition().duration(speed).call(yAxis); data.sort( d3.select("div.chart").select("#sort").property("checked") ? (a, b) => b.billability - a.billability : (a, b) => months.indexOf(a.month) - months.indexOf(b.month) ) x.domain(data.map(d => d.month)) svg.selectAll(".x-axis").transition().duration(speed).call(xAxis) // 选择元素增加类名限定,仅选中业务柱状图 const bar = svg.selectAll("rect.bar") .data(data, d => d.month) bar.exit().remove(); bar.enter().append("rect") .attr("class", "bar") .attr("fill", "steelblue") .attr("width", x.bandwidth()) .merge(bar) .transition().duration(speed) .attr("x", d => x(d.month)) .attr("y", d => y(d.billability)) .attr("height", d => y(0) - y(d.billability)) } // 初始化渲染 update(d3.select("div.chart").select("#year").property("value"), 1000) this.chartUpdate = update // 事件绑定仅执行一次 d3.select("div.chart").select("#year") .style("border-radius", "5px") .on("change", function() { update(this.value, 1000) }) d3.select("div.chart").select("#sort") .style("margin-left", "45%") .on("click", function() { update(d3.select("#year").property("value"), 1000) }) } } } </script>
内容的提问来源于stack exchange,提问作者Babas
相关产品推荐
相关产品推荐

