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”。我尝试过的:

我在您发布的代码中看到的内容: <Chart id="chartImage" ...>。所以你在 DOM 中有两次相同的 id,这听起来像你得到的错误。尝试将生成的唯一 ID 添加到此组件。

我找到了一个可行的方法,但它需要重构上面的代码。要使此方法起作用,每个图表都必须在自己的容器中,例如 <div>,或者在我的例子中,<TileFormat>。它已确认可用于 Chartjs 3.6.0Vue 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