Demonstrates how to customize specific label formats on a High Precision Date Axis, using external libraries like date-fns.
drawExample.ts
index.tsx
theme.ts
1import {
2 DateTimeNumericAxis,
3 EllipsePointMarker,
4 MouseWheelZoomModifier,
5 ZoomExtentsModifier,
6 ZoomPanModifier,
7 XyDataSeries,
8 NumericAxis,
9 FastLineRenderableSeries,
10 SciChartSurface,
11 EAutoRange,
12 SmartDateLabelProvider,
13 EResamplingMode,
14 EDatePrecision,
15 EHighPrecisionLabelMode,
16 ETradeChartLabelFormat,
17 NumberRange,
18 RubberBandXyZoomModifier,
19 NativeTextAnnotation,
20 EHorizontalAnchorPoint,
21 ECoordinateMode,
22} from "scichart";
23import { format, fromUnixTime } from "date-fns";
24
25const toUTC = (date: Date) => {
26 // SciChart internally uses UTC dates, so convert to UTC
27 return new Date(date.valueOf() + date.getTimezoneOffset() * 60 * 1000);
28};
29
30export const drawExample = async (rootElement: string | HTMLDivElement) => {
31 const { wasmContext, sciChartSurface } = await SciChartSurface.create(rootElement);
32
33 const startDate = new Date("2025-01-01T00:00:00Z");
34 const startTimeSeconds = startDate.getTime() / 1000;
35
36 const xAxis = new DateTimeNumericAxis(wasmContext, {
37 axisTitle: "Time",
38 datePrecision: EDatePrecision.Nanoseconds, // Very important -> 1 x-value increment == 1 nanosecond
39 highPrecisionLabelMode: EHighPrecisionLabelMode.Suffix,
40 dateOffset: startTimeSeconds, // in seconds
41
42 showWiderDateOnFirstLabel: true,
43 showYearOnWiderDate: true,
44
45 splitWideDateWithComma: false,
46
47 showSecondsOnWideDate: true, // Usually these 2 should be opposites: when wide date shows seconds, precise date shouldn't.
48 showSecondsOnPreciseDate: true,
49 });
50 sciChartSurface.xAxes.add(xAxis);
51
52 const labelProvider = xAxis.labelProvider as SmartDateLabelProvider;
53
54 // 1. Capture the original SciChart implementations
55 const defaultFormatDateWide = labelProvider.formatDateWide.bind(labelProvider);
56 const defaultFormatDatePrecise = labelProvider.formatDatePrecise.bind(labelProvider);
57
58 // 2. Define the Custom "date-fns" implementations
59 // Wide dates -> e.g. "Jan 01, 2025 12:00:00"
60 const customFormatDateWide = (labelRange: ETradeChartLabelFormat | string, valueInSeconds: number) => {
61 const date = toUTC(fromUnixTime(valueInSeconds));
62 if (
63 labelRange === ETradeChartLabelFormat.Nanoseconds ||
64 labelRange === ETradeChartLabelFormat.Microseconds ||
65 labelRange === ETradeChartLabelFormat.MilliSeconds
66 ) {
67 return format(date, "MMM dd, yyyy HH:mm:ss");
68 } else if (labelRange === ETradeChartLabelFormat.Seconds || labelRange === ETradeChartLabelFormat.Minutes) {
69 return format(date, "MMM dd, yyyy");
70 } else if (labelRange === ETradeChartLabelFormat.Days) {
71 return format(date, "MMM yyyy");
72 } else {
73 return format(date, "yyyy");
74 }
75 };
76
77 // Precise dates -> e.g. "12:59:59" or "03s12345ns"
78 const customFormatDatePrecise = (
79 labelRange: ETradeChartLabelFormat | string,
80 valueInSeconds: number,
81 rawValue?: number
82 ) => {
83 const date = toUTC(fromUnixTime(valueInSeconds));
84
85 // High precision logic
86 if (
87 labelRange === ETradeChartLabelFormat.Nanoseconds ||
88 labelRange === ETradeChartLabelFormat.Microseconds ||
89 labelRange === ETradeChartLabelFormat.MilliSeconds
90 ) {
91 const mode = labelProvider.highPrecisionLabelMode;
92
93 if (mode === EHighPrecisionLabelMode.Suffix && rawValue !== undefined) {
94 const tps = labelProvider.datePrecision;
95 const wholeSeconds = Math.floor(rawValue / tps);
96 const ticksWithinSecond = rawValue - wholeSeconds * tps;
97 const subSecondOffset = ticksWithinSecond / tps;
98
99 const seconds = date.getUTCSeconds();
100 const secondsStr = seconds.toString().padStart(2, "0");
101
102 if (labelRange === ETradeChartLabelFormat.Nanoseconds) {
103 const ns = Math.round(subSecondOffset * 1_000_000_000);
104 return `${secondsStr}:${ns}ns`;
105 }
106 if (labelRange === ETradeChartLabelFormat.Microseconds) {
107 const us = Math.round(subSecondOffset * 1_000_000);
108 return `${secondsStr}:${us}µs`;
109 }
110 const ms = Math.round(subSecondOffset * 1_000);
111 return `${secondsStr}:${ms}ms`;
112 }
113 return format(date, "ss.SSS");
114 }
115
116 if (labelRange === ETradeChartLabelFormat.Seconds) return format(date, "HH:mm:ss");
117 if (labelRange === ETradeChartLabelFormat.Minutes) return format(date, "HH:mm");
118 if (labelRange === ETradeChartLabelFormat.Days || labelRange === ETradeChartLabelFormat.Months)
119 return format(date, "dd");
120
121 return format(date, "dd/MM/yy");
122 };
123
124 // 3. Logic to toggle between them
125 const setUseDateFns = (useCustom: boolean) => {
126 if (useCustom) {
127 labelProvider.formatDateWide = customFormatDateWide;
128 labelProvider.formatDatePrecise = customFormatDatePrecise;
129 } else {
130 labelProvider.formatDateWide = defaultFormatDateWide;
131 labelProvider.formatDatePrecise = defaultFormatDatePrecise;
132 }
133 // Force redraw to apply changes to existing labels
134 sciChartSurface.invalidateElement();
135 };
136
137 // Apply custom by default initially
138 setUseDateFns(true);
139
140 sciChartSurface.yAxes.add(
141 new NumericAxis(wasmContext, {
142 axisTitle: "Signal (mV)",
143 autoRange: EAutoRange.Always,
144 growBy: new NumberRange(0.1, 0.1),
145 })
146 );
147
148 // Data Generation
149 const xValues: number[] = [];
150 const yValues: number[] = [];
151 type Generator = (i: number) => number;
152
153 const addCluster = (startOffsetNano: number, count: number, generator: Generator) => {
154 for (let i = 0; i < count; i++) {
155 const x = startOffsetNano + i * 10_000_000;
156 const noise = (Math.random() - 0.5) * 0.05;
157 const y = generator(i) + noise;
158
159 xValues.push(x);
160 yValues.push(y);
161 }
162 };
163
164 const ONE_SEC = 1_000_000_000; // Because we use `datePrecision = Nanoseconds`
165 const ONE_MIN = ONE_SEC * 60;
166
167 // 1. Damped Ringing
168 addCluster(0, 2000, (i) => Math.sin(i * 0.1) * Math.exp(-i * 0.002) * 2);
169
170 // 2. Frequency Chirp
171 addCluster(1 * ONE_MIN, 2000, (i) => {
172 const freq = 0.01 + i * 0.00005;
173 return Math.sin(i * freq);
174 });
175
176 // 3. Amplitude Modulated Packet
177 addCluster(3 * ONE_MIN, 2000, (i) => {
178 const carrier = Math.sin(i * 0.1);
179 const envelope = Math.sin(i * 0.03); // Slow envelope
180 return carrier * envelope * 1.5;
181 });
182
183 // 4. Noisy "Heartbeat"
184 addCluster(4 * ONE_MIN, 2000, (i) => {
185 // spike every 200 points
186 if (i % 200 < 20) return 1.0 + Math.random();
187 return Math.random() * 0.2;
188 });
189
190 const lineSeries = new FastLineRenderableSeries(wasmContext, {
191 dataSeries: new XyDataSeries(wasmContext, {
192 xValues,
193 yValues,
194 containsNaN: false,
195 isSorted: true,
196 dataSeriesName: "Sensor A",
197 }),
198 stroke: "#50C7E0",
199 strokeThickness: 2,
200 resamplingMode: EResamplingMode.None,
201 pointMarker: new EllipsePointMarker(wasmContext, {
202 width: 4,
203 height: 4,
204 fill: "#2e3a59",
205 stroke: "#50C7E0",
206 strokeThickness: 1,
207 }),
208 });
209 lineSeries.rolloverModifierProps.tooltipLabelX = "X";
210 sciChartSurface.renderableSeries.add(lineSeries);
211
212 sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier(), new ZoomExtentsModifier(), new ZoomPanModifier());
213 sciChartSurface.zoomExtents();
214
215 sciChartSurface.annotations.add(
216 new NativeTextAnnotation({
217 xCoordinateMode: ECoordinateMode.Relative,
218 yCoordinateMode: ECoordinateMode.Relative,
219 x1: 0.5,
220 y1: 0.08,
221 horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
222 text: `Zoom in & out while toggling the switch to see different axis label formats
223 \nSee how you can also customize date formatting at lines 60, 80 in drawExample.ts!`,
224 lineSpacing: 10,
225 fontSize: 16,
226 opacity: 0.7,
227 textColor: "#FFFFFF",
228 })
229 );
230
231 return {
232 wasmContext,
233 sciChartSurface,
234 controls: { setUseDateFns },
235 };
236};
237This example demonstrates how to use DateTimeNumericAxis with nanosecond-precision data and the built-in SmartDateLabelProvider, while safely overriding date formatting logic using date-fns.
It highlights how SciChart dynamically switches between wide and precise date labels as you zoom, and how you can fully customize that behavior without breaking cursor labels, tick calculations, or high-precision rendering.
Use the toggle above the chart to switch between SciChart’s default formatting and a custom date-fns implementation.
The X-axis is configured with:
datePrecision = EDatePrecision.NanosecondsdateOffset (Unix seconds)This allows raw X values to represent nanosecond ticks, while still displaying full calendar dates and times on the axis. Each increment of 1 on the X-axis equals 1 nanosecond, making this suitable for high-frequency sensor data, trading systems, or scientific signals.
The SmartDateLabelProvider automatically adapts label formats based on the visible time range:
Jan 01, 2025 12:00:00)59s345ms, 123456ns)As you zoom in and out, the provider dynamically switches between: Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Days / Months.
This logic is driven by internal label thresholds and the current visible range.
This demo overrides two key methods on the SmartDateLabelProvider:
formatDateWideformatDatePreciseInstead of replacing the entire label provider, the original SciChart implementations are preserved and can be restored instantly. This ensures:
The toggle switches between:
This pattern is recommended when integrating third-party date libraries.
Use DateTimeNumericAxis with SmartDateLabelProvider when:
This pattern is ideal for high-frequency trading, telemetry, scientific measurement, and real-time monitoring applications.
Related Documentation:

Demonstrates how to use arbitrary text for axis labels, rather than formatted data values, using the new TextLabelProvider

Demonstrates how to use Images as Axis Labels

Rotate to create vertical axis labels and fit more on an axis