Vue-chartjs not rendering chart until page resize - vue.js

I am using vue-chartjs to create charts for my application. I am passing the chartData as a prop. My chart doesn't render at first but does when I resize the window. Here is my code. First the chart component:
<script>
import { Doughnut, mixins } from "vue-chartjs";
const { reactiveProp } = mixins;
export default {
extends: Doughnut,
mixins: [reactiveProp],
mounted() {
this.render();
},
methods: {
render() {
console.log(this.chartData)
let options = {
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
},
};
this.renderChart(this.chartData, options);
},
},
};
</script>
Now here is the code from the component where the chart is displayed:
template part
<v-container>
<ProjectDoughnutChart :chart-data="chartData" />
</v-container>
script part
components: {
ProjectDoughnutChart,
},
data() {
return {
chartData: {
labels: [],
datasets: [
{
backgroundColor: [],
hoverBackgroundColor: [],
data: [],
},
],
},
};
},
setChartsTimesheets() {
this.timesheets.forEach((timesheet) => {
let typeTotal = 0;
this.timesheets
.filter((timesheet1) => timesheet1.type==timesheet.type)
.forEach((timesheet1) => {
typeTotal+=timesheet1.billableAmount;
});
if (this.chartData.labels.indexOf(timesheet.type) === -1) {
let colors = this.getTaskColors(timesheet.type);
this.chartData.labels.push(timesheet.type);
this.chartData.datasets[0].data.push(typeTotal);
this.chartData.datasets[0].backgroundColor.push(colors.color);
this.chartData.datasets[0].hoverBackgroundColor.push(colors.hover);
}
});
},

Solved the problem using a similar solution as "Chart with API data" from the documentation.
TL;DR: Adding a v-if on the chart

For people, that have similar problem, but not using vue.js or the official solution doesnt cut it. I had to chart.update() the graph to show values, that were added after the graph was created.
See the example. If you comment the chart.update() line, the graph will not refresh until the window is resized.
let chart = new Chart(document.getElementById("chart"), {
type: "line",
data: {
labels: ["a", "b", "c", "d", "e", "f"],
datasets: [{
label: 'Dataset 1',
data: [1, 5, 12, 8, 2, 3],
borderColor: 'green',
}]
},
options: {
interaction: {
mode: 'index',
intersect: true,
},
stacked: false,
responsive: true,
}
});
// adding data to graph after it was created (like data from API or so...)
chart.data.labels.push("new data");
chart.data.datasets[0].data.push(9);
// with chart.update(), the changes are shown right away
// without chart.update(), you need to resize window first
chart.update();
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>
<canvas id="chart"></canvas>

Related

How to update data to pie chart using vue-apexcharts

I want to create pie-chart with vue-apexcharts. I had data from API but I don't know how to update the chart's data.
mounted() {
axios
.get("/data/episodes.json")
.then(response => {
console.log(response);
});
}
The chart I want to create is like this
Here is my understanding on doing, ...
first you have to define the data and option values of chart.
components: {
apexchart: VueApexCharts,
},
data: {
series: [],
chartOptions: {
chart: {
width: 380,
type: "pie",
},
labels: [],
responsive: [
{
breakpoint: 480,
options: {
chart: {
width: 200,
},
legend: {
position: "bottom",
},
},
},
],
},
},
Now on mount you have call your api to get the data and update the chart information. like this ...
I'm thinking of you server would return an array of objects having value and key.
mounted() {
axios.get("/data/episodes.json").then((response) => {
for (let i = 0; i < response.data.length; i++) {
this.data.series.push(response.data[i].value);
this.data.chartOptions.labels.push(response.data[i].key);
}
});
},
Finally, you have to add the chart component to the vue template. like this.
<div id="chart">
<apexchart type="pie" width="380" :options="chartOptions" :series="series"></apexchart>
</div>
Done,
Ask me if it is not clear for you.

How to correctly pass array with data to a chart from the VueApexCharts library in vue3?

