Automatic scrolling of real-time chart

I looked at a sample I found earlier showing a real-time chart where the data scrolls off the page by shifting the series data.
I do not want to shift the old data out of the series, but instead, scroll the chart such that the zoom factor remains the same (the same number of data points is displayed).
The code below is the example I have been looking at. If I eliminate the series shift statement, the zoom factor changes such that all the data is displayed. Instead, I want the chart to be automatically scrolled such that the user can scroll back to see recorded data.
Possibly stop the timer when the user scrolls back and re-enable the time when the user scrolls forward all the way (return to real-time).

Can this be done by using the scrolling directives?
Or, is there some other way to do this?

import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { ILoadedEventArgs, Series, ChartTheme, ChartComponent } from '@syncfusion/ej2-ng-charts';
import { getElement } from '@syncfusion/ej2-charts';

@Component({
    selector: 'app-root',
    template:
            `<ejs-chart #chart id='chart-container' align='center' [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis' isTransposed='true' (loaded)='loaded($event)'>
        <e-series-collection>
            <e-series [dataSource]='series1' type='Line' xName='x' yName='y' width=2 [animation]='animation1'>
            </e-series>
        </e-series-collection>
    </ejs-chart>`,
    encapsulation: ViewEncapsulation.None
})
export class AppComponent {
    public series1: Object[] = [];
    public value: number = 10;
    public intervalId: any;
    public setTimeoutValue: number;
    public i: number = 0;
    //Initializing Primary Y Axis
    public primaryYAxis: Object = {
        minimum:0,
        maximum: 50
    };
    public primaryXAxis: Object = {
      isInversed: true,
      enableScrollBar: true
    }
    @ViewChild('chart')
    public chart: ChartComponent;

    public marker: Object = {
        visible: true
    };
    public animation1: Object = {
        enable: false
    };
    public loaded(args: ILoadedEventArgs): void {
        this.setTimeoutValue = 500;
        this.intervalId = setInterval(
            () => {
                let i: number;
                if (getElement('chart-container') === null) {
                    clearInterval(this.intervalId);
                } else {
                    if (Math.random() > .5) {
                        if (this.value < 25) {
                            this.value += Math.random() * 2.0;
                        } else {
                            this.value -= 2.0;
                        }
                    }
                    this.i++;
                    this.series1.push({ x: this.i, y: this.value });
                    this.series1.shift();
                    args.chart.series[0].dataSource = this.series1;

                    args.chart.refresh();
                }
            },
            this.setTimeoutValue);
    }
    constructor() {
        for (; this.i < 100; this.i++) {
            if (Math.random() > .5) {
                if (this.value < 25) {
                    this.value += Math.random() * 2.0;
                } else {
                    this.value -= 2.0;
                }
            }
            this.series1[this.i] = { x: this.i, y: this.value };

        }

    };
}



11 Replies

DD Dharanidharan Dharmasivam Syncfusion Team October 23, 2018 03:26 PM UTC

Hi Dennis, 
 
Greetings from Syncfusion. 
 
We have analyzed your query. Your requirement can be achieved using the loaded event of chart. Here we have calculated the zoomFactor and zoomPosition of the axis dynamically and displayed the 100 points in the view port of chart. Also, we have enabled the scrollbar to scroll back the recorded data.  
 
Find the code snippet below to achieve this requirement. 
[app.component.ts] 
 
<button (click)='stoptimer($event)'>Stop Timer</button> 
<button (click)='starttimer($event)'>Start Timer</button> 
 
<ejs-chart (loaded)='loaded($event)' [zoomSettings]='zoom'> 
</ejs-chart> 
 
public loaded(args: ILoadedEventArgs): void { 
        if(!this.intervalId) { 
             this.liveupdate = this.liveupdate.bind(this); 
             this.intervalId = setInterval(this.liveupdate, 100); 
        } 
    } 
 
//Zooming 
public zoom: ZoomSettingsModel = { 
        enablePan: true, 
        enableSelectionZooming: true, 
        enableScrollbar: true 
    }; 
 
 
//Stop the live update 
public stoptimer(e: Event) { 
        clearInterval(this.intervalId); 
        this.intervalId = 100; 
    } 
 
