Timeseries Chart
@cloudflare/kumo

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";

/**
 * 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";

/**
 * 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";

/**
 * 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";

/**
 * 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.color(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";

/**
 * 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.color(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()}`,
        );
      }}
    />
  );
}

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";

/**
 * Timeseries chart rendered as a stacked bar chart.
 */
export function BarChartDemo() {
  const isDarkMode = useIsDarkMode();

  const data = useMemo(
    () => [
      {
        name: "Requests",
        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"
    />
  );
}

Legend Highlight

Hovering a legend item highlights the corresponding series on the chart and fades the others. Use onPointerEnter and onPointerLeave on ChartLegend items together with dispatchAction on the chart ref.

Read latency
P99
124ms
P95
76ms
P75
32ms
P50
10ms
import { ChartPalette, TimeseriesChart, ChartLegend, LayerCard } from "@cloudflare/kumo";
import * as echarts from "echarts/core";
import { useMemo, useRef, useState } from "react";

/**
 * Timeseries chart with legend items that highlight the corresponding series on hover.
 * Hovering a legend item dispatches a highlight action to the chart and fades the other legend items.
 */
export function LegendHighlightDemo() {
  const isDarkMode = useIsDarkMode();
  const chartRef = useRef<echarts.ECharts>(null);
  const [hoveredSeries, setHoveredSeries] = useState<string | null>(null);

  const series = useMemo(
    () => [
      {
        name: "P99",
        color: ChartPalette.semantic("Attention", isDarkMode),
        value: "124",
        unit: "ms",
      },
      {
        name: "P95",
        color: ChartPalette.semantic("Warning", isDarkMode),
        value: "76",
        unit: "ms",
      },
      {
        name: "P75",
        color: ChartPalette.semantic("Neutral", isDarkMode),
        value: "32",
        unit: "ms",
      },
      {
        name: "P50",
        color: ChartPalette.semantic("NeutralLight", isDarkMode),
        value: "10",
        unit: "ms",
      },
    ],
    [isDarkMode],
  );

  const data = useMemo(
    () =>
      series.map((s, i) => ({
        name: s.name,
        data: buildSeriesData(3 - i, 30, 60_000, 1 - i * 0.2),
        color: s.color,
      })),
    [series],
  );

  return (
    <LayerCard>
      <LayerCard.Secondary>Read latency</LayerCard.Secondary>
      <LayerCard.Primary>
        <div className="flex divide-x divide-kumo-line gap-4 px-2 mb-2">
          {series.map((s) => (
            <ChartLegend.LargeItem
              key={s.name}
              name={s.name}
              color={s.color}
              value={s.value}
              unit={s.unit}
              inactive={hoveredSeries !== null && hoveredSeries !== s.name}
              onPointerEnter={() => {
                setHoveredSeries(s.name);
                chartRef.current?.dispatchAction({
                  type: "highlight",
                  seriesName: s.name,
                });
              }}
              onPointerLeave={() => {
                setHoveredSeries(null);
                chartRef.current?.dispatchAction({
                  type: "downplay",
                  seriesName: s.name,
                });
              }}
            />
          ))}
        </div>
        <TimeseriesChart
          ref={chartRef}
          xAxisName="Time (UTC)"
          echarts={echarts}
          isDarkMode={isDarkMode}
          data={data}
          height={300}
        />
      </LayerCard.Primary>
    </LayerCard>
  );
}

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";

/**
 * 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>
  );
}