JavaScript Freehand Drawing Demo for trading and financial charts using SciChart.js. Can be used for drawing trends, arrow, markers, text, etc.
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};
342This example demonstrates FreehandDrawingModifier and FreehandDrawingAnnotation. Select color, switch between drawing and edit modes, delete annotations using backspace button and delete icon. This tool can be used for drawing trend lines, event markers, text or any arbitrary drawing for financial and trading charts.

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.

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.