JavaScript Chart Editable Annotations

Demonstrates how to edit Annotations (shapes, boxes, lines, text, horizontal and vertical line) to a JavaScript Chart using SciChart.js, High Performance JavaScript Charts

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

theme.ts

Copy to clipboard
Minimise
Fullscreen
1import { appTheme } from "../../../theme";
2// import SciChartImage from "./scichart-logo-white.png";
3import {
4    SciChartSurface,
5    NumericAxis,
6    NumberRange,
7    ZoomPanModifier,
8    MouseWheelZoomModifier,
9    LineAnnotation,
10    HorizontalLineAnnotation,
11    VerticalLineAnnotation,
12    BoxAnnotation,
13    CustomAnnotation,
14    TextAnnotation,
15    EHorizontalAnchorPoint,
16    EVerticalAnchorPoint,
17    ECoordinateMode,
18    ELabelPlacement,
19    ZoomExtentsModifier,
20    EWrapTo,
21    NativeTextAnnotation,
22    AnnotationHoverEventArgs,
23    AnnotationHoverModifier,
24    AnnotationBase,
25    EHoverMode,
26    translateFromCanvasToSeriesViewRect,
27    DpiHelper,
28    GenericAnimation,
29    easing,
30    Thickness,
31    translateToNotScaled,
32    EAnnotationType,
33} from "scichart";
34
35const getImageAnnotation = (x1: number, y1: number, image: any, width: number, height: number): CustomAnnotation => {
36    return new CustomAnnotation({
37        x1,
38        y1,
39        verticalAnchorPoint: EVerticalAnchorPoint.Top,
40        horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
41        svgString: `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg" style="background-color:transparent">
42                        <image href="${image}" height="${height}" width="${width}"/>
43                    </svg>`,
44    });
45};
46
47export const drawExample = (SciChartImage: string) => async (rootElement: string | HTMLDivElement) => {
48    const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
49        theme: appTheme.SciChartJsTheme,
50    });
51
52    // Create an X,Y axis
53    sciChartSurface.xAxes.add(
54        new NumericAxis(wasmContext, {
55            visibleRange: new NumberRange(0, 10),
56        })
57    );
58    sciChartSurface.yAxes.add(
59        new NumericAxis(wasmContext, {
60            visibleRange: new NumberRange(0, 10),
61        })
62    );
63
64    const textColor = appTheme.ForegroundColor;
65
66    const text1 = new TextAnnotation({ text: "Editable Chart Annotations", fontSize: 24, x1: 0.3, y1: 9.7, textColor });
67    const text2 = new TextAnnotation({
68        text: "Click, Drag and Resize annotations with the mouse",
69        fontSize: 18,
70        x1: 0.5,
71        y1: 9,
72        textColor,
73    });
74
75    const horizontalLineAnnotation1 = new HorizontalLineAnnotation({
76        stroke: appTheme.VividOrange,
77        strokeThickness: 3,
78        y1: 5,
79        x1: 5,
80        showLabel: true,
81        labelPlacement: ELabelPlacement.TopLeft,
82        labelValue: "Not Editable",
83    });
84    const horizontalLineAnnotation2 = new HorizontalLineAnnotation({
85        stroke: appTheme.VividSkyBlue,
86        strokeThickness: 3,
87        y1: 4,
88        showLabel: true,
89        labelPlacement: ELabelPlacement.TopRight,
90        labelValue: "Draggable HorizontalLineAnnotation",
91        axisLabelFill: appTheme.VividSkyBlue,
92        axisLabelStroke: appTheme.ForegroundColor,
93        isEditable: true,
94    });
95
96    const verticalLineAnnotation = new VerticalLineAnnotation({
97        stroke: appTheme.VividSkyBlue,
98        strokeThickness: 3,
99        x1: 9,
100        showLabel: true,
101        labelPlacement: ELabelPlacement.TopRight,
102        labelValue: "Draggable VerticalLineAnnotation",
103        axisLabelFill: appTheme.VividSkyBlue,
104        axisLabelStroke: appTheme.ForegroundColor,
105        isEditable: true,
106    });
107
108    const lineAnnotation = new LineAnnotation({
109        stroke: appTheme.VividOrange,
110        strokeThickness: 3,
111        x1: 5.5,
112        x2: 7.0,
113        y1: 6.0,
114        y2: 9.0,
115        isEditable: true,
116    });
117
118    const boxAnnotation = new BoxAnnotation({
119        stroke: appTheme.VividSkyBlue,
120        strokeThickness: 1,
121        fill: appTheme.VividSkyBlue + "33",
122        x1: 1.0,
123        x2: 4.0,
124        y1: 5.0,
125        y2: 7.0,
126        isEditable: true,
127    });
128
129    const imageAnnotation = getImageAnnotation(7, 7, SciChartImage, 241, 62);
130    imageAnnotation.isEditable = true;
131
132    const textAnnotation = new TextAnnotation({
133        x1: 1,
134        y1: 2,
135        xCoordinateMode: ECoordinateMode.DataValue,
136        yCoordinateMode: ECoordinateMode.DataValue,
137        horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
138        verticalAnchorPoint: EVerticalAnchorPoint.Center,
139        textColor,
140        fontSize: 26,
141        fontFamily: "Arial",
142        text: "Unmovable text",
143        isEditable: false,
144    });
145
146    const hoverableTextAnnotation = new TextAnnotation({
147        x1: 1,
148        y1: 1,
149        xCoordinateMode: ECoordinateMode.DataValue,
150        yCoordinateMode: ECoordinateMode.DataValue,
151        horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
152        verticalAnchorPoint: EVerticalAnchorPoint.Center,
153        textColor,
154        fontSize: 26,
155        fontFamily: "Arial",
156        text: "Hover me to select",
157        isEditable: true,
158        onHover: (args: AnnotationHoverEventArgs) => {
159            const { isHovered, sender } = args;
160            if (isHovered) {
161                // This sets isSelected on the target annotation
162                sender.parentSurface.adornerLayer.selectAnnotation(args.mouseArgs);
163            } else {
164                // this does not actually deselect the annotation on the surface
165                sender.isSelected = false;
166                // so we manually deselect it
167                sender.parentSurface.adornerLayer.deselectAnnotation(sender);
168            }
169        },
170    });
171
172    const textAnnotationSciChart = new TextAnnotation({
173        x1: 1,
174        y1: 3,
175        xCoordinateMode: ECoordinateMode.DataValue,
176        yCoordinateMode: ECoordinateMode.DataValue,
177        horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
178        verticalAnchorPoint: EVerticalAnchorPoint.Center,
179        textColor,
180        fontSize: 26,
181        fontFamily: "Arial",
182        text: "Moveable TextAnnotation",
183        isEditable: true,
184    });
185
186    const nativetextWrap = new NativeTextAnnotation({
187        x1: 5,
188        x2: 9,
189        y1: 3,
190        xCoordinateMode: ECoordinateMode.DataValue,
191        yCoordinateMode: ECoordinateMode.DataValue,
192        horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
193        verticalAnchorPoint: EVerticalAnchorPoint.Center,
194        textColor: appTheme.PalePurple,
195        fontSize: 24,
196        fontFamily: "Arial",
197        text: "Native Text Annotations support wordwrap.  Resize me!",
198        isEditable: true,
199        wrapTo: EWrapTo.Annotation,
200    });
201
202    const nativetextScale = new NativeTextAnnotation({
203        x1: 5,
204        x2: 9,
205        y1: 2,
206        xCoordinateMode: ECoordinateMode.DataValue,
207        yCoordinateMode: ECoordinateMode.DataValue,
208        horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
209        verticalAnchorPoint: EVerticalAnchorPoint.Center,
210        textColor: appTheme.PalePurple,
211        fontSize: 24,
212        fontFamily: "Arial",
213        text: "Native Text Annotations can scale on resize.",
214        isEditable: true,
215        scaleOnResize: true,
216    });
217
218    const tooltipPreviewAnnotation = new TextAnnotation({
219        x1: 1,
220        y1: 8,
221        xCoordinateMode: ECoordinateMode.DataValue,
222        yCoordinateMode: ECoordinateMode.DataValue,
223        textColor: appTheme.ForegroundColor,
224        fontSize: 16,
225        text: `Move mouse over an annotation to get a tooltip with its type.<tspan x="4" dy="1.2em">The tooltip itself is also an annotation.</tspan>`,
226        padding: Thickness.fromNumber(4),
227        background: "black",
228    });
229
230    const tooltipAnnotation = new TextAnnotation({
231        x1: 0,
232        y1: 0,
233        xCoordShift: 20,
234        yCoordShift: 20,
235        xCoordinateMode: ECoordinateMode.Pixel,
236        yCoordinateMode: ECoordinateMode.Pixel,
237        horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
238        verticalAnchorPoint: EVerticalAnchorPoint.Top,
239        textColor: appTheme.ForegroundColor,
240        fontSize: 16,
241        text: "",
242        padding: Thickness.fromNumber(4),
243        background: "black",
244        isHidden: true,
245    });
246
247    sciChartSurface.annotations.add(
248        text1,
249        text2,
250        horizontalLineAnnotation1,
251        horizontalLineAnnotation2,
252        verticalLineAnnotation,
253        lineAnnotation,
254        boxAnnotation,
255        imageAnnotation,
256        textAnnotation,
257        textAnnotationSciChart,
258        nativetextWrap,
259        nativetextScale,
260        hoverableTextAnnotation,
261        // customAnnotation,
262        tooltipPreviewAnnotation,
263        tooltipAnnotation
264    );
265
266    sciChartSurface.chartModifiers.add(new ZoomPanModifier({ enableZoom: true }));
267    sciChartSurface.chartModifiers.add(new ZoomExtentsModifier());
268    sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier());
269
270    let currentTooltipAnimation: GenericAnimation<number>;
271    const animateTooltip = () => {
272        currentTooltipAnimation?.cancel();
273        tooltipAnnotation.isHidden = true;
274        currentTooltipAnimation = new GenericAnimation<number>({
275            from: 0,
276            to: 1,
277            duration: 0,
278            delay: 500,
279            ease: easing.linear,
280            onAnimate: (from: number, to: number, progress) => {},
281            onCompleted: () => {
282                tooltipAnnotation.isHidden = false;
283            },
284        });
285        sciChartSurface.addAnimation(currentTooltipAnimation);
286    };
287
288    const annotationHoverModifier = new AnnotationHoverModifier({
289        // check hover on all annotations except the one used for tooltip
290        targets: (modifier) =>
291            modifier.parentSurface.annotations.asArray().filter((annotation) => annotation !== tooltipAnnotation),
292        // ignore tooltip annotation if it is overlapping with other
293        hoverMode: EHoverMode.TopmostIncluded,
294        // needed to update tooltip position when moving the cursor within an annotation
295        notifyPositionUpdate: true,
296        // manage tooltip visibility and position
297        onHover: (args) => {
298            const [hoveredAnnotation] = args.hoveredEntities as AnnotationBase[];
299            if (hoveredAnnotation) {
300                if (hoveredAnnotation.isEditable) {
301                    sciChartSurface.domChartRoot.style.cursor = "grab";
302                }
303                if (hoveredAnnotation.isDraggingStarted) {
304                    tooltipAnnotation.isHidden = true;
305                    return;
306                }
307
308                const borders = tooltipAnnotation.getAnnotationBorders(true);
309                tooltipAnnotation.text = mapAnnotationTypeToName(hoveredAnnotation.type);
310
311                const handleAnnotationsOutsideSeriesViewRect = true;
312                const translatedMousePoint = translateFromCanvasToSeriesViewRect(
313                    args.mouseArgs.mousePoint,
314                    sciChartSurface.seriesViewRect,
315                    handleAnnotationsOutsideSeriesViewRect
316                );
317                tooltipAnnotation.x1 = translateToNotScaled(translatedMousePoint.x);
318                tooltipAnnotation.y1 = translateToNotScaled(translatedMousePoint.y);
319
320                // initial default offset from pointer
321                tooltipAnnotation.xCoordShift = 20;
322                const width = Math.abs(borders.x2 - borders.x1);
323                const expectedX2Coordinate = tooltipAnnotation.x1 + tooltipAnnotation.xCoordShift + width;
324                const unscaledViewWidth = translateToNotScaled(sciChartSurface.seriesViewRect.width);
325                if (expectedX2Coordinate > unscaledViewWidth) {
326                    tooltipAnnotation.xCoordShift = unscaledViewWidth - width - tooltipAnnotation.x1;
327                }
328
329                animateTooltip();
330            } else {
331                sciChartSurface.domChartRoot.style.cursor = "auto";
332                tooltipAnnotation.isHidden = true;
333                currentTooltipAnimation?.cancel();
334            }
335        },
336    });
337
338    sciChartSurface.chartModifiers.add(annotationHoverModifier);
339
340    return { sciChartSurface, wasmContext };
341};
342
343const mapAnnotationTypeToName = (type: EAnnotationType): string => {
344    switch (type) {
345        case EAnnotationType.RenderContextAxisMarkerAnnotation:
346            return "AxisMarkerAnnotation";
347        case EAnnotationType.RenderContextBoxAnnotation:
348            return "BoxAnnotation";
349        case EAnnotationType.RenderContextLineAnnotation:
350            return "LineAnnotation";
351        case EAnnotationType.RenderContextHorizontalLineAnnotation:
352            return "HorizontalLineAnnotation";
353        case EAnnotationType.RenderContextVerticalLineAnnotation:
354            return "VerticalLineAnnotation";
355        case EAnnotationType.SVGTextAnnotation:
356            return "TextAnnotation";
357        case EAnnotationType.RenderContextNativeTextAnnotation:
358            return "NativeTextAnnotation";
359        case EAnnotationType.SVGCustomAnnotation:
360            return "CustomAnnotation";
361        case EAnnotationType.SVG:
362            return "SvgAnnotation";
363        case EAnnotationType.SvgLineAnnotation:
364            return "SvgLineAnnotation";
365        case EAnnotationType.HtmlCustomAnnotation:
366            return "HtmlCustomAnnotation";
367        case EAnnotationType.HtmlTextAnnotation:
368            return "HtmlAnnotation";
369        case EAnnotationType.RenderContextArcAnnotation:
370            return "ArcAnnotation";
371        case EAnnotationType.SVGPolarPointerAnnotation:
372            return "PolarPointerAnnotation";
373        case EAnnotationType.RenderContextLineArrowAnnotation:
374            return "LineArrowAnnotation";
375        case EAnnotationType.RenderContextPolarArcAnnotation:
376            return "PolarArcAnnotation";
377        // @ts-ignore
378        case EAnnotationType.RenderContextCustomAnnotation:
379            return "RenderContextCustomAnnotation";
380        default: {
381            const handleInvalidType = (value: never): never => {
382                throw new Error(`Invalid annotation type: ${value}`);
383            };
384            return handleInvalidType(type);
385        }
386    }
387};
388