I am writing a small covid project and trying to plot confirmed infection data using ApexCharts, but the graph is not showing. I enter the data from vuex in two tables. The data is valid however it comes from api and sa in the proxy object. What am I doing wrong? (I am using ApexCharts because vue Chartjs is not compatible with vue 3).
<template>
<apexchart width="500" type="bar" :options="options" :series="series"></apexchart>
</template>
<script>
import VueApexCharts from "vue3-apexcharts";
export default {
components: {
apexchart: VueApexCharts,
},
data(){
return {
series: [],
options: {
chart: {
type: "bar",
height: 400,
stacked: true
},
plotOptions: {
bar: {
horizontal: false
}
},
dataLabels: {
enabled: false
},
tooltip: {
shared: true,
followCursor: true
},
stroke: {
width: 1,
colors: ["#fff"]
},
fill: {
opacity: 1
},
legend: {
position: "top",
horizontalAlign: "center",
offsetX: 40
},
colors: ["rgb(95, 193, 215)", "rgb(226, 37, 43)", "rgb(94, 181, 89)"]
}
};
},
computed: {
getDate(){
return this.$store.getters['chart/getDate'];
},
getConfirmed(){
return this.$store.getters['chart/getConfirmed'];
}
},
methods: {
fillDate(){
this.options = {
xaxis: {
categories: this.getDate
}
}
this.series = [
{
name: 'Confirmed',
data: this.getConfirmed
}
];
}
},
async mounted() {
await this.fillDate();
}
}
The data from the vuex store are two arrays.
Proxy
[[Handler]]: Object
[[Target]]: Array(45)
[[IsRevoked]]: false
Instead of using mounted hook and method try to watch the computed properties then update the data based on that ones:
computed: {
getDate(){
return this.$store.getters['chart/getDate'];
},
getConfirmed(){
return this.$store.getters['chart/getConfirmed'];
}
},
watch:{
getDate:{
handler(newVal){
this.options = {
xaxis: {
categories: this.getDate
}
},
deep:true
},
getConfirmed:{
handler(newVal){
this.series = [
{
name: 'Confirmed',
data: this.getConfirmed
}
];
},
deep:true
}
}

update vue chart-js y axis max value without re rendering entire chart

I am working on a project where I am implementing some charts from the Vue-Chartjs library. I need the Y-axis max value to change everytime the user changes the filters given. I Import an existing barchart from the vue-chartjs library. In the code there is a javascript file that has some defaults already, to set extra options I can use the extraOptions object as a prop to personalize each chart accordingly. Here is the default component:
import { Bar } from 'vue-chartjs'
import { hexToRGB } from "./utils";
import reactiveChartMixin from "./mixins/reactiveChart";
let defaultOptions = {
tooltips: {
tooltipFillColor: "rgba(0,0,0,0.5)",
tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
tooltipFontSize: 14,
tooltipFontStyle: "normal",
tooltipFontColor: "#fff",
tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
tooltipTitleFontSize: 14,
tooltipTitleFontStyle: "bold",
tooltipTitleFontColor: "#fff",
tooltipYPadding: 6,
tooltipXPadding: 6,
tooltipCaretSize: 8,
tooltipCornerRadius: 6,
tooltipXOffset: 10,
},
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
fontColor: "#9f9f9f",
fontStyle: "bold",
beginAtZero: true,
display: false,
min: 0,
max: 100
},
gridLines: {
display: false,
drawBorder: false,
}
}],
xAxes: [{
gridLines: {
display: false,
drawBorder: false,
},
}],
}
};
export default {
name: 'BarChart',
extends: Bar,
mixins: [reactiveChartMixin],
props: {
labels: {
type: [Object, Array],
description: 'Chart labels. This is overridden when `data` is provided'
},
datasets: {
type: [Object, Array],
description: 'Chart datasets. This is overridden when `data` is provided'
},
data: {
type: [Object, Array],
description: 'Chart.js chart data (overrides all default data)'
},
color: {
type: String,
description: 'Chart color. This is overridden when `data` is provided'
},
extraOptions: {
type: Object,
description: 'Chart.js options'
},
title: {
type: String,
description: 'Chart title'
},
},
methods: {
assignChartData() {
let { gradientFill } = this.assignChartOptions(defaultOptions);
let color = this.color || this.fallBackColor;
return {
labels: this.labels || [],
datasets: this.datasets ? this.datasets : [{
label: this.title || '',
backgroundColor: gradientFill,
borderColor: color,
pointBorderColor: "#FFF",
pointBackgroundColor: color,
pointBorderWidth: 2,
pointHoverRadius: 4,
pointHoverBorderWidth: 1,
pointRadius: 4,
fill: true,
borderWidth: 1,
data: this.data || []
}]
}
},
assignChartOptions(initialConfig) {
let color = this.color || this.fallBackColor;
const ctx = document.getElementById(this.chartId).getContext('2d');
const gradientFill = ctx.createLinearGradient(0, 170, 0, 50);
gradientFill.addColorStop(0, "rgba(128, 182, 244, 0)");
gradientFill.addColorStop(1, hexToRGB(color, 0.6));
let extraOptions = this.extraOptions || {}
return {
...initialConfig,
...extraOptions,
gradientFill
};
}
},
mounted() {
this.chartData = this.assignChartData({});
this.options = this.assignChartOptions(defaultOptions);
this.renderChart(this.chartData, this.options, this.extraOptions);
}
}
I use this js file to import the bar chart inside a vue component like you see down below.
everytime the input of the form changes i need to re render the chart. I use the onInputChange() method to turn the boolean loaded to false and call the loadData() method.
Inside the loadData() method I make an axios request that gets me the right data every time. I also get the maximum value for my Y axis.
Then in the response I call on updateChart() so that I can update the data and the max value of the chart. then i turn the boolean loaded to true again so that my chart renders accordingly.
The problem with this approach is that the chart disappears completely for a split of a second. Before deciding to change the max Value of the Y axis I was able to update the data of my chart without having to use the v-if="loaded".
I need to find a solution where the chart re renders without it completely disappearing from the page. I know some suggested to use computed variables but i don't fully understand how it is supposed to work. Here is the component minus the form fields.
I guess in it's essence what I want is to update the Y axis max value without having to re render the entire chart.
<template>
<div>
<BarChart v-if="loaded" :labels="chartLabels"
:datasets="datasets"
:height="100"
:extraOptions="extraOptions"
>
</BarChart>
<br>
</div>
</template>
<script>
import BarChart from '../../components/Library/UIComponents/Charts/BarChart'
import Dropdown from "../../components/Library/UIComponents/Dropdown"
import GroupedMultiSelectWidget from "~/components/widgets/GroupedMultiSelectWidget"
import SelectWidget from "../../components/widgets/SelectWidget";
export default{
name: 'PopularChart',
components: {BarChart, Dropdown, SelectWidget, GroupedMultiSelectWidget},
data(){
return {
loaded:true,
form:{
day: 'Today',
workspace:'',
machine_family: [],
duration: [],
user_group: [],
dt_start:'',
dt_end:''
},
url: `/api/data_app/job_count_by_hour/`,
chart_data: [],
days: [ {day:"Today", id:"Today"},
{day:"Monday", id:"0"},
{day:"Tuesday",id:"1"},
{day:"Wednesday",id:"2"},
{day:"Thursday",id:"3"},
{day:"Friday",id:"4"},
{day:"Saturday",id:"5"},
{day:"sunday",id:"6"} ],
chartLabels: ["00u", "1u", "2u", "3u","4u","5u", "6u", "7u", "8u", "9u", "10u", "11u", "12u", "13u", "14u", "15u","16u", "17", "18u","19u","20u","21u","22u","23u"],
datasets: [],
maximumValue: '',
extraOptions:{}
}
},
methods: {
onInputChange() {
this.loaded = false
this.loadData()
},
async loadData() {
await this.$axios.get(`${this.url}?day=${this.form.day}&date_start=${this.form.dt_start}&date_end=${this.form.dt_end}&workspace=${this.form.workspace}&user_group=${this.form.user_group}&machine_family=${this.form.machine_family}`)
.then(response => {
this.updateChart(response.data.results,response.data.maximum)
this.loaded = true
})
},
updateChart(data,maxValue) {
this.datasets = [{
label: ["jobs %"],
backgroundColor:"#f93232",
data: data
},]
this.maximumValue = maxValue
this.extraOptions = {
tooltips: {
callbacks:{
label: function (tooltipItems,){
if (tooltipItems.value > ((50/100) * maxValue)){
return 'busy';
}else if (tooltipItems.value < ((30/ 100) * maxValue) ){
return ' not busy';
}else if ( tooltipItems.value < ((40/ 100) * maxValue )){
return 'kind of busy'
}
}
}
},
scales: {
yAxes: [{
gridLines: {
zeroLineColor: "transparent",
display: false,
drawBorder: false,
},
ticks: {
max: this.maximumValue,
display: true,
}
}],
xAxes: [{
gridLines: {
zeroLineColor: "transparent",
display: false,
drawBorder: false,
},
}],
},
}
},
},
mounted() {
this.loadData()
},
}
</script>
You can bind the chart to a variable in data(), initialize it with some defaults in mounted() and whenever you want to update the chart data, you use the Chart.js API.
An implementation could look something like this:
export default {
data() {
return {
chart: null
};
},
mounted() {
this.chart = new Chart(/* defaults */);
},
methods: {
updateChart(data) {
this.chart.data.datasets = data;
/* rest of the chart updating */
this.chart.update();
}
}
};

