Demonstrates how to use the HtmlCustomAnnotation to create responsive text content using SciChart.js, High Performance JavaScript Charts
drawExample.ts
index.html
vanilla.ts
theme.ts
1import {
2 HtmlCustomAnnotation,
3 CategoryAxis,
4 EAnnotationLayer,
5 ECoordinateMode,
6 EHorizontalAnchorPoint,
7 EVerticalAnchorPoint,
8 EXyDirection,
9 MouseWheelZoomModifier,
10 NumberRange,
11 NumericAxis,
12 SciChartSurface,
13 ZoomPanModifier,
14 SmartDateLabelProvider,
15 AnnotationHoverModifier,
16 HtmlTextAnnotation,
17 AnnotationBase,
18 easing,
19 EHoverMode,
20 GenericAnimation,
21 translateFromCanvasToSeriesViewRect,
22 translateToNotScaled,
23 IAnnotation,
24} from "scichart";
25
26import "./styles.css";
27import { appTheme } from "../../../theme";
28
29const data1 = [
30 {
31 start: 1745830800,
32 end: 1745834400,
33 title: "Standup Meeting",
34 color: "#FF6B6B",
35 },
36 {
37 start: 1745834400,
38 end: 1745838900,
39 title: "Planning Session",
40 color: "#4ECDC4",
41 },
42 {
43 start: 1745838900,
44 end: 1745842500,
45 title: "Lunch Break",
46 color: "#FFD93D",
47 },
48 {
49 start: 1745842500,
50 end: 1745847900,
51 title: "Presentation Prep",
52 color: "#1A535C",
53 },
54 {
55 start: 1745847900,
56 end: 1745851500,
57 title: "One-on-One Meeting",
58 color: "#FF9F1C",
59 },
60 {
61 start: 1745851500,
62 end: 1745855100,
63 title: "Email Responses",
64 color: "#6A4C93",
65 },
66];
67
68const data2 = [
69 {
70 start: 1745830800,
71 end: 1745832600,
72 title: "Morning Sync",
73 color: "#FFB5E8",
74 },
75 {
76 start: 1745832600,
77 end: 1745836200,
78 title: "Design Review",
79 color: "#B5EAD7",
80 },
81 {
82 start: 1745836200,
83 end: 1745839800,
84 title: "Code Implementation",
85 color: "#C7CEEA",
86 },
87 {
88 start: 1745839800,
89 end: 1745843400,
90 title: "Lunch + Walk",
91 color: "#FFDAC1",
92 },
93 {
94 start: 1745843400,
95 end: 1745848800,
96 title: "Dev Handoff",
97 color: "#E2F0CB",
98 },
99 {
100 start: 1745848800,
101 end: 1745855100,
102 title: "Documentation",
103 color: "#FFABAB",
104 },
105];
106
107const data3 = [
108 {
109 start: 1745830800,
110 end: 1745833500,
111 title: "Daily Briefing",
112 color: "#FFD6A5",
113 },
114 {
115 start: 1745833500,
116 end: 1745838000,
117 title: "UX Interviews",
118 color: "#9BF6FF",
119 },
120 {
121 start: 1745838000,
122 end: 1745842500,
123 title: "Lunch & Networking",
124 color: "#A0C4FF",
125 },
126 {
127 start: 1745842500,
128 end: 1745846100,
129 title: "Sprint Planning",
130 color: "#BDB2FF",
131 },
132 {
133 start: 1745846100,
134 end: 1745849700,
135 title: "Code Review",
136 color: "#FFC6FF",
137 },
138 {
139 start: 1745849700,
140 end: 1745855100,
141 title: "Backlog Grooming",
142 color: "#FFFFD1",
143 },
144];
145
146const data4 = [
147 {
148 start: 1745830800,
149 end: 1745834100,
150 title: "System Check-In",
151 color: "#FFADAD",
152 },
153 {
154 start: 1745834100,
155 end: 1745837700,
156 title: "Architecture Planning",
157 color: "#FFD6A5",
158 },
159 {
160 start: 1745837700,
161 end: 1745842200,
162 title: "Lunch Break",
163 color: "#FDFFB6",
164 },
165 {
166 start: 1745842200,
167 end: 1745846700,
168 title: "Testing Session",
169 color: "#CAFFBF",
170 },
171 {
172 start: 1745846700,
173 end: 1745851200,
174 title: "QA Sync",
175 color: "#9BF6FF",
176 },
177 {
178 start: 1745851200,
179 end: 1745855100,
180 title: "End-of-Day Recap",
181 color: "#A0C4FF",
182 },
183];
184
185export const drawExample = async (rootElement: string | HTMLDivElement) => {
186 const { sciChartSurface, wasmContext } = await SciChartSurface.create(rootElement);
187 const xAxis = new CategoryAxis(wasmContext, {
188 isInnerAxis: true,
189 labelStyle: {
190 color: "black",
191 fontSize: 12,
192 },
193 majorGridLineStyle: {
194 color: "gray",
195 strokeDashArray: [2, 2],
196 },
197 drawMinorGridLines: false,
198 drawMajorBands: false,
199 labelProvider: new SmartDateLabelProvider({ rotation: -90 }),
200 visibleRangeLimit: new NumberRange(data1[0].start - 4 * 24 * 60, data1[data1.length - 1].end + 4 * 24 * 60),
201 visibleRange: new NumberRange(data1[0].start, data1[data1.length - 1].end),
202 });
203 const yAxis = new NumericAxis(wasmContext, { isVisible: false });
204
205 sciChartSurface.xAxes.add(xAxis);
206 sciChartSurface.yAxes.add(yAxis);
207
208 sciChartSurface.chartModifiers.add(
209 new ZoomPanModifier({ xyDirection: EXyDirection.XDirection }),
210 new MouseWheelZoomModifier({ xyDirection: EXyDirection.XDirection })
211 );
212
213 const crateTimeSlotAnnotation =
214 (offset: number) =>
215 ({ start, end, title, color }: any) => {
216 const textAnnotation = new HtmlCustomAnnotation({
217 // move to the background to allow drawing grid lines and labels above the annotations
218 annotationLayer: EAnnotationLayer.Background,
219 yCoordinateMode: ECoordinateMode.Relative,
220 x1: start,
221 y1: offset * 0.25,
222 x2: end,
223 y2: (offset + 1) * 0.25,
224 horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
225 verticalAnchorPoint: EVerticalAnchorPoint.Center,
226 });
227
228 textAnnotation.htmlElement.innerHTML = `<div class="responsiveTextAnnotation">${title}</div>`;
229 textAnnotation.htmlElement.classList.add("responsiveTextAnnotationContainer");
230 textAnnotation.htmlElement.style.background = color;
231 return textAnnotation;
232 };
233
234 const annotations1 = data1.map(crateTimeSlotAnnotation(0));
235 const annotations2 = data2.map(crateTimeSlotAnnotation(1));
236 const annotations3 = data3.map(crateTimeSlotAnnotation(2));
237 const annotations4 = data4.map(crateTimeSlotAnnotation(3));
238
239 const titleAnnotation1 = addLaneTitleAnnotation("Employee1", "#FFB6C1", 0);
240 const titleAnnotation2 = addLaneTitleAnnotation("Employee2", "#40E0D0", 0.25);
241 const titleAnnotation3 = addLaneTitleAnnotation("Employee3", "#6A5ACD", 0.5);
242 const titleAnnotation4 = addLaneTitleAnnotation("Employee4", "#ADFF2F", 0.75);
243
244 sciChartSurface.annotations.add(
245 ...annotations1,
246 ...annotations2,
247 ...annotations3,
248 ...annotations4,
249 titleAnnotation1,
250 titleAnnotation2,
251 titleAnnotation3,
252 titleAnnotation4
253 );
254
255 addTooltipForAnnotations(sciChartSurface, [...annotations1, ...annotations2, ...annotations3, ...annotations4]);
256
257 return { sciChartSurface };
258};
259
260function addLaneTitleAnnotation(text: string, background: string, yOffset: number) {
261 return new HtmlTextAnnotation({
262 x1: 0,
263 y1: yOffset,
264 xCoordinateMode: ECoordinateMode.Relative,
265 yCoordinateMode: ECoordinateMode.Relative,
266 horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
267 verticalAnchorPoint: EVerticalAnchorPoint.Top,
268 textContainerStyle: {
269 fontSize: "10px",
270 color: "black",
271 opacity: "0.7",
272 background,
273 },
274 text,
275 });
276}
277
278function addTooltipForAnnotations(sciChartSurface: SciChartSurface, targets: IAnnotation[]) {
279 const tooltipAnnotation = new HtmlTextAnnotation({
280 x1: 0,
281 y1: 0,
282 xCoordShift: 20,
283 yCoordShift: 20,
284 xCoordinateMode: ECoordinateMode.Pixel,
285 yCoordinateMode: ECoordinateMode.Pixel,
286 horizontalAnchorPoint: EHorizontalAnchorPoint.Left,
287 verticalAnchorPoint: EVerticalAnchorPoint.Top,
288 textContainerStyle: {
289 fontSize: "12px",
290 color: appTheme.ForegroundColor,
291 padding: "4px",
292 background: "rgba(0, 0, 139, 0.4)",
293 backdropFilter: "blur(10px)",
294 borderRadius: "0px 15px 15px 15px",
295 },
296 text: "",
297 isHidden: true,
298 });
299
300 sciChartSurface.modifierAnnotations.add(tooltipAnnotation);
301
302 let currentTooltipAnimation: GenericAnimation<number>;
303 const animateTooltip = () => {
304 currentTooltipAnimation?.cancel();
305 tooltipAnnotation.isHidden = true;
306 currentTooltipAnimation = new GenericAnimation<number>({
307 from: 0,
308 to: 1,
309 duration: 0,
310 delay: 500,
311 ease: easing.linear,
312 onAnimate: (from: number, to: number, progress) => {},
313 onCompleted: () => {
314 tooltipAnnotation.isHidden = false;
315 },
316 });
317 sciChartSurface.addAnimation(currentTooltipAnimation);
318 };
319
320 const annotationHoverModifier = new AnnotationHoverModifier({
321 // check hover on provided annotations
322 targets,
323 // ignore tooltip annotation if it is overlapping with other
324 hoverMode: EHoverMode.TopmostIncluded,
325 // needed to update tooltip position when moving the cursor within an annotation
326 notifyPositionUpdate: true,
327 // manage tooltip visibility and position
328 onHover: (args) => {
329 const [hoveredAnnotation] = args.hoveredEntities as AnnotationBase[];
330 if (hoveredAnnotation) {
331 if (hoveredAnnotation.isEditable) {
332 sciChartSurface.domChartRoot.style.cursor = "grab";
333 }
334 if (hoveredAnnotation.isDraggingStarted) {
335 tooltipAnnotation.isHidden = true;
336 return;
337 }
338
339 const borders = tooltipAnnotation.getAnnotationBorders(true);
340 tooltipAnnotation.text = `${formatTime(hoveredAnnotation.x1)} - ${formatTime(hoveredAnnotation.x2)}`;
341
342 const handleAnnotationsOutsideSeriesViewRect = true;
343 const translatedMousePoint = translateFromCanvasToSeriesViewRect(
344 args.mouseArgs.mousePoint,
345 sciChartSurface.seriesViewRect,
346 handleAnnotationsOutsideSeriesViewRect
347 );
348 tooltipAnnotation.x1 = translateToNotScaled(translatedMousePoint.x);
349 tooltipAnnotation.y1 = translateToNotScaled(translatedMousePoint.y);
350
351 // initial default offset from pointer
352 tooltipAnnotation.xCoordShift = 20;
353 const width = Math.abs(borders.x2 - borders.x1);
354 const expectedX2Coordinate = tooltipAnnotation.x1 + tooltipAnnotation.xCoordShift + width;
355 const unscaledViewWidth = translateToNotScaled(sciChartSurface.seriesViewRect.width);
356 if (expectedX2Coordinate > unscaledViewWidth) {
357 tooltipAnnotation.xCoordShift = unscaledViewWidth - width - tooltipAnnotation.x1;
358 }
359
360 animateTooltip();
361 } else {
362 sciChartSurface.domChartRoot.style.cursor = "auto";
363 tooltipAnnotation.isHidden = true;
364 currentTooltipAnimation?.cancel();
365 }
366 },
367 });
368
369 sciChartSurface.chartModifiers.add(annotationHoverModifier);
370}
371
372function formatTime(timestamp: number) {
373 const date = new Date(timestamp * 1000);
374 const hours = date.getHours().toString().padStart(2, "0");
375 const minutes = date.getMinutes().toString().padStart(2, "0");
376 return `${hours}:${minutes}`;
377}
378This example demonstrates responsive HTML annotations in SciChart.js, creating an interactive schedule visualization with time-bound annotations. It showcases HtmlCustomAnnotation and HtmlTextAnnotation to display employee schedules with CSS container queries for responsive text layout.
The implementation uses CategoryAxis with SmartDateLabelProvider for time-based data. Annotations are positioned using ECoordinateMode with responsive CSS that adapts text orientation based on container size. An AnnotationHoverModifier provides interactive tooltips with time formatting.
Key features include: dynamic annotation sizing, CSS-based responsive text layout, interactive hover tooltips, and multi-lane schedule visualization. The example leverages modern CSS features like container queries and viewport units for responsive behavior.
The implementation shows best practices for time-based data visualization, including proper axis configuration and annotation layering using EAnnotationLayer. Performance is maintained by limiting DOM updates during interactions.

Create a JavaScript Histogram Chart with custom texture fills and patterns. Try the SciChart.js library for seamless integration today.

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

Create a JavaScript 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 JavaScript Multi-Layer Map Example, using FastTriangleRenderableSeries with GeoJSON data-points using a constrained delaunay triangulation algorithm.

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

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

Build a JavaScript Waterfall Chart with dynamic coloring, multi-line data labels and responsive design. Try SciChart.js for seamless integration today.

Try the JavaScript Box-Plot Chart examples with developer-friendly chart lifecycle management, dynamic sub-surface positioning, and custom styling.

Create JavaScript 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 JavaScript Treemap Chart to define rectangle positions based on total value. Use SciChart FastRectangleRenderableSeries and d3-hierarchy.js layouts.

Design a highly dynamic JavaScript 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 JavaScript Linear Gauge Chart example to combine rectangles & annotations. Create a linear gauge dashboard with animated indicators and custom scales.

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

JavaScript HTML Chart Control example demonstrates advanced HTML annotation integration and how to render HTML components within charts. Try the SciChart demo.

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