Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling update to refresh the chart. #291

Closed
katsuragi545 opened this issue Jul 5, 2016 · 49 comments
Closed

Calling update to refresh the chart. #291

katsuragi545 opened this issue Jul 5, 2016 · 49 comments

Comments

@katsuragi545
Copy link

How do you go about updating the chart? After getting the chart to display, I changed the color of points to red if they are clicked. If the user clicks multiple points, then the older points go back to their original color. However, it only goes back if I hover my mouse over the old point. I would like this behavior to be automatic - I would imagine that calling update on the chart would work, but I get the error:

TypeError: this.chartObject.update is not a function...

Any ideas on how to do this with or without calling update?

@luchillo17
Copy link

Well, in my case i just change the values of the data and it updates by itself, however idk if it works the same for those components.

@michaelknoch
Copy link

michaelknoch commented Jul 11, 2016

i try to push values to my dataset and label arrays, but no refresh is triggered.
Current workaround for me:

@ViewChild(BaseChartComponent) private _chart;
_chart.ngOnChanges()

@jrmoser
Copy link

jrmoser commented Jul 11, 2016

@michaelknoch I am having the same problem, however I can't seem to implement your workaround for it to work for me. Did you set the _chart.ngOnChanges() within some interval?

@michaelknoch
Copy link

in my workaround, _chart.ngOnChanges() is called after data has changed. Think of $scope.$apply from angular 1. @jrmoser

@katsuragi545
Copy link
Author

katsuragi545 commented Jul 12, 2016

@michaelknoch's workaround worked for me when adding data to the chart. ...going to test this on the chart point colors later

@jrmoser
Copy link

jrmoser commented Jul 13, 2016

@michaelknoch Thanks, I ended up putting it inside of an ngDoCheck... probably not the best for optimization, but it's what is working for now

@katsuragi545
Copy link
Author

katsuragi545 commented Jul 13, 2016

hmm, doesn't work when changing point attributes like size and color - seems like calling ngOnChanges resets all points to their default style

@DmitryEfimenko
Copy link

DmitryEfimenko commented Aug 18, 2016

in my case I'm trying to display a realtime chart. So I'd like to update chart whenever a new data point is available.

Workaround suggested by @michaelknoch indeed refreshes chart, but it re-draws the whole chart rather than just adding a new data-point making it very hard to understand that it's a realtime data.

I know chart.js is capable of this functionality. Even this older example shows that it's possible. I just don't know how to achieve this via this wrapper.

@hongbo-miao
Copy link
Contributor

hongbo-miao commented Aug 29, 2016

This is because Angular 2 Change Detection only runs when value or reference changes.

So you can manually trigger the change detection like the ways provided in this post, or give a different reference.

This similar issue may help you understand: #378

@DmitryEfimenko
Copy link

For these having an issue with updating chart in realtime, this is actually an issue with charts.js itself. In v2 they removed these helpers that allowed to do this in v1. See issue 1997

@hongbo-miao
Copy link
Contributor

hongbo-miao commented Aug 29, 2016

@DmitryEfimenko cool, glad you find the issue. Thanks for help. I was actually writing you to let you create a new feature request.

Now I can close this I guess.

@hongbo-miao
Copy link
Contributor

hongbo-miao commented Sep 1, 2016

I think maybe we can provide a more friendly way to refresh the chart? @valorkin

@my9074
Copy link

my9074 commented Nov 10, 2016

@hongbo-miao @valorkin Now, if the data change, chart change more troublesome.I suggest make a new API to update chart when datasets/labels changed

@arimus
Copy link

arimus commented Dec 14, 2016

Yeah, the suggested fix works, but it is really hacky. An update method would be nice and clean. I ended up with the following for a one-time async data load:

import {SimpleChanges} from '@angular/core';

// ... all the things ...

someFunction() {
  // ... get data into chartLabels / chartDatasets
  this.chartComponent.ngOnChanges({} as SimpleChanges);
}

@thfellner
Copy link

thfellner commented Feb 23, 2017

I found that using the public chart attribute of the BaseChartDirective is a clean way to have an update method.

@ViewChild(BaseChartDirective)
public chart: BaseChartDirective;

    ...
    this.chart.chart.update();
    ...

@zbagley
Copy link

zbagley commented Mar 19, 2017

This looks like the closest topics, and I don't want to open a new issue since mine's similar.

