drawExample.ts
index.tsx
tradingAnnotationExampleUtils.ts
1import {
2 AnnotationHoverModifier,
3 ECursorStyle,
4 EObservableArrayChangedAction,
5 EXyDirection,
6 MouseWheelZoomModifier,
7 NumberRange,
8 ZoomExtentsModifier,
9} from "scichart";
10import {
11 EAnnotationVisibilityMode,
12 ESnapMode,
13 FreehandDrawingAnnotation,
14 FreehandDrawingModifier,
15 IFreehandDrawingAnnotationOptions,
16} from "scichart-financial-tools";
17import { createFinancialChart, TRADING_ANNOTATION_COLORS } from "../_shared/tradingAnnotationExampleUtils";
18
19export type TFreehandVariant = "editableOutline" | "nonEditableLine" | "thickHighlight" | "locked";
20
21const generateHandDrawnTrendline = () => {
22 const startX = 1705104000; // 2024-01-13 00:00 UTC
23 const endX = 1705276800; // 2024-01-15 00:00 UTC
24 const startY = 65000;
25 const endY = 67500;
26 const count = 110;
27 let seed = 1337;
28 const rand = () => {
29 seed = (seed * 9301 + 49297) % 233280;
30 return seed / 233280;
31 };
32 const points: { x: number; y: number }[] = [];
33 for (let i = 0; i < count; i++) {
34 const t = i / (count - 1);
35 const drift = Math.sin(t * Math.PI * 1.6 + 0.4) * 18;
36 const yJitter = (rand() - 0.5) * 16;
37 const xJitter = (rand() - 0.5) * ((endX - startX) / count) * 0.35;
38 points.push({
39 x: startX + (endX - startX) * t + xJitter,
40 y: startY + (endY - startY) * t + drift + yJitter,
41 });
42 }
43 return points;
44};
45
46const handDrawnTrendlinePoints = generateHandDrawnTrendline();
47
48type TInitialFreehandAnnotation = {
49 points: { x: number; y: number }[];
50 stroke: string;
51 strokeThickness: number;
52};
53
54const initialFreehandAnnotations: TInitialFreehandAnnotation[] = [
55 {
56 points: [
57 { x: 1705074424.9760892, y: 65731.6718827903 },
58 { x: 1705075600.6684432, y: 65642.12282942746 },
59 { x: 1705087445.885085, y: 65307.71857034999 },
60 { x: 1705085666.0820355, y: 65356.5315837517 },
61 { x: 1705081130.604812, y: 65410.78777490682 },
62 { x: 1705070214.7892404, y: 65438.14998565657 },
63 { x: 1705086971.8905394, y: 65356.648641337786 },
64 { x: 1705090526.8496335, y: 65388.83947751397 },
65 { x: 1705094908.9756804, y: 65460.77136416947 },
66 { x: 1705091521.3087788, y: 65449.416778318235 },
67 ],
68 stroke: "#F97066",
69 strokeThickness: 4,
70 },
71 {
72 points: [
73 { x: 1705025064.4852417, y: 66127.82401853298 },
74 { x: 1705025394.4226215, y: 65871.17526101925 },
75 { x: 1705024855.3700008, y: 65954.7251130947 },
76 { x: 1705029753.3136415, y: 65982.08732384446 },
77 { x: 1705035097.3697963, y: 65992.00795426602 },
78 { x: 1705040343.8388386, y: 65986.09654616822 },
79 { x: 1705044791.0229604, y: 65964.20677756841 },
80 { x: 1705045278.9585223, y: 65933.80106958018 },
81 { x: 1705043201.74713, y: 65906.67297400262 },
82 { x: 1705035543.48231, y: 65873.80905670639 },
83 { x: 1705027936.3345492, y: 65859.3231804271 },
84 { x: 1705022132.2248645, y: 65859.11832965145 },
85 ],
86 stroke: "#F97066",
87 strokeThickness: 4,
88 },
89 {
90 points: [
91 { x: 1705060251.609766, y: 65972.86903893946 },
92 { x: 1705060688.4282691, y: 65910.50660994725 },
93 { x: 1705065716.4880598, y: 65861.5180101664 },
94 { x: 1705069043.7438917, y: 65854.55308379373 },
95 { x: 1705070656.2547488, y: 65860.55228508111 },
96 { x: 1705072877.523307, y: 65910.76998951596 },
97 { x: 1705073388.6938958, y: 65974.47858074827 },
98 { x: 1705080935.4305873, y: 65866.11252042063 },
99 ],
100 stroke: "#F97066",
101 strokeThickness: 4,
102 },
103 {
104 points: [
105 { x: 1705091646.7779233, y: 65990.3691480607 },
106 { x: 1705091860.5401695, y: 65928.09451225805 },
107 { x: 1705094035.338674, y: 65896.19632004711 },
108 { x: 1705100819.966488, y: 65890.43123393191 },
109 { x: 1705104123.987293, y: 65914.89626942581 },
110 { x: 1705104742.0390048, y: 65990.60326323289 },
111 { x: 1705105169.5634973, y: 65837.34561863774 },
112 { x: 1705103979.9301271, y: 65712.03547272283 },
113 { x: 1705100095.033653, y: 65693.04287937887 },
114 { x: 1705092195.1245549, y: 65700.32971411331 },
115 { x: 1705088156.876904, y: 65734.97875959748 },
116 { x: 1705088291.6400592, y: 65779.40211352061 },
117 { x: 1705102172.2450452, y: 65854.20191103544 },
118 { x: 1705112846.4163384, y: 65896.78160797758 },
119 ],
120 stroke: "#F97066",
121 strokeThickness: 4,
122 },
123 {
124 points: [
125 { x: 1705271913.4095333, y: 68272.31899579709 },
126 { x: 1705254473.1984477, y: 68281.71286708124 },
127 { x: 1705243325.0326085, y: 68256.80886563948 },
128 { x: 1705241071.2350128, y: 68237.43583514073 },
129 { x: 1705240253.362071, y: 68204.68897543059 },
130 { x: 1705263358.2726805, y: 68162.25560047108 },
131 { x: 1705266676.2345016, y: 68137.556449805 },
132 { x: 1705266866.7617211, y: 68104.31209535395 },
133 { x: 1705263558.0939107, y: 68079.99338184268 },
134 { x: 1705255137.720213, y: 68056.46480703754 },
135 { x: 1705245188.4817545, y: 68058.92301634554 },
136 { x: 1705237929.859395, y: 68076.89135581115 },
137 ],
138 stroke: "#4EC385",
139 strokeThickness: 4,
140 },
141 {
142 points: [
143 { x: 1705282146.115318, y: 68129.39168317485 },
144 { x: 1705292318.4100335, y: 68129.39168317485 },
145 { x: 1705293675.335596, y: 68157.39771064813 },
146 { x: 1705293670.6885908, y: 68187.07180872327 },
147 { x: 1705292443.8791778, y: 68194.03673509593 },
148 { x: 1705280305.9011989, y: 68194.82687380207 },
149 { x: 1705275919.1281466, y: 68183.64787432998 },
150 { x: 1705274227.6181984, y: 68106.59471828281 },
151 { x: 1705277666.402159, y: 68075.6037223641 },
152 { x: 1705283721.4501324, y: 68055.26496678006 },
153 { x: 1705292304.4690173, y: 68053.74321816083 },
154 { x: 1705297546.2910542, y: 68064.07355013373 },
155 ],
156 stroke: "#4EC385",
157 strokeThickness: 4,
158 },
159 {
160 points: [
161 { x: 1705314953.9731023, y: 68304.74394714547 },
162 { x: 1705314517.1545992, y: 68072.50169633258 },
163 ],
164 stroke: "#4EC385",
165 strokeThickness: 4,
166 },
167 {
168 points: [
169 { x: 1705332956.4718356, y: 68300.50060964952 },
170 { x: 1705332956.4718356, y: 68178.23396097307 },
171 { x: 1705330897.8484647, y: 68051.95808997288 },
172 ],
173 stroke: "#4EC385",
174 strokeThickness: 4,
175 },
176 {
177 points: [
178 { x: 1705294609.3836718, y: 67929.01836017639 },
179 { x: 1705294474.6205165, y: 67825.6272472578 },
180 { x: 1705285682.4863908, y: 67528.6814157308 },
181 { x: 1705283005.8113081, y: 67676.43735377946 },
182 { x: 1705285306.0789573, y: 67543.25508519965 },
183 { x: 1705300334.4942653, y: 67679.27600024227 },
184 ],
185 stroke: "#4EC385",
186 strokeThickness: 4,
187 },
188];
189
190const variantOptions = (variant: TFreehandVariant, background: string): IFreehandDrawingAnnotationOptions => {
191 const base: IFreehandDrawingAnnotationOptions = {
192 isEditable: true,
193 strokeThickness: 4,
194 showBoxOutline: false,
195 boxOutlineStrokeDashArray: [6, 4],
196 snapMode: ESnapMode.None,
197 annotationsGripsRadius: 4,
198 gripSvgTemplate: (annotation: any, x: number, y: number) => {
199 const ann = annotation as FreehandDrawingAnnotation;
200 return `<circle cx="${x}" cy="${y}" r="${ann.annotationsGripsRadius}" fill="${background}" stroke="${ann.annotationsGripsStroke}" stroke-width="${ann.strokeThickness}" />`;
201 },
202 };
203
204 switch (variant) {
205 case "editableOutline":
206 return {
207 ...base,
208 stroke: TRADING_ANNOTATION_COLORS.freehand,
209 annotationsGripsStroke: TRADING_ANNOTATION_COLORS.freehand,
210 allowMove: true,
211 showBoxOutlineOnlyWhenSelected: false,
212 };
213 case "locked":
214 return {
215 ...base,
216 stroke: TRADING_ANNOTATION_COLORS.lockedFreehand,
217 annotationsGripsStroke: TRADING_ANNOTATION_COLORS.lockedFreehand,
218 keepAspectRatioOnResize: true,
219 forcedAspectRatio: 1,
220 showBoxOutlineOnlyWhenSelected: false,
221 };
222 case "nonEditableLine":
223 return {
224 ...base,
225 isEditable: false,
226 isSelected: false,
227 stroke: TRADING_ANNOTATION_COLORS.foreground,
228 strokeThickness: 2,
229 showBoxOutline: false,
230 gripVisibility: EAnnotationVisibilityMode.OnInteraction,
231 allowMove: false,
232 showBoxOutlineOnlyWhenSelected: false,
233 };
234 case "thickHighlight":
235 return {
236 ...base,
237 stroke: TRADING_ANNOTATION_COLORS.warning,
238 fill: `${TRADING_ANNOTATION_COLORS.warning}33`,
239 annotationsGripsStroke: TRADING_ANNOTATION_COLORS.warning,
240 strokeThickness: 4,
241 showBoxOutlineOnlyWhenSelected: false,
242 };
243 }
244};
245
246export const drawExample = async (rootElement: string | HTMLDivElement) => {
247 const { sciChartSurface, xAxis } = await createFinancialChart(rootElement, {
248 volatility: 0.0023,
249 title: "BTC / USDT - Freehand Drawing",
250 startDate: new Date("2024-01-01T00:00:00Z"),
251 dataSeed: 2024,
252 });
253
254 // Jan 20 2024 00:00 UTC
255 xAxis.visibleRange = new NumberRange(xAxis.visibleRange.min, 1705708800);
256
257 const freehandDrawingModifier = new FreehandDrawingModifier({
258 isDrawing: false,
259 keepDrawingAfterComplete: true,
260 pointSamplingDistancePx: 1.2,
261 simplifyTolerancePx: 0.8,
262 maxPoints: 6000,
263 });
264
265 sciChartSurface.chartModifiers.add(
266 new MouseWheelZoomModifier({ xyDirection: EXyDirection.XDirection }),
267 new ZoomExtentsModifier({ xyDirection: EXyDirection.XDirection }),
268 new AnnotationHoverModifier({
269 enableHover: true,
270 enableCursor: true,
271 idleCursor: ECursorStyle.Crosshair,
272 }),
273 freehandDrawingModifier
274 );
275
276 sciChartSurface.annotations.add(
277 ...initialFreehandAnnotations.map(
278 (data) =>
279 new FreehandDrawingAnnotation({
280 points: data.points,
281 stroke: data.stroke,
282 strokeThickness: data.strokeThickness,
283 isEditable: true,
284 isSelected: false,
285 allowMove: true,
286 showBoxOutline: false,
287 opacity: 0.9,
288 gripVisibility: EAnnotationVisibilityMode.OnInteraction,
289 })
290 ),
291 new FreehandDrawingAnnotation({
292 points: handDrawnTrendlinePoints,
293 isEditable: true,
294 isSelected: false,
295 allowMove: true,
296 showBoxOutline: false,
297 stroke: "#686c70",
298 annotationsGripsStroke: "#9AA0A6",
299 strokeThickness: 7,
300 opacity: 0.9,
301 gripVisibility: EAnnotationVisibilityMode.OnInteraction,
302 })
303 );
304
305 return {
306 sciChartSurface,
307 startDrawing: (variant: TFreehandVariant, color?: string) => {
308 const options = variantOptions(variant, sciChartSurface.background);
309 if (color) {
310 options.stroke = color;
311 options.annotationsGripsStroke = color;
312 }
313 freehandDrawingModifier.startDrawing(options);
314 },
315 stopDrawing: () => freehandDrawingModifier.stopDrawing(true),
316 clear: () => sciChartSurface.annotations.clear(true),
317 removeLast: () => {
318 const annotations = sciChartSurface.annotations;
319 if (annotations.size() > 0) {
320 annotations.removeAt(annotations.size() - 1, true);
321 }
322 },
323 exportAnnotations: () =>
324 sciChartSurface.annotations
325 .asArray()
326 .filter((a): a is FreehandDrawingAnnotation => a instanceof FreehandDrawingAnnotation)
327 .map((a) => ({
328 points: a.points.map((p) => ({ x: p.x, y: p.y })),
329 stroke: a.stroke,
330 strokeThickness: a.strokeThickness,
331 })),
332 setKeepDrawingAfterComplete: (enabled: boolean) => {
333 (freehandDrawingModifier as any).keepDrawingAfterCompleteProperty = enabled;
334 },
335 setSampling: (pointSamplingDistancePx: number, simplifyTolerancePx: number, maxPoints: number) => {
336 freehandDrawingModifier.pointSamplingDistancePx = pointSamplingDistancePx;
337 (freehandDrawingModifier as any).simplifyTolerancePxProperty = simplifyTolerancePx;
338 (freehandDrawingModifier as any).maxPointsProperty = maxPoints;
339 },
340 };
341};
342
Discover how to create a Angular Candlestick Chart or Stock Chart using SciChart.js. For high Performance JavaScript Charts, get your free demo now.

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

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

Create an Angular heatmap chart showing historical orderbook levels, using the high performance SciChart.js chart library. Get free demo now.

Create a Angular 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 Angular Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.

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

Demonstrates how to place Buy/Sell arrow markers on a Angular 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.
