[Chartjs]-Ng2-charts update labels and data

56👍

Apparently, if you do not modify the original reference to the labels array, it seems to work, at least for me. I mean, if you want a completely different set of labels, you should do something like this:

In the template:

<canvas baseChart
  [datasets]="lineChartData"
  [labels]="lineChartLabels"
  [options]="lineChartOptions"
  [chartType]="'line'"></canvas>

In the ts component:

this.lineChartLabels.length = 0;
for (let i = tempLabels.length - 1; i >= 0; i--) {
  this.lineChartLabels.push(tempLabels[i]);
}

Or, using new ECMAScript syntax:

this.lineChartLabels.length = 0;
this.lineChartLabels.push(...tempLabels);

The key is maybe the this.lineChartLabels.length = 0; statement, which practically ’empties’ your array by setting its length to 0, without modifying the reference.
Hope this helps!

17👍

Recently i had to use ng2-charts and i was having a very big issues with updating my data untill i found this sollution:

<div class="chart">
    <canvas baseChart [datasets]="datasets_lines" [labels]="labels_line" [colors]="chartColors" [options]="options" [chartType]="lineChartType">
    </canvas>
</div>

and here what i have in my component :

import { Component, OnInit, Pipe, ViewChild, ElementRef } from '@angular/core';
import { BaseChartDirective } from 'ng2-charts/ng2-charts';

@Component({
    moduleId: module.id,
    selector: 'product-detail',
    templateUrl: 'product-detail.component.html'
})

export class ProductDetailComponent {
    @ViewChild(BaseChartDirective) chart: BaseChartDirective;

    private datasets_lines: { label: string, backgroundColor: string, borderColor: string, data: Array<any> }[] = [
        {
        label: "Quantities",
        data: Array<any>()
    }
];

private labels_line = Array<any>();

private options = {
    scales: {
        yAxes: [{
            ticks: {
                beginAtZero: true
            }
        }]
    }
};


constructor() { }
ngOnInit() {

    this.getStats();

}
getStats() {

    this._statsService.getStatistics(this.startDate, this.endDate, 'comparaison')
        .subscribe(
        res => {
            console.log('getStats success');
            this.stats = res;

            this.labels_line = this.getDates();
            this.datasets_lines = [];

            let arr: any[];
            arr = [];
            for (let stat of this.stats) {
                arr.push(stat.quantity);
            }

            this.datasets_lines.push({
                label: 'title',
                data: arr
            });

            this.refresh_chart();

        },
        err => {
            console.log("getStats failed from component");
        },
        () => {
            console.log('getStats finished');
        });
}

refresh_chart() {
    setTimeout(() => {
        console.log(this.datasets_lines_copy);
        console.log(this.datasets_lines);
        if (this.chart && this.chart.chart && this.chart.chart.config) {
            this.chart.chart.config.data.labels = this.labels_line;
            this.chart.chart.config.data.datasets = this.datasets_lines;
            this.chart.chart.update();
        }
    });
}

getDates() {
    let dateArray: string[] = [];
    let currentDate: Date = new Date();
    currentDate.setTime(this.startDate.getTime());
    let pushed: string;
    for (let i = 1; i < this.daysNum; i++) {
        pushed = currentDate == null ? '' : this._datePipe.transform(currentDate, 'dd/MM/yyyy');
        dateArray.push(pushed);
        currentDate.setTime(currentDate.getTime() + 24 * 60 * 60 * 1000);
    }
    re

turn dateArray;
    }    
}

i m sure this is the right way to do it, and hope this would be helpfull

10👍

Like Deyd pointed out before, this is caused by a combination of Angular 2+’s change detection and a bug in ng2-charts.

According to my own observations (correct me if I’m wrong), Angular merges several changes within a very short timeframe into a single collection (changes: SimpleChanges) when ngOnChanges is called.