I was having some serious problems getting the chart to properly initialize, and none of these solutions worked straight forward (data was being loaded async, but the chart would draw before the process finished and wouldn't re-render).

A quick, easy, solution is to just wait to draw the canvas until your asyncs are finished. In your component:

isDataAvailable:boolean = false;
ngOnInit() {
    asyncFnWithCallback(()=>{ this.isDataAvailable = true});
}

and wrap your entire template with:

<div *ngIf="isDataAvailable">
. . . chart canvas + any other template code  . . .
</div>

Spend several hours testing other workarounds, and thought this might help someone (it also seems a lot more straight-forward w/o need for adding any new imports). It's not a solution for later updates, but it's definitely a solution for a lot of the people that seem to have stumbled on this and the similar topics posted above that just want their chart initialized properly.

@luchillo17
Copy link

@zbagley In my case it did update when i change the data, however the animation restarts so it's like if the animation cuts in the middle of it and starts all over, looks ugly.

I did though something like your solution, but i had to revert to previous scenario, in my case, being an Ionic 2 app (mobile) not rendering anything for a few seconds (0.5 - 2 depending on hardware) would make the app look unresponsive, i prefer it to chop the animation instead, at least it looks like it's doing something.

@berkerdemirer
Copy link

I handle the issue by implementing:
@ViewChild(BaseChartDirective) private _chart;
this._chart.chart.update();

However, i cannot update another chart. I need to update two different line charts as data comes in. Only one of them works.

@hemanrnjn
Copy link

@zbagley your solution worked for me. Thanks a ton for that. I've been stuck for 2 days straight because I couldn't asynchronously update value in datasets of my bar chart. Now it works like a charm!

@jadamconnor
Copy link

@zbagley 's solution is a pretty standard way to do this kind of thing. But the library should have some async functionality.

@adadgio
Copy link

adadgio commented Aug 22, 2017

True, the solution is to modify the current array (labels/values) reference (push/slice, etc...) and then call chart.update(). Then the chart will update properly without triggering the whole animation.

On the other hand if you reassign values to the arrays like chartValue = newcChartValues it will indeed restart the animation and.. bam! Ugly effect !

@mikezks
Copy link

mikezks commented Aug 22, 2017

@adadgio, as described above (including example) I experienced the opposite:

  • modifying the array leads to the wrong/ugly animation.
  • reassigning a new array leads to a correct animation.

@adadgio
Copy link

adadgio commented Aug 22, 2017

That's strange, i have the latest version and the behavior is exactly what i described... hmmm.

@mikezks
Copy link

mikezks commented Aug 23, 2017

@adadgio, yes it is indeed.
Though the Plunker example uses version 1.5.0 of ng2-charts, version 1.6.0 does not bring any changes for me.

If I use chart.update() as suggested by you, the animation for the removed first item does not work.
If I use chart.ngOnChanges({} as SimpleChanges) the whole chart redraws.

As several issues deal with change management in ng2-charts and related to this with correctly working animations, it would be helpful if you share your code. Maybe we can find a better alternative than mine. This would be interesting to me.

@adadgio
Copy link

adadgio commented Aug 24, 2017

All right, i'm going to post a bit of raw code because i don't have much time, sorry.

So here you have: a working example of pushing data that succesfuly redraws the chart when updated (ie. chart does redraw only last values correctly without triggering the whole animation).

My angular2 app is a very simple bare bone app with just an app component and using "ng2-charts": "^1.6.0".

I created a wrapper component for the chart just to have clean code, this is what i am going to post here. It basically fetches values from a remote file (you can even test with the IP given in the code as such).

Note i am not using ngOnChanges here or anything, just pushing into chartData.

app.component.html

<!-- dont mind input here, irrelevant for this issue, its just to know which remote sensor values to draw from the logs file -->
<chart-component flex-fill [name]="'Soil moisture'" [showValsOf]="'sh'"></chart-component>

chart.component.html

Info: I am using this component 3 times for 3 charts, thus a few if in the following component depending on what values i want to render. But whatever.

<canvas baseChart #chart
        [chartType]="'line'"
        [options]="chartOptions"
        [labels]="chartLabels"
        [colors]="chartColors"
        [datasets]="chartData"
    ></canvas>

chart.component.ts

What this component does is fetch data via GET request and parse multiple sensor values, remove and format some data and push the relevant data into chartData (see method refresh and end of method parseChartDataFromLogs for the relevant parts).

import * as moment from 'moment';
import { Http }                 from '@angular/http';
import { Component, OnInit }    from '@angular/core';
import { ViewChild }            from '@angular/core';
import { Input }                from '@angular/core';
import { BaseChartDirective }   from 'ng2-charts/ng2-charts';

const CHART_POINT_EVERY_X_MIN = 3;

@Component({
    selector: 'chart-component',
    templateUrl: './chart.component.html',
    styleUrls: ['./chart.component.scss']
})
export class ChartComponent implements OnInit {
    @ViewChild(BaseChartDirective) chart: BaseChartDirective;
    @Input('name') name: string = null;

    // @docs-A01
    // the logs contains all sensor data such as
    // Wed Aug 23 2017 06:50:02 GMT+0000 (UTC)| 563;50;24
    // the 563;50;24 is soil humidity, air humidity and air temperature
    // by convention (also named "sh", "ah" and "at" in arduino)
    @Input('showValsOf') showValsOf: string = null;

    // history hold previous data when user opened the browser
    // so the chart does not redraw every time (prevent setting a whole new chartData(s)...)
    private _dataHistory: any = { "sh": {}, "ah": {}, "at": {} };
    private _showValsOfMapIndex: any = { "sh": 0, "ah": 1, "at": 2 };

    chartLabels: Array<any> = [];

    chartData: Array<any> = [
        {
            data: [],
            label: null,
        }
    ];

    chartColors: Array<any> = [
        {
            pointRadius: 0,
            pointBorderWidth: 0,
            pointBorderColor: '#123752',
            pointBackgroundColor: '#123752',
            borderColor: '#123752',
            backgroundColor: 'rgba(112, 142, 164, 0.7)',
            fill: true,
        }
    ];

    chartOptions: any = {
        legend: {
            labels: {
                fontColor: '#fff',
            }
        },
        scales: {
            xAxes: [{
                display: true,
                gridLines: {
                    display: true,
                },
                ticks: {
                  fontColor: '#fff',
                },
            }],
            yAxes: [{
                display: true,
                gridLines: {
                    display: true,
                },
                ticks: {
                    // min: -10,
                    // max: 40,
                    fontColor: '#fff',
                    callback: (originalValue) => {
                        // depending on the chart to show for each sensor, i want ticks labels to 
                        // have celcius, precentage, or no unit at all
                        if (this.showValsOf === 'at') {
                            return `${originalValue}°`;
                        } else if (this.showValsOf === 'ah') {
                            return `${originalValue}%`;
                        } else {
                            return originalValue;
                        }
                    }
                },
            }],
        }
    };

    constructor(private http: Http)
    {

    }

    ngOnInit()
    {
        this.chartData[0].label = this.name;

        // bug/ng2-chart or angular, its not updated here
        // but strangely works from ngOnInit (and not ngAfterViewInit !!)...

        // min and max values differ if mode is "sh", "ah" or "at"
        // configure different axis scales and other colors
        // depending on the graph we would like to show
        switch(this.showValsOf) {
            case 'sh':
                // soil humidity is a value between 100, theoritically 0 and 1024 (with 5v, othervise 600 or 800 i think)
                this.chartOptions.scales.yAxes[0].ticks.min = 0;
                this.chartOptions.scales.yAxes[0].ticks.max = 1050;
                this.chartOptions.scales.yAxes[0].ticks.maxTicksLimit = 5;
                this.chartOptions.scales.yAxes[0].ticks.stepSize = ((1050-0)/5); // 210
            break;
            case 'ah':
                // air humidity is in percentages
                this.chartOptions.scales.yAxes[0].ticks.min = 0;
                this.chartOptions.scales.yAxes[0].ticks.max = 120;
                this.chartOptions.scales.yAxes[0].ticks.maxTicksLimit = 8;
                this.chartOptions.scales.yAxes[0].ticks.stepSize = 20;
            break;
            case 'at':
                // air temperature is in C° 
                this.chartOptions.scales.yAxes[0].ticks.min = -20;
                this.chartOptions.scales.yAxes[0].ticks.max = 60;
                this.chartColors[0].borderColor = '#da3b15';
                this.chartColors[0].fill = false;
                this.chartOptions.scales.yAxes[0].ticks.maxTicksLimit = 8;
                this.chartOptions.scales.yAxes[0].ticks.stepSize = 10;
            break;
            default:
                // noooothing to do here!
            break;
        }
    }

    ngAfterViewInit()
    {

    }

    refresh()
    {
        const url = '';
        const req = this.http.get('http://37.139.14.78:1880/api/garden/logs').toPromise();

        req.then((res: any) => {
            this.parseChartDataFromLogs(res._body);
        }).catch(e => {
            console.log(e);
        });
    }

    private parseChartDataFromLogs(data: any)
    {
        let prevTime = null;
        const lines = data.split("\n");

       //for chart one, two or three, i need to know wich sensor vals to draw (values from "sh", "ah", or "at" ?
        const valIndex = this._showValsOfMapIndex[this.showValsOf];

        for (let line of lines) {
            // skip empty lines
            if (line === "") { continue; }

            const parts = line.split("| ");
            if (parts.length !== 2) { continue; }

            const date = moment(parts[0].trim()).utc();
            const values = parts[1].split(";");
            // const sh = parseInt(values[0]);
            // const ah = parseInt(values[1]);
            // const at = parseInt(values[2]);
            // see documentation at @docs-A01
            const pointValue = parseInt(values[valIndex]);

            const label = date.format("hh:mm"); // "dd Mo, hh:mm"
            const uuid = date.toString();

            // skip some value, and lets just use a tick every 30 seconds
            const timestamp = date.valueOf();
            if (prevTime === null) { prevTime = timestamp; }

            if (timestamp - prevTime < 1000*60*CHART_POINT_EVERY_X_MIN) {
                continue;
            } else {
                prevTime = timestamp;
            }

            // if this is already in the data history...
            if (typeof this._dataHistory[this.showValsOf][uuid] === 'undefined') {

                // THIS IS THE RELEVANT PART FOR THIS ISSUE!
                this.chartData[0].data.push(pointValue);
                this.chartLabels.push(label);
                this._dataHistory[this.showValsOf][uuid] = true; // and push bool to data history (point exists)
            }
        }

        // i DO CALL chart update...
        this.chart.chart.update();
    }
}

Note: the use of _dataHistory is not a hacky part nor relevant for this issue, its just here to help me skip values already parsed inside my foreach data point loop{...} just because i fetch the entire logs file at every http request and not the last values only (which is highly inefficient - i agree - but does not really matter for this example... ;-) ).

