Get Started: Tutorials, Examples > Tutorials (JavaScript / npm / Webpack) > Tutorial 05 - Zoom and Pan with Realtime Updates
Tutorial 05 - Zoom and Pan with Realtime Updates

In Tutorial 04 - Adding Realtime Updates, we showed you how to dynamically update DataSeries to enable Real-time updates in SciChart.js. In this tutorial, were going to show you how to allow zooming and panning while scrolling data.

If you haven't read it already, also check out Tutorial 03 - Adding Zooming Panning Behavior as we will assume you have the knowledge to add zoom and pan behaviors to a SciChart.js JavaScript chart.

The source code for this tutorial can be found at SciChart.JS.Examples Github Repository  

Creating the Base Application

We're going to start off with the code we created in the previous Tutorial 04 - Adding Realtime Updates. If you haven't already started that tutorial, please run through it first so you can understand the concepts.

Start with this code to begin with. This will create a real-time chart with scrolling data, but no zooming or panning yet.

Before Code
Copy Code
import {SciChartSurface} from "scichart/Charting/Visuals/SciChartSurface";
import {NumericAxis} from "scichart/Charting/Visuals/Axis/NumericAxis";
import {XyDataSeries} from "scichart/Charting/Model/XyDataSeries";
import {FastLineRenderableSeries} from "scichart/Charting/Visuals/RenderableSeries/FastLineRenderableSeries";
import {XyScatterRenderableSeries} from "scichart/Charting/Visuals/RenderableSeries/XyScatterRenderableSeries";
import {EllipsePointMarker} from "scichart/Charting/Visuals/PointMarkers/EllipsePointMarker";
import {NumberRange} from "scichart/Core/NumberRange";
async function initSciChart() {   
    // Create the SciChartSurface in the div 'scichart-root'
    // The SciChartSurface, and webassembly context 'wasmContext' are paired. This wasmContext
    // instance must be passed to other types that exist on the same surface.
    const {sciChartSurface, wasmContext} = await SciChartSurface.create("scichart-root");
    // Create an X,Y Axis and add to the chart
    const xAxis = new NumericAxis(wasmContext);
    const yAxis = new NumericAxis(wasmContext);
   
    sciChartSurface.xAxes.add(xAxis);
    sciChartSurface.yAxes.add(yAxis);   
    // Create a Scatter series, and Line series and add to chart
    const scatterSeries = new XyScatterRenderableSeries(wasmContext, {
        pointMarker: new EllipsePointMarker(wasmContext, { width: 7, height: 7, fill: "White", stroke: "SteelBlue" }),
    });
    const lineSeries = new FastLineRenderableSeries(wasmContext, { stroke: "#4083B7", strokeThickness: 2 });
    sciChartSurface.renderableSeries.add(lineSeries, scatterSeries);
    // Create and populate some XyDataSeries with static data
    // Note: you can pass xValues, yValues arrays to constructors, and you can use appendRange for bigger datasets
    const scatterData = new XyDataSeries(wasmContext, { dataSeriesName: "Cos(x)" });
    const lineData = new XyDataSeries(wasmContext, { dataSeriesName: "Sin(x)" });
    for(let i = 0; i < 1000; i++) {
        lineData.append(i, Math.sin(i*0.1));
        scatterData.append(i, Math.cos(i*0.1));
    }
    // Assign these dataseries to the line/scatter renderableseries
    scatterSeries.dataSeries = scatterData;
    lineSeries.dataSeries = lineData;
    // SciChart will now redraw with static data
    //
    // Part 2: Appending data in realtime
    //
   
    const updateDataFunc = () => {
        // Append another data-point to the chart. We use dataSeries.count()
        // to determine the current length before appending
        const i = lineData.count();
        lineData.append(i, Math.sin(i * 0.1));
        scatterData.append(i, Math.cos(i * 0.1));
        // ZoomExtents after appending data.
        // Also see XAxis.AutoRange, and XAxis.VisibleRange for more options
        xAxis.visibleRange = new NumberRange(i-1000, i);
        // Repeat at 60Hz       
        setTimeout(updateDataFunc, 1/60);
        // Warning, this will repeat forever, it's not best practice!
    };
    updateDataFunc();
}
initSciChart();
Before Code
Copy Code
<html lang="en-us">
    <head>
        <meta charset="utf-8" />
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
        <title>SciChart.js Tutorial 5 - Zooming and Panning Realtime Updates</title>
        <script async type="text/javascript" src="bundle.js"></script>
        <style>
            body { font-family: 'Arial'}
        </style>
    </head>
    <body>
        <h1>Hello SciChart.js world!</h1>
        <p>In this example we explore how to zoom and pan a real-time chart with SciChart.js</p>
       
        <!-- the Div where the SciChartSurface will reside -->
        <div id="scichart-root" style="width: 800px; height: 600px;"></div>
    </body>
</html>

Adding Zooming and Panning Behavior

From Tutorial 03 - Adding Zooming, Panning Behavior, we learned that we can add ChartModifiers to the sciChartSurface.chartModifiers collection to add specific zoom, or pan behaviors to the chart.

