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

Reactivity of Options #106

Closed
crholliday opened this issue May 8, 2017 · 10 comments
Closed

Reactivity of Options #106

crholliday opened this issue May 8, 2017 · 10 comments

Comments

@crholliday
Copy link

I need to have Options be reactive so that it will update the title property. It does not appear to be watched in the mixin? No matter what I do below, the title will not re-render on the chart.

// lineChart.js
import { Line, mixins } from 'vue-chartjs'

export default Line.extend({
  mixins: [mixins.reactiveProp],
  props: ['chartData', 'options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
})
// Nevermind the inconsistency that the "bar" component is delivering a "line" :)
// chart component
<template lang="pug">
.columns
  .column.is-11.is-offset-1
    .card
      bar-chart(v-bind:chartData='chartData' 
                v-bind:options='chartOptions' 
                v-bind:height='340', v-bind:width='960' )
</template>

<script>
const moment = require('moment')
const axios = require('axios')
const config = require('../config')
import BarChart from '../components/BarChart.js'

require('moment-duration-format')

export default {
  name: 'flight-trend-chart',
  components: {
    BarChart
  },
  data () {
    return {
      chartData: {},
      cheapFlightsByEndpoint: [],
      chartOptions: {}
    }
  },
  computed: {
    title: function () {
      return 'Flights from ' + this.origin + ' to ' + this.destination
    }
  },
  methods: {
    loadCheapFlightsByEndpoint: function (origin, destination) {
      axios.get(config.api_base_url + '/flights-by-endpoints?origin=' + origin + '&destination=' + destination)
        .then(response => {
          this.cheapFlightsByEndpoint = response.data
          this.chartData = this.makeChartData()
          console.log(this.title)
          this.chartOptions = this.makeChartOption(this.title)
        })
    },
    makeChartOption: function (title) {
      return {
        responsive: false,
        title: {
          display: true,
          text: title,
          fontSize: 16
        },
        scales: {
          yAxes: [{
            gridLines: {
              display: true,
              color: 'rgba(0,0,0,0.1)'
            },
            ticks: {
              beginAtZero: false
            }
          }]
        }
      }
    },
    makeChartData: function () {
      let dates = this.cheapFlightsByEndpoint.map(function (a) { return moment(a.created).format('MM/DD/YYYY') })
      let lowPrices = this.cheapFlightsByEndpoint.map(function (a) { return a.low_price })
      let avgPrices = this.cheapFlightsByEndpoint.map(function (a) { return a.avg_price })
      let data = {
        labels: dates,
        datasets: [
          {
            label: 'Low Prices',
            borderWidth: 1,
            pointRadius: 2,
            data: lowPrices,
            backgroundColor: 'rgba(0, 14, 14, 0.57)'
          },
          {
            label: 'Avg Prices',
            borderWidth: 1,
            pointRadius: 2,
            data: avgPrices,
            backgroundColor: 'rgba(4, 112, 110, 0.57)'
          }
        ]
      }
      return data
    }
  },
  props: {
    origin: {type: String, required: true},
    destination: {type: String, required: true}
  },
  created: function () {
    this.loadCheapFlightsByEndpoint(this.origin, this.destination)
  },
  watch: {
    origin: function () {
      this.loadCheapFlightsByEndpoint(this.origin, this.destination)
    },
    destination: function () {
      this.loadCheapFlightsByEndpoint(this.origin, this.destination)
    }
  }
}
</script>

Environment

  • vue.js version: 2.1.10
  • vue-chart.js version: 2.5.6
  • npm version: 3.10.3
@euledge
Copy link
Contributor

euledge commented May 8, 2017

I feel the same thing as you. Now I implement with the following way.
I hope it will be possible to watch options as mixins.reactiveProp too.

export default Line.extend({
  mixins: [mixins.reactiveProp],
  props: ['chartData', 'options'],
  watch: {
    'options': {
      handler(newOption, oldOption) {
        this.renderChart(this.chartData, this.options)
      },
      deep: true
    }
  }
});

@crholliday
Copy link
Author

Euledge, thank you very much for helping. Unfortunately, when I change my chart code to include the watch and subsequent renderChart(), it is affecting the size of the chart (it appears to be doubling the size of the chart on each subsequent re-render). My chart js now looks like this:

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

export default Line.extend({
  mixins: [mixins.reactiveProp],
  props: ['chartData', 'options'],
  watch: {
    'options': {
      handler (newOption, oldOption) {
        this.renderChart(this.chartData, this.options)
      },
      deep: true
    }
  },
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
})

Did I misinterpret your changes?

Thanks!

@apertureless
Copy link
Owner

apertureless commented May 8, 2017

Hi @crholliday
Yeah the options are not reactive by default. But like @euledge pointed out, you can simply add a watcher by yourself.

You could try to call this._chart.update() which is the internal chart.js method to update the chart instance. However I am not sure if this will work with options.

If this is not working, calling renderChart() is the right way. But you should destroy the old instance before you do so. Because renderChart() creates a new chartjs instance. Which could lead to weird problems.

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

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

I will see if I can implement it as a feature ✌️ so it will work out of the box

@euledge
Copy link
Contributor

euledge commented May 8, 2017

Hi @crholliday
I omitted the mounted block in the code, because I wrote complex processing on that block.
It's right as you wrote in mounted()

@apertureless
Thank you for show me destroy() is necessary.

@crholliday
Copy link
Author

Thanks Euledge and Apertureless... it is working now.

@bodinsamuel
Copy link

I can confirm, calling again renderChart was too buggy.
destroying before was the right solution.
The only difference is, I had to change a line

this.$data._chart.destroy();

@apertureless
Copy link
Owner

Yeah due to the changes in v3

this._chart is now this.$data._chart

@janswist
Copy link

janswist commented Apr 13, 2018

When I update data in Vue form which Chart get the data nothing happens. Console logs nothing...

Vue.component('lineChartKw', {
  extends: VueChartJs.Bar,
  mixins: [mixins.reactiveProp],
  props: ['chartData'],
  watch: {
    'chartData': {
      handler (newOption, oldOption) {
        console.log('refresh watcher')
        this.$data._chart.destroy()
        this.renderChart(this.chartData,{
          responsive: true,
          maintainAspectRatio: true
        })
      },
      deep: true
    }
  },
  mounted() {
    if (this.chartData) {
      this.renderChart(
        this.chartData, {
          responsive: true,
          maintainAspectRatio: true
        })
    }
  }
});

This is how data looks (and I update only data in first datasets):

patterndatakw: {
        labels: ['Backend', 'Full Stack', 'Mobile/Embedded', 'Testing', 'Frontend', 'Dev Ops', 'Business Intelligence', 'IT Trainee', 'Project Management', 'Support', 'UX Designer', 'Business Analyst', 'Other'],
        datasets: [{
          label: 'Refair.me Profile ',
          backgroundColor: '#a84979',
          data: [0.05, 0.2, 0.1, 0.5, 0.2, 0.05, 0, 0, 0, 0, 0]
        }]
      }

For now it only works if I update whole patterndatakw.

@apertureless
Copy link
Owner

@janswist

Well, but you are talking now about your data, right? Not about your options.
Because this issue is about the options.

There are a ton of other issues, regarding the reactivity of data.

First of all, you are importing the reactivity mixin but overwriting it in your local component. The mixin, will check of changes, and depending on the changes it will either update() the chart or do a complete rerender.

It depends on how you are updating your data.

http://vue-chartjs.org/#/home?id=limitations

There are some limitations in javascript and vue, how to detect changes and what the watcher can detect.

@sontd-0882
Copy link

sontd-0882 commented Nov 19, 2020

I think destroying the chart then render again is not a good behavior.
This should work as expected.

export default {
  extends: Line,
  props: ['chartData', 'options'],
  watch: {
    options: {
      handler() {
        this.$data._chart.options = this.options
        this.$data._chart.update()
      },
      deep: true
    }
  },
  mounted() {
    this.renderChart(this.chartData, this.options)
  }
}

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

6 participants