How do I add dynamic data to apexcharts using vue.js?

I am trying to use dynamic data in my donut apexchart, but I don't know the correct way to do it.
I tried setting "test: 5" under data and then change the static number in series: [this.test, 4, 1]. Did not output anything, I am new to programming.
<script>
import VueApexCharts from 'vue-apexcharts'
export default {
name: 'Dashboard',
components: {
apexcharts: VueApexCharts
},
data () {
return {
test: 6,
series: [this.test, 5, 4],
chartOptions: {
plotOptions: {
pie: {
donut: {
labels: {
show: true,
name: {
fontSize: '24px'
},
value: {
fontSize: '34px',
color: '#fff'
}
}
}
}
},
tooltip: {
enabled: false
},
legend: {
show: false
},
fill: {
colors: ['#4b367c', '#2aa5ed', '#db2828']
},
dataLabels: {
enabled: false
},
labels: ['Utkast', 'Öppen', 'Förfallen'],
colors: ['#4b367c', '#2aa5ed', '#db2828']
}
}
},
mounted () {
this.$store.commit('setPageTitle', { title: 'Översikt' })
}
}
</script>
My goal is to reach in to firebase and use the data from there, but to start with I just wanted to test out how to add any data and failed here already.
You should not use one variable in another directly in data.
series: [this.test, 5, 4],
This will return null in this.test.
You can set series empty intially and set values later.
series:[]
And then populate series in some function.
Or else use computed() as follows:
computed: {
series() {
return this.test;
}
}
You can update the chart data dynamically like this.
this.chartOptions = {labels:['dynamic data here']}
this.series = ['dynamic data here']

