Dragabble Event Markers

Demonstrates how FastRectangleRenderableSeries and BoxAnnotation to make dragabble, labled, event markers, using SciChart.js High Performance JavaScript Charts

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

RandomWalkGenerator.ts

theme.ts

Copy to clipboard
Minimise
Fullscreen
1import {
2    AUTO_COLOR,
3    BoxAnnotation,
4    CustomChartModifier2D,
5    deleteSafe,
6    EAxisAlignment,
7    EColumnMode,
8    EColumnYMode,
9    ECoordinateMode,
10    EDraggingGripPoint,
11    EHorizontalAnchorPoint,
12    ENumericFormat,
13    EValueName,
14    EVerticalAnchorPoint,
15    EXyDirection,
16    FastLineRenderableSeries,
17    FastRectangleRenderableSeries,
18    formatNumber,
19    IChartModifierBaseOptions,
20    ModifierMouseArgs,
21    MouseWheelZoomModifier,
22    NumberRange,
23    NumericAxis,
24    RectangleDataLabelState,
25    RectangleSeriesDataLabelProvider,
26    SciChartSurface,
27    SweepAnimation,
28    TextAnnotation,
29    XyDataSeries,
30    XyxDataSeries,
31    ZoomExtentsModifier,
32    ZoomPanModifier,
33} from "scichart";
34import { RandomWalkGenerator } from "../../../ExampleData/RandomWalkGenerator";
35import { appTheme } from "../../../theme";
36
37const defaultHeight = 1;
38class RectangleDragModifier extends CustomChartModifier2D {
39    private series: FastRectangleRenderableSeries;
40    private dataSeries: XyxDataSeries;
41    private annotation: BoxAnnotation;
42    private selectedIndex: number = -1;
43
44    public constructor(series: FastRectangleRenderableSeries, options?: IChartModifierBaseOptions) {
45        super(options);
46        this.series = series;
47        this.dataSeries = series.dataSeries as XyxDataSeries;
48    }
49
50    public override onAttach(): void {
51        super.onAttach();
52        // Create an annotation where only the selection box will be visible
53        this.annotation = new BoxAnnotation({
54            xAxisId: this.series.xAxisId,
55            yAxisId: this.series.yAxisId,
56            strokeThickness: 0,
57            stroke: "transparent",
58            selectionBoxStroke: "#88888844",
59            opacity: 0,
60            isEditable: true,
61            resizeDirections: EXyDirection.XDirection,
62            // dragPoints: [
63            //     EDraggingGripPoint.Body,
64            //     EDraggingGripPoint.x1y1,
65            //     EDraggingGripPoint.x2y1,
66            //     EDraggingGripPoint.x1y2,
67            //     EDraggingGripPoint.x2y2,
68            // ],
69        });
70
71        // Update the selected data point when the annotation is dragged
72        this.annotation.dragDelta.subscribe((data) => {
73            if (this.selectedIndex >= 0) {
74                const newX = this.annotation.x1;
75                const newY = Math.floor(this.annotation.y1 + defaultHeight);
76                const newx1 = this.annotation.x2;
77
78                // Do not allow close to be less than open as this breaks our custom hitTest
79                this.dataSeries.updateXyz(
80                    this.selectedIndex,
81                    newX,
82                    newY,
83                    newx1,
84                    this.dataSeries.getMetadataAt(this.selectedIndex)
85                );
86            }
87        });
88
89        // Manually set the selected status of the point using metadata.  This will drive the DataPointSelectionPaletteProvider
90        this.annotation.selectedChanged.subscribe((data) => {
91            this.dataSeries.getMetadataAt(this.selectedIndex).isSelected = data;
92        });
93
94        this.parentSurface.modifierAnnotations.add(this.annotation);
95    }
96
97    public override onDetach(): void {
98        this.parentSurface.modifierAnnotations.remove(this.annotation);
99        this.annotation = deleteSafe(this.annotation);
100    }
101
102    public override modifierMouseUp(args: ModifierMouseArgs): void {
103        const point = args.mousePoint;
104        const hitTestInfo = this.series.hitTestProvider.hitTest(point.x, point.y, 5);
105
106        if (hitTestInfo.isHit) {
107            if (this.selectedIndex >= 0 && this.selectedIndex !== hitTestInfo.dataSeriesIndex) {
108                this.dataSeries.getMetadataAt(this.selectedIndex).isSelected = false;
109            }
110
111            const x1Values = this.dataSeries.getYValuesByName(EValueName.X1);
112
113            const x1Value = x1Values.get(hitTestInfo.dataSeriesIndex);
114
115            // Place the annotation over the selected box
116            this.selectedIndex = hitTestInfo.dataSeriesIndex;
117            this.annotation.x1 = hitTestInfo.xValue;
118            this.annotation.x2 = x1Value;
119            this.annotation.y1 = Math.round(hitTestInfo.yValue) - defaultHeight / 2;
120            this.annotation.y2 = Math.round(hitTestInfo.yValue) + defaultHeight / 2;
121            // Make the annotation selected.  Both these lines are required.
122            this.annotation.isSelected = true;
123            this.dataSeries.getMetadataAt(this.selectedIndex).isSelected = true;
124            this.parentSurface.adornerLayer.selectedAnnotation = this.annotation;
125        }
126    }
127}
128
129export const drawExample = async (rootElement: string | HTMLDivElement) => {
130    // Create a SciChartSurface
131    const { wasmContext, sciChartSurface } = await SciChartSurface.create(rootElement, {
132        theme: appTheme.SciChartJsTheme,
133    });
134
135    const POINTS = 1000;
136
137    // Shared X axis
138    const xAxis = new NumericAxis(wasmContext, { visibleRange: new NumberRange(0, POINTS) });
139    sciChartSurface.xAxes.add(xAxis);
140
141    // Separate Y axes for the data and events
142    const yAxis = new NumericAxis(wasmContext, {
143        axisAlignment: EAxisAlignment.Left,
144        growBy: new NumberRange(0.05, 0.1),
145    });
146    const yEventAxis = new NumericAxis(wasmContext, {
147        isVisible: false,
148        visibleRange: new NumberRange(0, 12),
149    });
150
151    sciChartSurface.yAxes.add(yAxis, yEventAxis);
152
153    // Create arrays of x, y values (just arrays of numbers)
154    const { xValues, yValues } = new RandomWalkGenerator().getRandomWalkSeries(POINTS);
155
156    // Create a line Series and add to the chart
157    sciChartSurface.renderableSeries.add(
158        new FastLineRenderableSeries(wasmContext, {
159            dataSeries: new XyDataSeries(wasmContext, { xValues, yValues }),
160            stroke: AUTO_COLOR,
161            strokeThickness: 3,
162            animation: new SweepAnimation({ duration: 500, fadeEffect: true }),
163        })
164    );
165
166    // Our event bars will have variable width, but fixed height, so we use Xyx data
167    const eventDataSeries = new XyxDataSeries(wasmContext);
168
169    // Create event data.  Prevent overlap of events
170    const rows = new Map<number, number>();
171    const EVENTCOUNT = 30;
172    let start = 0;
173    for (let i = 0; i < EVENTCOUNT; i++) {
174        start = start + Math.random() * ((2 * POINTS) / EVENTCOUNT);
175        const end = start + 1 + Math.random() * ((2 * POINTS) / EVENTCOUNT);
176        let row = 10;
177        if (i === 0) {
178            rows.set(row, end);
179        } else {
180            let last = rows.get(row);
181            while (last > start) {
182                row -= defaultHeight;
183                last = rows.get(row) ?? 0;
184            }
185            rows.set(row, end);
186        }
187
188        eventDataSeries.append(start, row, end, { isSelected: false });
189    }
190
191    // Create a custom DataLabelProvider which uses the width of the bar as the value to display
192    class BarWidthDataLabelProvider extends RectangleSeriesDataLabelProvider {
193        public getText(state: RectangleDataLabelState): string {
194            const usefinal = !this.updateTextInAnimation && state.parentSeries.isRunningAnimation;
195            const yval = usefinal ? state.yValAfterAnimation() : state.yVal();
196            if (isNaN(yval)) {
197                return undefined;
198            } else {
199                const diff = Math.abs(state.x1Val() - state.xVal());
200                if (this.engineeringPrefix) {
201                    return formatNumber(diff, this.numericFormat, this.precision, this.engineeringPrefixProperty);
202                } else {
203                    return formatNumber(diff, this.numericFormat ?? ENumericFormat.Decimal, this.precision);
204                }
205            }
206        }
207    }
208
209    const eventSeries = new FastRectangleRenderableSeries(wasmContext, {
210        dataSeries: eventDataSeries,
211        yAxisId: yEventAxis.id,
212        columnXMode: EColumnMode.StartEnd,
213        columnYMode: EColumnYMode.CenterHeight,
214        defaultY1: defaultHeight,
215        stroke: "ff0000cc",
216        strokeThickness: 1,
217        fill: "ff00004D",
218        // opacity: 0.8,
219        dataLabelProvider: new BarWidthDataLabelProvider({
220            style: {
221                fontSize: 12,
222            },
223            color: "white",
224        }),
225    });
226
227    sciChartSurface.renderableSeries.add(eventSeries);
228
229    // Configure modifiers to only affect the data Y axis
230    sciChartSurface.chartModifiers.add(
231        new ZoomExtentsModifier({ includedYAxisIds: [yAxis.id] }),
232        new MouseWheelZoomModifier({ includedYAxisIds: [yAxis.id] }),
233        new ZoomPanModifier({ includedYAxisIds: [yAxis.id] }),
234        new RectangleDragModifier(eventSeries)
235    );
236
237    // Add instructions
238    sciChartSurface.annotations.add(
239        new TextAnnotation({
240            x1: 0.01,
241            y1: 0.03,
242            xCoordinateMode: ECoordinateMode.Relative,
243            yCoordinateMode: ECoordinateMode.Relative,
244            horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
245            verticalAnchorPoint: EVerticalAnchorPoint.Top,
246            text: "The boxes are rendered with a fast rectangle series and can be selected and dragged like an annotation.",
247            textColor: appTheme.ForegroundColor + "77",
248        })
249    );
250
251    xAxis.visibleRange = xAxis.getMaximumRange();
252
253    return { wasmContext, sciChartSurface };
254};
255

