2
votes

I am attempting to make a vue with vue-chartjs. I have followed all the associated documentation with regards to re-rendering the chart when the chartData prop is updated. My chart will only update after i resize the browser window.

my parenttestchart.vue file:

<template>
    <div>
        <h1>Chart datacollection update test</h1>

        <p>This component demonstrates fetching data from the server.</p>

        <p v-if="!datacollection"><em>Loading...</em></p>

        <b-form-select 
                    v-model="testthingSelect.selected"
                    text="Select testthing"
                    variant="primary"
                    :options="testthingSelect.options"
                    :on-change="changetestthing"
                    class="m-md-2">                   
        </b-form-select>     
       <test-chart v-cloak :chart-data="datacollection" :chart-option="options"></test-chart>

    </div>
</template>

<script>

    import TestChart from './TestChart'

    export default {
        components: {
            TestChart 
        },
        data() {
            return {
                yAxisValues: [],
                datacollection: {},
                options: {
                    scales: {
                        yAxes: [{
                            ticks: {
                                beginAtZero: false
                            },
                            gridLines: {
                                display: true
                            }
                        }],
                        xAxes: [{
                            type: "date",
                            display:true,
                            gridLines: {
                                display: false
                            }
                        }]
                    },
                    legend: {
                        display: true
                    },
                    responsive: true,
                    maintainAspectRatio: false
                },
                testthingSelect: {
                    selected: "EIA/COAL",
                    disabled: false,
                    readonly: false,
                    visible: true,
                    color: "",
                    options: [
                        {
                            value: null,
                            text: 'Select a testthing'
                        },
                        {
                            value: "item1",
                            text: "item1"
                        },
                        {
                            value: "item1",
                            text: "item1"
                        },
                        {
                            value: "item1",
                            text: "item1"
                        }
                    ]
                }
            }
        },        
        methods: {
            changetestthing: async function () {
                try {

                    let response = await this.$http.get(url for my data);
                    this.datacollection.datasets = [];
                    this.datacollection.labels = [];

                    ...required data transformations

                    this.$set(this.datacollection, "labels", labels);
                    this.$set(this.datacollection, "datasets", datasets);
                   // this.datacollection.labels = labels;
                  //  this.datacollection.datasets = datasets;

                } catch (error) {
                    console.log(error)
                }
            }
        },
        watch: {
            'commoditySelect.selected': function () { this.changeCommodity(); }

        },
        async created() {
            // ES2017 async/await syntax via babel-plugin-transform-async-to-generator
            // TypeScript can also transpile async/await down to ES5            

            try {               
                let response = await this.$http.get(url for my data);
                            this.datacollection.datasets = [];
                            this.datacollection.labels = [];

                            ...required data transformations

                            this.$set(this.datacollection, "labels", labels);
                            this.$set(this.datacollection, "datasets", datasets);
                           // this.datacollection.labels = labels;
                          //  this.datacollection.datasets = datasets;



                    } catch (error) {
                        console.log(error)
            }
        } catch (error) {
            console.log(error)
        }
    }
}

my TestChart.vue file

<script>
    import { Line, mixins } from 'vue-chartjs'
    const { reactiveProp } = mixins

    //Exporting this so it can be used in other components
    export default {
        extends: Line,
        mixins: [reactiveProp],
        props: ['chartData', 'options'],        
        mounted() {
            this.renderChart(this.chartData, this.options)
        }
    }
</script>

So this is working in terms of selecting and changing the datacollection within the parent vue. Unless i resize the window though the chart does not re-render. When the parenttestchart.vue initially loads i am receiving the following error: 'Uncaught TypeError: Cannot read property 'transition' of null' . But this does not seem to effect anything, as resizing the window will render the initial display of the chart. Updating the data changetestthing method does update the datacollection (chartData) correctly but unless i resize the window it does not re-render.

How can I force the chart to re-render? How can i get a reference to the TestChart component in the parent?? this.TestChart is undefined. I'm only curious about getting the reference in case from the reference i can manually call 'renderChart' on it. thanks.

*** Update

I was able to get the chart to update with the following modifications:

I removed the following lines from the changetestthing method:

this.datacollection.datasets = null;
this.datacollection.labels = null;

And changed the datacollection value assignment statement to this:

this.datacollection = Object.assign({}, this.datacollection, { labels: labels, datasets: datasets });

I can't say for sure that this is the best practice. According to vue.js documentation (https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats)

new properties added to the object will not trigger changes. In such
cases, create a fresh object with properties from both the original
object and the mixin object

So I believe by not setting the datacollection.labels and .datasets properties to null and then using the Object.assign the changes are triggered. I'm still not sure why they weren't triggered using the mixins or manual watch. Any comments appreciated.

3

3 Answers

3
votes

Billy Jean final note worked for me PIECHART.JS:

import {
 Pie,
 mixins
} from 'vue-chartjs'
const {
reactiveProp
} = mixins

export default Pie.extend({
   mixins: [reactiveProp],
   props: ['options'],
watch: {
'options': {
  handler(newOption, oldOption) {
    this._chart.destroy()
    this.renderChart(this.chartData, this.options)
  },
  deep: true
}
    },
  mounted() {      
    this.renderChart(this.chartData, {responsive: true, maintainAspectRadio: 
    false})
   }
})

This is in my view.vue File:

 let datasets = []
  let colorlist = [ '#4e5fa4', '#5e5fa4', '#6e5fa4',
  '#7e5fa4', '#8e5fa4', '#a08be4', '#b08bd4']
        datasets.push({
          label: this.data_by_city_label,
          data: this.data_by_city_data,
          fill: false,
          backgroundColor: colorlist,
          pointBorderWidth: 1,
          borderWidth: 1,
          pointRadius: 0
        })
  this.CityDatac = Object.assign({}, this.datasets, {labels: this.data_by_city_label, datasets: datasets})

With this i can send my data to the PIECHART js and generated in my view.vue as so:

   <pie-chart-city :chartData="CityDatac" :width="300" :height="250"></pie-chart-city>

Hope that helps anyone in VUE.js -> Vue-chartjs.js

5
votes

One way that works is to add a watcher to the chart,

props: ['chartData', 'options'],
mounted () {
  this.renderChart(this.chartData, this.options)
},
watch: {
  'chartData' (to, from) {
    this.renderChart(this.chartData, this.options)
  }
},

See example CodePen


Edit - watcher not working

The difference between your code and the working CodePen is that CodePen mutates the variable passed into the chart, therefore it's watcher reacts.
Your code mutates the .datasets property of the variable - there's a known issue with this ref: Mixins don't seem to trigger a refresh of the chart #44.

Try replacing

this.datacollection.datasets = [];
this.datacollection.labels = [];

with this (both places)

this.datacollection = { datasets: [], labels: [] };
0
votes

The code posted by Alexis Poo points me in the right direction.

In my case, I need to modify the watch object a little bit.

When trying to destroy the chart by calling this._chart.destroy() when option changed, I received an error message: cannot read property 'destroy' of undefined.

To fix the issue, I have to do this.$data._chart.destroy() instead.

Hope it helps.