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 labelStyle: {
42 color: "#FFFFFF", // make them stand out a bit
43 },
44
45 showWiderDateOnFirstLabel: true,
46 showYearOnWiderDate: true,
47
48 splitWideDateWithComma: false,
49
50 showSecondsOnWideDate: true, // Usually these 2 should be opposites: when wide date shows seconds, precise date shouldn't.
51 showSecondsOnPreciseDate: true,
52 });
53 sciChartSurface.xAxes.add(xAxis);
54
55 const labelProvider = xAxis.labelProvider as SmartDateLabelProvider;
56
57 // 1. Capture the original SciChart implementations
58 const defaultFormatDateWide = labelProvider.formatDateWide.bind(labelProvider);
59 const defaultFormatDatePrecise = labelProvider.formatDatePrecise.bind(labelProvider);
60
61 // 2. Define the Custom "date-fns" implementations
62 // Wide dates -> e.g. "Jan 01, 2025 12:00:00"
63 const customFormatDateWide = (labelRange: ETradeChartLabelFormat | string, valueInSeconds: number) => {
64 const date = toUTC(fromUnixTime(valueInSeconds));
65 if (
66 labelRange === ETradeChartLabelFormat.Nanoseconds ||
67 labelRange === ETradeChartLabelFormat.Microseconds ||
68 labelRange === ETradeChartLabelFormat.MilliSeconds
69 ) {
70 return format(date, "MMM dd, yyyy HH:mm:ss");
71 } else if (labelRange === ETradeChartLabelFormat.Seconds || labelRange === ETradeChartLabelFormat.Minutes) {
72 return format(date, "MMM dd, yyyy");
73 } else if (labelRange === ETradeChartLabelFormat.Days) {
74 return format(date, "MMM yyyy");
75 } else {
76 return format(date, "yyyy");
77 }
78 };
79
80 // Precise dates -> e.g. "12:59:59" or "03s12345ns"
81 const customFormatDatePrecise = (
82 labelRange: ETradeChartLabelFormat | string,
83 valueInSeconds: number,
84 rawValue?: number
85 ) => {
86 const date = toUTC(fromUnixTime(valueInSeconds));
87
88 // High precision logic
89 if (
90 labelRange === ETradeChartLabelFormat.Nanoseconds ||
91 labelRange === ETradeChartLabelFormat.Microseconds ||
92 labelRange === ETradeChartLabelFormat.MilliSeconds
93 ) {
94 const mode = labelProvider.highPrecisionLabelMode;
95
96 if (mode === EHighPrecisionLabelMode.Suffix && rawValue !== undefined) {
97 const tps = labelProvider.datePrecision;
98 const wholeSeconds = Math.floor(rawValue / tps);
99 const ticksWithinSecond = rawValue - wholeSeconds * tps;
100 const subSecondOffset = ticksWithinSecond / tps;
101
102 const seconds = date.getUTCSeconds();
103 const secondsStr = seconds.toString().padStart(2, "0");
104
105 if (labelRange === ETradeChartLabelFormat.Nanoseconds) {
106 const ns = Math.round(subSecondOffset * 1_000_000_000);
107 return `${secondsStr}:${ns}ns`;
108 }
109 if (labelRange === ETradeChartLabelFormat.Microseconds) {
110 const us = Math.round(subSecondOffset * 1_000_000);
111 return `${secondsStr}:${us}µs`;
112 }
113 const ms = Math.round(subSecondOffset * 1_000);
114 return `${secondsStr}:${ms}ms`;
115 }
116 return format(date, "ss.SSS");
117 }
118
119 if (labelRange === ETradeChartLabelFormat.Seconds) return format(date, "HH:mm:ss");
120 if (labelRange === ETradeChartLabelFormat.Minutes) return format(date, "HH:mm");
121 if (labelRange === ETradeChartLabelFormat.Days || labelRange === ETradeChartLabelFormat.Months)
122 return format(date, "dd");
123
124 return format(date, "dd/MM/yy");
125 };
126
127 // 3. Logic to toggle between them
128 const setUseDateFns = (useCustom: boolean) => {
129 if (useCustom) {
130 labelProvider.formatDateWide = customFormatDateWide;
131 labelProvider.formatDatePrecise = customFormatDatePrecise;
132 } else {
133 labelProvider.formatDateWide = defaultFormatDateWide;
134 labelProvider.formatDatePrecise = defaultFormatDatePrecise;
135 }
136 // Force redraw to apply changes to existing labels
137 sciChartSurface.invalidateElement();
138 };
139
140 // Apply custom by default initially
141 setUseDateFns(true);
142
143 sciChartSurface.yAxes.add(
144 new NumericAxis(wasmContext, {
145 axisTitle: "Signal (mV)",
146 autoRange: EAutoRange.Always,
147 growBy: new NumberRange(0.1, 0.1),
148 })
149 );
150
151 // Data Generation
152 const xValues: number[] = [];
153 const yValues: number[] = [];
154 type Generator = (i: number) => number;
155
156 const addCluster = (startOffsetNano: number, count: number, generator: Generator) => {
157 for (let i = 0; i < count; i++) {
158 const x = startOffsetNano + i * 10_000_000;
159 const noise = (Math.random() - 0.5) * 0.05;
160 const y = generator(i) + noise;
161
162 xValues.push(x);
163 yValues.push(y);
164 }
165 };
166
167 const ONE_SEC = 1_000_000_000; // Because we use `datePrecision = Nanoseconds`
168 const ONE_MIN = ONE_SEC * 60;
169
170 // 1. Damped Ringing
171 addCluster(0, 2000, (i) => Math.sin(i * 0.1) * Math.exp(-i * 0.002) * 2);
172
173 // 2. Frequency Chirp
174 addCluster(1 * ONE_MIN, 2000, (i) => {
175 const freq = 0.01 + i * 0.00005;
176 return Math.sin(i * freq);
177 });
178
179 // 3. Amplitude Modulated Packet
180 addCluster(3 * ONE_MIN, 2000, (i) => {
181 const carrier = Math.sin(i * 0.1);
182 const envelope = Math.sin(i * 0.03); // Slow envelope
183 return carrier * envelope * 1.5;
184 });
185
186 // 4. Noisy "Heartbeat"
187 addCluster(4 * ONE_MIN, 2000, (i) => {
188 // spike every 200 points
189 if (i % 200 < 20) return 1.0 + Math.random();
190 return Math.random() * 0.2;
191 });
192
193 const lineSeries = new FastLineRenderableSeries(wasmContext, {
194 dataSeries: new XyDataSeries(wasmContext, {
195 xValues,
196 yValues,
197 containsNaN: false,
198 isSorted: true,
199 dataSeriesName: "Sensor A",
200 }),
201 stroke: "#50C7E0",
202 strokeThickness: 2,
203 resamplingMode: EResamplingMode.None,
204 pointMarker: new EllipsePointMarker(wasmContext, {
205 width: 4,
206 height: 4,
207 fill: "#2e3a59",
208 stroke: "#50C7E0",
209 strokeThickness: 1,
210 }),
211 });
212 lineSeries.rolloverModifierProps.tooltipLabelX = "X";
213 sciChartSurface.renderableSeries.add(lineSeries);
214
215 sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier(), new ZoomExtentsModifier(), new ZoomPanModifier());
216 sciChartSurface.zoomExtents();
217
218 sciChartSurface.annotations.add(
219 new NativeTextAnnotation({
220 xCoordinateMode: ECoordinateMode.Relative,
221 yCoordinateMode: ECoordinateMode.Relative,
222 x1: 0.5,
223 y1: 0.08,
224 horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
225 text: `Zoom in & out while toggling the switch to see different axis label formats
226 \nSee how you can also customize date formatting at lines 60, 80 in drawExample.ts!`,
227 lineSpacing: 10,
228 fontSize: 16,
229 opacity: 0.7,
230 textColor: "#FFFFFF",
231 })
232 );
233
234 return {
235 wasmContext,
236 sciChartSurface,
237 controls: { setUseDateFns },
238 };
239};
240This 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