@mikezks
Copy link

mikezks commented Aug 24, 2017

@adadgio, thanks for sharing your code.
I had to fix same issues before it was able to run (e.g. the API result and parseChartDataFromLogs(data: any) did not fit).

I don't know how and when refresh() gets called resp. how many values are emitted. This is the relevant part for the questions about the working animation I was talking about above. If you do not remove values from the chart with shift(), than you can not reproduce the issue thus, as mentioned above, chart.update() can not cause a wrong animation behavior.

@adadgio
Copy link

adadgio commented Aug 24, 2017

Yep sorry I changed the API this morning ! Refresh is only called when user clicks a button, or programmatically when i'm sure new data is there. Not very relevant either.

It works for me when I remove values. I'm going to pseudo code , but basicaly what i did was:

this.chartData[0].data.push(48);
this.chartLabels.push("hey");
this.chart.chart.update();

... works

this.chartData[0].data.shift()
this.chartLabels.shift()
this.chart.chart.update();

... also works.

If you would like to investigate further, here is the full, updated code as up today (look at the same files). You should even be able to view the graph in real time if you run the app.

https://github.com/adadgio/angular-garden

@mikezks
Copy link

mikezks commented Aug 24, 2017

No problem - understandable as you are working on this project. 😃

To make it clear: if I constantly add new values and therefore the number of data points increases everything is fine with chart.update().

