JavaScript Custom Paired Dashed Polyline Annotation

Extend PolyLineAnnotation with custom paired dash-line rendering.

Place polyline with N points:

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

tradingAnnotationExampleUtils.ts

Copy to clipboard
Minimise
Fullscreen
1import {
2    CoordinateCalculatorBase,
3    deleteSafe,
4    EAnnotationClippingMode,
5    EBaseType,
6    ETextAlignment,
7    EVerticalTextPosition,
8    IRenderContext2D,
9    Rect,
10    registerType,
11    SciChartSurface,
12    Thickness,
13} from "scichart";
14import {
15    MultiPointAnnotationPlacementModifier,
16    EAnnotationVisibilityMode,
17    EMultiPointLabelAnchorMode,
18    EAxisLabelDrawMode,
19    ESegmentLabelRotationMode,
20    PolyLineAnnotation,
21    IPolyLineAnnotationOptions,
22    IMultiPointLabelStyleFormatParams
23} from "scichart-financial-tools";
24import {
25    addDefaultFinancialModifiers,
26    createFinancialChart,
27    createTradingAnnotationOptions,
28} from "../_shared/tradingAnnotationExampleUtils";
29
30const PAIRED_DASHED_POLYLINE = "PairedDashedPolylineAnnotation";
31
32const PAIRED_POLYLINE_COLORS = {
33    primaryStroke: "#4F8EF7",
34    primaryFill: "#4F8EF724",
35    primaryConnector: "#A7C7FF",
36
37    secondaryStroke: "#B77AF7",
38    secondaryFill: "#B77AF720",
39    secondaryConnector: "#F8FAFC",
40
41    placementStroke: "#16A34A",
42    placementFill: "#16A34A24",
43    placementConnector: "#86EFAC",
44};
45
46const DEFAULT_PAIR_STROKE_DASH_ARRAY = [6, 4];
47
48const normalizePlacementPointCount = (pointCount = 5) => {
49    const safeNumber = Number.isFinite(pointCount) ? Math.floor(pointCount) : 5;
50
51    return Math.max(3, Math.min(9, safeNumber));
52};
53
54interface IPairedDashedPolylineAnnotationOptions extends IPolyLineAnnotationOptions {
55    pairStroke?: string;
56    pairStrokeThickness?: number;
57    pairStrokeDashArray?: number[];
58    showPairConnectors?: boolean;
59    placementPointCount?: number;
60}
61
62// @ts-ignore - placementPointCountProperty is private in `PolyLineAnnotation`
63class PairedDashedPolylineAnnotation extends PolyLineAnnotation {
64    public readonly type = PAIRED_DASHED_POLYLINE as any;
65
66    private pairStrokeProperty = PAIRED_POLYLINE_COLORS.primaryConnector;
67    private pairStrokeThicknessProperty = 1.5;
68    private pairStrokeDashArrayProperty = DEFAULT_PAIR_STROKE_DASH_ARRAY;
69    private showPairConnectorsProperty = true;
70    // @ts-ignore - placementPointCountProperty is private in `PolyLineAnnotation`
71    private placementPointCountProperty = 5;
72    private pairStrokePenCache: any;
73
74    constructor(options?: IPairedDashedPolylineAnnotationOptions) {
75        super(options);
76
77        this.pairStrokeProperty = options?.pairStroke ?? this.pairStrokeProperty;
78        this.pairStrokeThicknessProperty = options?.pairStrokeThickness ?? this.pairStrokeThicknessProperty;
79        this.pairStrokeDashArrayProperty = options?.pairStrokeDashArray ?? this.pairStrokeDashArrayProperty;
80        this.showPairConnectorsProperty = options?.showPairConnectors ?? this.showPairConnectorsProperty;
81        this.placementPointCountProperty = normalizePlacementPointCount(
82            options?.placementPointCount ?? this.placementPointCountProperty
83        );
84    }
85
86    public get showPairConnectors(): boolean {
87        return this.showPairConnectorsProperty;
88    }
89
90    public set showPairConnectors(value: boolean) {
91        this.showPairConnectorsProperty = !!value;
92        this.notifyPropertyChanged("showPairConnectors");
93    }
94
95    public override onAttach(scs: SciChartSurface): void {
96        super.onAttach(scs);
97
98        this.pairStrokePenCache = this.updatePenCache(
99            this.pairStrokePenCache,
100            this.pairStrokeProperty,
101            this.pairStrokeThicknessProperty,
102            this.opacity,
103            this.pairStrokeDashArrayProperty
104        );
105    }
106
107    public override delete(): void {
108        this.pairStrokePenCache = deleteSafe(this.pairStrokePenCache);
109        super.delete();
110    }
111
112    public override drawWithContext(
113        renderContext: IRenderContext2D,
114        xCalc: CoordinateCalculatorBase,
115        yCalc: CoordinateCalculatorBase,
116        seriesViewRect: Rect,
117        surfaceViewRect: Rect,
118        chartViewRect: Rect
119    ): void {
120        super.drawWithContext(renderContext, xCalc, yCalc, seriesViewRect, surfaceViewRect, chartViewRect);
121
122        if (!this.showPairConnectorsProperty || this.points.length < 4) {
123            return;
124        }
125
126        const pixelPoints = this.getPixelPoints(xCalc, yCalc);
127        const clipRect = this.getClippingRect(this.clipping, seriesViewRect, surfaceViewRect, chartViewRect);
128        const pairPen = this.resolvePenFromCache(
129            this.pairStrokePenCache,
130            this.pairStrokeThicknessProperty,
131            true
132        );
133
134        if (!pairPen) return;
135
136        this.getConnectorPairs().forEach(([startIndex, endIndex]) => {
137            const start = pixelPoints[startIndex];
138            const end = pixelPoints[endIndex];
139
140            if (!start || !end) return;
141
142            renderContext.drawLines([start.x, start.y, end.x, end.y], pairPen, seriesViewRect, clipRect);
143        });
144    }
145
146    private getConnectorPairs(): Array<readonly [number, number]> {
147        const lastPointIndex = this.points.length - 1;
148        const pairs: Array<readonly [number, number]> = [];
149
150        for (let startIndex = 1; startIndex + 2 <= lastPointIndex; startIndex += 2) {
151            pairs.push([startIndex, startIndex + 2]);
152        }
153
154        if (lastPointIndex > 0) {
155            pairs.push([0, lastPointIndex]);
156        }
157
158        return pairs;
159    }
160
161    public override toJSON() {
162        const json = super.toJSON();
163
164        Object.assign(json.options, {
165            pairStroke: this.pairStrokeProperty,
166            pairStrokeThickness: this.pairStrokeThicknessProperty,
167            pairStrokeDashArray: this.pairStrokeDashArrayProperty,
168            showPairConnectors: this.showPairConnectorsProperty,
169            placementPointCount: this.placementPointCountProperty,
170        });
171
172        return json;
173    }
174
175    protected override getPlacementPointCountInternal(): number {
176        return this.placementPointCountProperty;
177    }
178
179    protected override notifyPropertyChanged(propertyName: string): void {
180        super.notifyPropertyChanged(propertyName);
181
182        if (["pairStroke", "pairStrokeThickness", "pairStrokeDashArray", "opacity"].includes(propertyName)) {
183            this.pairStrokePenCache = this.updatePenCache(
184                this.pairStrokePenCache,
185                this.pairStrokeProperty,
186                this.pairStrokeThicknessProperty,
187                this.opacity,
188                this.pairStrokeDashArrayProperty
189            );
190        }
191    }
192}
193
194registerType(
195    EBaseType.Annotation,
196    PAIRED_DASHED_POLYLINE,
197    (options?: IPairedDashedPolylineAnnotationOptions) => new PairedDashedPolylineAnnotation(options),
198    true
199);
200
201const createConnectorLabels = (pointCount: number, prefix: string, drawPointLabels: boolean = true) => {
202    const lastPointIndex = pointCount - 1;
203    const labels: any[] = [];
204
205    for (let startIndex = 1; startIndex + 2 <= lastPointIndex; startIndex += 2) {
206        labels.push({
207            id: `${prefix}-peak-${startIndex + 1}-${startIndex + 3}`,
208            anchorMode: EMultiPointLabelAnchorMode.Segment,
209            segmentStartIndex: startIndex,
210            segmentEndIndex: startIndex + 2,
211            segmentRatio: 0.5,
212            segmentLabelRotationMode: ESegmentLabelRotationMode.Parallel,
213            verticalTextPosition: EVerticalTextPosition.Above,
214            alignment: ETextAlignment.Center,
215            text: `Peak ${startIndex + 1} -> ${startIndex + 3}`,
216            padding: new Thickness(2, 8, 2, 8),
217        });
218    }
219
220    labels.push({
221        id: `${prefix}-odd-1-${pointCount}`,
222        anchorMode: EMultiPointLabelAnchorMode.Segment,
223        segmentStartIndex: 0,
224        segmentEndIndex: lastPointIndex,
225        segmentRatio: 0.5,
226        segmentLabelRotationMode: ESegmentLabelRotationMode.Parallel,
227        verticalTextPosition: EVerticalTextPosition.Below,
228        alignment: ETextAlignment.Center,
229        text: `Odd 1 -> ${pointCount}`,
230        padding: new Thickness(2, 8, 2, 8),
231    });
232
233    labels.push(
234        {
235            id: `${prefix}-axis-1`,
236            anchorMode: EMultiPointLabelAnchorMode.Axis,
237            axisLabelDrawMode: EAxisLabelDrawMode.Both,
238            pointIndex: 0,
239        },
240        {
241            id: `${prefix}-axis-${pointCount}`,
242            anchorMode: EMultiPointLabelAnchorMode.Axis,
243            axisLabelDrawMode: EAxisLabelDrawMode.Both,
244            pointIndex: lastPointIndex,
245        }
246    );
247
248    // also add point labels:
249    if (drawPointLabels) {
250        for (let i = 0; i <= lastPointIndex; i++) {
251            labels.push({
252                id: `${prefix}-point-${i}`,
253                anchorMode: EMultiPointLabelAnchorMode.Point,
254                pointIndex: i,
255                verticalTextPosition: i % 2 === 0 ? EVerticalTextPosition.Below : EVerticalTextPosition.Above,
256                alignment: ETextAlignment.Center,
257                yOffset: i % 2 === 0 ? 5 : -5, // add more spacing between labels and grips
258                text: `(${i + 1})`,
259            });
260        }
261    }
262
263    return labels;
264};
265
266export const drawExample = async (rootElement: string | HTMLDivElement) => {
267    const { sciChartSurface, xAt, yAt, candlestickSeries } = await createFinancialChart(rootElement, {
268        volatility: 0.0022,
269        title: "BTC / USDT - Custom Paired Polyline",
270    });
271
272    const placementModifier = new MultiPointAnnotationPlacementModifier();
273
274    addDefaultFinancialModifiers(sciChartSurface);
275    sciChartSurface.chartModifiers.add(placementModifier);
276
277    const createPrimaryDemoAnnotation = () =>
278        new PairedDashedPolylineAnnotation({
279            ...createTradingAnnotationOptions("PAIR", 5),
280            isEditable: true,
281            stroke: PAIRED_POLYLINE_COLORS.primaryStroke,
282            strokeThickness: 2,
283            fill: PAIRED_POLYLINE_COLORS.primaryFill,
284            pairStroke: PAIRED_POLYLINE_COLORS.primaryConnector,
285            pairStrokeThickness: 1.5,
286            pairStrokeDashArray: [6, 4],
287            placementPointCount: 5,
288            points: [
289                { x: xAt(226), y: yAt(226, -0.1) },
290                { x: xAt(245), y: yAt(245, 0.1) },
291                { x: xAt(278), y: yAt(278, -0.12) },
292                { x: xAt(304), y: yAt(304, 0.1) },
293                { x: xAt(324), y: yAt(324, -0.1) },
294            ],
295            labels: createConnectorLabels(5, "PAIR", false),
296            segmentLabelVisibility: EAnnotationVisibilityMode.Always,
297            axisLabelVisibility: EAnnotationVisibilityMode.OnInteraction,
298            clipping: EAnnotationClippingMode.SeriesViewRect,
299        });
300
301    const createSecondaryDemoAnnotation = () =>
302        new PairedDashedPolylineAnnotation({
303            ...createTradingAnnotationOptions("LAD", 7),
304            isEditable: true,
305            stroke: PAIRED_POLYLINE_COLORS.secondaryStroke,
306            strokeThickness: 2,
307            fill: PAIRED_POLYLINE_COLORS.secondaryFill,
308            pairStroke: PAIRED_POLYLINE_COLORS.secondaryConnector,
309            pairStrokeThickness: 1.5,
310            pairStrokeDashArray: [4, 3],
311            placementPointCount: 7,
312            points: [
313                { x: xAt(336), y: yAt(336, -0.1) },
314                { x: xAt(358), y: yAt(358, 0.1) },
315                { x: xAt(380), y: yAt(380, -0.1) },
316                { x: xAt(404), y: yAt(404, 0.1) },
317                { x: xAt(428), y: yAt(428, -0.1) },
318                { x: xAt(452), y: yAt(452, 0.1) },
319                { x: xAt(476), y: yAt(476, -0.1) },
320            ],
321            labels: createConnectorLabels(7, "LAD"),
322            formatLabelStyle: ({ label, defaultStyle, ...rest }: IMultiPointLabelStyleFormatParams) => {
323                if (label.anchorMode === EMultiPointLabelAnchorMode.Point) {
324                    return { ...defaultStyle, color: "#FFF"};
325                }
326                return { ...defaultStyle };
327            },
328            segmentLabelVisibility: EAnnotationVisibilityMode.Always,
329            axisLabelVisibility: EAnnotationVisibilityMode.OnInteraction,
330            clipping: EAnnotationClippingMode.SeriesViewRect,
331        });
332
333    const reset = () => {
334        sciChartSurface.annotations.clear(true);
335        sciChartSurface.annotations.add(createPrimaryDemoAnnotation(), createSecondaryDemoAnnotation());
336    };
337
338    reset();
339
340    return {
341        sciChartSurface,
342
343        startPlacement: (pointCount = 5) => {
344            const safePointCount = normalizePlacementPointCount(pointCount);
345
346            placementModifier.startPlacement({
347                type: PAIRED_DASHED_POLYLINE as any,
348                options: {
349                    ...createTradingAnnotationOptions("NEW", safePointCount),
350                    isEditable: true,
351                    stroke: PAIRED_POLYLINE_COLORS.placementStroke,
352                    strokeThickness: 2,
353                    fill: PAIRED_POLYLINE_COLORS.placementFill,
354                    pairStroke: PAIRED_POLYLINE_COLORS.placementConnector,
355                    pairStrokeThickness: 1.5,
356                    pairStrokeDashArray: [7, 4],
357                    showPairConnectors: true,
358                    placementPointCount: safePointCount,
359                    labels: createConnectorLabels(safePointCount, "NEW"),
360                    pointLabelVisibility: EAnnotationVisibilityMode.OnInteraction,
361                    segmentLabelVisibility: EAnnotationVisibilityMode.Always,
362                    axisLabelVisibility: EAnnotationVisibilityMode.OnInteraction,
363                    clipping: EAnnotationClippingMode.SeriesViewRect,
364                } as any,
365            });
366        },
367
368        stopPlacement: () => placementModifier.stopPlacement(true),
369
370        reset,
371
372        deleteAllAnnotations: () => {
373            placementModifier.stopPlacement(true);
374            sciChartSurface.annotations.clear(true);
375        },
376
377        togglePairConnectors: () => {
378            sciChartSurface.annotations.asArray().forEach((annotation) => {
379                if (annotation instanceof PairedDashedPolylineAnnotation) {
380                    annotation.showPairConnectors = !annotation.showPairConnectors;
381                }
382            });
383        },
384    };
385};
386

