最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do I make my ApexCharts reactive with data in Pinia Store using Vue.js 3 and composition API? - Stack Overflow

programmeradmin6浏览0评论

I am building a dashboard using Vue.js 3 and ApexCharts with the composition API. The data for the charts is held in a Pinia data store.

I have a function that pulls data from a sample .json file (which models what an API will return once it is set up), processes, and returns the data as a multidimensional array for the charts that is stored in the Pinia store.

The problem I'm having is when I run the function to update the data, I can see it update in the Pinia store via vue devtools, but the chart does not change to reflect the new values like the rest of the app does.

At the advice of other stack overflow answers, I've wrapped chart options and series in ref(). I've also read the info on this GitHub repo, but as I'm fairly new to Vue and have only worked with the composition API and <script setup>, I'm unsure what exactly to implement in my code to make the charts reactive to Pinia data.

I have created a sample repo with a single chart and sample data on stackblitz showing the behavior I'm experiencing.

Chart component with options and series code:

<template>
  <VueApexCharts type="bar" height="360px" :options="chartOptions" :series="chartSeries">
  </VueApexCharts>
</template>

<script setup>
/*********
 Imports
 *********/
import VueApexCharts from "vue3-apexcharts";
import { ref } from "vue";
import { useDataStore } from "@/stores/dataStore.js";

const storeData = useDataStore();

/*********
 Apex Chart
 *********/
let chartOptions = ref({
  chart: {
    id: "grocerChart",
    type: "bar",
    // height: 350,
    stacked: true,
    stackType: "100%",
    toolbar: {
      show: true,
      tools: {
        download: true,
        zoom: false,
        zoomin: true,
        zoomout: true,
        reset: true,
      },
    },
  },
  title: {
    text: storeData.selectedRegion,
    align: "center",
  },
  plotOptions: {
    bar: {
      horizontal: true,
    },
  },
  grid: {
    padding: {
      bottom: 20,
      right: 20,
      top: 5,
    },
  },
  xaxis: {
    categories: storeData.chartData[0], // ['Fruit', "Meat", "Vegetables"],
    tickPlacement: "on",
    labels: {
      rotate: 0,
      style: {
        fontSize: 10,
      },
    },
  },
  fill: {
    opacity: 1,
  },
  legend: {
    fontSize: 10,
    offsetX: 0,
    offsetY: 0,
  },
  dataLabels: {
    enabled: false,
  },
  noData: {
    text: "No Data",
    style: {
      fontSize: "24px",
    },
  },
});

let chartSeries = ref([
  {
    name: storeData.chartData[1][0][0], // 'fruit',  
    data: storeData.chartData[1][0][1], // [10, 14, 10], 
  },
  {
    name: storeData.chartData[1][1][0], // 'meat', 
    data: storeData.chartData[1][1][1], // [10, 10, 4],
  },
  {
    name: storeData.chartData[1][2][0], // 'vegetable',
    data: storeData.chartData[1][2][1], // [9, 7, 12],
  },
]);

// console.log(storeData.chartDataTest[1][0][1])
</script>

<style scoped>
</style>

Pinia code:

import {ref} from 'vue'
import {defineStore} from 'pinia'

export const useDataStore = defineStore('data', () => {

    let selectedRegion = ref('No Region Selected');
    let chartData = ref([
        [],
        [
          [[], []],
          [[], []],
          [[], []]
        ]
    ]);

    return {
        selectedRegion,
        chartData
      }
})

App.vue with function to process model .json data and return multidimensional array for chart:

<template>
  <div class="w-100">
    <p>Select a region: </p>
    <v-btn class="ma-3" @click="storeData.selectedRegion = 'East'">East</v-btn>
    <v-btn class="ma-3" @click="storeData.selectedRegion = 'Midwest'">Midwest</v-btn>
    <p>Selected Region: {{ storeData.selectedRegion }}</p>
    <p class="mt-5">After selecting a region above, click "Update Chart" below:</p>
    <v-btn 
    class="ma-3" 
    @click="storeData.chartData = updateData(grocerData, storeData.selectedRegion)">
      Update Chart
    </v-btn>
    <v-card class="w-75 mt-5">
      <StoreInventoryChart/>
    </v-card>
  </div>

</template>

<script setup>
import {useDataStore} from "@/stores/dataStore.js";
import StoreInventoryChart from "@/components/storeInventoryChart.vue";
import grocerData from "./models/sample-api-data.json";

const storeData = useDataStore();

function updateData(data, selectedRegion) {
    // Filter data by selected state 
    const selRegionData = data.filter(item => item.state === selectedRegion);

    // Extract and sort store names
    const stores = [...new Set(selRegionData.map(item => item.store))].sort();

    // Extract and sort category values
    const categories = [...new Set(selRegionData.map(item => item.category))].sort();

    // Initialize the result array for categorized data
    const categorizedData = categories.map(category => [category, Array(stores.length).fill(0)]);

    // Populate the categorized data
    selRegionData.forEach(item => {
        const storeIndex = stores.indexOf(item.store);
        const categoryIndex = categories.indexOf(item.category);
        categorizedData[categoryIndex][1][storeIndex] += item.inventory;
    });

    return [stores, categorizedData];
}