//Start the live update 
    public starttimer(e: Event) { 
        this.intervalId = 0; 
        this.loaded(null); 
    } 
 
    public liveupdate() { 
        let i: number; 
        if (getElement('chart-container') === null) { 
            clearInterval(this.intervalId); 
        } else { 
            //... 
            this.series1.push({ x: this.i, y: this.value }); 
            //Calculated the zoom factor and position             
            this.chart.primaryXAxis.zoomFactor = 100 / this.series1.length; 
            this.chart.primaryXAxis.zoomPosition = (this.series1.length - 100) / this.series1.length; 
            //... 
        } 
    } 
 
 
 
Sample for reference can be found below. 
 
Steps to run the sample. 
·       npm install 
·       npm start 
 
Thanks, 
Dharani. 



DK Dennis Kroger October 23, 2018 06:23 PM UTC

Thank you for the quick response. It really helps a lot.
However, I have a couple of issues. First, the scrollbar never appears. The zoom property which enables the scrollbar is never reference after it is defined. Is this normal? Second, if I invert the x-axis, the program stops working properly. I assume this has to do with the calculation for the scroll position. I noticed the calculations for the zoom factor and the zoom position always sum to 1. If I negate the zoom position, it sort of works, but the chart grid scrolls twice as fast as the curve and the curve eventually scrolls off the top of the inverted chart.

This is the component:
import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { ILoadedEventArgs, ZoomSettingsModel, ChartComponent } from '@syncfusion/ej2-ng-charts';
import { getElement } from '@syncfusion/ej2-charts';

@Component({
    selector: 'app-root',
    template:
            `<button (click)='stopTimer($event)'>Stop Timer</button>
            <button (click)='startTimer($event)'>Start Timer</button>
            <ejs-chart #chart id='chart-container' (loaded)='loaded($event)' [zoomSettings]='zoom' align='center' [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis' isTransposed='true'>
        <e-series-collection>
            <e-series [dataSource]='series1' type='Line' xName='x' yName='y' width=2 [animation]='animation1'>
            </e-series>
        </e-series-collection>
    </ejs-chart>`,
    encapsulation: ViewEncapsulation.None
})
export class AppComponent {
    public series1: Object[] = [];
    public value: number = 10;
    public intervalId: any;
    public setTimeoutValue: number;
    public i: number = 0;
    //Initializing Primary Y Axis
    public primaryYAxis: Object = {
        minimum:0,
        maximum: 50
    };
    public primaryXAxis: Object = {
      isInversed: true
    }
    public zoom: ZoomSettingsModel = {
        enablePan: true,
        enableSelectionZooming: true,
        enableScrollbar: true
    }
    @ViewChild('chart')
    public chart: ChartComponent;

    public marker: Object = {
        visible: true
    };
    public animation1: Object = {
        enable: false
    };
    public loaded(args: ILoadedEventArgs): void {
        if (!this.intervalId) {
            this.liveUpdate = this.liveUpdate.bind(this);
            this.intervalId = setInterval(this.liveUpdate, 1000);
        }
    }
    public stopTimer(e: Event) {
        clearInterval(this.intervalId);
        this.intervalId = 1000;
    }
    public startTimer(e: Event) {
        this.intervalId = 0;
        this.loaded(null);
    }
    public liveUpdate() {
        if (getElement('chart-container') === null) {
            clearInterval(this.intervalId);
        } else {
            if (Math.random() > .5) {
                if (this.value < 25) {
                    this.value += Math.random() * 2.0;
                } else {
                    this.value -= 2.0;
                }
            }
            this.i++;
            this.series1.push({ x: this.i, y: this.value });
            this.chart.primaryXAxis.zoomFactor = 100 / this.series1.length;
            this.chart.primaryXAxis.zoomPosition = -(this.series1.length - 100) / this.series1.length;
            console.log(this.series1.length);
            console.log(this.chart.primaryXAxis.zoomFactor);
            console.log(this.chart.primaryXAxis.zoomPosition);
            this.chart.series[0].dataSource = this.series1;
            this.chart.refresh();
        }
    }
    constructor() {
        for (; this.i < 100; this.i++) {
            if (Math.random() > .5) {
                if (this.value < 25) {
                    this.value += Math.random() * 2.0;
                } else {
                    this.value -= 2.0;
                }
            }
            this.series1[this.i] = { x: this.i, y: this.value };
        }
    };
}