But if I have a constant number of data values and remove the most left item with shift() as I push() a new one, then the animation works with the code I posted above only.

Also be aware that the animation for newly added values is different than for a constant number of values which move form the right to the left.

See:
http://plnkr.co/edit/PLp8H1HnmtpiU2qLREKe?p=preview

You can change the (click) call to (click)="clickData_WrongAnimation() to see the wrong animation behavior.

@adadgio
Copy link

adadgio commented Aug 24, 2017

I see. And what animation did you expect from the clickData_WrongAnimation again ? Just going the other way ? (i have okay-ish animations any time on your plunker, the "wrong" one seems strange but at least it does not redraw...)

@mikezks
Copy link

mikezks commented Aug 24, 2017

I expected to get another animation than the white area after the shift(). Would be good if the last value moves out of the scale.

My workaround solves this, but the values do not move from right to left, but instead each value transforms form the old to the new value, which is better than the white area, but not that good for a live chart that constantly gets new values.

@mikezks
Copy link

mikezks commented Aug 24, 2017

I opened an issue in the charts.js repo: chartjs/Chart.js#4695.

@conniekim
Copy link

conniekim commented Oct 24, 2017

@katsuragi545 how did you change the color to red if they're clicked? Whenever I click mine, they only stay red for a second before going back to its default.. :(

