Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mixins don't seem to trigger a refresh of the chart #44

Closed
berendberendsen opened this issue Feb 24, 2017 · 19 comments
Closed

Mixins don't seem to trigger a refresh of the chart #44

berendberendsen opened this issue Feb 24, 2017 · 19 comments

Comments

@berendberendsen
Copy link

berendberendsen commented Feb 24, 2017

Expected Behavior

Adding a label and value to the dataset should add the item to the chart

Actual Behavior

Adding a label and value to the data doesn't change the chart. The chart only gets redrawn when I resize the browser

Line chart:

import { Line, mixins } from 'vue-chartjs'

export default Line.extend({
  props: ["options", "chartData"],
  mixins: [mixins.reactiveProp],
  mounted () {
    this.renderChart(this.chartData,this.options);
    this._chart.update();
  },
})

Component:

<template>
    <div class="charts">
      <div class="col-md-12">
        <line-chart :chart-data="chartData" :options="chartOptions"></line-chart>
        <md-button @click.native="addData">add data</md-button>
      </div>
    </div>
</template>
<style scoped>
</style>
<script>
import lineChart from './chartOverviewTimeline.js';

export default {
    name: 'chartDashboard',
    components : {
        lineChart
    },
    data: function () {
        return {
          chartData : {},
          chartOptions : {mainAspectRatio : false}

        }
    },
    mounted(){
      this.prepareChartData();
    },
    methods: {
      addData: function(){
      for(let i = 0 ; i < 1; i ++){
          this.chartData.labels.push(100 + i);
          this.chartData.datasets[0].data.push(Math.random() * 100);
        }
      },
      prepareChartData(){
        this.chartOptions = {
          maintainAspectRatio:false,
          scaleShowHorizontalLines : true,
          scales: {
            xAxes: [{
              stacked: false
            }],
            yAxes: [{
              stacked: false,
              gridLines:{
                display: true
              }
            }]
          }
        };
        this.chartData = {
          labels:[],
          datasets:[
            {
              label: 'Sensor 1',
              backgroundColor: '#734375',
              data: [],
              fill: false,
              lineTension: 0
            }]
        };
        for(let i = 0 ; i < 5; i++){
          this.chartData.labels.push(i);
          this.chartData.datasets[0].data.push(Math.random() * 100);
        }
      }
    }    
}
</script>

I would expect that the addData function triggers a change in the prop and therefor a redraw in the chart.

Vue chartjs version 2.3.7

@berendberendsen berendberendsen changed the title Mixins don't seem to work Mixins doesn't seem to work Feb 24, 2017
@berendberendsen berendberendsen changed the title Mixins doesn't seem to work Mixins don't seem to trigger a refresh of the chart Feb 24, 2017
@apertureless apertureless self-assigned this Feb 27, 2017
@apertureless
Copy link
Owner

Seems that sometimes the

watch: {
 'chartData': function () {}
}

does not trigger a change. Right not I am not exactly sure why and when.

@berendberendsen
Copy link
Author

The way how I have solved it is that instead of just adding one element to the array I actually recreate the array. Not ideal but it seems to work.

However since the latest update to Vue 2.2 things have gotten a bit 'weirder'. I now get a undefined error on this.chartId whenever I change any data on the page. Will create a separate issue for this, however it might be webpack related.

You can close this issue unless you want to keep on digging.

Great work on the package by the way!

@apertureless
Copy link
Owner

Well I leave it open because this watcher bug seems to apear from time to time like in #41
If I find some time, I will dig into it.

Yeah with vue 2.2 some things changed. I still need to update the dependencies and do some testing before I merge.

@apertureless
Copy link
Owner

Well a few points here, about your example, as I tried to repoduce the bug.

First

You don't need the this._chart.update(); in your line chart. Because the mixin handle it.

Second

You should not name your data property the same as the line-chart prop. As data properties, Poprs and computed properties share all the same namespace. This could lead to some problems!

I think kind of the problem is, that you're setting the data one by one directly on your data property.
I am not completely sure how the lifecycle of the reactivity is. But I guess, what happens is that you're changing the data prop one by one and you pass it to the :chart-data prop in your linechart. Seems that the reactive props still hold the old references.

Furthermore its not the best idea to pass the data one by one. Because in the worst case scenario the data would be passed to the prop while incomplete and the mixin would rerender the chart or update it. And I don't know how forgiving chart.js is if you have bad data passed to the render function.

The mixins are also pretty generic. And does not fit all use cases. However you can implement it by your own, pretty easy.

You could also make it event based and if you finished changing your data emit an event

$emit('data:changed') and in the chart component listen on it and update the chart based on the event.

You can also update to the latest version of vue-chartjs, which now got the current vue.js version.

@SemyonL95
Copy link

Hello, i got same issue, and i found that if you push on charts labels it is updating, but not working when i updating props, is there any solution how to slove it?

@apertureless
Copy link
Owner

Can you provide some sample code ?

@SemyonL95
Copy link

SemyonL95 commented Mar 2, 2017

Hi, i found what is a problem in, all in vue.$watch.
When you change the object (not replacing), vue watch new value is the same as old, so i solve it by replacing object, but in your plugin there are a little issue, when you replacing object with the same dataset lenght, it's give the chart.js update error. Thanks for attention, and sorry for my english.

@thegreyfellow
Copy link