DD Dharanidharan Dharmasivam Syncfusion Team October 24, 2018 12:57 PM UTC

Hi Dennis, 
 
 
Thanks for the information. 
 
 
We have analyzed your queries and find the responses below. 
 
 
Query #1: the scrollbar never appears 
 
Kindly ensure that you have injected the highlighted modules, so that the scrollbar will appear properly. 
 
 
 
 
 
Query #2:  The zoom property which enables the scrollbar is never reference after it is defined. 
 
We would like to let you know that we have defined the zoom property and used the zoom property in the zoomSettings of chart. Find the code snippet below. 
 
 
 
<ejs-chart [zoomSettings]='zoom'> 
</ejs-chart> 
 
 
public zoom: ZoomSettingsModel = { 
        enablePan: true, 
        enableSelectionZooming: true, 
        enableScrollbar: true 
    } 
 
 
 
Query #3: if I invert the x-axis, the program stops working properly 
 
Since you are inverting the axis, you can specify the zoom position of the x axis to 0, so that scrollbar is working properly. Also, we would like to let you know that the zoom factor and position values are ranges from 0 to 1. We have modified the sample based on this scenario and the sample can be found from below link. 
 
 
 
Thanks, 
Dharani. 
 
 



DK Dennis Kroger October 24, 2018 02:12 PM UTC

Thanks again.
I had forgotten to include the zoom and scrollbar services.
Is there a way to invert the scroll bar as I have inverted the x-axis? The way the chart behaves now in counter intuitive since the x-axis is inverted and scrolling down moves the displayed interval to lesser values which scrolls the chart up, instead of down.



DD Dharanidharan Dharmasivam Syncfusion Team October 25, 2018 08:54 AM UTC

Hi Dennis, 
 
 
We have analyzed your query. We have logged bug report for the reported scenario and the fix will be included in our next weekly patch release. We will update you once weekly patch release is rolled out with fix for reported scenario. We appreciate your patience until then. 
 
 
Thanks, 
Dharani. 



DK Dennis Kroger November 12, 2018 04:45 PM UTC

Has there been any update concerning inverting the scrollbar on a vertical chart?

Thanks.


DD Dharanidharan Dharmasivam Syncfusion Team November 13, 2018 12:56 PM UTC

Hi Dennis, 
  
We are glad to announce that our v16.3.32 patch release is rolled out, we have included the fix for the reported issue and is available for download under the following link. 
  
 
We thank you for your support and appreciate your patience in waiting for this release. Please get in touch with us if you would require any further assistance. 
  
Thanks,          
Dharani. 



DK Dennis Kroger November 13, 2018 02:51 PM UTC

Thank you for the timely update.
The scrollbar now works as expected, but the chart no longer automatically scrolls. I have to stop the timer and move the scrollbar in order to see the latest data.
When I start the timer again, the scroll bar moves back up to the top. Is there a way to keep the scroll tab at the bottom while the timer is running in order to see the latest data continuously?

If I change the statement 'this.promaryXAxis.zoomPosition = 0;' to 'this.primaryXAxis.zoomPosition = this.series1.length;', the chart will scroll automatically, but then the scroll bar does not update. It simply has a down pointer at the bottom of the chart. If I stop the timer, the scroll bar still does not work.

If I add the line 'this.chart.primaryXAxis.zoomPosition = 0;' to the stopTimer function, the scrollbar will work after the timer stops, but the chart scrolls all the way to the top and I have to scroll it back down to see the latest data again.

I am using @syncfusion/ej2-angular-charts version 16.3.32.


app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ChartModule } from '@syncfusion/ej2-angular-charts';
import { LineSeriesService, ScrollBarService, ZoomService } from '@syncfusion/ej2-angular-charts';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ChartModule
  ],
  providers: [LineSeriesService, ScrollBarService, ZoomService],
  bootstrap: [AppComponent]
})
export class AppModule { }