Unfortunately, ng2-charts only checks if the dataset has been changed with this collection and updates it. Otherwise it completely rebuilds the entire chart. However, because of the way the change detection works, more than one property might have been changed. Then, only the dataset gets updated even if the labels and possibly other properties have been updated as well. See ngOnChanges in ng2-charts:
valor-software/ng2-charts/src/charts/charts.ts

And if you don’t want to have a separate copy of ng2-charts in your app and fix the problem yourself, a possible workaround for this problem is to set the dataset with a short delay using JavaScript’s built-in function setTimeout(callback: () => void, delay: number).

Before:

@Component({
  selector: 'app-root',
  template: `
  <select (change)="onChange($event.target.value)">
    <option value="" disabled selected>Select your option</option>
    <option value="0">Option 0</option>
    <option value="1">Option 1</option>
  </select>

  <canvas baseChart
          chartType="bar"
          [datasets]="barChartData"
          [labels]="barChartLabels"
          [colors]="barChartColors">
  </canvas>
  `
})
export class AppComponent implements OnInit {
  chartData: string[];
  chartLabels: string[];
  chartColors: string[];

  onChange(id: string) {
    getFromApiById(id)
      .then(result => this._setChart(result.data, result.labels, result.colors));
  }

  private _setChart(data: string[], labels: string[], colors: string[]) {
    this.chartData = data;
    this.chartLabels = labels;
    this.chartColors = colors;
  }
}

After:

@Component({
  selector: 'app-root',
  template: `
  <select (change)="onChange($event.target.value)">
    <option value="" disabled selected>Select your option</option>
    <option value="0">Option 0</option>
    <option value="1">Option 1</option>
  </select>

  <canvas baseChart
          chartType="bar"
          [datasets]="barChartData"
          [labels]="barChartLabels"
          [colors]="barChartColors">
  </canvas>
  `
})
export class AppComponent implements OnInit {
  chartData: string[];
  chartLabels: string[];
  chartColors: string[];

  onChange(id: string) {
    getFromApiById(id)
      .then(result => this._setChart(result.data, result.labels, result.colors));
  }

  private _setChart(data: string[], labels: string[], colors: string[]) {
    this.chartLabels = labels;
    this.chartColors = colors;

    setTimeout(() => {
      this.chartData = data;
    }, 50);
  }
}

8👍

Using BaseChartDirective i did chart update and it served the purpose. Sample below:

import { BaseChartDirective } from 'ng2-charts/ng2-charts';

inside the class add as below

@ViewChild(BaseChartDirective) chart: BaseChartDirective;

While you have the values to be changed, add as below

setTimeout(() => {
if (this.chart && this.chart.chart && this.chart.chart.config) {
  this.chart.chart.config.data.labels = this.labels_pie;
  this.chart.chart.update();
}
});

5👍

The trick is in clearing the label and data array, the below code didnt work for me 🙁
“`

clearCharts() {
    this.barChartLabels= [];
    this.barChartData= [
      {data: [], label: 'label1'},
      {data: [], label: 'label2'}
    ];
  }


However when I changed the way I cleared the data helped me (Using object reference)

clearCharts() {
    this.barChartLabels= [];
    this.emptyChartData(this.barChartData);
  }
   emptyChartData(obj) {
     obj[0].data = [];
     obj[1].data = [];
     obj[0].label = 'label1';
     obj[1].label = 'label2';
  }

“`

5👍

Using BaseChartDirective i did chart update and it served the purpose. Sample below:

import { BaseChartDirective } from 'ng2-charts';

inside the class add as below

@ViewChild(BaseChartDirective) chart: BaseChartDirective;

While you have the values to be changed, add as below

 this.chart.ngOnChanges({});

2👍

This is an issue in the library ng2-charts, to resolve it I have cloned the github of ng2-charts in my app directory and have done following steps :

  • npm install
  • in appmodule import ng-2charts.ts from src directory.
  • add this updateChartLabels function to chart.ts file
  • call it in the onChanges function.

