Creating line and bar graphs in Vue.js using Chart.js through API calls
Published: 2024-01-23 14:35:01


I accomplished this task while working on a project, and I believe it would be beneficial to share the template with other developers who may find it useful in the future.

## Step-by-Step Guide: Creating a Line Graph with Chart.js in Vue

Step 1: Set Up Your Vue Environment.
Begin by setting up your Vue environment using the Vue CLI. Navigate to the 'src' folder to start your work.

```
npm create vue@latest
```
Step 2: Create a Component for Your Line Chart (e.g., Chart.vue)

Create a new component to manage your line chart. You can name it, for example, Chart.vue. Before you proceed, ensure that you have installed the Chart.js library in your project, Using this command. 

```
  npm install chart.js
```
Step 3: Making API Calls

In this step, you'll initiate API calls to fetch data. In my approach, I make the API call in the parent component and pass the retrieved data as a prop to the child component (Chart.js). However, you may opt to make the API call directly within the child component, depending on your preference.

Parent Component (Index.vue): 



```
<template>
  <div>
    <div v-if="isLoading">Loading please wait...</div>
    <Chats :data="prices" v-if="!isLoading && prices.length > 0"></Chats>
  </div>
</template>

<script setup>
import axios from 'axios';
import Chats from "../components/Chats.vue"
import { ref, onMounted } from 'vue';

const prices = ref([]);
const isLoading = ref(true);

onMounted(async () => {
  try {
    const { data } = await axios.get(`http://localhost:5500/prices`);
    console.log('Data fetched successfully:', data);
    prices.value = data;
    isLoading.value = false;
  } catch (error) {
    console.error('Error fetching data:', error);
    prices.value = [];
    isLoading.value = false;
  }
});
</script>

```


As shown in line 4, I use the v-if directive to display "Loading, please wait" during the API call, ensuring that the Chart component is displayed only when the data is ready. I employ an asynchronous function within the mounted lifecycle hook to consistently call the API when the page is loaded, ensuring the data is always up to date.

It's important to note that I store the retrieved data in line 22 under the "price.value" ref. Storing the data in a ref is crucial as it allows for dynamic updates. I use prices.value = data; to achieve this. 
  

```
prices.value = data;
```
  
Following that, I bind the state [price] to the prop data that the Chart.vue component will utilize. 
     
 ```
:data="prices" 
```

It's essential to ensure that the data passed consists of more than one array, as indicated in 
line 5 to eliminate any potential bugs 

```
v-if="!isLoading && prices.length 
```

This condition ensures that the Chart component is displayed only when there is data available, and the loading process has completed.


step 4:Chart.js

```
 <template>
  <div class="main">
    <canvas ref="lineChart" :style='{ height: "40rem", width: "100%" }'></canvas>
  </div>
</template>

<script>
import Chart from 'chart.js';

export default {
  props: {
    data: Array,
  },
  data() {
    return {
      chart: null,
    };
  },
  mounted() {
    this.createChart();
  },

  methods: {
    createChart() {
      const data = this.data || [];

      const timestamps = data.map(entry => new Date(entry.timestamp).toLocaleTimeString());
      const values = data.map(entry => entry.value);

      this.chart = new Chart(this.$refs.lineChart.getContext('2d'), {
        type: 'line',
        data: {
          labels: timestamps,
          datasets: [{
            label: 'Value',
            data: values,
            borderColor: 'blue', // change color
            borderWidth: 2,
            fill: false,
          }],
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          scales: {
            x: {
              type: 'linear',
              position: 'bottom',
              title: {
                display: true,
                text: 'Timestamp',
              },
            },
            y: {
              type: 'linear',
              position: 'left',
              title: {
                display: true,
                text: 'Value',
              },
            },
          },
        },
      });
    },
  },

  beforeUnmount() {
    if (this.chart) {
      this.chart.destroy();
    }
  },
};
</script>

<style scoped>
.main {
  height: 80vh;
  width: 100%;
}
</style>


```
 

First you want to pass your props, import Chart from Chart.js and for this it is advisable you use vUE option api of vue as I did above  and I will explain why later. 

Now, line 15 to line 68 is provided by chartjs to run the code, but note line 27 and 28 is my x and y axis on the graph so I extracted it from from my api using the map properties. note it varies depending on you api structure. 

In your chartjs provided code, there is the object of x and y replace what there with with x and y text in a string. Note it must be a string is "string" 

        

```
 x: {
               type: 'linear',
                position: 'bottom',
                title: {
                  display: true,
                  text: 'Timestamp',
                },
              },
              y: {
                type: 'linear',
                position: 'left',
                title: {
                  display: true,
                  text: 'Value',
                }
```

To display your graph insert <canvas > component into your desired div with the ref. Dont forget your ref as shown below for line 
line 3
         

```
  <canvas ref="lineChart"></canvas>
```

The destroy() method is a Chart.js function that cleans up and removes the chart, releasing any resources associated with it.

```
  beforeUnmount() {
      if (this.chart) {
        this.chart.destroy();
      }
    }

```

##  Bars
if you want to use bar rather than line chart use the child code below. change the type and ref. 

```
<template>
  <div class="main">
    <canvas ref="barChart" :style='{height:"40rem", width:"100%"}'></canvas>
  </div>
</template>

<script>
import Chart from 'chart.js';

export default {
  props: {
    data: Array,
  },
  data() {
    return {
      chart: null,
    };
  },
  mounted() {
    this.createChart();
  },
  methods: {
    createChart() {
      const data = this.data || [];

      const timestamps = data.map(entry => new Date(entry.timestamp).toLocaleTimeString());
      const values = data.map(entry => entry.value);

      this.chart = new Chart(this.$refs.barChart.getContext('2d'), {
        type: 'bar', // Change type to 'bar'
        data: {
          labels: timestamps,
          datasets: [{
            label: 'Value',
            data: values,
            backgroundColor: 'blue', // Change background color for bars if you want to
            borderWidth: 2,
          }],
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          scales: {
            x: {
              type: 'linear',
              position: 'bottom',
              title: {
                display: true,
                text: 'Timestamp',
              },
            },
            y: {
              type: 'linear',
              position: 'left',
              title: {
                display: true,
                text: 'Value',
              },
            },
          },
        },
      });
    },
  },
  beforeUnmount() {
    if (this.chart) {
      this.chart.destroy();
    }
  },
};
</script>

<style scoped>
.main {
  height: 80vh;
  width: 100%;
}
</style>
```

Finally, I opted for using the Vue Option API without the setup function. The chart was originally designed to seamlessly integrate with the Vue Option API, and I encountered issues when attempting to modify the code using the Composition API. Therefore, my advice is to stick with the Option API for smoother integration and to avoid potential bugs. 

             `   this.chart = new Chart(this.$refs.lineChart.getContext('2d')`
 
line 30 was giving refs bug with Vue compostion api. that is 

 `<script ></script>`
instead of 
`<script setup></script>` 
as used in the parent component. `
  

## Outcome


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9nyb6sfompkzwm5ao4fd.jpg)