React Freehand Drawing Tools

React Freehand Drawing Demo for trading and financial charts using SciChart.js. Can be used for drawing trends, arrow, markers, text, etc.

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

tradingAnnotationExampleUtils.ts

Copy to clipboard
Minimise
Fullscreen
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

React Freehand Drawing Tools

This 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.

react Chart Examples & Demos

See Also: Financial Charts (11 Demos)

React Candlestick Chart | Online JavaScript Chart Examples

React Candlestick Chart

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

React OHLC Chart | React Charts | SciChart.js Demo

React OHLC Chart

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

React Realtime Ticking Stock Chart | SciChart.js Demo

React Realtime Ticking Stock Charts

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

NEW!
React Orderbook Heatmap | React Charts | SciChart.js Demo

Order Book Heatmap

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

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

React Multi-Pane Stock Charts using Subcharts

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

Tenor Curves Demo | React 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.

React Multi-Pane Stock Chart | View JavaScript Charts

React Multi-Pane Stock Charts using Sync Multi-Chart

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

React Market Depth Chart | React Charts | SciChart.js Demo

React Market Depth Chart

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

React Chart Hoverable Buy Sell Marker Annotations | SciChart

React Chart Hoverable Buy Sell Marker Annotations

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

React User Annotated Stock Chart | React Charts | SciChart.js

React 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.

NEW!
React Trading Drawing Tools | React Charts | SciChart.js

React Trading Drawing Tools

Create an interactive React 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.

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