Showcases what's possible with HTML Annotations using SciChart.js, High Performance JavaScript Charts
drawExample.ts
index.tsx
theme.ts
styles.css
1import {
2 HtmlCustomAnnotation,
3 ECoordinateMode,
4 EHorizontalAnchorPoint,
5 MouseWheelZoomModifier,
6 NumberRange,
7 NumericAxis,
8 SciChartSurface,
9 ZoomPanModifier,
10 HtmlTextAnnotation,
11 SplineMountainRenderableSeries,
12 XyDataSeries,
13 Thickness,
14 SciChartJSDarkTheme,
15 ZoomExtentsModifier,
16 EAutoRange,
17 easing,
18} from "scichart";
19
20import "./styles.css";
21import { appTheme } from "../../../theme";
22
23export const drawExample = async (rootElement: string | HTMLDivElement) => {
24 const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement);
25
26 const xAxis = new NumericAxis(wasmContext, {
27 zoomExtentsRange: new NumberRange(0, 100),
28 visibleRange: new NumberRange(40, 60),
29 visibleRangeLimit: new NumberRange(-1000, 1000),
30 autoRange: EAutoRange.Never,
31 });
32 const yAxis = new NumericAxis(wasmContext, {
33 drawLabels: false,
34 zoomExtentsRange: new NumberRange(0, 10),
35 visibleRange: new NumberRange(4, 6),
36 visibleRangeLimit: new NumberRange(-1000, 1000),
37 autoRange: EAutoRange.Never,
38 });
39
40 sciChartSurface.xAxes.add(xAxis);
41 sciChartSurface.yAxes.add(yAxis);
42
43 sciChartSurface.chartModifiers.add(new ZoomPanModifier(), new MouseWheelZoomModifier(), new ZoomExtentsModifier());
44
45 const textContent1 =
46 "HTML-based annotations let you leverage the native HTML API to create and style elements, while SciChart handles their positioning within the chart.";
47
48 const textContent2 = "For example, you can add text annotations that are stylable using CSS.";
49
50 const textContent3 = "Or insert any arbitrary HTML content — like the interactive control shown below.";
51
52 const textContent4 = "This feature also allows you to render annotation content using your preferred UI framework.";
53
54 const textContent5 = "You can even embed a separate chart at specific data coordinates.";
55
56 // the annotation styled via CSS-in-JS
57 const textAnnotation1 = new HtmlTextAnnotation({
58 xCoordinateMode: ECoordinateMode.Relative,
59 yCoordinateMode: ECoordinateMode.Relative,
60 x1: 0,
61 y1: 0,
62 x2: 1,
63 xCoordShift: 0,
64 yCoordShift: 0,
65 text: textContent1,
66 // style object with CSSStyleDeclaration format. Supports camel-cased property names.
67 // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration
68 textContainerStyle: {
69 padding: "4px",
70 fontSize: "1em",
71 color: "white",
72 background: "radial-gradient(ellipse at center, #C52E60 0%, #264B93 100%)",
73 border: "1px dotted black",
74 borderRadius: "15px",
75 },
76 });
77
78 sciChartSurface.annotations.add(textAnnotation1);
79
80 // the annotation styled via the stylesheet
81 const textAnnotation2 = new HtmlTextAnnotation({
82 xCoordinateMode: ECoordinateMode.DataValue,
83 yCoordinateMode: ECoordinateMode.DataValue,
84 x1: 10,
85 y1: 8,
86 xCoordShift: 0,
87 yCoordShift: 0,
88 text: textContent2,
89 });
90 textAnnotation2.htmlElement.classList.add("styledTextAnnotation");
91
92 sciChartSurface.annotations.add(textAnnotation2);
93
94 // the annotations with specified size and a default HTML tooltip
95 const textAnnotation3 = new HtmlTextAnnotation({
96 xCoordinateMode: ECoordinateMode.Pixel,
97 yCoordinateMode: ECoordinateMode.Relative,
98 x1: 0,
99 x2: 300,
100 y1: 1,
101 xCoordShift: 0,
102 yCoordShift: -250,
103 text: textContent3,
104 // style object with CSSStyleDeclaration format. Supports camel-cased property names.
105 // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration
106 textContainerStyle: {
107 pointerEvents: "all", // allows the native tooltip to be displayed
108 padding: "4px",
109 fontSize: "1em",
110 fontWeight: "500",
111 fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif",
112 color: appTheme.VividOrange,
113 maxWidth: "300px",
114 overflow: "hidden",
115 textOverflow: "ellipsis",
116 wordWrap: "normal",
117 background: "radial-gradient(circle at center,rgb(45, 140, 116), #14233C)",
118 },
119 });
120 textAnnotation3.htmlElement.title = "Some Tooltip";
121 sciChartSurface.annotations.add(textAnnotation3);
122
123 const textAnnotation4 = new HtmlTextAnnotation({
124 xCoordinateMode: ECoordinateMode.DataValue,
125 yCoordinateMode: ECoordinateMode.DataValue,
126 x1: 60,
127 y1: 7,
128 y2: 5,
129 xCoordShift: 0,
130 yCoordShift: 0,
131 text: textContent4,
132 // style object with CSSStyleDeclaration format. Supports camel-cased property names.
133 // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration
134 textContainerStyle: {
135 fontSize: "0.8em",
136 color: "white",
137 maxWidth: "200px",
138 },
139 });
140
141 sciChartSurface.annotations.add(textAnnotation4);
142
143 const textAnnotation5 = new HtmlTextAnnotation({
144 xCoordinateMode: ECoordinateMode.DataValue,
145 yCoordinateMode: ECoordinateMode.DataValue,
146 horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
147 x1: 70,
148 y1: 3.5,
149 xCoordShift: 0,
150 yCoordShift: 0,
151 text: textContent5,
152 // style object with CSSStyleDeclaration format. Supports camel-cased property names.
153 // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration
154 textContainerStyle: {
155 fontSize: "0.8em",
156 color: "white",
157 width: "200px",
158 whiteSpace: "no-wrap",
159 },
160 });
161
162 sciChartSurface.annotations.add(textAnnotation5);
163
164 // the annotation that serves as a root node for the element generated with a framework (e.g. via `React.createPortal`)
165 const containerAnnotation = new HtmlCustomAnnotation({
166 xCoordinateMode: ECoordinateMode.DataValue,
167 yCoordinateMode: ECoordinateMode.DataValue,
168 horizontalAnchorPoint: EHorizontalAnchorPoint.Right,
169 x1: 80,
170 y1: 5,
171 isEditable: true,
172 });
173 sciChartSurface.annotations.add(containerAnnotation);
174
175 const formAnnotation = new HtmlCustomAnnotation({
176 xCoordinateMode: ECoordinateMode.Relative,
177 yCoordinateMode: ECoordinateMode.Relative,
178 x1: 0,
179 y1: 1,
180 yCoordShift: -150,
181 });
182 sciChartSurface.annotations.add(formAnnotation);
183
184 // the annotation that serves as a root element for the nested chart
185 const nestedChartRootAnnotation = new HtmlCustomAnnotation({
186 xCoordinateMode: ECoordinateMode.DataValue,
187 yCoordinateMode: ECoordinateMode.DataValue,
188 x1: 70,
189 y1: 2,
190 x2: 100,
191 y2: 0,
192 isEditable: true,
193 });
194 sciChartSurface.annotations.add(nestedChartRootAnnotation);
195
196 const selector = new NumberRangeSelector(formAnnotation.htmlElement, xAxis.visibleRange, (min, max) => {
197 xAxis.visibleRange = new NumberRange(min, max);
198 });
199
200 xAxis.visibleRangeChanged.subscribe((args) => {
201 selector.setRange(args.visibleRange.min, args.visibleRange.max);
202 });
203
204 // this creates a separate charts which could be positioned within the main one.
205 // If you need some kind of this functionality, consider checking out SubCharts API as well
206 const nestedChart = await drawNestedChart(nestedChartRootAnnotation.htmlElement as HTMLDivElement);
207
208 // bind cleanup call
209 sciChartSurface.addDeletable(nestedChart.sciChartSurface);
210
211 sciChartSurface.zoomExtents(600, easing.inOutCirc);
212
213 return { sciChartSurface, containerAnnotation };
214};
215
216const drawNestedChart = async (rootElement: string | HTMLDivElement) => {
217 const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement, {
218 theme: new SciChartJSDarkTheme(),
219 padding: Thickness.fromNumber(2),
220 });
221
222 const xAxis = new NumericAxis(wasmContext, { drawLabels: false });
223 const yAxis = new NumericAxis(wasmContext, { drawLabels: false });
224
225 sciChartSurface.xAxes.add(xAxis);
226 sciChartSurface.yAxes.add(yAxis);
227
228 sciChartSurface.renderableSeries.add(
229 new SplineMountainRenderableSeries(wasmContext, {
230 strokeThickness: 3,
231 stroke: appTheme.VividOrange,
232 fill: appTheme.PalePink,
233 opacity: 0.5,
234 dataSeries: new XyDataSeries(wasmContext, {
235 xValues: [0, 2, 3, 8, 11, 29],
236 yValues: [44, 77, 2, 56, 9, 5],
237 }),
238 })
239 );
240
241 return { sciChartSurface };
242};
243class NumberRangeSelector {
244 private minInput: HTMLInputElement;
245 private maxInput: HTMLInputElement;
246 private slider: HTMLInputElement;
247 private container: HTMLElement;
248 private onChange: (min: number, max: number) => void;
249
250 constructor(rootElement: HTMLElement, initialRange: NumberRange, onChange: (min: number, max: number) => void) {
251 this.container = rootElement;
252 this.onChange = onChange;
253
254 // Style the root container
255 this.container.style.pointerEvents = "all";
256 this.container.style.padding = "0.8em";
257 this.container.style.borderRadius = "12px";
258 this.container.style.background = "linear-gradient(135deg, #EC0F6C, #F48420)";
259 this.container.style.color = "white";
260 this.container.style.fontFamily = "sans-serif";
261 this.container.style.width = "fit-content";
262
263 // Title
264 const title = document.createElement("h3");
265 title.textContent = "Visible Range Selector";
266 title.style.marginBottom = "0.8em";
267 title.style.whiteSpace = "nowrap";
268
269 // Inputs
270 this.minInput = document.createElement("input");
271 this.maxInput = document.createElement("input");
272 this.minInput.type = "number";
273 this.maxInput.type = "number";
274 this.minInput.value = `${initialRange.min}`;
275 this.maxInput.value = `${initialRange.max}`;
276 this.minInput.style.width = "50px";
277 this.maxInput.style.width = "50px";
278
279 // Labels
280 const minLabel = document.createElement("label");
281 minLabel.textContent = "Min: ";
282 minLabel.appendChild(this.minInput);
283
284 const maxLabel = document.createElement("label");
285 maxLabel.textContent = "Max: ";
286 maxLabel.appendChild(this.maxInput);
287
288 // Wrapper for inputs
289 const inputWrapper = document.createElement("div");
290 inputWrapper.className = "inputWrapper";
291 inputWrapper.style.display = "flex";
292 inputWrapper.style.gap = "0.8em";
293 inputWrapper.style.alignItems = "center";
294 inputWrapper.appendChild(minLabel);
295 inputWrapper.appendChild(maxLabel);
296
297 // Slider (range input)
298 this.slider = document.createElement("input");
299 this.slider.type = "range";
300 this.slider.min = "-1000";
301 this.slider.max = "1000";
302 this.slider.value = "50";
303 this.slider.step = "1";
304 this.slider.style.width = "100%";
305 this.slider.style.marginTop = "0.5em";
306
307 // Slider container (with label)
308 const sliderWrapper = document.createElement("div");
309 sliderWrapper.style.marginTop = "0.5em";
310 sliderWrapper.appendChild(this.slider);
311
312 // Event listeners
313 this.minInput.addEventListener("input", () => this.handleInput());
314 this.maxInput.addEventListener("input", () => this.handleInput());
315
316 this.slider.addEventListener("input", () => {
317 const center = parseFloat(this.slider.value);
318 const range = 100; // Adjustable spread
319 this.minInput.value = (center - range / 2).toString();
320 this.maxInput.value = (center + range / 2).toString();
321 this.handleInput();
322 });
323
324 // Add elements to container
325 this.container.appendChild(title);
326 this.container.appendChild(inputWrapper);
327 this.container.appendChild(sliderWrapper);
328
329 // Initial trigger
330 this.handleInput();
331 }
332
333 private handleInput() {
334 const min = parseFloat(this.minInput.value);
335 const max = parseFloat(this.maxInput.value);
336
337 if (!isNaN(min) && !isNaN(max) && min <= max) {
338 this.minInput.setCustomValidity("");
339 this.maxInput.setCustomValidity("");
340 this.onChange(min, max);
341 } else {
342 this.minInput.setCustomValidity("Invalid range");
343 this.maxInput.setCustomValidity("Invalid range");
344 }
345 }
346
347 public setRange(min: number, max: number): void {
348 if (!isNaN(min) && !isNaN(max) && min <= max) {
349 this.minInput.value = min.toString();
350 this.maxInput.value = max.toString();
351
352 // Update slider to reflect the new range center
353 const center = (min + max) / 2;
354 this.slider.value = center.toString();
355
356 this.handleInput();
357 } else {
358 console.warn("Invalid range provided to setRange:", { min, max });
359 }
360 }
361}
362This React example demonstrates advanced HTML annotation integration using SciChart.js, including rendering React components within charts via createPortal. It showcases how to combine SciChart's high-performance rendering with React's component model.
The example uses SciChartReact component for chart initialization. HTML annotations are created in the drawExample function, while React components are injected into annotations using portals. The implementation includes both CSS-in-JS and external stylesheet approaches for annotation styling.
Notable features include: dynamic React component rendering within chart coordinates, responsive annotation positioning, and interactive form controls. The example also demonstrates how to create nested charts within annotations and implement two-way data binding between chart ranges and UI controls.
The solution showcases proper React-SciChart integration patterns, including state management for chart APIs and cleanup handling. When using HTML annotations with React, consider performance implications and use portals judiciously for dynamic content.

