React Responsive HTML Annotations Example

Demonstrates how to use the HtmlCustomAnnotation to create responsive text content using SciChart.js, High Performance JavaScript Charts

Fullscreen

Edit

 Edit

Docs

drawExample.ts

index.tsx

theme.ts

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

Responsive HTML Annotations Example - React

Overview

This React example demonstrates responsive schedule visualization using SciChart's HTML annotations. It combines SciChart's high-performance rendering with React's component model to create an interactive employee schedule with time-bound annotations.

Technical Implementation

The chart is initialized via SciChartReact component. Annotations use HtmlCustomAnnotation with advanced CSS container queries for responsive text layout. The implementation includes custom hover behavior using AnnotationHoverModifier with smooth animations.

Features and Capabilities

Notable features include: responsive text orientation changes, interactive time range tooltips, multi-employee schedule lanes, and CSS-powered adaptive layouts. The example showcases how to combine SciChart's annotation system with modern CSS features.

Integration and Best Practices

The solution demonstrates proper React-SciChart integration patterns, including async chart initialization and responsive design techniques. Performance considerations for DOM-based annotations are addressed through efficient event handling and CSS optimizations.

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