[Chartjs]-JS Vue3 dynamic bar chart graph with real-time reactive data

1👍

With the Vue-ApexCharts library and learning from Daniel’s answer, I was able to get a reactive bar chart to work.

Install Vue-ApexCharts in Vue3:

npm install --save apexcharts
npm install --save vue3-apexcharts

main.ts

import { createApp } from 'vue'
import App from './App.vue'
import VueApexCharts from "vue3-apexcharts";

const app = createApp(App);
app.use(VueApexCharts);
app.mount("#app");

App.vue

<template>
  <div id="app">
    <button @click="increment">Increment bar 1</button>
    <button @click="addSeries">Add data series</button>
    <button @click="incrementExtra">Increment appended series data</button>
    <VueApexCharts :options="chartData.options" :series="chartData.series"/>
  </div>
</template>


<script setup lang="ts">
import { ref, reactive } from 'vue'
import VueApexCharts from 'vue3-apexcharts'

const seriesData1 = reactive([0.3, 1])
const seriesDataExtra = ref([0.1, 0.7])

const chartData = reactive({
  series: [
    {
      name: 'My First series',
      data: seriesData1
    },
    {
      name: 'My Second series',
      data: [0.4, 0.1]
    }
  ],
  options: {
    chart: {
      type: 'bar',
      // https://apexcharts.com/docs/options/chart/animations/
      animations: {
        enabled: true,
        easing: 'linear',
        speed: 200,
        animateGradually: {
          enabled: false
        },
        dynamicAnimation: {
          enabled: true,
          speed: 150
        }
      }
    },
    xaxis: {
      categories: ['January', 'February']
    }
  }
})

function increment() {
  seriesData1[0] = ((seriesData1[0] * 10 + 1) / 10) % 1  // + 0.1
  // chartData.series[0].data[0] = ((chartData.series[0].data[0] * 10 + 1) / 10) % 1

  console.log(seriesData1)
}

function incrementExtra() {
  seriesDataExtra.value = seriesDataExtra.value.map(element => ((element * 10 + 1) / 10) % 1)
  console.log(seriesDataExtra)
}

function addSeries() {
  console.log("Add extra series")
  chartData.series.push({
    name: 'My Next series',
    data: seriesDataExtra
  })
}
</script>

The above code can update a single bar’s data, which triggers only an animation for that bar. This is done by:

  1. Creating a reactive variable: const seriesData1 = reactive([0.3, 1])
  2. With data: seriesData1 we assign this reactive array to the Chart
  3. The first button triggers the function increment, which increments the first bar’s data by 0.1: seriesData1[0] = ((seriesData1[0] * 10 + 1) / 10) % 1

Note that for step 3 we could have also directly updated the data in the Chart with chartData.series[0].data[0] = ((chartData.series[0].data[0] * 10 + 1) / 10) % 1 and this would give the same effect (this line of code also updates the variable seriesData1).

However, by updating the reactive variable seriesData1, we can separate data and the chart logic. You don’t have to know how to assign data to the chart, but still get your reactive chart by just modifying the data.


As a bonus I also added a demonstration on how to:

  • Add an extra data stream / series to the plot with the function addSeries and
  • Reactively animate this new data by incrementing all values in a ref array using the function incrementExtra.
  • Modify the animation behavior so it updates all bars at the same time without a bouncy effect (default)

(video) Vue 3 ApexCharts reactive bar chart

Demo video: https://i.imgur.com/e5a0y8Z.mp4

2👍

It depends on what kind of changes you want to make.

If you only want to change existing data, then it’s relatively easy. If you want to add additional data to the chart it gets quite a bit harder, but based on your example, you’re looking to mutate the a specific data inside an array so I won’t cover the later.

The problem appears to be that these libraries don’t handle the reactive data. Whether you have a reactive in ref or just using reactive The data passed to chart.js loses it’s reactivity. I haven’t looked into any of them to see why, but seems like prime-vue, vue-chartjs, and @j-t-mcc/vue3-chartjs all lose reactivity. I suspect they might be watching reactivity at a higher level, and when arrays and objects are mixed within reactive, it doesn’t work well

to get around it, you can can manually call the update of the chartjs component. To do that pass the component a ref and then call the update() method when you are making an update (or using a watch)

<script>
  import { reactive, ref, watch } from "vue";

  export default {
    setup() {
      const barGraph = ref(null); // define the $ref

      const basicData = reactive({
        labels: ["January", "February"],
        datasets: [
          {
            label: "My First dataset",
            backgroundColor: "#42A5F5",
            data: [0.3, 1],
          },
          {
            label: "My Second dataset",
            backgroundColor: "#FFA726",
            data: [0.4, 0.1],
          },
        ],
      });

      const horizontalOptions = {
        animation: {
          duration: 400,
        },
      };

      function increment() {
        let val = basicData.datasets[0].data[0];
        basicData.datasets[0].data[0] = ((val * 10 + 1) / 10) % 1;

        // when the data changes and the barGraph $ref is not null
        if (barGraph.value) {
          // call update method
          barGraph.value.chart.update();
        }
      }

      return {
        basicData,
        horizontalOptions,
        increment,
        barGraph,
      };
    },
  };
</script>

<template>
  <button @click="increment">
    count is: {{ basicData.datasets[0].data[0] }}
  </button>
  <div>
    <div class="card">
      <h5>Horizontal</h5>
      <Chart
        type="bar"
        :data="basicData"
        :options="horizontalOptions"
        ref="barGraph"
      />
    </div>
  </div>
</template>

Leave a comment