EDIT
nevermind, I saw one of your other posts
chartjs/Chart.js#2989

@arrow-dev
Copy link

arrow-dev commented Nov 8, 2017

@zbagley This is great, don't know why I didn't think of this! *ngIf="chartData" on the chart wrapper and it's happy days!

@jhermann21
Copy link

This worked for me after trying a few of the above solutions with no luck: http://plnkr.co/edit/Ra6cfYkctsq3ZnqxTHYf?p=preview

@rdelriods01
Copy link

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

@binario200
Copy link

binario200 commented Apr 6, 2018

I am using angular 4, and this is what works for me
at the html

<canvas *ngIf="lineChartData.length" baseChart width="800" height="300"
                            [datasets]="lineChartData"
                            [labels]="lineChartLabels"
                            [options]="lineChartOptions"
                            [colors]="lineChartColors"
                            [legend]="lineChartLegend"
                            [chartType]="lineChartType"
                            (chartHover)="chartHovered($event)"
                            (chartClick)="chartClicked($event)"></canvas>

at the component.ts

export class ResultsGraphPanelComponent implements OnInit {
  @ViewChild(BaseChartDirective) public chart: BaseChartDirective;
   // other initializing component
}

function to populate the lineCharData Array (datasets)

buildCountingDataSet() {
       // code to populate lineCharData
    
        if (this.chart !== undefined && this.chart.chart !== undefined) {
        this.lineChartData.push({data: dataEnter, label: enterLabel});
        this.lineChartData.push({data: dataExit, label: exitLabel});

       // this is what actually update the chart 
        this.lineChartLabels = xLabels;
        this.chart.labels = xLabels;
        this.chart.chart.data.labels = xLabels;

        this.chart.chart.update();
      } else {
        this.lineChartData = [{data: dataEnter, label: enterLabel},
          {data: dataExit, label: exitLabel}];
        this.lineChartLabels = xLabels;
      }
}

@crlang44
Copy link

crlang44 commented May 1, 2018

With angular 4, I got it to work with the timeout method above:

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

...

@ViewChild(BaseChartDirective)
public chart: BaseChartDirective;

...

setTimeout(() => {
    this.chart.getChartBuilder(this.chart.ctx);
}, 10);

@CareyWillette
Copy link

@zbagley Your code block from Mar 18, 2017 is epic. #wearenotworthy

@paviad paviad closed this as completed Mar 5, 2019
@maksof-kashif
Copy link

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

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.
}

@digringo
Copy link

digringo commented Jul 5, 2019

you can assing a value to ChartOptions even if it's the original one it dosen't matter

@pedropagotto
Copy link

@zbagley
works for me!
tks

@Jay98patel
Copy link

I found that using the public chart attribute of the BaseChartDirective is a clean way to have an update method.

@ViewChild(BaseChartDirective)
public chart: BaseChartDirective;

    ...
    this.chart.chart.update();
    ...

not working for me,
Actually I have one separate component for graph and one is parent component from which I am putting selector of child component and calling the update method (return this.chart.chart.update();)from parent component

@pedropagotto
Copy link

I found that using the public chart attribute of the BaseChartDirective is a clean way to have an update method.

@ViewChild(BaseChartDirective)
public chart: BaseChartDirective;

    ...
    this.chart.chart.update();
    ...

not working for me,
Actually I have one separate component for graph and one is parent component from which I am putting selector of child component and calling the update method (return this.chart.chart.update();)from parent component

You tired that implementation?
#291 (comment)

@ayyoub-hiit
Copy link

i somehow collected fixes from here and there I made it work

@ViewChild(BaseChartDirective)
public chart: BaseChartDirective;
....
updateChart() {
this.chart?.chart.update();
this.chart?.ngOnChanges({} as SimpleChanges);
}

@AsgrimS
Copy link

AsgrimS commented Aug 5, 2022

A little late addition but I've bumped into the same issue and for me, the following solution worked like a charm:

@ViewChild(BaseChartDirective)
public chart: BaseChartDirective;

...  

updateChart() {
this.chart.render();
}

update() method itself seemed to do nothing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests