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 Angular example illustrates how to implement HTML annotations in SciChart.js within an Angular component. It demonstrates annotation positioning, styling, and embedding interactive content while leveraging Angular's component architecture.
The chart is initialized through the SciChart Angular component binding. The drawExample function creates various HTML annotations including text elements and container annotations for custom content. Coordinate modes are configured using ECoordinateMode for flexible positioning.
The example highlights: dynamic HTML content rendering at chart coordinates, CSS styling approaches, and interactive range selection. It also shows how to implement nested charts within annotations and handle visibility range changes through DOM events.
The implementation follows Angular best practices by separating chart logic into a service-like function. For production use, consider creating reusable annotation components and implementing proper change detection strategies to optimize performance.

Demonstrates how to place Annotations (lines, arrows, markers, text) over a Angular Chart using SciChart.js Annotations API

Demonstrates per-point coloring in JavaScript chart types with SciChart.js PaletteProvider API

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

Demonstrates how to add draggable thresholds which change the series color in the chart in SciChart.js

Demonstrates how to edit Annotations (shapes, boxes, lines, text, horizontal and vertical line) over a Angular Chart using SciChart.js Annotations API

Demonstrates how to color areas of the chart surface using background Annotations using SciChart.js Annotations API

Demonstrates how layering works a Angular Chart using SciChart.js Annotations API

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