The timeseries chart is a specialized chart for displaying time-based data.
Each data point is a tuple of [timestamp_in_ms, value].
Basic Line Chart
A simple line chart displaying multiple data series over time.
import { ChartPalette, TimeseriesChart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useMemo } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* Basic line chart example showing simple time-based data visualization.
*/
export function BasicLineChartDemo() {
const isDarkMode = useIsDarkMode();
const data = useMemo(
() => [
{
name: "Requests",
data: buildSeriesData(0, 50, 60_000, 1),
color: ChartPalette.semantic("Neutral", isDarkMode),
},
{
name: "Errors",
data: buildSeriesData(1, 50, 60_000, 0.3),
color: ChartPalette.semantic("Attention", isDarkMode),
},
],
[isDarkMode],
);
return (
<TimeseriesChart
echarts={echarts}
isDarkMode={isDarkMode}
data={data}
xAxisName="Time (UTC)"
yAxisName="Count"
/>
);
} Custom X-Axis Label Format
Use the xAxisTickLabelFormat prop to control how x-axis tick
labels are rendered. The formatter receives the raw timestamp in milliseconds
and returns a display string, overriding ECharts’ built-in time formatting.
import { ChartPalette, TimeseriesChart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useMemo } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* Timeseries chart with custom axis tick label formats for both x-axis (HH:MM) and y-axis (compact numbers).
*/
export function CustomAxisLabelFormatDemo() {
const isDarkMode = useIsDarkMode();
const data = useMemo(
() => [
{
name: "Requests",
data: buildSeriesData(0, 50, 60_000, 1000),
color: ChartPalette.semantic("Neutral", isDarkMode),
},
],
[isDarkMode],
);
return (
<TimeseriesChart
echarts={echarts}
isDarkMode={isDarkMode}
data={data}
xAxisName="Time (UTC)"
yAxisName="Requests"
xAxisTickFormat={(ts) => {
const d = new Date(ts);
return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}`;
}}
yAxisTickFormat={(value) => {
if (value >= 1000) return `${value / 1000}k`;
return value.toString();
}}
tooltipValueFormat={(value) => `${(value / 1000).toFixed(1)}k requests`}
/>
);
} Gradient Fill
Set gradient to true to render a vertical gradient
fill beneath each line series. The fill fades from the series color at the top
to transparent at the bottom, giving the chart a polished area-chart look
without losing the clarity of individual lines.
import { ChartPalette, TimeseriesChart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useMemo } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* Timeseries chart with gradient fill beneath each line series.
*/
export function GradientLineChartDemo() {
const isDarkMode = useIsDarkMode();
const data = useMemo(
() => [
{
name: "Requests",
data: buildSeriesData(0, 50, 60_000, 1),
color: ChartPalette.semantic("Neutral", isDarkMode),
},
{
name: "Errors",
data: buildSeriesData(1, 50, 60_000, 0.3),
color: ChartPalette.semantic("Attention", isDarkMode),
},
],
[isDarkMode],
);
return (
<TimeseriesChart
echarts={echarts}
isDarkMode={isDarkMode}
data={data}
xAxisName="Time (UTC)"
yAxisName="Count"
gradient
/>
);
} Incomplete Data
Use the incomplete prop to indicate regions where data may be
incomplete or still being collected.
import { ChartPalette, TimeseriesChart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useMemo } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* Timeseries chart with incomplete data regions highlighted.
*/
export function IncompleteDataChartDemo() {
const isDarkMode = useIsDarkMode();
const data = useMemo(
() => [
{
name: "Bandwidth",
data: buildSeriesData(0, 50, 60_000, 1),
color: ChartPalette.categorical(0, isDarkMode),
},
],
[isDarkMode],
);
const incompleteTimestamp = data[0].data[data[0].data.length - 5][0];
return (
<TimeseriesChart
echarts={echarts}
isDarkMode={isDarkMode}
data={data}
xAxisName="Time (UTC)"
yAxisName="Mbps"
incomplete={{ after: incompleteTimestamp }}
/>
);
} Time Range Selection
Enable time range selection by providing the onTimeRangeChange
callback. Users can click and drag on the chart to select a time range.
import { ChartPalette, TimeseriesChart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useMemo } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* Timeseries chart with time range selection enabled.
*/
export function TimeRangeSelectionChartDemo() {
const isDarkMode = useIsDarkMode();
const data = useMemo(
() => [
{
name: "CPU Usage",
data: buildSeriesData(0, 50, 60_000, 1),
color: ChartPalette.categorical(0, isDarkMode),
},
],
[isDarkMode],
);
return (
<TimeseriesChart
echarts={echarts}
isDarkMode={isDarkMode}
data={data}
xAxisName="Time (UTC)"
yAxisName="%"
onTimeRangeChange={(from, to) => {
alert(
`Selected range:\nFrom: ${new Date(from).toLocaleString()}\nTo: ${new Date(to).toLocaleString()}`,
);
}}
/>
);
} Tooltip Cursor Tracking
Use the tooltipFollowCursor prop to control which axis the tooltip
tracks the cursor on. The default is “both”, which follows the
cursor freely. Set it to “x” for a Recharts-style axis-locked
tooltip that only moves horizontally.
import { ChartPalette, TimeseriesChart, Select } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useMemo, useState } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* Interactive demo showing the `tooltipFollowCursor` prop. Use the dropdown to
* switch between cursor-tracking modes and see how the tooltip behaves.
*/
export function TooltipFollowCursorDemo() {
const isDarkMode = useIsDarkMode();
const [selected, setSelected] = useState<FollowCursorOption>(FOLLOW_CURSOR_OPTIONS[0]);
const data = useMemo(
() => [
{
name: "P99",
data: buildSeriesData(0, 50, 60_000, 1),
color: ChartPalette.semantic("Attention", isDarkMode),
},
{
name: "P50",
data: buildSeriesData(1, 50, 60_000, 0.4),
color: ChartPalette.semantic("Neutral", isDarkMode),
},
],
[isDarkMode],
);
return (
<div className="flex w-full flex-col gap-4">
<Select
label="Tooltip follow cursor"
value={selected}
onValueChange={(v) => { if (v) setSelected(v); }}
renderValue={(v) => v.label}
>
{FOLLOW_CURSOR_OPTIONS.map((opt) => (
<Select.Option key={opt.value} value={opt}>
{opt.label}
</Select.Option>
))}
</Select>
<TimeseriesChart
echarts={echarts}
isDarkMode={isDarkMode}
data={data}
xAxisName="Time (UTC)"
yAxisName="Latency (ms)"
tooltipFollowCursor={selected.value}
/>
</div>
);
} Tooltip Boundary
Use the tooltipBoundary prop to constrain the tooltip to a
specific container element. By default the tooltip avoids overflowing any
clipping ancestor (scroll containers, viewports). Pass a DOM element to
restrict it further — useful when the chart lives inside a card or panel
and the tooltip shouldn’t escape it.
import { ChartPalette, TimeseriesChart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useCallback, useMemo, useState } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* Demo showing the `tooltipBoundary` prop. The chart is inside a small
* scrollable container — the tooltip is constrained to stay within it
* instead of overflowing into the surrounding page.
*/
export function TooltipBoundaryDemo() {
const isDarkMode = useIsDarkMode();
const [boundary, setBoundary] = useState<HTMLDivElement | null>(null);
const boundaryRef = useCallback((el: HTMLDivElement | null) => setBoundary(el), []);
const data = useMemo(
() => [
{
name: "Requests",
data: buildSeriesData(0, 50, 60_000, 1),
color: ChartPalette.semantic("Neutral", isDarkMode),
},
{
name: "Errors",
data: buildSeriesData(1, 50, 60_000, 0.3),
color: ChartPalette.semantic("Attention", isDarkMode),
},
],
[isDarkMode],
);
return (
<div
ref={boundaryRef}
className="w-full overflow-auto rounded-lg border border-kumo-line"
style={{ height: 300 }}
>
<TimeseriesChart
echarts={echarts}
isDarkMode={isDarkMode}
data={data}
xAxisName="Time (UTC)"
yAxisName="Count"
height={280}
tooltipBoundary={boundary ?? undefined}
/>
</div>
);
} Bar Chart
Set type=“bar” to render series as stacked bars instead of lines.
All other props — axes, tooltips, colors — work identically.
import { ChartPalette, TimeseriesChart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useMemo } from "react";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* Timeseries chart rendered as a stacked bar chart.
*/
export function BarChartDemo() {
const isDarkMode = useIsDarkMode();
const data = useMemo(
() => [
{
name: "Requests where age > 10",
data: buildSeriesData(0, 20, 3_600_000, 1),
color: ChartPalette.semantic("Neutral", isDarkMode),
},
{
name: "Errors",
data: buildSeriesData(1, 20, 3_600_000, 0.3),
color: ChartPalette.semantic("Attention", isDarkMode),
},
],
[isDarkMode],
);
return (
<TimeseriesChart
echarts={echarts}
isDarkMode={isDarkMode}
type="bar"
data={data}
xAxisName="Time (UTC)"
yAxisName="Count"
tooltipValueFormat={(r) => r.toFixed(2)}
/>
);
} Loading State
Set loading to true to display an animated sine-wave
skeleton while data is being fetched. The chart canvas is hidden until loading
completes; swap back to loading={false} to reveal the chart.
import { TimeseriesChart } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useIsDarkMode } from "~/lib/use-is-dark-mode";
/**
* Timeseries chart in loading state, showing the animated sine-wave skeleton.
* Loads for 5 seconds then reveals the real chart. A button restarts the cycle.
*/
export function LoadingChartDemo() {
const isDarkMode = useIsDarkMode();
return (
<div className="flex flex-col flex-1 w-full">
<TimeseriesChart
echarts={echarts}
isDarkMode={isDarkMode}
xAxisName="Time (UTC)"
yAxisName="Count"
data={[]}
loading
/>
</div>
);
}