Vue 3 一页上的多个图表
Multiple Charts on One Page in Vue 3
期望的结果
在单个页面上显示同一图表类型的多个实例。示例图像包含从以下代码中删除的其他数据。
初始图表 canvas object 使用 Chart.vue
组件创建:
<!-- Chart.vue -->
<template>
<div :class="chartType">
<canvas style="height: 100%; width: 100%;"></canvas>
</div>
</template>
<script>
import Chart from "chart.js/auto";
Chart.defaults.elements.point.radius = 0;
export default {
props:{
chartType:String,
chartData:Object,
chartOptions:Object
},
methods: {
chartConstructor(chartType, chartData, chartOptions) {
const chartElement = document.querySelector(`.${this.chartType} canvas`);
const chart = new Chart(chartElement, {
type: chartType,
data: chartData,
options: chartOptions,
});
},
},
mounted(){
let {chartType,chartData,chartOptions} = this;
this.chartConstructor(chartType, chartData, chartOptions);
}
};
</script>
图表选项和数据当前建立在“图表类型”组件中,在本例中是条形图和折线图的组合 BarLine.vue
(其他图表类型还有其他组件)。虽然这里提供了数据,但数据最终还是要从外部获取的。
<!-- BarLine.vue -->
<template>
<div class="chart">
<Chart id="chartImage" :chartData="chartData" :chartOptions="chartOptions" :chartType="chartType" :style="{ width: chartWidth + 'px', height: chartHeight + 'px' }"/>
</div>
</template>
<script>
import Chart from "@/components/Chart.vue";
export default {
props:{
chartWidth: {default: 500, type: Number},
chartHeight: {default: 250, type: Number},
},
components: {
Chart,
},
data() {
return {
chartType: "bar",
chartData: {
labels: ["T", "F", "S", "S", "M", "T", "W", "T"],
datasets: [
{
type: 'line',
backgroundColor: "rgba(128, 0, 0, 0.2)",
borderColor: "rgba(128, 0, 0, 1)",
borderRadius: 3,
borderWidth: 1,
data: [55, 43, 38, 38, 38, 53, 54, 42],
hoverBackgroundColor: "rgba(128, 0, 0, 0.5)",
label: "H",
yAxisID: 'y',
},
{
type: 'bar',
backgroundColor: "rgba(0, 200, 255, 0.2)",
borderColor: "rgb(0,200,255, 0.6)",
borderRadius: 5,
borderWidth: 1,
data: [100, 50, 0, 40, 0, 0, 40, 0],
hoverBackgroundColor: "rgba(0, 200, 255, 0.5)",
label: "P",
yAxisID: 'y1',
}
]
},
chartOptions: {
layout: {
padding: {
left: 5,
right: 20,
top: 15,
}
},
plugins: {
tooltip: {
position: 'average',
mode: 'index',
},
legend: {
position: 'bottom',
labels: {
boxWidth: 20,
},
},
},
responsive: true,
scales: {
x: {
barPercentage: 0.5,
categoryPercentage: 0.5,
stacked: false,
fontSize: 5,
grid: { // x grid doesn't make much sense for this chart.
color: "#333333",
display: true,
borderDash: [1, 2],
},
},
y: {
grid:{
color: "#333333",
display: true,
borderDash: [1, 2],
},
ticks: {
stepSize: 20,
},
barPercentage: 0,
categoryPercentage: 0,
fontSize: 5,
stacked: false,
position: 'left',
},
y1: {
ticks: {
stepSize: 25,
},
barPercentage: 0,
categoryPercentage: 0,
fontSize: 5,
stacked: false,
position: 'right',
min: 0,
max: 100,
}
},
maintainAspectRatio: false,
animation: {
duration: 2000,
easing:'easeInOutQuart'
}
},
};
},
};
</script>
然后图表 object 显示在 Home.vue
上(最终 App.vue
):
<!-- Home.vue -->
<template>
<div class="home">
<TileFormat class="A" tileSize="tile-double" :showButton="false" :yOverflow="false" header="A">
<BarLine class="ChartBarTile" :chartHeight="160" :chartWidth="375" style="display: flex; justify-content: center;"/>
</TileFormat>
<TileFormat class="B" tileSize="tile-double" :showButton="false" :yOverflow="false" header="B">
This will be a second chart just as soon as I figure out how to do it.
</TileFormat>
</div>
</template>
<script>
import TileFormat from '@/components/Tile.vue'
import BarLine from '@/components/BarLine.vue'
export default {
components: {
TileFormat,
BarLine,
}
}
</script>
我尝试过的:
控制台报告“Canvas 已在使用中。必须先销毁 ID 为“0”的图表,然后才能重新使用 canvas”。我尝试过的:
- 在调用之间多次尝试破坏 canvas。
- 为每个组件创建唯一的图表 ID。
- 重复的图表类型组件。
- 将
Chart.vue
代码移动到图表类型组件 Vue 文件中。
- 制作不同类型的第二个图表 object 显示 .
我在您发布的代码中看到的内容: <Chart id="chartImage" ...>
。所以你在 DOM 中有两次相同的 id,这听起来像你得到的错误。尝试将生成的唯一 ID 添加到此组件。
我找到了一个可行的方法,但它需要重构上面的代码。要使此方法起作用,每个图表都必须在自己的容器中,例如 <div>
,或者在我的例子中,<TileFormat>
。它已确认可用于 Chartjs 3.6.0
和 Vue 3.0
。
ChartComponent.vue
<template>
<canvas width="100%" height="100%" :id="id"/>
</template>
<script>
import Chart from "chart.js/auto";
export default {
props: ['backgroundColor', 'borderWidth', 'borderColor', 'data', 'fill', 'height', 'id', 'labels', 'title', 'type', 'width',],
mounted() {
let ctx = document.getElementById(this.id).getContext('2d');
new Chart(ctx, {
type: this.type ? this.type : 'bar',
data: {
labels: this.labels,
datasets: [{
label: this.title,
data: this.data,
fill: this.fill,
backgroundColor: this.backgroundColor,
borderColor: this.borderColor,
borderWidth: this.borderWidth ? this.borderWidth : 1
}]
}
});
}
}
</script>
Home.vue
<template>
<div class="home">
<TileFormat class="A" tileSize="tile-double" :showButton="false" :yOverflow="false" header="Chart A" @click="wasClicked">
<Chart id="firstChart"
title="Chart A"
:labels='["Red", "Blue", "Yellow", "Green","Purple", "Orange"]'
:data='[1, 3, 5, 6, 4, 2]'
:background-color='["red", "blue", "yellow", "green","purple", "orange"]'
:border-color="'rgba(255, 255, 255, 1)'"
/>
</TileFormat>
<TileFormat class="B" tileSize="tile-double" :showButton="false" :yOverflow="false" header="Chart B" @click="wasClicked">
<Chart id="secondChart"
title="Chart B"
:labels='["Orange", "Purple", "Green", "Yellow", "Blue", "Red"]'
:data='[6, 4, 2, 1, 3, 5]'
:background-color='["orange", "purple", "green", "yellow", "blue", "red"]'
:border-color="'rgba(255, 255, 255, 1)'"
/>
</TileFormat>
</div>
</template>
<script>
import TileFormat from '@/components/Tile.vue'
import Chart from '@/components/ChartComponent.vue'
export default {
components: {
TileFormat,
Chart
}
}
</script>
错误源于您以非唯一方式选择 canvas 元素
const chartElement = document.querySelector(`.${this.chartType} canvas`);
如果您多次显示相同的图表类型,您将获得文档中的第一个 canvas 而不是组件中的第一个 canvas,这似乎是合乎逻辑的,从而导致您的错误
相反,您应该使用 ref 来定位您的 canvas。您可能还想添加一个 v-once 指令以避免可能的重新渲染破坏您的 canvas 以及您的图表
在您的模板中:
<canvas v-once ref="canvas" style="height: 100%; width: 100%;"></canvas>
在你的方法中:
const chartElement = this.$refs.canvas
期望的结果
在单个页面上显示同一图表类型的多个实例。示例图像包含从以下代码中删除的其他数据。
初始图表 canvas object 使用 Chart.vue
组件创建:
<!-- Chart.vue -->
<template>
<div :class="chartType">
<canvas style="height: 100%; width: 100%;"></canvas>
</div>
</template>
<script>
import Chart from "chart.js/auto";
Chart.defaults.elements.point.radius = 0;
export default {
props:{
chartType:String,
chartData:Object,
chartOptions:Object
},
methods: {
chartConstructor(chartType, chartData, chartOptions) {
const chartElement = document.querySelector(`.${this.chartType} canvas`);
const chart = new Chart(chartElement, {
type: chartType,
data: chartData,
options: chartOptions,
});
},
},
mounted(){
let {chartType,chartData,chartOptions} = this;
this.chartConstructor(chartType, chartData, chartOptions);
}
};
</script>
图表选项和数据当前建立在“图表类型”组件中,在本例中是条形图和折线图的组合 BarLine.vue
(其他图表类型还有其他组件)。虽然这里提供了数据,但数据最终还是要从外部获取的。
<!-- BarLine.vue -->
<template>
<div class="chart">
<Chart id="chartImage" :chartData="chartData" :chartOptions="chartOptions" :chartType="chartType" :style="{ width: chartWidth + 'px', height: chartHeight + 'px' }"/>
</div>
</template>
<script>
import Chart from "@/components/Chart.vue";
export default {
props:{
chartWidth: {default: 500, type: Number},
chartHeight: {default: 250, type: Number},
},
components: {
Chart,
},
data() {
return {
chartType: "bar",
chartData: {
labels: ["T", "F", "S", "S", "M", "T", "W", "T"],
datasets: [
{
type: 'line',
backgroundColor: "rgba(128, 0, 0, 0.2)",
borderColor: "rgba(128, 0, 0, 1)",
borderRadius: 3,
borderWidth: 1,
data: [55, 43, 38, 38, 38, 53, 54, 42],
hoverBackgroundColor: "rgba(128, 0, 0, 0.5)",
label: "H",
yAxisID: 'y',
},
{
type: 'bar',
backgroundColor: "rgba(0, 200, 255, 0.2)",
borderColor: "rgb(0,200,255, 0.6)",
borderRadius: 5,
borderWidth: 1,
data: [100, 50, 0, 40, 0, 0, 40, 0],
hoverBackgroundColor: "rgba(0, 200, 255, 0.5)",
label: "P",
yAxisID: 'y1',
}
]
},
chartOptions: {
layout: {
padding: {
left: 5,
right: 20,
top: 15,
}
},
plugins: {
tooltip: {
position: 'average',
mode: 'index',
},
legend: {
position: 'bottom',
labels: {
boxWidth: 20,
},
},
},
responsive: true,
scales: {
x: {
barPercentage: 0.5,
categoryPercentage: 0.5,
stacked: false,
fontSize: 5,
grid: { // x grid doesn't make much sense for this chart.
color: "#333333",
display: true,
borderDash: [1, 2],
},
},
y: {
grid:{
color: "#333333",
display: true,
borderDash: [1, 2],
},
ticks: {
stepSize: 20,
},
barPercentage: 0,
categoryPercentage: 0,
fontSize: 5,
stacked: false,
position: 'left',
},
y1: {
ticks: {
stepSize: 25,
},
barPercentage: 0,
categoryPercentage: 0,
fontSize: 5,
stacked: false,
position: 'right',
min: 0,
max: 100,
}
},
maintainAspectRatio: false,
animation: {
duration: 2000,
easing:'easeInOutQuart'
}
},
};
},
};
</script>
然后图表 object 显示在 Home.vue
上(最终 App.vue
):
<!-- Home.vue -->
<template>
<div class="home">
<TileFormat class="A" tileSize="tile-double" :showButton="false" :yOverflow="false" header="A">
<BarLine class="ChartBarTile" :chartHeight="160" :chartWidth="375" style="display: flex; justify-content: center;"/>
</TileFormat>
<TileFormat class="B" tileSize="tile-double" :showButton="false" :yOverflow="false" header="B">
This will be a second chart just as soon as I figure out how to do it.
</TileFormat>
</div>
</template>
<script>
import TileFormat from '@/components/Tile.vue'
import BarLine from '@/components/BarLine.vue'
export default {
components: {
TileFormat,
BarLine,
}
}
</script>
我尝试过的:
控制台报告“Canvas 已在使用中。必须先销毁 ID 为“0”的图表,然后才能重新使用 canvas”。我尝试过的:
- 在调用之间多次尝试破坏 canvas。
- 为每个组件创建唯一的图表 ID。
- 重复的图表类型组件。
- 将
Chart.vue
代码移动到图表类型组件 Vue 文件中。 - 制作不同类型的第二个图表 object 显示 .
我在您发布的代码中看到的内容: <Chart id="chartImage" ...>
。所以你在 DOM 中有两次相同的 id,这听起来像你得到的错误。尝试将生成的唯一 ID 添加到此组件。
我找到了一个可行的方法,但它需要重构上面的代码。要使此方法起作用,每个图表都必须在自己的容器中,例如 <div>
,或者在我的例子中,<TileFormat>
。它已确认可用于 Chartjs 3.6.0
和 Vue 3.0
。
ChartComponent.vue
<template>
<canvas width="100%" height="100%" :id="id"/>
</template>
<script>
import Chart from "chart.js/auto";
export default {
props: ['backgroundColor', 'borderWidth', 'borderColor', 'data', 'fill', 'height', 'id', 'labels', 'title', 'type', 'width',],
mounted() {
let ctx = document.getElementById(this.id).getContext('2d');
new Chart(ctx, {
type: this.type ? this.type : 'bar',
data: {
labels: this.labels,
datasets: [{
label: this.title,
data: this.data,
fill: this.fill,
backgroundColor: this.backgroundColor,
borderColor: this.borderColor,
borderWidth: this.borderWidth ? this.borderWidth : 1
}]
}
});
}
}
</script>
Home.vue
<template>
<div class="home">
<TileFormat class="A" tileSize="tile-double" :showButton="false" :yOverflow="false" header="Chart A" @click="wasClicked">
<Chart id="firstChart"
title="Chart A"
:labels='["Red", "Blue", "Yellow", "Green","Purple", "Orange"]'
:data='[1, 3, 5, 6, 4, 2]'
:background-color='["red", "blue", "yellow", "green","purple", "orange"]'
:border-color="'rgba(255, 255, 255, 1)'"
/>
</TileFormat>
<TileFormat class="B" tileSize="tile-double" :showButton="false" :yOverflow="false" header="Chart B" @click="wasClicked">
<Chart id="secondChart"
title="Chart B"
:labels='["Orange", "Purple", "Green", "Yellow", "Blue", "Red"]'
:data='[6, 4, 2, 1, 3, 5]'
:background-color='["orange", "purple", "green", "yellow", "blue", "red"]'
:border-color="'rgba(255, 255, 255, 1)'"
/>
</TileFormat>
</div>
</template>
<script>
import TileFormat from '@/components/Tile.vue'
import Chart from '@/components/ChartComponent.vue'
export default {
components: {
TileFormat,
Chart
}
}
</script>
错误源于您以非唯一方式选择 canvas 元素
const chartElement = document.querySelector(`.${this.chartType} canvas`);
如果您多次显示相同的图表类型,您将获得文档中的第一个 canvas 而不是组件中的第一个 canvas,这似乎是合乎逻辑的,从而导致您的错误
相反,您应该使用 ref 来定位您的 canvas。您可能还想添加一个 v-once 指令以避免可能的重新渲染破坏您的 canvas 以及您的图表
在您的模板中:
<canvas v-once ref="canvas" style="height: 100%; width: 100%;"></canvas>
在你的方法中:
const chartElement = this.$refs.canvas