</script>

<style scoped>

</style>

Any help is greatly appreciated!

I am building a dashboard using Vue.js 3 and ApexCharts with the composition API. The data for the charts is held in a Pinia data store.

I have a function that pulls data from a sample .json file (which models what an API will return once it is set up), processes, and returns the data as a multidimensional array for the charts that is stored in the Pinia store.

The problem I'm having is when I run the function to update the data, I can see it update in the Pinia store via vue devtools, but the chart does not change to reflect the new values like the rest of the app does.

At the advice of other stack overflow answers, I've wrapped chart options and series in ref(). I've also read the info on this GitHub repo, but as I'm fairly new to Vue and have only worked with the composition API and <script setup>, I'm unsure what exactly to implement in my code to make the charts reactive to Pinia data.

I have created a sample repo with a single chart and sample data on stackblitz showing the behavior I'm experiencing.

Chart component with options and series code:

<template>
  <VueApexCharts type="bar" height="360px" :options="chartOptions" :series="chartSeries">
  </VueApexCharts>
</template>

<script setup>
/*********
 Imports
 *********/
import VueApexCharts from "vue3-apexcharts";
import { ref } from "vue";
import { useDataStore } from "@/stores/dataStore.js";

const storeData = useDataStore();

/*********
 Apex Chart
 *********/
let chartOptions = ref({
  chart: {
    id: "grocerChart",
    type: "bar",
    // height: 350,
    stacked: true,
    stackType: "100%",
    toolbar: {
      show: true,
      tools: {
        download: true,
        zoom: false,
        zoomin: true,
        zoomout: true,
        reset: true,
      },
    },
  },
  title: {
    text: storeData.selectedRegion,
    align: "center",
  },
  plotOptions: {
    bar: {
      horizontal: true,
    },
  },
  grid: {
    padding: {
      bottom: 20,
      right: 20,
      top: 5,
    },
  },
  xaxis: {
    categories: storeData.chartData[0], // ['Fruit', "Meat", "Vegetables"],
    tickPlacement: "on",
    labels: {
      rotate: 0,
      style: {
        fontSize: 10,
      },
    },
  },
  fill: {
    opacity: 1,
  },
  legend: {
    fontSize: 10,
    offsetX: 0,
    offsetY: 0,
  },
  dataLabels: {
    enabled: false,
  },
  noData: {
    text: "No Data",
    style: {
      fontSize: "24px",
    },
  },
});

let chartSeries = ref([
  {
    name: storeData.chartData[1][0][0], // 'fruit',  
    data: storeData.chartData[1][0][1], // [10, 14, 10], 
  },
  {
    name: storeData.chartData[1][1][0], // 'meat', 
    data: storeData.chartData[1][1][1], // [10, 10, 4],
  },
  {
    name: storeData.chartData[1][2][0], // 'vegetable',
    data: storeData.chartData[1][2][1], // [9, 7, 12],
  },
]);

// console.log(storeData.chartDataTest[1][0][1])
</script>

<style scoped>
</style>

Pinia code:

import {ref} from 'vue'
import {defineStore} from 'pinia'

export const useDataStore = defineStore('data', () => {

    let selectedRegion = ref('No Region Selected');
    let chartData = ref([
        [],
        [
          [[], []],
          [[], []],
          [[], []]
        ]
    ]);

    return {
        selectedRegion,
        chartData
      }
})

App.vue with function to process model .json data and return multidimensional array for chart:

<template>
  <div class="w-100">
    <p>Select a region: </p>
    <v-btn class="ma-3" @click="storeData.selectedRegion = 'East'">East</v-btn>
    <v-btn class="ma-3" @click="storeData.selectedRegion = 'Midwest'">Midwest</v-btn>
    <p>Selected Region: {{ storeData.selectedRegion }}</p>
    <p class="mt-5">After selecting a region above, click "Update Chart" below:</p>
    <v-btn 
    class="ma-3" 
    @click="storeData.chartData = updateData(grocerData, storeData.selectedRegion)">
      Update Chart
    </v-btn>
    <v-card class="w-75 mt-5">
      <StoreInventoryChart/>
    </v-card>
  </div>

</template>

<script setup>
import {useDataStore} from "@/stores/dataStore.js";
import StoreInventoryChart from "@/components/storeInventoryChart.vue";
import grocerData from "./models/sample-api-data.json";

const storeData = useDataStore();

