React Mountain Chart Draggable Thresholds

Demonstrates interaction by dragging vertical and horizontal line thresholds on a mountain chart. As the thresholds move, the chart colour updates. The vertical mountain fill is done using a separate renderableSeries and a dataFilter which reshapes the data to draw only the portion above the threshold.

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

ExampleDataProvider.ts

theme.ts

Copy to clipboard
Minimise
Fullscreen
1import {
2    BaseDataSeries,
3    ECoordinateMode,
4    EFillPaletteMode,
5    EHorizontalAnchorPoint,
6    ELabelPlacement,
7    EStrokePaletteMode,
8    EVerticalAnchorPoint,
9    FastMountainRenderableSeries,
10    GradientParams,
11    HorizontalLineAnnotation,
12    IFillPaletteProvider,
13    IRenderableSeries,
14    IStrokePaletteProvider,
15    MouseWheelZoomModifier,
16    NumberRange,
17    NumericAxis,
18    parseColorToUIntArgb,
19    Point,
20    SciChartSurface,
21    TextAnnotation,
22    VerticalLineAnnotation,
23    XyDataSeries,
24    XyFilterBase,
25    XyyFilterBase,
26    ZoomExtentsModifier,
27    ZoomPanModifier,
28} from "scichart";
29import { ExampleDataProvider } from "../../../ExampleData/ExampleDataProvider";
30import { appTheme } from "../../../theme";
31
32// tslint:disable:no-empty
33// tslint:disable:max-line-length
34
35class ThresholdFilter extends XyFilterBase {
36    private thresholdProperty = 1;
37
38    constructor(originalSeries: BaseDataSeries, threshold: number, dataSeriesName: string) {
39        super(originalSeries, { dataSeriesName });
40        this.thresholdProperty = threshold;
41        this.filterAll();
42    }
43
44    public get threshold() {
45        return this.thresholdProperty;
46    }
47
48    public set threshold(value: number) {
49        this.thresholdProperty = value;
50        this.filterAll();
51    }
52
53    protected filterAll() {
54        this.clear();
55        this.filter(0, this.getOriginalCount());
56    }
57
58    protected filterOnAppend(count: number): void {
59        // Overriding this so we do not have to reprocess the entire series on append
60        this.filter(this.getOriginalCount() - count, count);
61    }
62
63    protected filter(start: number, count: number): void {
64        const xValues: number[] = [];
65        const yValues: number[] = [];
66        for (let i = start; i < start + count; i++) {
67            xValues.push(this.getOriginalXValues().get(i));
68            const y = this.getOriginalYValues().get(i);
69            if (this.threshold > 0 && y < this.threshold) {
70                yValues.push(NaN);
71            } else if (y < 0) {
72                yValues.push(Math.max(y, this.threshold));
73            } else {
74                yValues.push(y);
75            }
76        }
77        this.appendRange(xValues, yValues);
78    }
79
80    protected onClear() {
81        this.clear();
82    }
83}
84
85export const drawExample = async (rootElement: string | HTMLDivElement) => {
86    const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
87        theme: appTheme.SciChartJsTheme,
88    });
89
90    // Add an XAxis, YAxis
91    sciChartSurface.xAxes.add(new NumericAxis(wasmContext));
92    sciChartSurface.yAxes.add(new NumericAxis(wasmContext, { growBy: new NumberRange(0.1, 0.1) }));
93
94    // Create a paletteprovider to colour the series depending on a threshold value
95    const thresholdPalette = new XThresholdPaletteProvider(8, appTheme.VividTeal);
96
97    // Add a Column series with some values to the chart
98    const { xValues, yValues } = ExampleDataProvider.getDampedSinewave(0, 10, 0, 0.001, 3000, 10);
99    const dataSeries = new XyDataSeries(wasmContext, {
100        xValues,
101        yValues,
102    });
103    sciChartSurface.renderableSeries.add(
104        new FastMountainRenderableSeries(wasmContext, {
105            stroke: appTheme.PaleSkyBlue,
106            strokeThickness: 5,
107            zeroLineY: 0.0,
108            dataSeries,
109            fillLinearGradient: new GradientParams(new Point(0, 0), new Point(0, 1), [
110                { color: appTheme.VividSkyBlue, offset: 0 },
111                { color: appTheme.VividSkyBlue + "77", offset: 1 },
112            ]),
113            paletteProvider: thresholdPalette,
114        })
115    );
116
117    const thresholdFilter = new ThresholdFilter(dataSeries, 4.0, "TopFill");
118    const topFill = new FastMountainRenderableSeries(wasmContext, {
119        stroke: appTheme.PaleSkyBlue,
120        strokeThickness: 5,
121        zeroLineY: 4.0,
122        dataSeries: thresholdFilter,
123        fill: appTheme.MutedOrange,
124        paletteProvider: thresholdPalette,
125    });
126    sciChartSurface.renderableSeries.add(topFill);
127
128    // Add a label to tell user what to do
129    const textAnnotation = new TextAnnotation({
130        verticalAnchorPoint: EVerticalAnchorPoint.Bottom,
131        xCoordinateMode: ECoordinateMode.Relative,
132        x1: 0.5,
133        y1: 4.2,
134        fontSize: 22,
135        text: "Drag the lines!",
136        textColor: "White",
137    });
138    // Add a horizontal threshold at Y=5
139    const horizontalLine = new HorizontalLineAnnotation({
140        y1: 4.0,
141        isEditable: true,
142        showLabel: true,
143        stroke: appTheme.VividOrange,
144        strokeThickness: 3,
145        axisLabelFill: appTheme.VividOrange,
146        axisLabelStroke: appTheme.ForegroundColor,
147        labelPlacement: ELabelPlacement.Axis,
148        onDrag: (args) => {
149            // When the horizontal line is dragged, update the
150            // threshold palette and redraw the SciChartSurface
151            topFill.zeroLineY = Math.max(0, horizontalLine.y1);
152            thresholdFilter.threshold = horizontalLine.y1;
153            textAnnotation.y1 = horizontalLine.y1 + 0.2;
154            sciChartSurface.invalidateElement();
155        },
156    });
157    sciChartSurface.annotations.add(horizontalLine);
158    sciChartSurface.annotations.add(textAnnotation);
159
160    // Add a vertical line
161    const verticalLine = new VerticalLineAnnotation({
162        x1: 8,
163        strokeThickness: 3,
164        isEditable: true,
165        showLabel: true,
166        stroke: appTheme.VividTeal,
167        axisLabelFill: appTheme.VividTeal,
168        axisLabelStroke: appTheme.ForegroundColor,
169        labelPlacement: ELabelPlacement.Axis,
170        onDrag: (args) => {
171            // When the vertical line is dragged, update the
172            // threshold palette and redraw the SciChartSurface
173            thresholdPalette.xThresholdValue = verticalLine.x1;
174            sciChartSurface.invalidateElement();
175        },
176    });
177    sciChartSurface.annotations.add(verticalLine);
178
179    // Add instructions
180    sciChartSurface.annotations.add(
181        new TextAnnotation({
182            x1: 0,
183            y1: 0,
184            xAxisId: "history",
185            xCoordinateMode: ECoordinateMode.Relative,
186            yCoordinateMode: ECoordinateMode.Relative,
187            horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
188            verticalAnchorPoint: EVerticalAnchorPoint.Top,
189            text: "SciChart.js supports editable, draggable annotations and dynamic color/fill rules. Drag a threshold line!",
190            textColor: appTheme.ForegroundColor + "77",
191        })
192    );
193
194    // Optional: Add some interactivity modifiers
195    sciChartSurface.chartModifiers.add(new ZoomPanModifier({ enableZoom: true }));
196    sciChartSurface.chartModifiers.add(new ZoomExtentsModifier());
197    sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier());
198
199    return { sciChartSurface, wasmContext };
200};
201
202/**
203 * A paletteprovider which colours a series if X value over a threshold, else use default colour
204 */
205export class XThresholdPaletteProvider implements IFillPaletteProvider {
206    public readonly fillPaletteMode: EFillPaletteMode = EFillPaletteMode.GRADIENT;
207    public readonly strokePaletteMode: EStrokePaletteMode = EStrokePaletteMode.GRADIENT;
208    public xThresholdValue: number;
209    private readonly xColor: number;
210
211    constructor(xThresholdValue: number, xColor: string) {
212        this.xThresholdValue = xThresholdValue;
213        this.xColor = parseColorToUIntArgb(xColor);
214    }
215
216    onAttached(parentSeries: IRenderableSeries): void {}
217
218    onDetached(): void {}
219
220    overrideFillArgb(xValue: number, yValue: number, index: number, opacity?: number): number {
221        // When the x-value of the series is greater than the x threshold
222        // fill with the xColor
223        if (xValue > this.xThresholdValue) {
224            return this.xColor;
225        }
226        // Undefined means use default color
227        return undefined;
228    }
229}
230