Custom Paired Dashed Polyline Annotation

This example defines a PairedDashedPolylineAnnotation class that extends PolyLineAnnotation. It keeps native multipoint editing and placement behavior, then adds dashed connector lines between peak points such as point 2 to 4 and point 4 to 6, plus one point-1-to-last-point connector.

javascript Chart Examples & Demos

See Also: Financial Charts (13 Demos)

JavaScript Candlestick Chart | Online JavaScript Chart Examples

JavaScript Candlestick Chart

Discover how to create a JavaScript Candlestick Chart or Stock Chart using SciChart.js. For high Performance JavaScript Charts, get your free demo now.

JavaScript OHLC Chart | Javascript Charts | SciChart.js Demo

JavaScript OHLC Chart

Easily create JavaScript OHLC Chart or Stock Chart using feature-rich SciChart.js chart library. Supports custom colors. Get your free trial now.

JavaScript Realtime Ticking Stock Chart | SciChart.js

JavaScript Realtime Ticking Stock Charts

Create a JavaScript Realtime Ticking Candlestick / Stock Chart with live ticking and updating, using the high performance SciChart.js chart library. Get free demo now.

JavaScript Orderbook Heatmap | Javascript Charts | SciChart.js

JavaScript Orderbook Heatmap

Create a Javascript heatmap chart showing historical orderbook levels using the high performance SciChart.js chart library. Get free demo now.

JavaScript Multi-Pane Stock Chart using Subcharts | View JavaScript Charts

JavaScript Multi-Pane Stock Charts using Subcharts

Create a JavaScript Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.

Tenor Curves Demo | Javascript Charts | SciChart.js Demo

Tenor Curves Demo

Demonstrating the capability of SciChart.js to create a composite 2D &amp; 3D Chart application. An example like this could be used to visualize Tenor curves in a financial setting, or other 2D/3D data combined on a single screen.

JavaScript Multi-Pane Stock Chart | View JavaScript Charts

JavaScript Multi-Pane Stock Charts using Sync Multi-Chart

Create a JavaScript Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.

Financial Data Filters | Javascript Charts | SciChart.js Demo

Financial Data Filters

Shows one seeded OHLC data set toggled between no filter, Heikin-Ashi, Renko, and Point & Figure views.

JavaScript Market Depth Chart | Javascript Charts | SciChart.js

JavaScript Market Depth Chart

Create a JavaScript Depth Chart, using the high performance SciChart.js chart library. Get free demo now.

JavaScript Chart Hoverable Buy Sell Marker Annotations

JavaScript Chart Hoverable Buy Sell Marker Annotations

Demonstrates how to place Buy/Sell arrow markers on a JavaScript Stock Chart using SciChart.js - Annotations API

JavaScript User Annotated Stock Chart | SciChart.js Demo

JavaScript User Annotated Stock Chart

This demo shows you how to create a <strong>{frameworkName} User Annotated Stock Chart</strong> using SciChart.js. Custom modifiers allow you to add lines and markers, then use the built in serialisation functions to save and reload the chart, including the data and all your custom annotations.

JavaScript Trading Drawing Tools | Javascript Charts | SciChart.js

JavaScript Trading Drawing Tools

Create an interactive JavaScript trading charts for technical analysis. Trading Drawing Tools Demo, which shows how to use Polylines, Extended Lines, Rays, Channels, Pitchforks, Pitchfans, Fibonnaci Retracements, Measure, Stop Loss and Take Profit chart drawing tools for Technical Analysis.

JavaScript Freehand Drawing Tools | SciChart.js Demo

JavaScript Freehand Drawing Tools

An example of using JavaScript FreehandDrawingModifier for arbitrary drawing on trading and financial charts. Can be used for drawing trends, arrow, markers, text, etc.

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