apertureless / vue-chartjs

📊 Vue.js wrapper for Chart.js
https://vue-chartjs.org
MIT License
5.55k stars 837 forks source link

Loading data from server and displaying #78

Closed cdubant closed 7 years ago

cdubant commented 7 years ago

Expected Behavior

In the example for Reactive Data in the docs, when implemented, you have to click the "Randomize" button to trigger the fillData function and get content displayed in the graph.

Actual Behavior

I'm looking for a way (as I serve the content to display from an API) to display the data as soon as the component gets a response from my API.

I tried to do the usual Vue 2.0 nextTick to call the fillData function when the component is mounted

mounted () {
      this.$nextTick(function () {
        this.fillData;
      })
    },

Below is the code of my component (slightly adapted to match my API call), of course it works perfectly when I click the "Randomize" button but I don't want to have to click this button for each and every graph in my "Dashboard" component (several graphs in this component).

<template>
    <div>
      <line-chart :chart-data="datacollection"></line-chart>
      <button @click="fillData()">Randomize</button>
    </div>
</template>

<script>
  import LineChart from './LineChart.js'

  export default {
    components: {
      LineChart
    },
    data () {
      return {
        datacollection: null
      }
    },
    mounted () {
      this.$nextTick(function () {
        this.fillData;
      })
    },
    methods: {
      fillData () {
        this.$http.get('/api/data/line/scenarios_by_day')
            .then(response => {
            this.datacollection = response.data;
        });
      },
    }
  }
</script>

Thank you in advance for your help !

Environment

apertureless commented 7 years ago

Well, you don't need to use Vue's $nextTick() function, because vue does not render the chart. The chart gets renderes by chart.js on a canvas.

You can simply make your api call in the mounted() hook.

However your response.data need to have a valid chart.js datatructure.

But keep in mind the limitations to the watcher. There are also some issues related to this: #44 #34

yomete commented 7 years ago

Hi @cdubant

I recently had to implement something like this and I was kinda stuck. Here's what I did (with help from Vuex);

First thing I did was store what I needed from the API call in Vuex so I could pretty much access it anywhere. Then I wrote a computed object that returns the the data in Vuex and uses it to build the chart; something like this

      datasetsfull () {
        return {
          labels: store.state.chartdays,
          datasets: [
            {
              label: 'Time Signed In',
              backgroundColor: '#40CF97',
              data: store.state.charttimes
            }
          ]
        }
      },
      chartdays () {
        return store.state.chartdays
      },
      charttimes () {
        return store.state.charttimes
      }
    }

And then in my template I simply used the datasetsfull function; something like this; <reports-charts :chart-data="datasetsfull" :height="400" :options="{responsive: true, maintainAspectRatio: false}"></reports-charts>

I hope that was useful to you 😄

cdubant commented 7 years ago

@apertureless the thing is that even if I do the following

LineChart.vue

<template>
    <div>
      <line-chart :chart-data="datacollection"></line-chart>
    </div>
</template>

<script>
  import LineChart from './LineChart.js'

  export default {
    components: {
      LineChart
    },
    data () {
      return {
        datacollection: null
      }
    },
    mounted () {
      this.fillData;
    },
    methods: {
      fillData () {
        this.$http.get('/api/data/line/scenarios_by_day')
            .then(response => {
            this.datacollection = response.data;
        });
      },
    }
  }
</script>

LineChart.js

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

export default Line.extend({
  mixins: [reactiveProp],
  props: ['options'],
  mounted () {
    // this.chartData is created in the mixin
    this.renderChart(this.chartData, this.options)
  }
})

The fillData method never gets triggered, I never see the API call.

When I add a button to trigger this method, then the API call is made and the data is displayed just fine. My response.data is valid JSON (expected format) built by my Laravel Spark backend.

The thing is that I can't have my users perform any action before displaying the charts, it would make no sense on an ergonomic POV.

@yomete Thanks for your insight but I'm afraid I'm not familiar with Vuex so it might be a little overkill to implement / configure Vuex for my (I hope) "simple" requirement. Anyway thanks again for providing a solution.

apertureless commented 7 years ago

Well, your fillData() is a method. You should call it like one.