app.component.html:
<button (click)='stopTimer($event)'>Stop Timer</button>
<button (click)='startTimer($event)'>Start Timer</button>
<ejs-chart #chart id='chart-container' (loaded)='loaded($event)' [zoomSettings]='zoom' align='center' [primaryXAxis]='primaryXAxis' [primaryYAxis]='primaryYAxis' isTransposed='true'>
  <e-series-collection>
    <e-series [dataSource]='series1' type='Line' xName='x' yName='y' width=2 [animation]='animation1'></e-series>
    <e-series [dataSource]='series2' type='Line' xName='x' yName='y' width=2 [animation]='animation1'></e-series>
  </e-series-collection>
</ejs-chart>

app.component.ts:
import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { ILoadedEventArgs, ZoomSettingsModel, ChartComponent, getElement } from '@syncfusion/ej2-angular-charts';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Live Chart';
  public series1: Object[] = [];
  public series2: Object[] = [];
  public value1: number = 10;
  public value2: number = 0;
  public intervalId: any;
  public setTimeoutValue: number;
  public i: number = 0;
  public sign1: number = 1;
  public sign2: number = 1;

  public primaryYAxis: Object = {
    minimum: 0,
    maximum: 100
  }

  public primaryXAxis: Object = {
    isInversed: true
  }

  public zoom: ZoomSettingsModel = {
    enablePan: true,
    enableSelectionZooming: true,
    mode: 'X',
    enableScrollbar: true
  }

  @ViewChild('chart')
  public chart: ChartComponent;

  public marker: Object = {
    visible: true
  }

  public animation1: Object = {
    enable: false
  }

  public loaded(args: ILoadedEventArgs): void {
    if (!this.intervalId) {
      this.liveUpdate = this.liveUpdate.bind(this);
      this.intervalId = setInterval(this.liveUpdate, 200);
    }
  }

  public stopTimer(e: Event) {
    clearInterval(this.intervalId);
    this.intervalId = 200;
    this.chart.primaryXAxis.zoomPosition = 0;
  }

  public startTimer(e: Event) {
    this.intervalId = 0;
    this.loaded(null);
  }

  public liveUpdate() {
    if (getElement('chart-container') === null) {
      clearInterval(this.intervalId);
    } else {
      if (this.value1 > 95) {
        this.sign1 = -1;
      } else if (this.value1 < 5) {
        this.sign1 = 1
      }
      this.value1 += (this.sign1 * Math.random() * 5.0);

      if (this.value2 > 95) {
        this.sign2 = -1;
      } else if (this.value2 < 5) {
        this.sign2 = 1;
      }
      if (this.i % 5 == 0) {
        this.sign1 *= -1;
        this.sign2 *= -1;
      }
      this.value2 += (this.sign2 * Math.random() * 2.0);

      this.i++;
      this.series1.push({ x: this.i, y: this.value1 });
      this.chart.primaryXAxis.zoomFactor = 100 / this.series1.length;
      this.chart.primaryXAxis.zoomPosition = this.series1.length;
     
      this.chart.series[0].dataSource = this.series1;
      this.chart.series[0].fill = "blue";
      this.series2.push({ x: this.i, y: this.value2 });
      this.chart.series[1].dataSource = this.series2;
      this.chart.series[1].fill = "red";
      this.chart.refresh();
    }
  }

  constructor() {
    for (; this.i < 100; this.i++) {
      if (Math.random() > .5) {
        if (this.value1 < 25) {
          this.value1 += Math.random() * 2.0;
        } else {
          this.value1 -= 2.0;
        }
      }
      this.series1[this.i] = { x: this.i, y: this.value1 };
      if (Math.random() > .75) {
        if (this.value2 < 75) {
          this.value2 += Math.random() * 5.0
        } else {
          this.value2 -= 5.0;
        }
      }
      this.series2[this.i] = { x: this.i, y: this.value2 };
    }
  }

}

Is there a way I can get the scrollbar tab to stay at the bottom of the chart when the timer is stopped and function as expected?
Also, is there an event I can handle when the user tries to scroll while the timer is running such that I can stop the timer?

Thanks for your halp.
Dennis Kroger


DD Dharanidharan Dharmasivam Syncfusion Team November 14, 2018 12:09 PM UTC

Hi Dennis, 
 
 
Query1:  Is there a way to keep the scroll tab at the bottom while the timer is running in order to see the latest data continuously? 
 
