Reputation: 569
I am using charts.js to add several charts into my ionic - angular app. When state changes, I change data in the charts. The problem is that when data changes, the charts don't update there graphs till I click on each one. Moreover, when clicking sometimes old data is shown in the graph and sometimes new data (it toggles). This don't occur at the initial start of the app, nor when I change the value of settings, only when I change intakes (ex. adding an intake from another page).
import { Component, OnInit, ViewChild, ElementRef, NgZone } from '@angular/core';
import { Chart } from 'chart.js';
import { Observable } from 'rxjs';
import { Intake } from 'src/app/models/intake.model';
import { DateIntake } from 'src/app/models/dateIntake.model';
import { Store } from '@ngrx/store';
import { IAppState } from 'src/app/store/state/app.state';
import { IIntakeState } from 'src/app/store/state/intake.state';
import { DatePipe } from '@angular/common';
import { GetGoalBegin } from 'src/app/store/actions/settings.actions';
import { ISettingsState } from 'src/app/store/state/setting.state';
import { GetIntakesBegin } from 'src/app/store/actions/intake.actions';
@Component({
selector: 'app-statistics',
templateUrl: './statistics.page.html',
styleUrls: ['./statistics.page.scss'],
})
export class StatisticsPage implements OnInit {
@ViewChild('barCanvasWeek') barCanvasWeek: ElementRef;
@ViewChild('doughnutCanvasToday') doughnutCanvasToday: ElementRef;
@ViewChild('lineCanvasMonth') lineCanvasMonth: ElementRef;
@ViewChild('lineCanvasYear') lineCanvasYear: ElementRef;
@ViewChild('lineCanvasAll') lineCanvasAll: ElementRef;
private barChartWeek: Chart;
private doughnutChartToday: Chart;
private lineChartMonth: Chart;
private lineChartYear: Chart;
private lineChartAll: Chart;
intakeState: Observable<{intakes: Intake[]}>;
datesIntakes: DateIntake[] = [];
settingsState: Observable<{goal: number}>;
goal: number;
constructor( private ngZone: NgZone,
private datePipe: DatePipe,
private store: Store<IAppState>) { }
ngOnInit() {
this.setDoughnutChartToday();
this.setBarChartThisWeek();
this.setLineChartThisMonth();
this.setLineChartThisYear();
this.setLineChartAll();
this.settingsState = this.store.select('settingsState');
this.settingsState.subscribe(
(data: ISettingsState) => {
if (data != null) {
this.goal = data.goal;
this.setDoughnutChartToday();
this.setBarChartThisWeek();
this.setLineChartThisMonth();
this.setLineChartThisYear();
this.setLineChartAll(); }
}
);
this.intakeState = this.store.select('intakeState');
this.intakeState.subscribe (
(dataIntakes: IIntakeState) => {
this.ngZone.run(() => {
this.datesIntakes = [...dataIntakes.datesIntakes];
this.setDoughnutChartToday();
this.setBarChartThisWeek();
this.setLineChartThisMonth();
this.setLineChartThisYear();
this.setLineChartAll();
});
}
);
this.store.dispatch(new GetGoalBegin());
this.store.dispatch(new GetIntakesBegin());
}
setDoughnutChartToday() {
const date: string = this.datePipe.transform(new Date().toISOString(), 'yyyy-MMM-dd');
const intake: DateIntake = this.datesIntakes[this.datesIntakes.findIndex(x => x.theDate === date)];
let proteinSum = 0;
if (intake !== null && intake !== undefined) {
proteinSum = intake.proteinSum;
}
const accomplishedPercentage: number = proteinSum * 100 / this.goal;
let remainingPercentage: number = 100 - accomplishedPercentage;
if (accomplishedPercentage > 100) {
remainingPercentage = 0;
}
this.doughnutChartToday = new Chart(this.doughnutCanvasToday.nativeElement, {
type: 'doughnut',
data: {
labels: ['% Accomplished', '% Remaining'],
datasets: [
{
label: 'Accomplished Today',
data: [accomplishedPercentage, remainingPercentage],
backgroundColor: [
'rgba(75, 192, 192, 0.2)',
'rgba(255, 99, 132, 0.2)',
],
hoverBackgroundColor: ['rgba(75, 192, 192, 1)', '#FF6384']
}
]
}
});
this.doughnutChartToday.update();
}
setBarChartThisWeek() {
const today = new Date();
const day = today.getDay();
const diff = today.getDate() - day + (day == 0 ? -6:1); // adjust when day is sunday
const monday = new Date(today.setDate(diff));
const dateIntakesInThisWeek: DateIntake[] = [];
this.datesIntakes.forEach(dI => {
if (new Date(dI.theDate) >= monday ) {
dateIntakesInThisWeek.push(dI);
}
});
const proteinSums = dateIntakesInThisWeek.map(x => x.proteinSum);
const dates = dateIntakesInThisWeek.map(x => this.datePipe.transform(x.theDate, 'yyyy-MMM-dd'));
const backgroundColors = this.datesIntakes.map(x => {
if (x.proteinSum < this.goal) {
return 'rgba(255, 99, 132, 0.2)';
} else {
return 'rgba(75, 192, 192, 0.2)';
}
});
const borderColors = this.datesIntakes.map(x => {
if (x.proteinSum < this.goal) {
return 'rgba(255,99,132,1)';
} else {
return 'rgba(75, 192, 192, 1)';
}
});
this.barChartWeek = null;
this.barChartWeek = new Chart(this.barCanvasWeek.nativeElement, {
type: 'bar',
data: {
labels: [...dates],
datasets: [
{
label: 'Amount of protein in grams',
data: [...proteinSums],
backgroundColor: [ ...backgroundColors],
borderColor: [ ...borderColors],
borderWidth: 1
}
]
},
options: {
scales: {
yAxes: [
{
ticks: {
beginAtZero: true
}
}
]
}
}
});
this.barChartWeek.update();
}
setLineChartThisMonth() {
const today = new Date();
const firstDayOfThisMonth = new Date(today.getFullYear(), today.getMonth(), 1);
const dateIntakesInThisMonth: DateIntake[] = [];
this.datesIntakes.forEach(dI => {
if (new Date(dI.theDate) >= firstDayOfThisMonth ) {
dateIntakesInThisMonth.push(dI);
}
});
const proteinSums = dateIntakesInThisMonth.map(x => x.proteinSum);
const dates = dateIntakesInThisMonth.map(x => this.datePipe.transform(x.theDate, 'yyyy-MMM-dd'));
this.lineChartMonth = new Chart(this.lineCanvasMonth.nativeElement, {
type: 'line',
data: {
labels: [...dates],
datasets: [
{
label: 'Amount of protein in grams',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(75,192,192,0.4)',
borderColor: 'rgba(75,192,192,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(75,192,192,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(75,192,192,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [...proteinSums],
spanGaps: false
}
]
}
});
this.lineChartMonth.update();
}
setLineChartThisYear() {
const today = new Date();
const firstDayOfThisYear = new Date(today.getFullYear(), 0, 1);
let dateIntakesInThisYear: DateIntake[] = [];
this.datesIntakes.forEach(dI => {
if (new Date(dI.theDate) >= firstDayOfThisYear ) {
dateIntakesInThisYear.push(dI);
}
});
let proteinSums = dateIntakesInThisYear.map(x => x.proteinSum);
const dates = dateIntakesInThisYear.map(x => this.datePipe.transform(x.theDate, 'yyyy-MMM-dd'));
this.lineChartYear = new Chart(this.lineCanvasYear.nativeElement, {
type: 'line',
data: {
labels: [...dates],
datasets: [
{
label: 'Amount of protein in grams',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(75,192,192,0.4)',
borderColor: 'rgba(75,192,192,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(75,192,192,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(75,192,192,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [...proteinSums],
spanGaps: false
}
]
}
});
this.lineChartYear.update();
}
setLineChartAll() {
const dates = this.datesIntakes.map(x => this.datePipe.transform(x.theDate, 'yyyy-MMM-dd'));
const proteinSums = this.datesIntakes.map(x => x.proteinSum);
this.lineChartAll = new Chart(this.lineCanvasAll.nativeElement, {
type: 'line',
data: {
labels: [...dates],
datasets: [
{
label: 'Amount of protein in grams',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(75,192,192,0.4)',
borderColor: 'rgba(75,192,192,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(75,192,192,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(75,192,192,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [...proteinSums],
spanGaps: false
}
]
}
});
this.lineChartAll.update();
}
}
Upvotes: 0
Views: 243
Reputation: 569
The error was in the update part, the steps I have solved my problem are as follows:
1.In ngOnInit(), and before subscriptions, I used setChart.... Where in this method I created the charts with dates and data are empty.
2.In both subscriptions i used the update method, the error was that when setting data I was using this.lineChartYear.data.datasets.data = [...proteinSums];
now changed to this.lineChartYear.data.datasets[0].data = [...proteinSums];
because I found that datasets is an array to add multiple lines to the same graph.
In the following I will add a sample of my correct code.
import { Component, OnInit, ViewChild, ElementRef, NgZone } from '@angular/core';
import { Chart } from 'chart.js';
import { Observable } from 'rxjs';
import { Intake } from 'src/app/models/intake.model';
import { DateIntake } from 'src/app/models/dateIntake.model';
import { Store } from '@ngrx/store';
import { IAppState } from 'src/app/store/state/app.state';
import { IIntakeState } from 'src/app/store/state/intake.state';
import { DatePipe } from '@angular/common';
import { GetGoalBegin } from 'src/app/store/actions/settings.actions';
import { ISettingsState } from 'src/app/store/state/setting.state';
import { GetIntakesBegin } from 'src/app/store/actions/intake.actions';
@Component({
selector: 'app-statistics',
templateUrl: './statistics.page.html',
styleUrls: ['./statistics.page.scss'],
})
export class StatisticsPage implements OnInit {
@ViewChild('lineCanvasYear') lineCanvasYear: ElementRef;
public lineChartYear: Chart = [];
intakeState: Observable<{intakes: Intake[]}>;
datesIntakes: DateIntake[] = [];
settingsState: Observable<{goal: number}>;
goal: number;
constructor(
private datePipe: DatePipe,
private store: Store<IAppState>) { }
ngOnInit() {
this.setLineChartThisYear();
this.settingsState = this.store.select('settingsState');
this.settingsState.subscribe(
(data: ISettingsState) => {
if (data != null) {
this.goal = data.goal;
this.updateLineChartThisYear();}
}
);
this.intakeState = this.store.select('intakeState');
this.intakeState.subscribe (
(dataIntakes: IIntakeState) => {
this.datesIntakes = [...dataIntakes.datesIntakes];
this.updateLineChartThisYear();
}
);
this.store.dispatch(new GetGoalBegin());
this.store.dispatch(new GetIntakesBegin());
}
setLineChartThisYear() {
this.lineChartYear = new Chart(this.lineCanvasYear.nativeElement, {
type: 'line',
data: {
labels: [],
datasets: [
{
label: 'Amount of protein in grams',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(75,192,192,0.4)',
borderColor: 'rgba(75,192,192,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(75,192,192,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(75,192,192,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [],
spanGaps: false
}
]
}
});
}
updateLineChartThisYear() {
const today = new Date();
const firstDayOfThisYear = new Date(today.getFullYear(), 0, 1);
let dateIntakesInThisYear: DateIntake[] = [];
this.datesIntakes.forEach(dI => {
if (new Date(dI.theDate) >= firstDayOfThisYear ) {
dateIntakesInThisYear.push(dI);
}
});
let proteinSums = dateIntakesInThisYear.map(x => x.proteinSum);
const dates = dateIntakesInThisYear.map(x => this.datePipe.transform(x.theDate, 'yyyy-MMM-dd'));
this.lineChartYear.data.labels.pop();
this.lineChartYear.data.labels = [...dates];
this.lineChartYear.data.datasets[0].data = [...proteinSums];
this.lineChartYear.update();
}
}
Upvotes: 0
Reputation: 302
Is there a specific reason for why you run this with (this.ngZone.run
)? Also, alot of things are happening in the component, maybe split things up a bit? I don't known chart.js very well, are you sure you need to reassign the chart instance on every data change? Because it seems like you shouldn't need to reassign according to the docs.
Upvotes: 1