mounted () {
  this.fillData();
},

Besides: I don't really understand why you have two LineChart components? Also with the exact same name. Is there a reason behind this?

cdubant commented 7 years ago

@apertureless you're right, though it doesn't help in rendering the chart.

After fixing the method call, I saw the API call but it didn't help in rendering the graph.

I created 2 different files because of http://vue-chartjs.org/#/home?id=reactive-data (I can see a js file and something that looks like a vue file, that's why I created both).

I found a simpler solution by having a single component and doing the following (as simple as that)

import { Line } from 'vue-chartjs'

export default Line.extend({
  data () {
    return {
      resultSet: null
    }
  },
  props: ['options'],
  mounted () {
    this.fillData();
  },
  methods: {
    fillData () {
      this.$http.get('/api/data/line/scenarios_by_day')
          .then(response => {
          this.resultSet = response.data;
          this.renderChart(this.resultSet, this.options)
      });
    },
  }
})

It now works as expected, thanks for your time. :+1:

moonlik commented 7 years ago

Have the same problem - chart is not redrawn after datasets change.

apertureless commented 7 years ago

Can you prodive a jsfiddle / codepen ? For reproduction? It heavily depends on how you populate your data, if you're using the reactive prop etc.

moonlik commented 7 years ago

@apertureless I can provide code here because I use API requests. My vue component looks like this:

<template>
  <div>
    <pie-chart :chart-data="pieChartData"></pie-chart>
  </div>
</template>

<script>
  import axios from 'axios'
  import PieChart from './ui/PieChart.js'

  export default {
    components: {
      PieChart
    },
    data () {
      return {
        pieChartData: {
          labels: ['Android', 'iOS'],
          datasets: [{
            backgroundColor: [
              '#a4c639',
              '#5fc9f8'
            ],
            hoverBackgroundColor: [
              '#a4c639',
              '#5fc9f8'
            ],
            data: null
          }]
        },
    mounted () {
      this.getDevices()
    },
    methods: {
      getDevices: function () {
        Promise.all([
          axios.get(`/devices?platform=android`),
          axios.get(`/devices?platform=ios`)
        ])
          .then(responses => {
            this.pieChartData.datasets[0].data = responses.map(function (response) {
              return response.data.total
            })
          })
        }
      }

And PieChart.js:

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

export default Pie.extend({
  mixins: [reactiveProp],
  props: ['chartData', 'options'],
  mounted () {
    // this.chartData is created in the mixin
    this.renderChart(this.chartData, this.options)
  }
})
apertureless commented 7 years ago

You could try the solution mentioned here https://github.com/apertureless/vue-chartjs/issues/10#issuecomment-289494398

The problem is like mentioned in #44 the limitations

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.

If it's not working you could try an event based system, where you trigger an event and listen on it and then call update() yourself.

moonlik commented 7 years ago

@apertureless anyway can't make it work. Vue Devtools shows me that the data is successfully changed, but the pie chart is still empty:

2017-04-10 18 03 31

If I click on the label, the chart is redrawn, but not automatically.

reactiveProp instructions didn't help me too.

apertureless commented 7 years ago

Have you tried the solution mention in #10 ? Without the reactive prop mixin, and add a own watcher?

Like I said, not really vue-chartjs related. Rather how you handle data. Sure the data is successfully changed, but like mentioned in #44 vue does not keep the old value in the watcher. So it does not recognize that there is a state change. Therefore it the watcher does not trigger.

moonlik commented 7 years ago

@apertureless yes, I changed my code like this:

import { Pie } from 'vue-chartjs'

export default Pie.extend({
  props: ['data'],
  mounted () {
    this.renderChart(this.data)
  },
  watch: {
    data: function () {
      this._chart.destroy()
      this.renderChart(this.data)
    }
  }
})

But in my case it doesn't help me, don't know why

dwk715 commented 7 years ago

@moonlik Have you solved? I have the same problem like you. If I click on the label twice, the chart will redrawn.

moonlik commented 7 years ago

@dwk715 Not yet, I have no idea how to solve it for now

apertureless commented 7 years ago

Well you could fire an event if new data arrives and then re-render or update the chart instance.