function updateData(data, selectedRegion) {
    // Filter data by selected state 
    const selRegionData = data.filter(item => item.state === selectedRegion);

    // Extract and sort store names
    const stores = [...new Set(selRegionData.map(item => item.store))].sort();

    // Extract and sort category values
    const categories = [...new Set(selRegionData.map(item => item.category))].sort();

    // Initialize the result array for categorized data
    const categorizedData = categories.map(category => [category, Array(stores.length).fill(0)]);

    // Populate the categorized data
    selRegionData.forEach(item => {
        const storeIndex = stores.indexOf(item.store);
        const categoryIndex = categories.indexOf(item.category);
        categorizedData[categoryIndex][1][storeIndex] += item.inventory;
    });

    return [stores, categorizedData];
}

</script>

<style scoped>

</style>

Any help is greatly appreciated!

Share Improve this question edited Jan 17 at 23:44 VeggieBear asked Jan 17 at 21:22 VeggieBearVeggieBear 151 silver badge6 bronze badges 5
  • I have created a sample repo with a single chart and sample data on stackblitz - "Sorry, we couldn’t find the page you’re looking for" - also, the code you've posted makes zero reference to apexcharts, perhaps that's why apexcharts isn't doing what you want, since you don't seem to be using it – Bravo Commented Jan 17 at 22:26
  • also read this post on GitHub - that's not a "post", that's a github repository for Vue 3 component for ApexCharts - there's also this documentation in apexcharts itself – Bravo Commented Jan 17 at 22:31
  • Thank you for the comments. I fixed the stackblitz link and added more of the code for ease. The ApexCharts documentation section on "Updating Vue Chart Data" seems to be directly updating the series data in the chart itself to trigger the update. Since the data in my app a Pinia store, do I have to trigger the chart update manually? – VeggieBear Commented Jan 17 at 23:06
  • do I have to trigger the chart update manually? - no, you just use pinia like you would anywhere else – Bravo Commented Jan 17 at 23:18
  • Thanks Bravo, it seems like the chart isn't recognizing when the data in pinia updates. I'm able to get it to work as I need it to by using updateOptions along with my updateData() function: ApexCharts.exec("grocerChart", "updateOptions", { series: [{name: storeData.chartData[1][0][0].... title: .... xaxis: ...) If there's a more elegant solution, I'd love to implement it. – VeggieBear Commented Jan 18 at 3:56
Add a comment  | 

1 Answer 1

Reset to default 1

You have to use computed instead of ref to receive all updates. Using ref only catches chart's data available at the component initialization ignoring further updates. The following code would work without extra effort:

- import { ref } from 'vue';
+ import { computed } from 'vue';

- let chartOptions = ref({
+ let chartOptions = computed(() => ({
...
- let chartSeries = ref([
+ let chartSeries = computed(() => [

The full working version:

import VueApexCharts from 'vue3-apexcharts';
import { computed } from 'vue';
import { useDataStore } from '@/stores/dataStore.js';

const storeData = useDataStore();

/*********
 Apex Chart
 *********/
let chartOptions = computed(() => ({
  chart: {
    id: 'grocerChart',
    type: 'bar',
    // height: 350,
    stacked: true,
    stackType: '100%',
    toolbar: {
      show: true,
      tools: {
        download: true,
        zoom: false,
        zoomin: true,
        zoomout: true,
        reset: true,
      },
    },
  },
  title: {
    text: storeData.selectedRegion,
    align: 'center',
  },
  plotOptions: {
    bar: {
      horizontal: true,
    },
  },
  grid: {
    padding: {
      bottom: 20,
      right: 20,
      top: 5,
    },
  },
  xaxis: {
    categories: storeData.chartData[0], // ['Fruit', "Meat", "Vegetables"],
    tickPlacement: 'on',
    labels: {
      rotate: 0,
      style: {
        fontSize: 10,
      },
    },
  },
  fill: {
    opacity: 1,
  },
  legend: {
    fontSize: 10,
    offsetX: 0,
    offsetY: 0,
  },
  dataLabels: {
    enabled: false,
  },
  noData: {
    text: 'No Data',
    style: {
      fontSize: '24px',
    },
  },
}));

let chartSeries = computed(() => [
  {
    name: storeData.chartData[1]?.[0]?.[0], // 'fruit',
    data: storeData.chartData[1]?.[0]?.[1], // [10, 14, 10],
  },
  {
    name: storeData.chartData[1]?.[1]?.[0], // 'meat',
    data: storeData.chartData[1]?.[1]?.[1], // [10, 10, 4],
  },
  {
    name: storeData.chartData[1]?.[2]?.[0], // 'vegetable',
    data: storeData.chartData[1]?.[2]?.[1], // [9, 7, 12],
  },
]);

The computed properties would catch every changes and reflect them to the underlying component (VueApexCharts).

In chartSeries computation, I've considered the nullable data using the null-coalescing operator (?.) preventing possible errors in absence of storeData.chartData.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论