Dragabble Event Markers (React)

Overview

This React example demonstrates how to transform a FastCandlestickRenderableSeries into draggable event markers within a SciChart.js chart. The purpose is to showcase interactive editing where users can select and drag individual event markers, making use of custom annotations and hit testing for a seamless experience.

Technical Implementation

The chart is initialized using the <SciChartReact/> component, which calls a custom draw function to set up axes, series, and modifiers. A custom chart modifier (CandleDragModifier) enables drag and drop interactions by listening to mouse events and updating the underlying data series. Custom hit testing is implemented to accurately detect user interactions with event markers. For additional technical context on creating similar custom interactions, refer to the Custom ChartModifiers documentation guide.

Features and Capabilities

The example offers real-time update capabilities and advanced features such as custom data labels, where the DataLabelProvider dynamically computes label text (for example, displaying the difference between open and close values) as shown in the Custom DataLabel Formatting with getText() - SciChart documentation. Smooth transitions are achieved using sweep animations, and the use of WebGL enhances performance when rendering large data sets.

Integration and Best Practices

Integration with React is streamlined using the <SciChartReact/> component, which simplifies chart initialization in a React environment as detailed in React Charts with SciChart.js: Introducing “SciChart React”. The example also demonstrates how to synchronize multiple axes, including hidden ones for event markers, ensuring consistent zooming and panning. Custom hit testing further refines the user experience, enabling precise manipulation of chart elements as explained in the Hit-Test API demo resource.

