ffuentes
ffuentes

Reputation: 1182

Why is this chartjs graph not being loaded? (Using Vue.js)

This is my chartjs component and I'm trying to pass two arrays from the backend. Somehow Vue.js gets the arrays within Vue and yet it doesn't display anything. I'm very new to Vue so bear with me.

Chartjs.vue

<template>
  <div class="main-content">
    <div class="page-header">
      <h3 class="page-title">Chart JS</h3>
      <ol class="breadcrumb">
        <li class="breadcrumb-item"><a href="#">Home</a></li>
        <li class="breadcrumb-item"><a href="#">Charts</a></li>
        <li class="breadcrumb-item active">Chart JS</li>
      </ol>
    </div>
    <div class="row">
      <div class="col-sm-12">
        <div class="card">
          <div class="card-header">
            <h6>Chartjs</h6>
          </div>
          <div class="card-body">
            <div class="mb-4">
              <h5 class="section-semi-title">
                Line Chart
              </h5>
              <line-chart
                :labels='["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"]'
                :values='values'
              />
            </div>
            <div class="mb-4">
              <h5 class="section-semi-title">
                Bar Chart
              </h5>
              <bar-line-chart
                :labels='["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"]'
                :values='values'
                :valuesline='valuesline'
              />
            </div>

          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script type="text/babel">
import LineChart from '../../../components/chartjs/LineChart.vue'
import BarChart from '../../../components/chartjs/BarChart.vue'
import BarLineChart from '../../../components/chartjs/BarLineChart.vue'
import PieChart from '../../../components/chartjs/PieChart.vue'
import DoughnutChart from '../../../components/chartjs/DoughnutGraph.vue'

export default {
  components: {
    LineChart,
    BarChart,
    BarLineChart,
    PieChart,
    DoughnutChart,
    values:[],
    valuesline:[]
  },
  methods: {
    async fetchBarChartData ({ anio, sort }) {
      await axios.get(`/api/ingresos-por-mes/${anio}`).then(res=>{
        this.values=res.data.qty;
        this.valuesline=res.data.perc;
       });
    }
  },
  mounted() {
      this.fetchBarChartData({anio:2018});
  },
  data () {
    return {
      pieAndDoughtnut: {
        labels: ['Red', 'Blue', 'Yellow'],
        data: [300, 50, 100],
        bgColors: [
          '#FF6384',
          '#36A2EB',
          '#FFCE56'
        ],
        hoverBgColors: [
          '#FF6384',
          '#36A2EB',
          '#FFCE56'
        ]
      },
      values: this.values,
      valuesline: this.valuesline
    }
  }
}
</script>

BarLineChart.vue

<template>
  <div class="graph-container">
    <canvas id="graph" ref="graph"/>
  </div>
</template>

<script>
import Chart from 'chart.js'

export default {
  props: {
    labels: {
      type: Array,
      require: true,
      default: Array
    },
    values: {
      type: Array,
      require: true,
      default: Array
    },
    valuesline: {
      type: Array,
      require: true,
      default: Array
    }
  },

  mounted () {
    let context = this.$refs.graph.getContext('2d')
    let options = {
      responsive: true,
      maintainAspectRatio: false,
      legend: {
        display: false
      }
    }

    let data = {
      labels: this.labels,
      datasets: [
        {
          label: 'My First dataset',
          backgroundColor: 'rgba(79, 196, 127,0.2)',
          borderColor: 'rgba(79, 196, 127,1)',
          borderWidth: 1,
          hoverBackgroundColor: 'rgba(79, 196, 127,0.4)',
          hoverBorderColor: 'rgba(79, 196, 127,1)',
          data: this.values
        },
        {
          label: 'My First dataset',
          data: this.valuesline,
          type: 'line'
        }
      ]
    }

    this.myBarChart = new Chart(context, {
      type: 'bar',
      data: data,
      options: options
    })
  },

  beforeDestroy () {
    this.myBarChart.destroy()
  }
}
</script>

<style scoped>
.graph-container {
  height: 300px;
}
</style>

What's odd is that if I make a mistake passing the arguments to the lower components. For example:

<line-chart
                :labels='["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"]'
                :values='values.data'
              />

Adding ".data" to values breaks that component but lets the other components load the chart below.

Is there anyone who had to deal with this in the past? I'm using a dashboard I found on laraspace.in just in case.

EDIT: I've added this to the codesandbox so you can help me more easily.

https://codesandbox.io/embed/vue-template-x4z7b

EDIT 2: Managed to make it work with local values in the sandbox but somehow it doesn't work in local when calling the api instead.

Upvotes: 1

Views: 1964

Answers (1)

Robot
Robot

Reputation: 3991

I apologize: vuejs documentation on how to fetch async data and integrate it into you application is not the best. I wish they would be more opinionated on best practices here with more examples.

Your problem: A component depending on async data

While your async api call is being made, this.values is an empty array (this.values.data doesn’t exist until the api call responds). Before the data comes back, vue creates the child component which creates a chart in it using the empty values array, resulting in a blank chart. You must wait until the data is loaded to create the child component (and hence the chart). So how do we do that?

The solution: Conditional component rendering

  1. Fetch data in the created hook, not the mounted hook. Reason: asking for data earlier is more performant, see: https://v2.vuejs.org/v2/guide/instance.html
  2. On data response, save that data into your vue component. Reason: you need that data, see: https://v2.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html#ad
  3. On save, signal to your html that the data is ready using a variable this.loaded = true, and use a v-if="loaded" on any child components that depend on that data, see: https://v2.vuejs.org/v2/guide/conditional.html

Here is a working code sandbox with a dummy api call replicating your changes: https://codesandbox.io/s/vue-template-4b3lm

All of the changes were made to the Chartjs.vue file.

Some other errors I saw you make:

  • Declaring variables in the component section
  • Setting this.values too many times in too many places. Just declare it once in data and set it later as needed.

Some other ways you could solve this:

  • Use a watcher in the child components instead of v-if: to re-render the chart inside the component (instead of delaying the whole component with v-if) when the data comes in (any time the props change).
  • Use vuex

Upvotes: 1

Related Questions