However, the code we added to scroll the chart on update is going to conflict with the user mouse-zooming behaviors. Take a look below:

Zooming and Panning Step 1
Copy Code
...
import {RubberBandXyZoomModifier} from "scichart/Charting/ChartModifiers/RubberBandXyZoomModifier";
import {ZoomExtentsModifier} from "scichart/Charting/ChartModifiers/ZoomExtentsModifier";
import {EZoomState} from "scichart/types/ZoomState";

async function initSciChart() {
         
    // Add this code to enable zooming by mouse-drag and double-click to zoom extents
    //        
    sciChartSurface.chartModifiers.add(new ZoomExtentsModifier({isAnimated: false}));       
    sciChartSurface.chartModifiers.add(new RubberBandXyZoomModifier());
    ...
    const updateDataFunc = () => {
        const i = lineData.count();
        lineData.append(i, Math.sin(i * 0.1));
        scatterData.append(i, Math.cos(i * 0.1));
        // However, user-zoom will conflict with this code which scrolls the chart on update
        xAxis.visibleRange = new NumberRange(i-1000, i);

        setTimeout(updateDataFunc, 1/60);
    };
    updateDataFunc();
    ...
}
initSciChart();

If we want to enable user-zoom, and also scroll the chart, we need to selectively implement that scroll. To do so we can use the sciChartSurface.zoomState property.

The sciChartSurface.zoomState Property

The sciChartSurface.zoomState property allows us to detect if the chart has been zoomed or panned by the user, or if the chart is at extents of the data. You can take a look at the values of the EZoomState Enum here.

If we modified our code, we can selectively use this property to detect if the user is zooming and halt any automatic scrolling. For example, try modifying the updateDataFunc as follows:

Using ZoomState
Copy Code
...
import {RubberBandXyZoomModifier} from "scichart/Charting/ChartModifiers/RubberBandXyZoomModifier";
import {ZoomExtentsModifier} from "scichart/Charting/ChartModifiers/ZoomExtentsModifier";
import {EZoomState} from "scichart/types/ZoomState";

async function initSciChart() {
         
    // Add this code to enable zooming by mouse-drag and double-click to zoom extents
    //        
    sciChartSurface.chartModifiers.add(new ZoomExtentsModifier({isAnimated: false}));       
    sciChartSurface.chartModifiers.add(new RubberBandXyZoomModifier());
    ...
    const updateDataFunc = () => {
        const i = lineData.count();
        lineData.append(i, Math.sin(i * 0.1));
        scatterData.append(i, Math.cos(i * 0.1));
        // Using zoomState, we only scroll if the state is not userZooming
        if (sciChartSurface.zoomState !== EZoomState.UserZooming) {
            xAxis.visibleRange = new NumberRange(i - 1000, i);
        }
        // Repeat at 60Hz
        setTimeout(updateDataFunc, 1 / 60);
        // Warning, this will repeat forever, it's not best practice!
    };

    updateDataFunc();
    ...
}
initSciChart();

Now run the application again, click left mouse button and move it to select an area. After releasing the button the chart will be zoomed in. To resume realtime updates perform double click.

Adding Panning Behavior to a Realtime Chart

In order to add ZoomPanModifier, update the code as follows. Don't forget to include the same ZoomState logic as we had before.

Example Title
Copy Code
...
import {ZoomPanModifier} from "scichart/Charting/ChartModifiers/ZoomPanModifier";
import {ZoomExtentsModifier} from "scichart/Charting/ChartModifiers/ZoomExtentsModifier";
import {EZoomState} from "scichart/types/ZoomState";
import {NumberRange} from "scichart/Core/NumberRange";
import { EExecuteOn } from "scichart/types/ExecuteOn";

async function initSciChart() {
    ...
    // Add ZoomExtentsModifier and disable extends animation
    sciChartSurface.chartModifiers.add(new ZoomExtentsModifier({isAnimated: false}));
    // Add ZoomPanModifier
    sciChartSurface.chartModifiers.add(new ZoomPanModifier({ executeOn: EExecuteOn.MouseRightButton }));
    ...
    const updateDataFunc = () => {
        ...
        // Prevent changing visibleRange if user is zooming
        if (sciChartSurface.zoomState !== EZoomState.UserZooming) {
            xAxis.visibleRange = new NumberRange(i - 1000, i);
        }
    };
    ...
}
initSciChart();

Now run the application again, left click the chart and move the mouse. As a result the chart will moving with the mouse. To pan the chart, use the right mouse button. To resume realtime updates perform double click.

 

Further Examples - the Realtime Ticking Stock Chart demo

In the SciChart.js Examples Suite - viewable at demo.scichart.com, we have an example of realtime updates with zooming & panning built into the chart. This is the JavaScript Realtime Ticking Stock Charts example.

In this example we use techniques similar to the above to selectively advance the chart by one candle only if the latest data-point is inside the viewport.

This allows you to create an intuitive user zooming, panning experience while advancing the chart or scrolling as new data comes in.

You can view the demo online at https://demo.scichart.com/javascript-realtime-ticking-stock-charts 

And the Source-code for how we achieved it at our Github repository.