Create a React Histogram Chart with custom texture fills and patterns. Try the SciChartReact wrapper component for seamless React integration today.

Build a React Gantt Chart with SciChart. View the demo for horizontal bars, rounded corners and data labels to show project timelines and task completion.

Create a React Choropleth map, a type of thematic map where areas are shaded or patterned in proportion to the value of a variable being represented.

Create a React Multi-Layer Map Example, using FastTriangleRenderableSeries with GeoJSON data-points using a constrained delaunay triangulation algorithm.

Bring annual comparison data to life with the React Animated Bar Chart example from SciChart. This demo showcases top 10 tennis players from 1990 to 2024.

View the React Vector Field Plot example from SciChart, including dynamic vector generation, gradient-colored segments, and interactive zoom/pan. Try demo.

Build a React Waterfall Chart with dynamic coloring, multi-line data labels and responsive design, using the SciChartReact component for seamless integration.

Try the React Box Plot Chart example for React-friendly chart lifecycle management, dynamic sub-surface positioning, and custom styling. Try the demo now.

Create React Triangle Meshes with the Triangle Series from SciChart. This demo supports strip mode, list mode and the drawing of polygons. View the example.

Create a React Treemap Chart to define rectangle positions based on total value. Use SciChart FastRectangleRenderableSeries and d3-hierarchy.js layouts.

Design a highly dynamic React Map Chart with Heatmap overlay with SciChart's feature-rich JavaScript Chart Library. Get your free demo today.

Demonstrating the capability of SciChart.js to create a JavaScript Audio Analyzer Bars and visualize the Fourier-Transform of an audio waveform in realtime.

View the React Linear Gauge Chart example to combine rectangles & annotations. Create a linear gauge dashboard with animated indicators and custom scales.

The React Order of Rendering example gives you full control of the draw order of series and annotations for charts. Try SciChart's advanced customizations.

Build Responsive React HTML Annotations with SciChart. Use the advanced CSS container queries for responsive text layout and custom design. View demo now.

Explore SciChart's Polar Interactivity Modifiers including zooming, panning, and cursor tracking. Try the demo to trial the Polar Chart Behavior Modifiers.