Draggable Horizontal Threshold - React

Overview

This example demonstrates how to integrate SciChart.js with a React application to create an interactive mountain chart with draggable horizontal and vertical thresholds. The chart updates in real time as users drag the annotations and dynamically modifies the underlying data through a custom data filter, showcasing a powerful blend of interactivity and performance.

Technical Implementation

The React integration is achieved using the <SciChartReact/> component with an initChart callback that draws the chart using SciChartSurface. Interactive annotations are implemented with onDrag event handlers to update both the chart visuals and the custom data filtering logic provided by the ThresholdFilter class. This filter, which extends SciChart's XyFilterBase, ensures that only data above a dynamically adjustable threshold is displayed. For more detail on integrating React with SciChart.js, refer to the Creating a SciChart React Component from the Ground Up guide. Additionally, the custom filtering logic follows the practices described in Creating a Custom Filter.

Features and Capabilities

The example offers real-time chart updates through interactive draggable annotations that adjust horizontal and vertical thresholds. A custom palette provider (XThresholdPaletteProvider) dynamically modifies the fill color of chart regions based on the defined thresholds, using techniques explained in The PaletteProvider API. High-performance rendering is ensured through the use of FastMountainRenderableSeries, which optimizes the rendering of large datasets.

Integration and Best Practices

This implementation exemplifies best practices for integrating SciChart.js within a React environment by leveraging core React concepts along with the powerful <SciChartReact/> component. The use of interactive modifiers like ZoomPanModifier, ZoomExtentsModifier, and MouseWheelZoomModifier further enhances the user experience by enabling efficient zooming and panning. Developers can explore advanced React integration and performance optimization techniques in the SciChart ecosystem by consulting the React Charts with SciChart.js: Introducing “SciChart React” article and the Tutorial 03 - Adding Zooming, Panning Behavior documentation.

This example serves as a comprehensive reference for developers looking to create scalable, interactive charting applications in React using SciChart.js.

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.