How To Use Api Data With Vue Chart.js

So I am new to using data visualization in an application and I am trying to set my data coming from my api as the data the doughnut chart uses to display however I cannot figure out how to properly access the data
I have installed vue-chartjs as a way to simplify it for component use
Here is the Chart component
<script>
import { Doughnut } from 'vue-chartjs'
import {mapGetters} from 'vuex'
export default {
name: 'FirmChart',
extends: Doughnut,
computed: {
...mapGetters(['chartEngagements']),
},
mounted () {
this.renderChart({
labels: ['Scanned', 'Recieved', 'Preparation', 'Review', '2nd Review', 'Complete'],
datasets: [
{
label: 'Data One',
borderColor: 'black',
pointBackgroundColor: 'white',
borderWidth: 1,
pointBorderColor: 'white',
backgroundColor: [
'#0077ff',
'#0022ff',
'#1133bb',
'#0088aa',
'#11ffdd',
'#aabbcc',
'#22aabb',
],
data: [
10,
10,
10,
10,
10,
10,
]
},
]
}, {responsive: true, maintainAspectRatio: false});
},
created() {
this.$store.dispatch('retrieveEngagementsChartData')
}
}
</script>
now my data is coming from the chartEngagements getter and this is the display of that data in the console
{Complete: Array(1), Review: Array(3), 2nd Review: Array(1), Recieved: Array(7), Preparation: Array(1), …}
My question is how do I set the Complete: Array(1) etc to the data[] attribute in my this.renderChart() method??
I have tried doing something like this but It will not display anything
mounted () {
this.renderChart({
labels: ['Scanned', 'Recieved', 'Preparation', 'Review', '2nd Review' 'Complete'],
datasets: [
{
label: 'Data One',
borderColor: 'black',
pointBackgroundColor: 'white',
borderWidth: 1,
pointBorderColor: 'white',
backgroundColor: [
'#0077ff',
'#0022ff',
'#1133bb',
'#0088aa',
'#11ffdd',
'#aabbcc',
'#22aabb',
],
data: [
this.chartEngagements.complete,
this.chartEngagements.review,
this.chartEngagements.2ndreview,
this.chartEngagements.preparation,
this.chartEngagements.recieved,
this.chartEngagements.scanned,
]
},
]
}, {responsive: true, maintainAspectRatio: false});
However it doesn't display anything.. any help would be greatly appreciated or a point in the right direction!
Have you checked out the examples in the docs?
https://vue-chartjs.org/guide/#chart-with-api-data
Your problem is, that your data api call is async. So your chart is rendered, even if your data is not fully loaded.
There is also an example with vuex which is a bit outdated https://github.com/apertureless/vue-chartjs-vuex
You have to make sure that your data is fully loaded before you render your chart.
I faced similar issue while implementing Charts with API data in one of my Vue JS apps I was working on. I am using Vuex for state management, a simple solution for this problem is to move "chartData" from "data" to "computed" which would make it reactive. Below is the sample code from my app.
<template>
<line-chart v-if="chartData"
style="height: 100%"
chart-id="big-line-chart"
:chart-data="chartData"
:extra-options="extraOptions"
>
</line-chart>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import * as expenseTypes from '../../store/modules/expense/expense-types';
import * as chartConfig from "../../components/reports/chart.config";
import BarChart from "../../components/reports/BarChart";
export default {
components: {
BarChart,
},
data () {
return {
extraOptions: chartConfig.chartOptionsMain
}
},
computed: {
...mapGetters({
allExpenses: expenseTypes.GET_ALL_EXPENSES,
expenseCount: expenseTypes.GET_EXPENSE_COUNT,
}),
expenseAmount() {
let expenseAmount = [];
this.allExpenses.map((item) => {
expenseAmount.push(item.amount);
})
return expenseAmount;
},
expenseLabels() {
let expenseLabels = [];
this.allExpenses.map((item) => {
expenseLabels.push(item.note);
})
return expenseLabels;
},
chartData() {
return {
datasets: [
{
data: this.expenseAmount
},
],
labels: this.expenseLabels
}
}
},
async mounted() {
await this.getAllExpenses();
await this.fillChartData();
},
methods: {
...mapActions({
getAllExpenses: expenseTypes.GET_ALL_EXPENSES_ACTION,
}),
},
};
</script>