public ngOnChanges(changes: SimpleChanges): void {
if (this.initFlag) {

  if(changes.hasOwnProperty('labels')){
    console.log('labels changes ...');
    this.updateChartLabels(changes['labels'].currentValue);
  }
//..
//...
}

private updateChartLabels(newLabelsValues: string[] | any[]): void {
this.chart.data.labels = newLabelsValues;
}

2👍

For those looking for a walk around, for now you can put your labels and data in an object and put that object in an array and just loop through the array in your html. This will redraw the element every time your array changes.

in your type script every time there’s a change.

data = [...]; labels = [...]; chartArray = [{data , labels }]

in your html

<canvas *ngFor="let chartData of chartArray " [datasets]="chartData.data" [labels]="chartData.labels" > </canvas>

1👍

This is an issue with the current ng2-charts library.

Try the new ng4-charts library which has fixed this issue.

https://www.npmjs.com/package/ng4-charts

1👍

There is another way to do it:

In your HTML you have

<canvas baseChart 
            [datasets]="ChartData"
            //...other stuff >
</canvas>

and in the component I have a function which update the chart with new data, and then I clone the datasets and re-assign it

drawChart(){
    this.ChartData=[{data: this.dataX, label: 'X'}]; // this.dataX has new values from some place in my code
    //nothing happened with my chart yet, until I add this lines:        
    let clone = JSON.parse(JSON.stringify(this.ChartData));
    this.ChartData=clone;
   //other stuff like labels etc.
}

this works for me, hope it works for you too

1👍

I was able to fix this issue by turning the handler into an arrow function

export class HistogramChartComponent implements OnInit {

  constructor(private dataService: MyFruitService ) { }
  
  barChartOptions: ChartOptions = { responsive: true };
  barChartLabels: Label[] = ['Apple', 'Banana', 'Kiwifruit', 'Blueberry', 'Orange', 'Grapes'];
  barChartType: ChartType = 'bar';
  barChartLegend = true;
  barChartPlugins = [];

  barChartData: ChartDataSets[] = [
    { data: [45, 37, 60, 70, 46, 33], label: 'Best Fruits' }
  ];

  ngOnInit() {
    this.dataService
      .getDocument("Foobar")
      .subscribe(this.handleResponse);
  }

  handleResponse = (doc: MyFruitDocument) => {
      console.log('document: ', doc);
      
      let labels = doc.dataPoints.map(p => p.fruitName);
      let data = { data: doc.dataPoints.map(p => p.value), label: 'Best Fruits' };

      this.barChartLabels = labels;    
      this.barChartData = [ data ];
  }
}

1👍

I have also faced this issue when trying to update the labels, (specifically when trying to send a shorter array), And this solved it:

@ViewChild(BaseChartDirective) chart!: BaseChartDirective;

and then, when updating the labels:

this.chart.chart!.config.data.labels = [...]

No need to call the update() method.

0👍

Since I didn’t manage to get one of the above solutions to work properly, I want to contribute my solution, in case someone stumbles across this post and also got stuck with the present approaches.

I have the HTML similar to @mustafa918:

 <div>
  <canvas #canvas id="canvas" 
    baseChart [datasets]="lineChartData" 
    [labels]="lineChartLabels" 
    [colors]="lineChartColors"
    [options]="lineChartOptions" 
    [chartType]="lineChartType" 
    [legend]="lineChartLegend" 
    (chartHover)="chartHovered($event)"
    (chartClick)="chartClicked($event)">
  </canvas>
</div>

And for the initialisation of the charts data in typescript i have:

public lineChartData: Array<any> = [
    { data: this.heights, label: 'Heights History', type: 'line', fill: false},
    { data: this.widths, label: 'Widths History', type: 'line', fill: false }];

And for me it worked only by setting the data and labels at the same time and don’t use chart.update() – chart is the reference to the BaseChartDirective.

I loaded the respective data and labels beforehand, so that in this.heights, this.width and this.lineChartLabels are corresponding data.

E.g. : The entries on heights[i], widths[i] and lineChartLabels[i] match with the element in my elementArray at index i => element ={ “height”:30, “width”:20, “label”:”box”}

setDatasets() {

//store data in chart references
var arrHeights = [];
for (var i in this.heights) {
  arrHeights.push({ x: this.lineChartLabels[i], y: this.heights[i] });
}

var arrWidths= [];
for (var i in this.widths) {
  arrWidths.push({ x: this.lineChartLabels[i], y: this.widths[i] });
}

this.lineChartData[0].data = arrHeights;
this.lineChartData[1].data = arrWidths;

}

I hope this helps someone 🙂 Good Luck!

0👍

Today i struggled with similar problem, it appears there is a huge bug inside the updateChartData function of ng2-charts library version 1.6.0.

Here is the original function:

        updateChartData = function (newDataValues) {
            if (Array.isArray(newDataValues[0].data)) {
                this.chart.data.datasets.forEach(function (dataset, i) {
                    dataset.data = newDataValues[i].data;
                    if (newDataValues[i].label) {
                        dataset.label = newDataValues[i].label;
                    }
                });
            }
            else {
                this.chart.data.datasets[0].data = newDataValues;
            }
        }

As you can see this updates only the data and the label, but all other properties are left behind. In my case i wanted to update also the pointBorderColor so i decided to override this.

First i get a reference to the ng2-charts library:

import { BaseChartDirective } from 'ng2-charts';

@ViewChild(BaseChartDirective) chart: any;

It is very important the type is “any”, because otherwise typescript will not allow me to override a private function.

Then i fix the bug in the function and override it in afterVIew init:

ngAfterViewInit(){
    if (this.chart) {
        this.chart.updateChartData = function (newDataValues) {
            if (Array.isArray(newDataValues[0].data)) {
                this.chart.data.datasets.forEach(function (dataset, i) {
                    dataset.data = newDataValues[i].data;
                    if (newDataValues[i].pointBorderColor) {
                        dataset.pointBorderColor = newDataValues[i].pointBorderColor;
                    }
                    if (newDataValues[i].label) {
                        dataset.label = newDataValues[i].label;
                    }
                });
            }
            else {
                this.chart.data.datasets[0].data = newDataValues;
            }
        }.bind(this.chart);
    }
}

0👍

Based on above answers, I extended this function and everything works fine now!

TS Code:

Declare : import { BaseChartDirective } from 'ng2-charts';

          @ViewChild(BaseChartDirective) chart: BaseChartDirective;

           public lineChartData: ChartDataSets[] = [
               { data: [0, 0, 0, 0, 0, 0, 0], label: 'Data 1' },
               { data: [0, 0, 0, 0, 0, 0, 0], label: 'Data 2' }
             ];

           public lineChartLabels: Label[] = ['Label1', 'Label2', 'Label3', 'Label4', 
                  'Label5', 'Label6';

TS Function:

    refresh_chart(){
        setTimeout(() => {
          if (this.chart && this.chart.chart && this.chart.chart.config) {
            this.chart.chart.config.data.datasets.forEach(x => {
              x.data = [];
            });
            let index = 0;
              this.chart.chart.config.data.datasets.forEach(x => {
                x.data = this.lineChartData[index].data;
                index++;
              });
              this.chart.chart.update();
          }
      }, 500);
      }

HTML Code:

<canvas baseChart [datasets]="lineChartData" class="canvas-wh" [labels]="lineChartLabels"
                                [options]="lineChartOptions" [colors]="lineChartColors" [legend]="lineChartLegend"
                                [chartType]="lineChartType" [plugins]="lineChartPlugins">

0👍

For me, it worked only after using ViewChildren and not ViewChild.

TS:

@ViewChildren('baseLineChart1') chart !: QueryList<BaseChartDirective>;

this.chart.forEach((child) => { child.update() })

HTML:

<canvas class="card-line-chart" baseChart #baseLineChart1>

Leave a comment