I'm using the following tutorial (pull download stats for NPM packages) to build a basis for my charted webapp :
I have extracted the below code from the tutorial and modified it so it does the pure basics. Get data and present data. Specifically from these:
Please note: The code executes the API call and retrieves data no problem. However it will only render that data in the chart if I make a code change. For example changing the color of a line to something else. It seems to only work on the next 'cycle' if that makes sense. Once the data has rendered, if I refresh that page it is once again blank. I suspect it has something to do with the pages timing. However not sure where to begin or what I'm looking for.
App.Vue
<template>
<v-app style="background-color: rgb(228, 228, 228);">
<section class="One">
<v-card class="One" color="rgb(255, 255, 255)" >
<LineChart :chart-data="downloads" :chart-labels="labels"/>
</v-card>
</section>
</v-app>
</template>
<script>
import axios from 'axios';
import LineChart from './components/test3.vue';
export default {
name: 'App',
components: {
LineChart,
},
data () {
return {
package: '',
packageName: '',
loaded: false,
loading: false,
downloads: [],
downloadsYear: [],
downloadsMonth: [],
downloadsWeek: [],
labels: [],
labelsYear: [],
labelsMonth: [],
labelsWeek: [],
showError: false,
showSettings: false,
errorMessage: 'Please enter a package name',
periodStart: '',
periodEnd: new Date(),
rawData: '',
totalDownloads: '',
dailyPng: null,
weeklyPng: null,
monthlyPng: null,
yearlyPng: null
}
},
mounted(){
this.loaded = false
axios.get(`https://api.npmjs.org/downloads/range/2017-01-01:2017-04-19/vue`)
.then(response => {
this.rawData = response.data.downloads
this.downloads = response.data.downloads.map(entry => entry.downloads)
this.labels = response.data.downloads.map(entry => entry.day)
this.packageName = response.data.package
this.totalDownloads = this.downloads.reduce((total, download) => total + download)
this.setURL()
this.groupDataByDate()
this.loaded = true
this.loading = false
})
.catch(err => {
this.errorMessage = err.response.data.error
this.loading = false
})
},
};
</script>
Chart Component:
<script>
import { Line } from 'vue-chartjs'
export default {
extends: Line,
props: {
chartData: {
type: Array,
required: false
},
chartLabels: {
type: Array,
required: true
}
},
data () {
return {
gradient: null,
options: {
showScale: true,
scales: {
yAxes: [{
ticks: {
beginAtZero: false,
},
gridLines: {
display: true,
color: '#EEF0F4',
borderDash: [5, 15]
}
}],
xAxes: [ {
gridLines: {
display: true,
color: '#EEF0F4',
borderDash: [5, 15]
}
}]
},
tooltips: {
backgroundColor: '#4F5565',
titleFontStyle: 'normal',
titleFontSize: 18,
bodyFontFamily: "'Proxima Nova', sans-serif",
cornerRadius: 3,
bodyFontColor: '#20C4C8',
bodyFontSize: 14,
xPadding: 14,
yPadding: 14,
displayColors: false,
mode: 'index',
intersect: false,
callbacks: {
title: tooltipItem => {
return `🗓 ${tooltipItem[0].xLabel}`
},
label: (tooltipItem, data) => {
let dataset = data.datasets[tooltipItem.datasetIndex]
let currentValue = dataset.data[tooltipItem.index]
return `📦 ${currentValue.toLocaleString()}`
}
}
},
legend: {
display: false
},
responsive: true,
maintainAspectRatio: false
}
}
},
mounted () {
this.gradient = this.$refs.canvas
.getContext('2d')
.createLinearGradient(0, 0, 0, 450)
this.gradient.addColorStop(0, 'rgba(52, 217, 221, 0.6)')
this.gradient.addColorStop(0.5, 'rgba(52, 217, 221, 0.25)')
this.gradient.addColorStop(1, 'rgba(52, 217, 221, 0)')
this.renderChart({
labels: this.chartLabels,
datasets: [
{
label: 'downloads',
borderColor: '#249EBF',
pointBackgroundColor: 'rgba(0,0,0,0)',
pointBorderColor: 'rgba(0,0,0,0)',
pointHoverBorderColor: '#249EBF',
pointHoverBackgroundColor: '#fff',
pointHoverRadius: 4,
pointHitRadius: 10,
pointHoverBorderWidth: 1,
borderWidth: 1,
backgroundColor: this.gradient,
data: this.chartData
}
]
}, this.options)
setTimeout(() => {
this.download()
}, 500)
},
methods: {
formatNumber (num) {
let numString = Math.round(num).toString()
let numberFormatMapping = [[6, 'm'], [3, 'k']]
for (let [numberOfDigits, replacement] of numberFormatMapping) {
if (numString.length > numberOfDigits) {
let decimal = ''
if (numString[numString.length - numberOfDigits] !== '0') {
decimal = '.' + numString[numString.length - numberOfDigits]
}
numString = numString.substr(0, numString.length - numberOfDigits) + decimal + replacement
break
}
}
return numString
}
}
}
</script>
You need to notify the child component to re-render itself.
add a watcher
is one way, watch the data change and update it.
Another easier way is, add a key
prop to it.
in your App.vue
, do like this:
<LineChart :chart-data="downloads" :chart-labels="labels" :key="downloads.length"/>
here i'm using the downloads
's length as key value. it's a simple and temp resolution to show you how to use key
. In your app you should use some other value as key, incase different api call returns same length data.
you can also set the key to another value, and change this value every time you call the api.