react Chart Examples & Demos

See Also: Performance Demos & Showcases (12 Demos)

Realtime React Chart Performance Demo | SciChart.js Demo

Realtime React Chart Performance Demo

This demo showcases the incredible realtime performance of our React charts by updating the series with millions of data-points!

Load 500 Series x 500 Points Performance Demo | SciChart

Load 500 Series x 500 Points Performance Demo

This demo showcases the incredible performance of our React Chart by loading 500 series with 500 points (250k points) instantly!

Load 1 Million Points Performance Demo | SciChart.js Demo

Load 1 Million Points Performance Demo

This demo showcases the incredible performance of our JavaScript Chart by loading a million points instantly.

Realtime Ghosted Traces | React Charts | SciChart.js Demo

Realtime Ghosted Traces

This demo showcases the realtime performance of our React Chart by animating several series with thousands of data-points at 60 FPS

Realtime Audio Spectrum Analyzer Chart | SciChart.js Demo

Realtime Audio Spectrum Analyzer Chart Example

See the frequency of recordings with the React audio spectrum analyzer example from SciChart. This real-time audio visualizer demo uses a Fourier Transform.

Oil & Gas Explorer React Dashboard | SciChart.js Demo

Oil & Gas Explorer React Dashboard

Demonstrates how to create Oil and Gas Dashboard

Client/Server Websocket Data Streaming | SciChart.js Demo

Client/Server Websocket Data Streaming

This demo showcases the incredible realtime performance of our JavaScript charts by updating the series with millions of data-points!

Server Traffic Dashboard | React Charts | SciChart.js Demo

Server Traffic Dashboard

This dashboard demo showcases the incredible realtime performance of our React charts by updating the series with millions of data-points!

Rich Interactions Showcase | React Charts | SciChart.js

Rich Interactions Showcase

This demo showcases the incredible realtime performance of our React charts by updating the series with millions of data-points!

Dynamic Layout Showcase | React Charts | SciChart.js Demo

Dynamic Layout Showcase

Demonstrates a custom modifier which can convert from single chart to grid layout and back.

React Population Pyramid | React Charts | SciChart.js Demo

React Population Pyramid

Population Pyramid of Europe and Africa

NEW!
High Performance SVG Cursor & Rollover | SciChart.js Demo

High Performance SVG Cursor & Rollover

Demonstrates how to use the SVG render layer in SciChart.js to maintain smooth cursor interaction on heavy charts with millions of points.

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