Search code examples
vue.jscanvaschart.jsvue-chartjschartjs-2.6.0

How to reuse a Chartjs Chart component in with different Data and get past the **Canvas is already in use** error?


I have a boxplot component in my Vue project and I want to reuse it on the same page for a table I am building.

enter image description here

However, despite the fact that my data is different, I still get this Canvas is already in use. Chart with ID '0' must be destroyed before the canvas can be reused. error.

I've seen this question asked previously on here, but none of the threads 'fixes' really helped me here. (Threads I've seen and video I tried to replicate).

Since I have no idea of how many rows (and charts) I will need to use, I don't think this fix is very practical. Is there anyway around this type of error?

Do I need a new id generated for each chart I am using? Since this is utilizing the basic Chartjs library and not the normal VueChart3 syntax I've been using throughout the rest of my app, I am a bit stumped.

Any help or advice would be greatly appreciated! Cheers!

CodeSandbox Link

BoxPlotChart.vue

<template>
  <canvas id="myChart"></canvas>
</template>

<script>
import { defineComponent, onMounted } from "vue";
import { Chart, registerables } from "chart.js";
import { BoxPlotChart } from "@sgratzl/chartjs-chart-boxplot";
Chart.register(...registerables);
export default defineComponent({
  name: "CBoxPlotChart",
  props: {
    data: {
      type: Object,
      required: true,
    },
    width: {
      type: String,
      // default: '400px'
    },
    height: {
      type: String,
      // default: '300px'
    },
    showLegend: {
      type: Boolean,
      default: true,
    },
  },
  setup(props) {
    onMounted(() => {
      // @ts-ignore
      const ctx = document.getElementById("myChart")?.getContext("2d");
      ctx.canvas.parentNode.style.width = `${props.width}px`;
      ctx.canvas.parentNode.style.height = `${props.height}px`;
      if (!ctx) return;
      const myChart = new BoxPlotChart(ctx, {
        // type: 'boxplot',
        // @ts-ignore
        data: props.data,
        options: {
          responsive: true,
          maintainAspectRatio: false,
          indexAxis: "y",
          // @ts-ignore
          clip: false,
          title: {
            display: true,
            text: "Chart.js Box Plot Chart",
          },
          plugins: {
            datalabels: {
              display: false,
            },
            legend: {
              display: false,
            },
            tooltip: {
              // if you want to hide the tooltip, just uncomment the line below
              // enabled: false,
              displayColors: false,
              bodyFont: {
                size: 12,
                family: "Inter",
              },
              bodyColor: "#4771FA",
              backgroundColor: "white",
              callbacks: {
                title: () => "",
                label: (context) => {
                  console.log(`context!`, context.parsed.max);
                  const mean = context.parsed.mean.toFixed(3);
                  const q1 = context.parsed.q1.toFixed(3);
                  const q3 = context.parsed.q3.toFixed(3);
                  const boxplotValues = [
                    `Q3: ${q3}`,
                    `Mean: ${mean}`,
                    `Q1: ${q1}`,
                  ];
                  return boxplotValues;
                },
              },
            },
          },
          layout: {
            padding: {
              left: 10,
              right: 5,
              top: 0,
              bottom: 10,
            },
          },
          scales: {
            x: {
              display: false,
            },
            y: {
              display: false,
            },
          },
        },
      });
    });
    return {
      // isResponsive,
      // getData,
      // options,
      // barChartProps,
      // myStyles,
    };
  },
});
</script>

Table.vue

<template>
  <table class="no-spacing" cellspacing="0">
    <tr>
      <th>Date</th>
      <th>Value</th>
      <th>Chart</th>
    </tr>
    <tr>
      <td>
        <div style="margin: 0.5rem">05/03/22</div>
      </td>
      <td>
        <div
          style="
            background: #FFE2E2;
            padding: 1rem;
            margin: 0.5rem;
            border-radius: 8px;
          "
        >
          12.93
        </div>
      </td>
      <td style="width: max-content">
        <BoxPlotChart :data="boxplotData" />
      </td>
    </tr>
    <tr>
      <td>
        <div style="margin: 0.5rem">05/03/22</div>
      </td>
      <td>
        <div
          style="
            background: #FFE2E2;
            padding: 1rem;
            margin: 0.5rem;
            border-radius: 8px;
          "
        >
          12.93
        </div>
      </td>
      <!-- <td style="width: max-content">
        <BoxPlotChart :data="boxplotData2" />
      </td> -->
    </tr>
  </table>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import BoxPlotChart from "./BoxPlotChart.vue";
import { boxplotData, boxplotData2 } from "./boxplot-mock-data";

export default defineComponent({
  name: "",
  props: {},
  emits: [],
  components: { BoxPlotChart },
  setup() {
    return { boxplotData, boxplotData2 };
  },
});
</script>

<style lang="sass" scoped>
table
  th
    text-align: center
  tr, td
    border: 1px solid #DCE5FA
</style>

Solution

  • ID's in web design should be unique. At the moment each time you create a new BoxPlotChart component it adds a canvas with id 'myChart' to the DOM.

    You either have to generate a random ID within your BoxPlotChart chart component or pass along an ID in the props which you give to the canvas