D3 更新条形图 VUE.js



嘿,我在 Vue 中的 d3 图表有问题。除了更新数据外,一切都加载正常。

我正在使用 props 来传递数据,并且由于数据正在更改我的图表而不是更新,它会在当前图表下方创建另一个图表。

我想做的是保留当前图形并在数据更改时仅更新柱线

<script>
export default {
props: {
arr: {
type: Array
}, 
colors: {
type: Array
}
},
watch: {
arr: {
immediate: true,
handler(val) {
setTimeout(() => {
this.barCharts(val)
}, 100);
}
}
},
methods: {
barCharts(data) {
let margin = { top: 40, right: 20, bottom: 50, left: 60 };
let width = 650
let height = 240; 
let color = d3.scaleOrdinal().range(this.colors);
let t = d3.transition().duration(750);
let g = d3
.select("#barChart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", "100%")
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
let xAxisGroup = g
.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${height})`);
let yAxisGroup = g.append("g").attr("class", "y axis");
// X Scale
let x = d3
.scaleBand()
.range([0, width])
.padding(0.2);
// Y Scale
let y = d3.scaleLinear().range([height, 0]);
// axis
x.domain(data.map(d => d.name));
y.domain([
0,
d3.max(data, function(d) {
return d.value;
})
]);
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.attr("class", "axis")
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-30)");
g.append("g")
.call(d3.axisLeft(y))
.attr("class", "axis");
// draw bars
let rects = g.selectAll("rect").data(data);
rects
.transition(t)
.attr("y", function(d) {
return y(d.value);
})
.attr("x", function(d) {
return x(d.name);
})
.attr("height", function(d) {
return height - y(d.value);
})
.attr("width", x.bandwidth())
.attr("fill", (d, i) => color(i));
rects
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.name);
})
.attr("width", x.bandwidth())
.attr("fill", (d, i) => color(i))
.attr("y", y(0))
.attr("height", 0)
.transition(t)
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
}
}
};
}
</script>

我可以实现另一个函数(例如更新(以在此函数内部调用以仅更新吟游诗人吗?

提前感谢!

您生成一个新的条形图,因为您要在barCharts方法中将新的 SVG 元素附加到目标。每次调用它时,它都会附加一个新的 SVG 和更新的数据,从而导致出现重复图表的问题。

由于 Vue 有自己的渲染和更新 DOM 元素的方法,所以你不应该使用 d3 来做到这一点,因为这会扰乱 Vue 的反应和清理(这意味着你最终可能会有内存泄漏或元素没有被删除(。

我的建议是在 vue<template>中定义你的<svg><g>和其他元素,并使用 v-bind 指令将条形图值传递给 vue 渲染的 SVG 元素。例如,您可以使用d3-scale中的方法在组件中创建xybarColor方法/计算属性。然后,您可以将每个条形的结果绑定到模板中的正确属性,例如<rect v-for="(bar, index) in bars" :x="x(bar.name)" :y="y(bar.value)" :width="x.bandwidth()" :height="height - y(bar.value)" :fill="barColor(index)" />或类似属性。

让我知道这是否足以让你上路,否则我将能够在今天晚些时候给你写一个完整的例子。我还推荐这些关于结合 vue 和 d3 的视频,这些视频对我来说非常有见地:

  • https://www.youtube.com/watch?v=30v9xnB-GEo
  • https://www.youtube.com/watch?v=CkFktv0p3pw

我已经根据您的建议对其进行了更改,我可以承认它很棒!!它完美运行,只有一个过渡问题。我不太确定我应该把它放在哪里...在输入时,我想从 0 过渡 y 到条形图的高度,然后在数据更改时进行另一个过渡。

我将再次感谢您的帮助!

<template>
<svg class="barChart" :width="width" height="340">
<g>
<!-- axis bottom -->
<g class="x-axis" fill="none" :transform="`translate(0, ${height})`">
<path stroke="currentColor" :d="`M0.5,6V0.5H${width}.5V6`"></path>
<g
class="tick"
opacity="1"
font-size="10"
font-family="sans-serif"
text-anchor="middle"
v-for="(bar, index) in bars"
:key="index"
:transform="`translate(${bar.x + bar.width / 2}, 0)`"
>
<line stroke="currentColor" y2="6"></line>
<text fill="currentColor" y="9" dy="0.71em">{{ bar.xLabel }}</text>
</g>
</g>
<!-- y-axis -->
<g class="y-axis" fill="none" :transform="`translate(0, 0)`">
<path
class="domain"
stroke="currentColor"
:d="`M0.5,${height}.5H0.5V0.5H-6`"
></path>
<g
class="tick"
opacity="1"  
font-size="10"
font-family="sans-serif"
text-anchor="end"
v-for="(tick, index) in yTicks"
:key="index"
:transform="`translate(0, ${y(tick) + 0.5})`"
>
<line stroke="currentColor" x2="-6"></line>
<text fill="currentColor" x="-9" dy="0.32em">{{ tick }}</text>
</g>
</g>
<!-- bars -->
<g class="bars" fill="none">
<rect 
v-for="(bar, index) in bars"
:fill="color(bar.xLabel)"
:key="index"
:height="bar.height"
:width="bar.width"
:x="bar.x"
:y="bar.y"
></rect>
</g>
</g>
</svg>
</template>
<script>
import * as d3 from "d3";
import { scaleBand, scaleLinear } from 'd3-scale'
export default { 
props: {
dataset: {
type: Array
}, 
colors: {
type: Array
}
},
data() {
return {
height: 240,
width: 650
}
},
computed: {
color() {
return d3.scaleOrdinal().range(this.colors); 
},
yTicks() {
return this.y.ticks(5);
},
x() {
return scaleBand()
.range([0, this.width])
.padding(0.3)
.domain(this.dataset.map(e => e[0]));
},
y() {
let values = this.dataset.map(e => e[1]);
return scaleLinear()
.range([this.height, 0])
.domain([0, Math.max(...values)]);
},
bars() {
let bars = this.dataset.map(d => {
return {
xLabel: d[0],
x: this.x(d[0]),
y: this.y(d[1]),
width: this.x.bandwidth(),
height: this.height - this.y(d[1])
};
});
return bars;
}
},
}
</script>

它应该在手表里吗??或者也许在计算的某个地方??还是应该创建一个方法来调用转换?

最新更新