Editable Annotations - JavaScript

Overview

This example demonstrates how to create an interactive 2D chart using SciChart.js with a focus on editable annotations such as text, lines, boxes, and custom SVG image annotations. The implementation is built using JavaScript and leverages the high-performance capabilities of SciChart.js with a WebAssembly context.

Technical Implementation

The chart is initialized using the SciChartSurface API with a WebAssembly context, as outlined in the SciChart.js Web Tutorial. Numeric X and Y axes are created and configured using the NumericAxis class. Various annotation types are then programmatically added to the chart by directly instantiating classes such as TextAnnotation, HorizontalLineAnnotation, VerticalLineAnnotation, LineAnnotation, BoxAnnotation, and even a CustomAnnotation created via an SVG string. Each annotation’s editability is controlled through the isEditable property, allowing users to click, drag, and resize annotations in real time. Custom behavior is introduced using event handlers like onHover within the annotations and the AnnotationHoverModifier for interactive tooltip feedback, as described in the Editable Annotations and Annotation Hover. Coordinate transformation functions such as translateFromCanvasToSeriesViewRect() and translateToNotScaled() are used to accurately position tooltips relative to the chart.

Features and Capabilities

The example provides real-time update capabilities where annotations can be immediately modified by the user. Advanced features include:

  • Editable Annotations: Annotations can be interactively moved and resized with simple mouse operations.
  • Custom SVG Annotations: A custom annotation is created using an SVG overlay, showcasing the flexibility of the CustomAnnotation API.
  • Chart Modifiers: ZoomPanModifier, MouseWheelZoomModifier, and ZoomExtentsModifier are used to enable smooth zooming and panning functionalities.
  • Native Text Annotation Enhancements: Editable NativeTextAnnotations support word-wrap and scale on resize, enhancing responsiveness as described in the NativeTextAnnotation Documentation.
  • Animated Tooltips: Tooltips are animated using the GenericAnimation class combined with easing functions, which helps provide dynamic feedback to the user. Detailed animation capabilities can be explored in the Generic Animations Documentation.

Integration and Best Practices

Although implemented in JavaScript, the example demonstrates a modular and maintainable architecture. Chart initialization, axis configuration, annotation creation, and modifier registration are clearly separated for clarity and performance. The use of a WebAssembly context greatly enhances rendering performance, as noted in the WebAssembly Performance resources. Developers following this approach can achieve a high level of interactivity and responsiveness in their applications by leveraging these best practices and detailed documentation links provided for each key technical concept.

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