We have analyzed your requirement with attached code snippet.  We have created sample by using your code snippet. When we start the timer the scroll bar moves based on latest data from bottom to top is the correct manner. In this sample we have added one data in the chart at the same time remove one data simultaneously. Then the old data going out to the top position when timer is running. So, the scrollbar moves top based on latest data. If we move the scrollbar at bottom position it shows only the old data not shows latest data. 
 
 
Quer2: Is there an event I can handle when the user tries to scroll while the timer is running such that I can stop the timer? 
 
We can achieve your requirement by using “scrollChanged” event of the scrollbar. This event triggers when we changes the scrollbar position. Using this event, we can stop the timer to call clear interval method. 
 
Code Snippet: 
public scrollChanged(args: IScrollEventArgs): void { 
    clearInterval(this.intervalId); 
    this.intervalId = 200; 
  } 
 
 
 
Regards, 
Dharani 



DK Dennis Kroger November 15, 2018 10:23 PM UTC

Thanks again for the quick response.
I still think that it is not intuitive for the scroll bar to work the way it does on an inverted vertical chart.
Moving the scroll tab up should show the older data, not the newer data. Moving the scroll tab down should show newer data. The way it works now, scrolling down moves the chart down showing older data.
I want to use this to show well depth related data. The scroll tab should move relative to the depth, moving down should show deeper depths, not shallower depths.
Is there not any way to handle this?

Also, running the example included, once there are more than about 1000 data points, the scroll stops working and clicking the Stop Timer button does not stop the timer until after a long delay. The application becomes non-responsive. I will need to chart thousands of data points.

One last point: If I use the same version of ej2-angular-charts (16.3.27)  that you used in your example, the chart works and scrolls automatically and the scrollbar does work (backwards in my opinion, but it does work). However, if I use the latest version (16.3.32), it no longer works. It goes back to not scrolling automatically just like before the fix discussed previously. So it appears that you have broken your update somewhere along the way.

Edit:
Well, I don't know just what is happening, but I changed the version back to 16.3.27 and the chart will not scroll automatically, but using the scroll tab, it does scroll the right way. Scrolling up moves to older data and scrolling down moves to newer data. I am confused at this point.

Edit2:
If I start with a new project and install the latest version of ej2-angular-charts (16.3.32) and use the same code as in the example you sent, then the chart does not scroll automatically, but after stopping the timer, the scroll tab works as I would expect it to. Moving the scroll tab down shows newer data and moving the scroll tab up shows older data. This is how I would expect it to work. 
The problem now is that the chart does not scroll while the timer is running and the scroll tab stays at the top of the chart and shrinks in size as more data is accumulated. So, I guess we are making progress, its just kind of two steps forward and one step back.

Edit3:
I am getting close. If, in the liveUpdate function, I add the line 'this.chart.PrimaryXAxis.zoomPosition = 1;' then the chart scrolls and the scroll tab acts as I would like, but eventually, the scroll tab starts to 'walk' up the scroll bar. It appears as though the top of the scroll tab remains stationary and the bottom of the tab moves up as the tab shrinks in size.
Then, after stopping the timer after several hundred data points are accumulated, the application is very sluggish in responding to scroll tab movements and I get several warnings that the mousemove handler took ms. If I leave the timer disabled for several seconds (1 minute), the scrollbar becomes responsive again.
When I restart the timer, the scroll tab jumps back up the scrollbar to where it was before stopping the timer.
So, setting the zoom position to 1 is not keeping the scroll tab all the way down the scrollbar after about 200 data points are added.

Edit4:
OK, I figured out the scroll tab positioning. If I use the statement 'this.chart.primaryXAxis.zoomPosition = 1 - this.chart.primaryXAxis.zoomFactor;' then the scroll tab stays at the bottom of the scrollbar. However, the application is still very sluggish in responding to scroll tab movements after stopping the timer.



KC Kalaimathi Chellaiah Syncfusion Team November 19, 2018 12:32 PM UTC

Hi Dennis, 
 
Thanks for being patience. 
 
  We have checked the reported query and unable to reproduce the issue hence we have created an incident under your direct trac account. kindly check the incident for further assistance 

Kindly revert us, if you have any queries. 

Regards, 
Kalai 


Loader.
Up arrow icon