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

RandomWalkGenerator.ts

angular.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 Chart Event Markers in Angular

Overview

This Angular example demonstrates how to repurpose a candlestick series into draggable, labeled event markers within a SciChart.js chart. The example integrates SciChart.js into an Angular standalone component using the scichart-angular package, providing a seamless way to embed high performance charts into Angular applications.

Technical Implementation

The implementation initializes a SciChartSurface with distinct axes for the main chart and the event markers. It synchronizes the axes by subscribing to axis.visibleRangeChanged, ensuring that both the primary and event axes remain in lock-step. A custom chart modifier, named CandleDragModifier, is implemented using the Custom Chart Modifier API provided by SciChart.js. This modifier enables precise selection via custom hit testing and allows users to drag event markers, updating the underlying data in real-time. The example also demonstrates advanced techniques such as dynamically attaching and detaching annotations, a process further explained in the Tutorial 06 - Adding Annotations documentation.

Features and Capabilities

This example boasts real-time update capabilities and smooth rendering through sweep animations and WebGL enhancements. Custom data labels are generated using a tailored DataLabelProvider that formats text based on computed values from the data series. Advanced features include synchronized axis ranges and custom hit testing logic, which together create an interactive experience where event markers can be selected, dragged, and visually highlighted.

Integration and Best Practices

Developers can leverage the best practices for Angular integration outlined in the Getting Started with SciChart JS guide to deploy similar solutions. The example emphasizes efficient resource management and performance optimization, as detailed in the Performance Tips & Tricks documentation. Furthermore, it showcases advanced interactivity through custom hit testing and modifier interactions, ensuring that the charts remain responsive and user-friendly in complex scenarios.

angular Chart Examples & Demos

See Also: Performance Demos & Showcases (12 Demos)

Realtime Angular Chart Performance Demo | SciChart.js

Realtime Angular Chart Performance Demo

This demo showcases the incredible realtime performance of our Angular 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 Angular 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 | Angular Charts | SciChart.js Demo

Realtime Ghosted Traces

This demo showcases the realtime performance of our Angular 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 Angular audio spectrum analyzer example from SciChart. This real-time audio visualizer demo uses a Fourier Transform.

Oil & Gas Explorer Angular Dashboard | SciChart.js Demo

Oil & Gas Explorer Angular 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 | Angular Charts | SciChart.js Demo

Server Traffic Dashboard

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

Rich Interactions Showcase | Angular Charts | SciChart.js

Rich Interactions Showcase

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

Dynamic Layout Showcase | Angular Charts | SciChart.js Demo

Dynamic Layout Showcase

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

Angular Population Pyramid | Angular Charts | SciChart.js

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