Hi, I got the same issue here, I only send the percentages to the Pie chart not the whole 'chartData' and I used my own watcher. so the I got the same problem described by @berendberendsen at the first comment, after a lot of headache with it I've found out that I was assigning the data instead of changing the object as @SemyonL95 said before me. so after I used 'push' instead of simple assignment of data it worked.

// inside parent component:
// not working code
...
  this.chartData[0] = 70
  this.chartData[1] = 30
...
// working code
...
  this.chartData.push(70)
  this.chartData.push(30)
...
// inside child component pieChart.js
...
  props:['chartData']
...
  datasets:[{ ... 
    data: this.chartData 
  }]

after having a look at the difference of direct assignement and push, it's all about performance but they do the same thing so if someone could care for an explanation it might show the solution to this weird bug.

@vnermolaev
Copy link

@thegreyfellow I believe it has to do with Vue itself and more deeply with JS in general, not with vue-chartjs. Changes like you describe are not identifyable, see
Objects: https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
Arrays: https://vuejs.org/v2/guide/list.html#Caveats

Particularly for array you can do array.splice(position, 1, new_datum)

@thegreyfellow
Copy link

@quicky84 Yes after reading these posts I understood the issue, as you said it is not related to the library neither the VueJS, but a JS issue. Thanks for the explanation :) .

@apertureless
Copy link
Owner

Thanks @quicky84 for the find. Additionally there is a note on vm.$watch

Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.

I added it to the docs, I guess I can close here.

@thegreyfellow
Copy link

Sorry to re-comment here but you should re-check the link you put on the docs it has an address of localhost:8080. I hope I'm not wrong

@apertureless
Copy link
Owner

apertureless commented Mar 8, 2017

Ups 🙈 ,

fixed it now.
Thanks @thegreyfellow .

@joelmandell
Copy link

joelmandell commented Jul 11, 2017

I did like below and it is working reactively that way :)

this.collection = Object.assign({})
  this.$set(this.collection,"labels",["Pojkar","Flickor","Kvinnor","Män"])
  this.$set(this.collection,"datasets",[])
  this.collection.datasets.push({
                            data: [this.pojkar, this.flickor, this.kvinnor, this.män],
                            backgroundColor: [
                                'rgba(255, 159, 64, 0.2)',
                                'rgba(54, 162, 235, 1)',
                                'rgba(155, 159, 64, 0.2)',
                                'rgba(255, 129, 64, 0.2)',
                            ],
                            borderColor: [
                                'rgba(255,99,132,1)',                        
                                'rgba(255, 206, 86, 1)',
                                'rgba(255, 206, 86, 1)',
                                'rgba(255, 206, 86, 1)',
                            ],
                            borderWidth: 1
                        
                    })

@apertureless
Copy link
Owner

Oh yeah, that would be a good workaround to use $set

@p-adams
Copy link

p-adams commented Jul 23, 2017

Here is what I came up with:

In the parent component:

`props: ['dist'],
  created () {
    let labels = [] // aux array for chartData labels
    let data = [] // aux array for chartData datasets data
    this.dist.forEach((freq, word) => {
        labels.push(word)
        data.push(freq)
    })
    this.chartData.labels = labels
    this.chartData.datasets[0].data = data
},
data () {
    return {
        chartData: {
            labels: [],
            datasets: [
                {
                    label: 'Frequency Distribution',
                    backgroundColor: '#f87979',
                    fontSize: '20px',
                    data: []
                }
            ]
        },
        chartOptions: {
            responsive: false, 
            maintainAspectRation: false
        },
    }
},`

Then in child component:

export default Bar.extend({
   name: 'freq-dist-chart',
   mixins: [reactiveProp],
   props: ['options'],
   mounted () {
       this.renderChart(this.chartData, this.options)
   }
  })

@wutmikee
Copy link

wutmikee commented Nov 25, 2017

Here's my method, which IMO is the easiest solution, for parent to child component data.

First, assign your chart component a ref on the parent component. i.e. <radar-chart ref="radarChart" :data="radarChartData"></radar-chart>

In your chart component, I remove all reactive mixing stuff. Not needed. Then I created the following methods:

mounted () {
	this.renderChart(this.data, this.options)
},

methods: {
	update() {
		this.$data._chart.update()
	}
}

Finally, in your parent, create a method along of the lines of this to update your data:

updateDataExample() {
	this.radarChartData.datasets[0].data = [4, 2, 8, 6]
	this.$refs.radarChart.update()
},

@woto
Copy link

woto commented Aug 18, 2018

      this.chart_data = Object.assign({}, this.chart_data, {
          labels: ['c', 'd'],
      });

      this.chart_data.datasets[0] = Object.assign({}, this.chart_data.datasets[0], {
          data: [3, 4]
      });

or

      this.$set(this.chart_data.datasets[0].data, 0, 10)

@jofftiquez
Copy link

Here's my method, which IMO is the easiest solution, for parent to child component data.

First, assign your chart component a ref on the parent component. i.e. <radar-chart ref="radarChart" :data="radarChartData"></radar-chart>

In your chart component, I remove all reactive mixing stuff. Not needed. Then I created the following methods:

mounted () {
	this.renderChart(this.data, this.options)
},

methods: {
	update() {
		this.$data._chart.update()
	}
}

Finally, in your parent, create a method along of the lines of this to update your data:

updateDataExample() {
	this.radarChartData.datasets[0].data = [4, 2, 8, 6]
	this.$refs.radarChart.update()
},

Github should have a "pin comment" feature for answers like this. Thanks @mbarwick83

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants