Extend PolyLineAnnotation with custom paired dash-line rendering.
drawExample.ts
index.tsx
tradingAnnotationExampleUtils.ts
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};
386This 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.

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

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

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.

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

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

Demonstrating the capability of SciChart.js to create a composite 2D & 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.

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

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

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

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

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.

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.

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