Plot
A canvas-based plotting library for React, Preact, and plain JavaScript. Compose plots using built-in and custom series, with configurable axes, scales, and styles. Explore your data through tooltip, selection, and zoom interactions.
Series types
Select a series type below to see its options and examples.
Composed examples
Combine multiple series in one plot to compare values, show trends, and add context.
import { area, line, rule, scatter, type PlotArgs, type Series } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function overviewExample(): PlotArgs<Node> { // Synthetic data, sampled every 15 minutes. No randomness: edits keep the same data. const forecast = Array.from({ length: 97 }, (_, i) => { const hour = i / 4 const morning = 150 * Math.exp(-(((hour - 9) / 2.8) ** 2)) const evening = 240 * Math.exp(-(((hour - 17) / 3.4) ** 2)) const expected = 85 + morning + evening const spread = 22 + 12 * Math.sin(hour / 5) ** 2 return { hour, expected, low: expected - spread, high: expected + spread } })
const observations = forecast.map((point, i) => { const burst = i >= 61 && i <= 68 ? 65 * Math.sin((i - 60) / 9 * Math.PI) : 0 const actual = Math.round(point.expected + 13 * Math.sin(i * 1.8) + 8 * Math.cos(i * 0.7) + burst) return { ...point, actual, outside: actual > point.high || actual < point.low } })
const trend = observations.map((point, i) => { const window = observations.slice(Math.max(0, i - 3), Math.min(observations.length, i + 4)) return { hour: point.hour, average: window.reduce((sum, row) => sum + row.actual, 0) / window.length } })
const meanExpected = forecast.reduce((sum, point) => sum + point.expected, 0) / forecast.length
const series: Series<Node>[] = [ area<Node>({ data: forecast, x: 'hour', y: 'high', y2: 'low', color: { light: '#ede9fe', dark: '#30274e' }, hoverable: false, }), rule<Node>({ y: meanExpected, color: { light: '#d97706', dark: '#fbbf24' }, width: 2, dash: [8, 4], hoverable: false, }), line<Node>({ data: forecast, x: 'hour', y: 'expected', color: { light: '#8b5cf6', dark: '#c4b5fd' }, width: 2, dash: [5, 5], tooltip: ({ x, y }) => document.createTextNode('Forecast at ' + x + 'h: ' + Math.round(Number(y)) + ' requests/s'), }), line<Node>({ data: trend, x: 'hour', y: 'average', color: { light: '#0891b2', dark: '#67e8f9' }, width: 3, tooltip: ({ x, y }) => document.createTextNode('Trend at ' + x + 'h: ' + Math.round(Number(y)) + ' requests/s'), }), scatter<Node>({ data: observations.filter(point => !point.outside), x: 'hour', y: 'actual', color: { light: '#0891b2', dark: '#67e8f9' }, size: 4, tooltip: ({ x, y }) => document.createTextNode('Observed at ' + x + 'h: ' + y + ' requests/s'), }), scatter<Node>({ data: observations.filter(point => point.outside), x: 'hour', y: 'actual', color: { light: '#e76f51', dark: '#fda489' }, mark: 'diamond', size: 9, stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 1 }, tooltip: ({ x, y }) => document.createTextNode('Outside forecast range: ' + x + 'h · ' + y + ' requests/s'), }), ]
const options = {
height: 400, border: false, chrome_color: { light: '#e2e8f0', dark: '#334155' }, background: { light: '#ffffff', dark: '#151b28' }, theme_invert: false, }
const axis = {
x: { min: 0, max: 24, label: 'Hour' },
y: { min: 0, max: 440, label: 'Requests/s' },
}
const margin = 12
const grid = false
const zoom_pan = { x: true, y: true, modifier: false }
// Replace the object to apply a change; Plot does not observe in-place mutation. const plotArgs: PlotArgs<Node> = { ...options, series, axis, margin, grid, zoom_pan }
return plotArgs}
const plot = document.createElement('a-plot') as APlotElementconst args = overviewExample()plot.plotArgs = { ...args, height: 280, margin: { top: 12, right: 16, bottom: 42, left: 74 }, zoom_pan: false,}document.body.append(plot)import { bar, scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function energyExample(): PlotArgs<Node> { const data = [ { x: 'A', solar: 28, wind: 35, demand: 72 }, { x: 'B', solar: 48, wind: 18, demand: 54 }, { x: 'C', solar: 19, wind: 25, demand: 62 }, { x: 'D', solar: 36, wind: 42, demand: 69 }, { x: 'E', solar: 52, wind: 12, demand: 78 }, { x: 'F', solar: 24, wind: 30, demand: 46 }, { x: 'G', solar: 41, wind: 28, demand: 58 }, { x: 'H', solar: 16, wind: 22, demand: 51 }, ] return { series: [ bar<Node>({ data, y: ['solar', 'wind'], color: [{ light: '#fbbf24', dark: '#d97706' }, { light: '#2dd4bf', dark: '#0d9488' }], inset: 5, border_radius: 3, tooltip: ({ x, y, label }) => document.createTextNode('Site ' + x + ' · ' + label + ': ' + y + ' kW') }), scatter<Node>({ data, y: 'demand', mark: 'diamond', size: 7, tooltip: ({ x, y }) => document.createTextNode('Site ' + x + ' · Demand: ' + y + ' kW'), color: { light: '#7c3aed', dark: '#c4b5fd' }, stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 1.5 } }), ], axis: { x: { label: 'Site' }, y: { min: 0, max: 90, label: 'Power (kW)' } }, background: { light: '#ffffff', dark: '#151b28' }, border: false, grid: false, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementconst args = energyExample()plot.plotArgs = { ...args, height: 280, margin: { top: 12, right: 16, bottom: 42, left: 58 }, zoom_pan: false,}document.body.append(plot)import { area, rect, line, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function latencyExample(): PlotArgs<Node> { const density = (x: number) => 48 * Math.exp(-(((x - 65) / 24) ** 2)) + 19 * Math.exp(-(((x - 135) / 38) ** 2)) const bins = Array.from({ length: 24 }, (_, i) => { const x = i * 10 return { x: x + 1, x2: x + 9, y: density(x + 5) * (0.8 + 0.3 * Math.sin(i * 2.3) ** 2), y2: 0 } }) const curve = Array.from({ length: 121 }, (_, i) => ({ x: i * 2, y: density(i * 2) })) return { series: [ area<Node>({ data: curve, y2: 0, color: { light: '#fce7f3', dark: '#452039' }, hoverable: false }), rect<Node>({ data: bins, color: { light: '#f472b6', dark: '#be185d' }, tooltip: ({ row, y }) => document.createTextNode( (Number(row.x) - 1) + '–' + (Number(row.x2) + 1) + ' ms · ' + Math.round(Number(y)) + ' requests', ) }), line<Node>({ data: curve, color: { light: '#9d174d', dark: '#fbcfe8' }, width: 2.5, tooltip: ({ x, y }) => document.createTextNode('Distribution at ' + x + ' ms: ' + Number(y).toFixed(1) + ' requests') }),
], axis: { x: { min: 0, max: 240, label: 'Latency (ms)' }, y: { min: 0, max: 60, label: 'Requests' } }, background: { light: '#ffffff', dark: '#151b28' }, border: false, grid: false, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementconst args = latencyExample()plot.plotArgs = { ...args, height: 280, margin: { top: 12, right: 16, bottom: 42, left: 58 }, zoom_pan: false,}document.body.append(plot)import { area, rect, line, scatter, rule, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function mixedExample(): PlotArgs<Node> { const points = [ { x: 12, y: 74 }, { x: 25, y: 57 }, { x: 39, y: 85 }, { x: 48, y: 18 }, { x: 62, y: 70 }, { x: 73, y: 37 }, { x: 86.5, y: 86 }, { x: 92, y: 22 }, { x: 15, y: 27 }, { x: 36, y: 64 }, { x: 57, y: 88 }, ] const paths = [ [ { x: 25, y: 0 }, { x: 36, y: 19 }, { x: 41, y: 15 }, { x: 56, y: 37 }, { x: 65, y: 23 }, { x: 72, y: 48 }, { x: 76, y: 44 }, { x: 93, y: 63 }, { x: 100, y: 68 }, ], [ { x: 0, y: 73 }, { x: 9, y: 87 }, { x: 13, y: 84 }, { x: 20, y: 92 }, { x: 29, y: 79 }, { x: 39, y: 96 }, { x: 42, y: 93 }, { x: 48, y: 100 }, ], ] // Sample cosine transitions to round each turn without changing its height. const smoothPaths = paths.map(path => path.flatMap((point, i) => { const next = path[i + 1] if (!next) return [point] return Array.from({ length: 16 }, (_, j) => { const t = j / 16 const blend = (1 - Math.cos(Math.PI * t)) / 2 return { x: point.x + (next.x - point.x) * t, y: point.y + (next.y - point.y) * blend } }) })) const colors = [ { light: '#7c3aed', dark: '#c4b5fd' }, { light: '#e11d48', dark: '#fda4af' }, { light: '#0284c7', dark: '#7dd3fc' }, { light: '#d97706', dark: '#fcd34d' }, ] return { series: [ area<Node>({ data: Array.from({ length: 81 }, (_, i) => { const x = i * 1.25 const center = 50 + 7 * Math.sin(i / 9) + 3 * Math.cos(i / 4) const spread = 3 + 1.5 * Math.sin(i / 11) ** 2 return { x, y: center + spread, y2: center - spread } }), color: { light: '#99f6e4', dark: '#115e59' }, stroke: { color: { light: '#14b8a6', dark: '#5eead4' }, width: 1 } }), ...points.map((point, i) => scatter<Node>({ data: [point], size: [7, 12, 21, 10, 15, 9, 26, 13, 11, 8, 14][i], mark: 'circle', color: colors[i % colors.length], tooltip: ({ x, y }) => document.createTextNode('Point ' + (i + 1) + ' · (' + x + ', ' + y + ')'), stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 1.5 } })), rect<Node>({ data: [{ x: 32, x2: 44, y: 31, y2: 47 }], color: { light: '#ddd6fe', dark: '#5b21b6' }, stroke: { color: { light: '#8b5cf6', dark: '#c4b5fd' }, width: 1.5 } }), rect<Node>({ data: [{ x: 76, x2: 97, y: 76, y2: 96 }], color: { light: 'rgba(254, 215, 170, 0.55)', dark: 'rgba(154, 52, 18, 0.55)' }, stroke: { color: { light: '#f97316', dark: '#fdba74' }, width: 1.5 } }), ...smoothPaths.map((data, i) => line<Node>({ data, width: 2.5, dash: i === 1 ? [7, 5] : [], tooltip: ({ x, y }) => document.createTextNode( 'Line ' + (i + 1) + ' · (' + Number(x).toFixed(1) + ', ' + Number(y).toFixed(1) + ')', ), color: i % 2 === 0 ? { light: '#0d9488', dark: '#5eead4' } : { light: '#f97316', dark: '#fdba74' }, })),
rule<Node>({ y: 10, color: { light: '#0284c7', dark: '#7dd3fc' }, width: 1.5, dash: [6, 4] }), ], axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, border: false, grid: false, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementconst args = mixedExample()plot.plotArgs = { ...args, height: 280, margin: { top: 12, right: 16, bottom: 42, left: 58 }, zoom_pan: false,}document.body.append(plot) Install
Install the package to use plots in React, Preact, or plain JavaScript.
npm install @antadesign/plot # or pnpm / bunImport Plot from /components and series factories from @antadesign/plot.
React 19 is a peer dependency. For Preact, use preact/compat aliases.
import { Plot } from '@antadesign/plot/components'import { line } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot-surface'import '@antadesign/anta/elements/a-tooltip'
const plotArgs = { height: 300, series: [line({ data: [{ x: 1, y: 3 }, { x: 2, y: 5 }, { x: 3, y: 4 }] })],}
function Example() { return <Plot plotArgs={plotArgs} />}Plot is a function component that uses Anta’s configured JSX factory and hooks.
It needs no DOM registration. Import the surface and tooltip registration modules
once in your browser entry; their elements install their styles automatically.
Plot handles canvas setup, drawing, interactions, and cleanup.
By default, Plot fills its parent. Give the parent a height or set plotArgs.height,
as above. Custom renderers supply their hooks through Anta’s configure().
With a worker renderer and a DOM bridge that transfers the surface’s canvases,
Plot runs in the worker while a-plot-surface stays in the browser.
For plain JavaScript or TypeScript, import @antadesign/plot/elements/a-plot, then use <a-plot> and assign its plotArgs property.
For custom host integrations, /components also exports PlotSurface.
Register it with @antadesign/plot/elements/a-plot-surface; your host manages
controllers, drawing, and tooltip content.
Plot arguments
PlotArgs configures the whole plot: its series, axes, and presentation. Pass it
as the required plotArgs prop to <Plot plotArgs={plotArgs} />, or assign it to
the plotArgs property on <a-plot>. Replace the object to apply changes;
in-place mutation is not observed.
Title, background, and grid
Set a title, reserve space with margin, and style the plot background and horizontal grid lines.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
const examplePoints = [ { x: 8, y: 14 }, { x: 36, y: 40 }, { x: 52, y: 32 }, { x: 44, y: 59 }, { x: 64, y: 54 }, { x: 92, y: 86 },]
function presentation(): PlotArgs<Node> { return { series: [scatter<Node>({ data: examplePoints, size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], title: { text: 'Plot title', size: 16, color: { light: '#374151', dark: '#e5e7eb' } }, height: 260, margin: { top: 38, right: 16, bottom: 42, left: 52 }, background: { light: '#f3f4f6', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, grid: { x: false, y: true }, border: true, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = presentation()document.body.append(plot)Border and spacing
Set border: true and grid: false to frame the plot without grid lines. Use margin to reserve space around the axes and their labels.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
const examplePoints = [ { x: 8, y: 14 }, { x: 36, y: 40 }, { x: 52, y: 32 }, { x: 44, y: 59 }, { x: 64, y: 54 }, { x: 92, y: 86 },]
function spacing(): PlotArgs<Node> { return { series: [scatter<Node>({ data: examplePoints, size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 40, right: 40, bottom: 68, left: 88 }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = spacing()document.body.append(plot)| Field | Type | Default | Description |
|---|---|---|---|
series | Series[] | Required | What to draw, in paint order. Build each one with a factory. |
axis? | { x?: AxisArgs, y?: AxisArgs } | Inferred from data | Per-axis scale, label, ticks, and domain. |
title? | string | { text, size?, color? } | None | Plot title. |
width? | number | Container width | Canvas width in pixels. Overrides the wrapper’s CSS width. |
height? | number | Parent height for Plot; 300 for <a-plot> | Canvas height in pixels. Overrides the host’s CSS height. |
margin? | number | { top?, right?, bottom?, left? } | 60 per side; 2 without axes or title | Space reserved around the plot area for axes and title. |
border? | boolean | true | Draws a border around the plot area. |
grid? | boolean | { x?, y? } | false | Grid lines, per axis. |
chrome_color? | string | { light, dark } | Theme defaults | Color for axis lines, tick marks, grid lines, and the plot border. Text colors are configured separately. |
background? | boolean | string | { light, dark } | White | Plot fill. false paints none; true and an absent value use #fff. |
theme_invert? | boolean | Automatic | Forces dark-mode inversion on or off. Absent means invert unless a series color or the background is a { light, dark } pair. |
zoom_pan? | boolean | { x?, y?, modifier? } | Both axes, Ctrl required | Require Ctrl for zoom and pan by default. Set modifier: false to allow gestures without Ctrl. Set both axes to false to disable zoom and pan. |
viewport? | { x?, y?, key? } | Full domain | Requested starting window per axis. Applied at mount and on each key change, clamped to the full domain. |
on_viewport_change? | (change: ViewportChange) => void | None | Fires with each axis’s current window and full extent after a gesture or reset. |
A zoomed plot shows a Reset zoom button in the plot area; it disappears once the view is back to its full extent.
Axes
axis.x and axis.y both accept AxisArgs. Configure them independently.
Omitted fields are inferred from the series or use the documented defaults.
ThemeColor accepts a CSS color string or a { light, dark } pair.
Logarithmic scale
Use a logarithmic axis to compare values across orders of magnitude. Format large tick values with a k suffix.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
const examplePoints = [ { x: 8, y: 14 }, { x: 36, y: 40 }, { x: 52, y: 32 }, { x: 44, y: 59 }, { x: 64, y: 54 }, { x: 92, y: 86 },]
function logarithmic(): PlotArgs<Node> { const data = examplePoints.map(({ x, y }) => ({ x, y: 10 ** (y / 20 - 1) })) return { series: [scatter<Node>({ data, size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 12, right: 14, bottom: 44, left: 80 }, axis: { x: { min: 0, max: 100, label: 'Batch' }, y: { scale: 'log', min: 0.1, max: 10000, label: 'Duration (ms)', tick_label: { format: value => Number(value) >= 1000 ? Number(value) / 1000 + 'k' : String(value) } }, }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: true, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = logarithmic()document.body.append(plot)Time and tick formatting
Plot timestamps on a UTC axis and format the horizontal ticks as hours and minutes. Add a percent suffix to the vertical ticks.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
const examplePoints = [ { x: 8, y: 14 }, { x: 36, y: 40 }, { x: 52, y: 32 }, { x: 44, y: 59 }, { x: 64, y: 54 }, { x: 92, y: 86 },]
function timeAxis(): PlotArgs<Node> { const start = Date.UTC(2026, 0, 12) const data = examplePoints.map(({ x, y }) => ({ x: start + x / 100 * 86400000, y })) return { series: [scatter<Node>({ data, size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 12, right: 16, bottom: 44, left: 76 }, axis: { x: { scale: 'utc', min: start, max: start + 86400000, label: 'Time (UTC)', padding: 8, tick_label: { format: value => new Date(Number(value)).toISOString().slice(11, 16) } }, y: { min: 0, max: 100, label: 'Utilization', tick_mark: false, tick_label: { format: value => value + '%' } }, }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: true, y: false }, border: true, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = timeAxis()document.body.append(plot)| Field | Type | Default | Description |
|---|---|---|---|
scale? | 'linear' | 'log' | 'category' | 'time' | 'utc' | Inferred | Numeric data uses a linear scale; string data uses categories. Time scales take timestamps in milliseconds; time formats in local time and utc in UTC. |
label? | LabelArg | Shared field name, if available | Axis name, as text or an object with presentation options. Use '' to omit it. |
min? | number | Inferred from data | Lower continuous-domain bound. |
max? | number | Inferred from data | Upper continuous-domain bound. |
categories? | string[] | Inferred from data | Explicit category list and order; also declares a categorical axis. |
padding? | number | 0 | Pixel padding at both ends of the axis, inside the plot area. |
padding_left? | number | padding | Left-end padding for axis.x. |
padding_right? | number | padding | Right-end padding for axis.x. |
padding_top? | number | padding | Top-end padding for axis.y. |
padding_bottom? | number | padding | Bottom-end padding for axis.y. |
grid_align? | 'center' | 'edge' | 'center' | Position grid lines at category centers or boundaries. Categorical axes only. |
line? | boolean | true | Draw the axis line. |
hidden? | boolean | false | Hide the axis presentation while retaining its scale. |
tick_label? | TickLabelArg | Automatic formatting | Tick text formatting, size, and color. |
tick_mark? | boolean | true | Draw tick marks. Does not hide tick labels. |
Series
A series describes a set of data and how to draw it. Pass one or more series
in plotArgs.series; they are drawn in array order. Import the series
functions from @antadesign/plot.
The default column describes omitted fields. Examples may override these defaults. See Data fields, Series colors, Strokes, and Mark shapes for shared configuration types. For callbacks, see Tooltips and Selection callbacks.
Scatter
Draw a mark at each data point. Use size, mark, and stroke to control
its appearance. Set tooltip: true for a tooltip showing the point’s values.
Size and color from data
Use a size accessor to vary mark diameter and a color accessor to highlight larger values. An outline keeps overlapping bubbles distinct.
import { scatter, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 24 }, (_, i) => ({ x: 12 + (i * 17 % 83), y: 15 + (i * 31 % 72), volume: 20 + (i * 43 % 180),}))
plot.plotArgs = { series: [scatter({ data, size: row => (5 + Math.sqrt(Number(row.volume)) * 1.8) * 1.3, color: row => Number(row.volume) > 120 ? { light: '#e76f51', dark: '#fda489' } : { light: '#0891b2', dark: '#67e8f9' }, stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 2 }, tooltip: ({ row }) => document.createTextNode('Volume: ' + row.volume), })], height: 260, margin: { top: 12, right: 12, bottom: 28, left: 36 }, axis: { x: { min: 0, max: 110, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, border: false, zoom_pan: false,}
document.body.append(plot)Mark shapes and outlines
Use different marks to distinguish groups without relying on color alone. Each series sets its own shape, size, and stroke.
import { scatter, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 18 }, (_, i) => ({ x: 10 + i * 4.5, y: 18 + i * 2.5 + Math.sin(i * 2.1) * 12,}))
plot.plotArgs = { series: [ scatter({ data, mark: 'square', size: 11, color: { light: '#c4b5fd', dark: '#8b5cf6' }, stroke: { color: { light: '#6d28d9', dark: '#ddd6fe' }, width: 2 }, tooltip: ({ x, y }) => document.createTextNode('Squares: ' + x + ', ' + Math.round(Number(y))), }), scatter({ data: data.map(point => ({ x: point.x + 5, y: 95 - point.y })), mark: 'diamond', size: 13, color: { light: '#99f6e4', dark: '#0d9488' }, stroke: { color: { light: '#0f766e', dark: '#5eead4' }, width: 2 }, tooltip: ({ x, y }) => document.createTextNode('Diamonds: ' + x + ', ' + Math.round(Number(y))), }), ], height: 260, margin: { top: 12, right: 12, bottom: 28, left: 36 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, border: false, zoom_pan: false,}
document.body.append(plot)Scatter arguments
| Field | Type | Default | Description |
|---|---|---|---|
data | Record<string, unknown>[] | Required | Rows used to build the series. |
x? | FieldArg | 'x' | Horizontal field name or accessor. |
y? | FieldArg | 'y' | Vertical field name or accessor. |
color? | ColorArg | Black | Series color; theme pairs provide separate light and dark colors. |
size? | number | ((row, index) => number) | 5 | Mark diameter in pixels, or a per-row accessor. A row’s size takes precedence. |
mark? | MarkShape | 'circle' | Mark shape: circle, square, diamond, or triangle. |
stroke? | StrokeArg | None | Outline color and optional width. |
tooltip? | TooltipArg | None | Use true for the default tooltip, or a callback for custom content. |
on_select? | SelectFn | None | Callback receiving the selected point and its source row. |
hoverable? | boolean | true | Allow the series to participate in hit testing. |
highlight? | boolean | true | Draw hover feedback for a hit point. |
import { scatter } from '@antadesign/plot'
const series = [scatter({ data: [{ time: 1, value: 3 }, { time: 2, value: 5 }, { time: 3, value: 4 }], x: 'time', y: 'value', color: (row) => Number(row.value) >= 5 ? 'coral' : 'steelblue', size: (row) => Number(row.value) * 2, mark: 'diamond', stroke: { color: 'navy', width: 1 }, tooltip: ({ x, y }) => `(${x}, ${y})`, on_select: ({ row }) => console.log('Selected row:', row), hoverable: true, highlight: true,})]Line
Connect points in data order. Set width and dash for the line, or add
mark and mark_size to show the individual points.
Solid and dashed lines
Compare measured values with a forecast. A solid line follows each fluctuation, while a dashed line distinguishes the expected trend.
import { line, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 97 }, (_, i) => ({ x: i / 4, expected: 45 + 22 * Math.sin(i / 18) + 12 * Math.cos(i / 7), measured: 45 + 22 * Math.sin(i / 18) + 12 * Math.cos(i / 7) + 7 * Math.sin(i * 1.1) + 4 * Math.cos(i * 2.3),}))
plot.plotArgs = { series: [ line<Node>({ data, y: 'expected', color: { light: '#c2410c', dark: '#fdba74' }, width: 2, dash: [7, 5], tooltip: ({ y }) => document.createTextNode('Expected: ' + Math.round(Number(y))), }), line<Node>({ data, y: 'measured', color: { light: '#0891b2', dark: '#67e8f9' }, width: 2, tooltip: ({ y }) => document.createTextNode('Measured: ' + Math.round(Number(y))), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false,}
document.body.append(plot)Point marks and outlines
Show individual samples with diamond marks. Set the line width, mark size, and mark outline independently to keep each measurement visible.
import { line, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 13 }, (_, i) => ({ x: i, y: 35 + i * 2 + 18 * Math.sin(i * 0.8) + 8 * Math.cos(i * 1.7),}))
plot.plotArgs = { series: [line<Node>({ data, color: { light: '#7c3aed', dark: '#c4b5fd' }, width: 3, mark: 'diamond', mark_size: 10, mark_stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 2 }, tooltip: ({ x, y }) => document.createTextNode('Sample ' + x + ': ' + Math.round(Number(y))), })], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { min: -0.5, max: 12.5, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false,}
document.body.append(plot)Line arguments
| Field | Type | Default | Description |
|---|---|---|---|
data | Record<string, unknown>[] | Required | Rows used to build the series. |
x? | FieldArg | 'x' | Horizontal field name or accessor. |
y? | FieldArg | 'y' | Vertical field name or accessor. |
color? | ThemeColor | Black | Series color; theme pairs provide separate light and dark colors. |
width? | number | 2 | Line width in pixels. |
dash? | number[] | Solid | Alternating dash and gap lengths in pixels. |
mark? | MarkShape | None | Shape drawn at each point. |
mark_size? | number | 6 | Point marker diameter in pixels. |
mark_stroke? | StrokeArg | None | Outline for the point markers. |
tooltip? | TooltipArg | None | Use true for the default tooltip, or a callback for custom content. |
on_select? | SelectFn | None | Callback receiving the selected point and its source row. |
hoverable? | boolean | true | Allow the series to participate in hit testing. |
highlight? | boolean | true | Draw hover feedback for a hit point. |
import { line } from '@antadesign/plot'
const series = [line({ data: [{ time: 1, value: 3 }, { time: 2, value: 5 }, { time: 3, value: 4 }], x: 'time', y: 'value', color: { light: 'steelblue', dark: 'lightskyblue' }, width: 2, dash: [6, 3], mark: 'circle', mark_size: 8, mark_stroke: { color: 'navy', width: 1 }, tooltip: ({ x, y }) => `(${x}, ${y})`, on_select: ({ row }) => console.log('Selected row:', row), hoverable: true, highlight: true,})]Bar
Draw one bar per category. Use a categorical field on one axis and a numeric
field on the other. Categories on x produce vertical bars; categories on
y produce horizontal bars.
Rounded bars and data colors
Set corner rounding and spacing independently. A color accessor highlights days with more than 80 requests per second.
import { bar, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = [ { day: 'Mon', requests: 42 }, { day: 'Tue', requests: 68 }, { day: 'Wed', requests: 91 }, { day: 'Thu', requests: 57 }, { day: 'Fri', requests: 84 }, { day: 'Sat', requests: 36 }, { day: 'Sun', requests: 24 },]
plot.plotArgs = { series: [bar<Node>({ data, x: 'day', y: 'requests', border_radius: 6, inset: 5, color: row => Number(row.requests) > 80 ? { light: '#c2410c', dark: '#fdba74' } : { light: '#0891b2', dark: '#67e8f9' }, hover_span_x: true, tooltip: ({ x, y }) => document.createTextNode(x + ': ' + y + ' requests/s'), })], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false,}
document.body.append(plot)Horizontal bars
Place categories on the y-axis to leave room for their names. Hover anywhere along a category’s row to inspect its value.
import { bar, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = [ { service: 'Search', requests: 92 }, { service: 'Feed', requests: 78 }, { service: 'Checkout', requests: 64 }, { service: 'Profile', requests: 47 }, { service: 'Upload', requests: 31 },]
plot.plotArgs = { series: [bar<Node>({ data, x: 'requests', y: 'service', border_radius: 4, inset: 8, color: { light: '#7c3aed', dark: '#c4b5fd' }, hover_span_y: true, tooltip: ({ x, y }) => document.createTextNode(y + ': ' + x + ' requests/s'), })], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 78 }, axis: { x: { min: 0, max: 100, label: '' }, y: { label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: true, y: false }, border: false, zoom_pan: false,}
document.body.append(plot)Positive and negative values
Bars extend from zero in either direction. Use a color accessor to distinguish monthly gains from losses.
import { bar, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = [ { month: 'Jan', change: 24 }, { month: 'Feb', change: -16 }, { month: 'Mar', change: 38 }, { month: 'Apr', change: 12 }, { month: 'May', change: -28 }, { month: 'Jun', change: -9 }, { month: 'Jul', change: 31 },]
plot.plotArgs = { series: [bar<Node>({ data, x: 'month', y: 'change', inset: 6, color: row => Number(row.change) >= 0 ? { light: '#0d9488', dark: '#5eead4' } : { light: '#c2410c', dark: '#fdba74' }, hover_span_x: true, tooltip: ({ x, y }) => document.createTextNode(x + ': ' + (Number(y) > 0 ? '+' : '') + y + '%'), })], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { label: '' }, y: { min: -40, max: 40, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false,}
document.body.append(plot)Custom category order
Set axis.x.categories to order bars by severity, independently of the source row order.
import { bar, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function categoryOrder(): PlotArgs<Node> { const data = [ { severity: 'High', count: 28 }, { severity: 'Low', count: 46 }, { severity: 'Critical', count: 12 }, { severity: 'Medium', count: 35 }, ] return { series: [bar<Node>({ data, x: 'severity', y: 'count', inset: 10, border_radius: 3, color: { light: '#64748b', dark: '#94a3b8' }, tooltip: ({ x, y }) => document.createTextNode(x + ': ' + y + ' issues'), })], height: 260, margin: { top: 5, right: 12, bottom: 24, left: 30 }, axis: { x: { categories: ['Critical', 'High', 'Medium', 'Low'], label: '' }, y: { min: 0, max: 50, label: '' }, }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = categoryOrder()document.body.append(plot)Bar arguments
| Field | Type | Default | Description |
|---|---|---|---|
data | Record<string, unknown>[] | Required | Rows used to build the series. |
x? | FieldArg | string[] | 'x' | Category or value field. An array of value fields creates horizontal stacks. |
y? | FieldArg | string[] | 'y' | Category or value field. An array of value fields creates vertical stacks. |
color? | ColorArg | ``ThemeColor``[] | Black | Uniform or per-row color; a color array maps to stacked fields in order. |
border_radius? | number | 0 | Bar corner radius in pixels. |
inset? | number | One sixth of band width | Inset on each side of a categorical band, in pixels. |
min_size? | number | 2 | Minimum rendered value length in pixels. |
hover_span_x? | boolean | false | Extend the horizontal hit region across the categorical band. |
hover_span_y? | boolean | false | Extend the vertical hit region across the categorical band. |
tooltip? | TooltipArg | None | Use true for the default tooltip, or a callback for custom content. |
on_select? | SelectFn | None | Callback receiving the selected point and its source row. |
hoverable? | boolean | true | Allow the series to participate in hit testing. |
highlight? | boolean | true | Draw hover feedback for a hit point. |
import { bar } from '@antadesign/plot'
const series = [bar({ data: [{ group: 'A', count: 8 }, { group: 'B', count: 12 }], x: 'group', y: 'count', color: { light: 'steelblue', dark: 'lightskyblue' }, border_radius: 3, inset: 4, min_size: 2, hover_span_x: true, hover_span_y: false, tooltip: ({ x, y }) => `(${x}, ${y})`, on_select: ({ row }) => console.log('Selected row:', row), hoverable: true, highlight: true,})]Stacked bars
Pass an array of value-field names to stack them within each category.
Use y for vertical stacks or x for horizontal stacks. An array of
colors assigns one color to each field, in the same order.
Stacked values
Pass multiple value fields to stack them within each category. Colors follow the field order; hover a segment to see its contribution.
import { bar, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = [ { day: 'Mon', network: 18, compute: 42, storage: 24 }, { day: 'Tue', network: 24, compute: 32, storage: 18 }, { day: 'Wed', network: 12, compute: 56, storage: 28 }, { day: 'Thu', network: 28, compute: 24, storage: 16 }, { day: 'Fri', network: 16, compute: 48, storage: 22 },]
plot.plotArgs = { series: [bar<Node>({ data, x: 'day', y: ['network', 'compute', 'storage'], color: [ { light: '#0d9488', dark: '#5eead4' }, { light: '#6366f1', dark: '#a5b4fc' }, { light: '#d97706', dark: '#fcd34d' }, ], inset: 8, tooltip: ({ label, y }) => document.createTextNode(label + ': ' + y + ' ms'), })], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false,}
document.body.append(plot)Horizontal stacks
Pass multiple fields to x and categories to y to compare contributions across services.
import { bar, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function stackedHorizontal(): PlotArgs<Node> { const data = [ { service: 'Search', network: 18, compute: 46, storage: 14 }, { service: 'Feed', network: 26, compute: 28, storage: 22 }, { service: 'Upload', network: 42, compute: 16, storage: 32 }, { service: 'Export', network: 12, compute: 36, storage: 18 }, ] return { series: [bar<Node>({ data, x: ['network', 'compute', 'storage'], y: 'service', color: [ { light: '#0d9488', dark: '#5eead4' }, { light: '#6366f1', dark: '#a5b4fc' }, { light: '#d97706', dark: '#fcd34d' }, ], inset: 8, border_radius: 3, tooltip: ({ label, x }) => document.createTextNode(label + ': ' + x + ' ms'), })], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 64 }, axis: { x: { min: 0, max: 100, label: '' }, y: { label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: true, y: false }, border: false, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = stackedHorizontal()document.body.append(plot)Positive and negative stacks
Positive and negative values stack separately from zero. Each segment retains its sign and field label.
import { bar, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function stackedDiverging(): PlotArgs<Node> { const data = [ { month: 'Jan', new: 28, expansion: 12, churn: -14, contraction: -6 }, { month: 'Feb', new: 18, expansion: 8, churn: -22, contraction: -10 }, { month: 'Mar', new: 36, expansion: 16, churn: -8, contraction: -4 }, { month: 'Apr', new: 24, expansion: 18, churn: -18, contraction: -12 }, { month: 'May', new: 32, expansion: 10, churn: -12, contraction: -8 }, ] return { series: [bar<Node>({ data, x: 'month', y: ['new', 'expansion', 'churn', 'contraction'], color: [ { light: '#0d9488', dark: '#2dd4bf' }, { light: '#5eead4', dark: '#99f6e4' }, { light: '#ea580c', dark: '#fb923c' }, { light: '#fdba74', dark: '#fed7aa' }, ], inset: 7, tooltip: ({ label, y }) => document.createTextNode(label + ': ' + (Number(y) > 0 ? '+' : '') + y), })], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 36 }, axis: { x: { label: '' }, y: { min: -40, max: 60, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = stackedDiverging()document.body.append(plot)Full-stack tooltips
Hover a segment to see every value in its stack. The callback reads the values from row and uses label to display the hovered segment’s name and value in bold.
import { bar, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function stackedTooltip(): PlotArgs<Node> { const data = [ { day: 'Mon', network: 18, compute: 42, storage: 24 }, { day: 'Tue', network: 24, compute: 32, storage: 18 }, { day: 'Wed', network: 12, compute: 56, storage: 28 }, { day: 'Thu', network: 28, compute: 24, storage: 16 }, { day: 'Fri', network: 16, compute: 48, storage: 22 }, ]
return { series: [bar<Node>({ data, x: 'day', y: ['network', 'compute', 'storage'], color: [ { light: '#0d9488', dark: '#5eead4' }, { light: '#6366f1', dark: '#a5b4fc' }, { light: '#d97706', dark: '#fcd34d' }, ], inset: 10, hover_span_x: false, hover_span_y: false, tooltip: ({ label, row }) => { const content = document.createElement('div') for (const field of ['network', 'compute', 'storage']) { const entry = document.createElement('div') const value = document.createElement(field === label ? 'strong' : 'span') value.textContent = field + ': ' + row[field] + ' ms' entry.append(value) content.append(entry) } return content }, })], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = stackedTooltip()document.body.append(plot)import { bar } from '@antadesign/plot'
const series = [bar({ data: [ { group: 'A', passed: 8, failed: 2 }, { group: 'B', passed: 12, failed: 3 }, ], x: 'group', y: ['passed', 'failed'], color: ['seagreen', 'coral'], border_radius: 3, inset: 4, min_size: 2, hover_span_x: true, hover_span_y: false, tooltip: ({ label, y }) => `${label}: ${y}`, on_select: ({ label, row }) => console.log('Selected segment:', label, row), hoverable: true, highlight: true,})]Area
Fill the space between two boundaries. Set y2 for a second vertical value
or x2 for a second horizontal value. The second boundary can be a field
name or a constant, such as a zero baseline.
Fill to a baseline
Fill beneath a signal with a constant zero baseline. A stroke traces the area’s boundary.
import { area, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 97 }, (_, i) => ({ x: i / 4, y: 28 + 42 * Math.exp(-(((i - 55) / 24) ** 2)) + 9 * Math.sin(i / 5) + 4 * Math.cos(i * 1.3),}))
plot.plotArgs = { series: [ area<Node>({ data, y2: 0, color: { light: '#99f6e4', dark: '#134e4a' }, stroke: { color: { light: '#0d9488', dark: '#5eead4' }, width: 2 }, tooltip: ({ y }) => document.createTextNode('Load: ' + Math.round(Number(y))), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false,}
document.body.append(plot)A range between fields
Use two value fields to draw a changing interval. A dashed outline distinguishes its boundaries.
import { area, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 81 }, (_, i) => { const center = 48 + 16 * Math.sin(i / 12) + 5 * Math.cos(i / 3) const spread = 8 + 10 * Math.sin(i / 17) ** 2 return { x: i * 0.3, low: center - spread, high: center + spread }})
plot.plotArgs = { series: [ area<Node>({ data, y: 'high', y2: 'low', color: { light: '#ddd6fe', dark: '#4c1d95' }, stroke: { color: { light: '#7c3aed', dark: '#c4b5fd' }, width: 2 }, dash: [5, 3], tooltip: ({ row }) => document.createTextNode(Math.round(Number(row.low)) + '–' + Math.round(Number(row.high))), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false,}
document.body.append(plot)Layered contributions
Compose adjacent areas with explicit cumulative bounds. Each band shows one contribution to the total.
import { area, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 97 }, (_, i) => { const first = 18 + 7 * Math.sin(i / 13) + 3 * Math.cos(i / 4) const second = first + 22 + 9 * Math.sin(i / 18 + 1) const total = second + 16 + 6 * Math.cos(i / 9) return { x: i / 4, first, second, total }})
plot.plotArgs = { series: [ area<Node>({ data, y: 'first', y2: 0, color: { light: '#2dd4bf', dark: '#0d9488' }, tooltip: ({ row }) => document.createTextNode('Network: ' + Math.round(Number(row.first))), }), area<Node>({ data, y: 'second', y2: 'first', color: { light: '#a5b4fc', dark: '#6366f1' }, tooltip: ({ row }) => document.createTextNode('Compute: ' + Math.round(Number(row.second) - Number(row.first))), }), area<Node>({ data, y: 'total', y2: 'second', color: { light: '#fcd34d', dark: '#b45309' }, tooltip: ({ row }) => document.createTextNode('Storage: ' + Math.round(Number(row.total) - Number(row.second))), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: false, y: true }, border: false, zoom_pan: false,}
document.body.append(plot)Horizontal intervals
Use x and x2 for horizontal bounds. The filled band shows how an interval shifts along the vertical axis.
import { area, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 81 }, (_, i) => { const center = 46 + 18 * Math.sin(i / 16) + 4 * Math.cos(i / 4) const spread = 9 + 6 * Math.cos(i / 11) ** 2 return { y: i * 0.3, low: center - spread, high: center + spread }})
plot.plotArgs = { series: [ area<Node>({ data, x: 'low', x2: 'high', color: { light: '#fed7aa', dark: '#7c2d12' }, stroke: { color: { light: '#ea580c', dark: '#fdba74' }, width: 2 }, tooltip: ({ row }) => document.createTextNode(Math.round(Number(row.low)) + '–' + Math.round(Number(row.high))), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 24, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: { x: true, y: false }, border: false, zoom_pan: false,}
document.body.append(plot)Area arguments
| Field | Type | Default | Description |
|---|---|---|---|
data | Record<string, unknown>[] | Required | Rows used to build the series. |
x? | FieldArg | 'x' | Horizontal field name or accessor. |
x2? | FieldArg | number | None | Second horizontal boundary, as a field, accessor, or constant. Use either x2 or y2. |
y? | FieldArg | 'y' | Vertical field name or accessor. |
y2? | FieldArg | number | Zero baseline if neither bound is set | Second vertical boundary, as a field, accessor, or constant. |
color? | ThemeColor | Black | Series color; theme pairs provide separate light and dark colors. |
stroke? | StrokeArg | None | Outline color and optional width. |
dash? | number[] | Solid | Alternating dash and gap lengths in pixels. |
tooltip? | TooltipArg | None | Use true for the default tooltip, or a callback for custom content. |
on_select? | SelectFn | None | Callback receiving the selected point and its source row. |
hoverable? | boolean | true | Allow the series to participate in hit testing. |
highlight? | boolean | true | Draw hover feedback for a hit point. |
import { area } from '@antadesign/plot'
const series = [area({ data: [ { time: 1, low: 2, high: 4 }, { time: 2, low: 3, high: 6 }, { time: 3, low: 2, high: 5 }, ], x: 'time', y: 'high', y2: 'low', color: { light: 'lightsteelblue', dark: 'midnightblue' }, stroke: { color: 'steelblue', width: 2 }, dash: [6, 3], tooltip: ({ x, y }) => `(${x}, ${y})`, on_select: ({ row }) => console.log('Selected row:', row), hoverable: true, highlight: true,})]For a horizontal area, use x2 instead of y2:
import { area } from '@antadesign/plot'
const series = [area({ data: [{ low: 2, high: 4, time: 1 }, { low: 3, high: 6, time: 2 }], x: 'high', x2: 'low', y: 'time', color: 'lightsteelblue',})]Rect
Draw rectangles with explicit bounds. Use x, x2, y, and y2 to select
the fields defining each rectangle’s corners. Rectangles can represent
intervals, regions, or cells in a grid.
Categorical cells
Combine two categorical axes to form a heatmap. Color each cell from its value and use inset to separate neighboring cells.
import { rect, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']const hours = ['09', '11', '13', '15', '17', '19']const data = days.flatMap((day, d) => hours.map((hour, h) => ({ day, hour, activity: Math.round(20 + 75 * Math.sin(h * 0.8 + d * 0.5) ** 2),})))
plot.plotArgs = { series: [ rect<Node>({ data, x: 'hour', y: 'day', inset: 2, color: row => ({ light: 'hsl(174, 65%, ' + (94 - Number(row.activity) * 0.6) + '%)', dark: 'hsl(174, 55%, ' + (16 + Number(row.activity) * 0.4) + '%)', }), tooltip: ({ row }) => document.createTextNode(row.day + ' ' + row.hour + ':00 · ' + row.activity + '%'), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 38 }, axis: { x: { categories: hours, label: '' }, y: { categories: days, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot)Intervals with explicit bounds
Set x and x2 to show durations within categorical rows. Each rectangle retains its start and end values as the plot resizes.
import { rect, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = [ { task: 'Fetch', start: 0.5, end: 3.2 }, { task: 'Parse', start: 2.8, end: 5.1 }, { task: 'Build', start: 4.5, end: 9.4 }, { task: 'Test', start: 7.8, end: 11.2 }, { task: 'Ship', start: 10.8, end: 12.5 },]
plot.plotArgs = { series: [ rect<Node>({ data, x: 'start', x2: 'end', y: 'task', inset: 9, color: { light: '#c4b5fd', dark: '#6d28d9' }, stroke: { color: { light: '#7c3aed', dark: '#c4b5fd' }, width: 1 }, hover_span_y: true, tooltip: ({ row }) => document.createTextNode(row.task + ': ' + row.start + '–' + row.end + ' s'), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 44 }, axis: { x: { min: 0, max: 14, label: '' }, y: { label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot)Fixed pixel dimensions
Set size to draw rectangles with a consistent width and height at numeric coordinates. An outline keeps nearby marks distinct.
import { rect, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 32 }, (_, i) => ({ x: 8 + (i * 23 % 84), y: 18 + (i * 13 % 65), value: 20 + (i * 17 % 80),}))
plot.plotArgs = { series: [ rect<Node>({ data, size: { width: 16, height: 9 }, color: row => Number(row.value) > 65 ? { light: '#c2410c', dark: '#fdba74' } : { light: '#0891b2', dark: '#67e8f9' }, stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 1 }, tooltip: ({ row }) => document.createTextNode('Value: ' + row.value), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot)Run history
Place fixed-size rectangles on a time axis in Passed and Failed lanes. Use band_align to anchor both sets of marks to the shared divider, with passing runs above and failures below.
import { rect, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const start = Date.UTC(2026, 0, 1, 9)const data = Array.from({ length: 36 }, (_, i) => ({ run: i + 1, end_ms: start + (i * 3 + (i % 3)) * 60_000, lane: [5, 6, 14, 23, 24, 25, 32].includes(i) ? 'Failed' : 'Passed',}))
plot.plotArgs = { series: [ rect<Node>({ data: data.filter(row => row.lane === 'Passed'), x: 'end_ms', y: 'lane', size: { width: 10, height: 40 }, band_align: 'end', inset: 0, color: { light: '#189e3e', dark: '#4ade80' }, stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 0.5 }, tooltip: ({ row }) => document.createTextNode('Run ' + row.run + ': ' + row.lane), }), rect<Node>({ data: data.filter(row => row.lane === 'Failed'), x: 'end_ms', y: 'lane', size: { width: 10, height: 40 }, band_align: 'start', inset: 0, color: { light: '#c41e5e', dark: '#fb7185' }, stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 0.5 }, tooltip: ({ row }) => document.createTextNode('Run ' + row.run + ': ' + row.lane), }), ], height: 260, margin: { top: 5, right: 8, bottom: 24, left: 56 }, axis: { x: { scale: 'utc', min: start - 5 * 60_000, max: start + 115 * 60_000, label: '', line: false, tick_label: { format: value => new Date(Number(value)).toISOString().slice(11, 16) }, }, y: { categories: ['Failed', 'Passed'], grid_align: 'edge', label: '', line: false }, }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: true, border: false, theme_invert: false, zoom_pan: false,}
document.body.append(plot)Rect arguments
| Field | Type | Default | Description |
|---|---|---|---|
data | Record<string, unknown>[] | Required | Rows used to build the series. |
x? | FieldArg | 'x' | Horizontal field name or accessor. |
x2? | FieldArg | 'x2' when present in data | Opposite horizontal corner. Numeric axes need a second corner or pixel size. |
y? | FieldArg | 'y' | Vertical field name or accessor. |
y2? | FieldArg | 'y2' when present in data | Opposite vertical corner. Numeric axes need a second corner or pixel size. |
span? | 'x' | 'y' | None | Stretch the rectangle across the full plot on this axis. |
size? | number | { width?: number; height?: number } | Derived from bounds or band | Fixed pixel size, shared or per dimension. |
band_align? | 'center' | 'start' | 'end' | 'center' | Alignment of a fixed-size rectangle within a categorical band. |
hover_span_x? | boolean | false | Extend the horizontal hit region across the categorical band. |
hover_span_y? | boolean | false | Extend the vertical hit region across the categorical band. |
inset? | number | One sixth of band width | Inset on each side of a categorical band, in pixels. |
min_size? | number | 0 | Minimum pixel length for a rectangle defined by data bounds. |
offset? | { x?: number; y?: number } | { x: 0, y: 0 } | Pixel offsets applied to each rectangle. |
color? | ColorArg | Black | Series color; theme pairs provide separate light and dark colors. |
stroke? | StrokeArg | None | Outline color and optional width. |
tooltip? | TooltipArg | None | Use true for the default tooltip, or a callback for custom content. |
on_select? | SelectFn | None | Callback receiving the selected point and its source row. |
hoverable? | boolean | true | Allow the series to participate in hit testing. |
highlight? | boolean | true | Draw hover feedback for a hit point. |
import { rect } from '@antadesign/plot'
const series = [rect({ data: [ { start: 1, end: 3, low: 2, high: 5 }, { start: 4, end: 6, low: 1, high: 4 }, ], x: 'start', x2: 'end', y: 'low', y2: 'high', min_size: 2, offset: { x: 0, y: 0 }, color: 'lightsteelblue', stroke: { color: 'steelblue', width: 1 }, tooltip: ({ x, y }) => `(${x}, ${y})`, on_select: ({ row }) => console.log('Selected row:', row), hoverable: true, highlight: true,})]For fixed-size marks in categorical bands, use size and band_align.
inset controls the space around a band when its size is not fixed:
import { rect } from '@antadesign/plot'
const series = [rect({ data: [{ group: 'A', row: 'First' }, { group: 'B', row: 'Second' }], x: 'group', y: 'row', size: { width: 12 }, band_align: 'start', inset: 4, offset: { x: 2, y: 0 }, hover_span_x: true, hover_span_y: true, color: 'steelblue', tooltip: true,})]Use span to fill one axis, leaving its coordinates unspecified. Add this
series before the data series to draw a background region:
import { rect } from '@antadesign/plot'
const series = [rect({ data: [{ low: 2, high: 5 }], span: 'x', y: 'low', y2: 'high', color: 'aliceblue', hoverable: false, highlight: false,})]Rule
Draw a reference line across the plot. Supply x for a vertical line or
y for a horizontal line. A single rule needs no data array; add it to the
same series array as the data it annotates.
Horizontal reference values
Use numeric y values for reference lines without a data array. A thin dashed target and a thick solid upper limit show how width and dash patterns distinguish reference values.
import { rule, line, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 97 }, (_, i) => ({ x: i / 4, y: 44 + 15 * Math.sin(i / 9) + 5 * Math.cos(i * 0.8) + 38 * Math.exp(-(((i - 62) / 10) ** 2)),}))
plot.plotArgs = { series: [ line<Node>({ data, color: { light: '#0891b2', dark: '#67e8f9' }, width: 2, }), rule<Node>({ y: 60, color: { light: '#7c3aed', dark: '#c4b5fd' }, width: 1, dash: [7, 4], tooltip: () => document.createTextNode('Target: 60 ms'), }), rule<Node>({ y: 90, color: { light: '#c2410c', dark: '#fdba74' }, width: 4, tooltip: () => document.createTextNode('Upper limit: 90 ms'), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 110, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot)Vertical event markers
Read x positions from data to mark events along a signal. A solid rule marks deployment; thinner dashed rules mark recovery events. Tooltips identify each event.
import { rule, line, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = Array.from({ length: 97 }, (_, i) => ({ x: i / 4, y: 35 + 12 * Math.sin(i / 11) + 4 * Math.cos(i * 0.7) + (i >= 28 && i < 64 ? 30 : 0),}))const events = [ { hour: 7, name: 'Deploy', recovery: false }, { hour: 16, name: 'Rollback', recovery: true }, { hour: 21, name: 'Verified', recovery: true },]
plot.plotArgs = { series: [ line<Node>({ data, color: { light: '#6366f1', dark: '#a5b4fc' }, width: 2, }), rule<Node>({ data: events.filter(event => !event.recovery), x: 'hour', color: { light: '#c2410c', dark: '#fdba74' }, width: 3, tooltip: ({ row }) => document.createTextNode(row?.name + ' at hour ' + row?.hour), }), rule<Node>({ data: events.filter(event => event.recovery), x: 'hour', color: { light: '#0d9488', dark: '#5eead4' }, width: 2, dash: [5, 4], tooltip: ({ row }) => document.createTextNode(row?.name + ' at hour ' + row?.hour), }), ], height: 260, margin: { top: 5, right: 8, bottom: 20, left: 30 }, axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 110, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot)Rule arguments
| Field | Type | Default | Description |
|---|---|---|---|
data? | Record<string, unknown>[] | None | Optional rows for multiple reference lines. |
x? | number | FieldArg | None | Vertical rule position, or a field/accessor when data is supplied. Set exactly one of x and y. |
y? | number | FieldArg | None | Horizontal rule position, or a field/accessor when data is supplied. |
color? | ColorArg | Black | Series color; theme pairs provide separate light and dark colors. |
width? | number | 1 | Rule width in pixels. |
dash? | number[] | Solid | Alternating dash and gap lengths in pixels. |
tooltip? | TooltipArg | None | Use true for the default tooltip, or a callback for custom content. |
on_select? | SelectFn | None | Callback receiving the selected point and its source row. |
hoverable? | boolean | true | Allow the series to participate in hit testing. |
highlight? | boolean | true | Draw hover feedback for a hit point. |
import { rule } from '@antadesign/plot'
const series = [rule({ data: [{ threshold: 5 }, { threshold: 8 }], y: 'threshold', color: (row) => Number(row.threshold) >= 8 ? 'tomato' : 'steelblue', width: 2, dash: [6, 4], tooltip: ({ x, y }) => `(${x}, ${y})`, on_select: ({ row }) => console.log('Selected row:', row), hoverable: true, highlight: true,})]For a single vertical reference line, supply a numeric x without data:
import { rule } from '@antadesign/plot'
const series = [rule({ x: 3, color: 'tomato', width: 2, hoverable: false, highlight: false,})]Custom
Provide a renderer to draw directly on the canvas. Its second argument
includes the canvas context, plot bounds, scales, and helpers that convert
data positions into pixel coordinates.
Confidence ellipses
Overlay 95% contours for two specified Gaussian models on their sampled points. Draw the ellipse in data coordinates so its shape follows the axis scales.
import { custom, scatter, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const groups = [ { x: 32, y: 40, sx: 10, sy: 5, angle: 0.6 }, { x: 68, y: 62, sx: 8, sy: 5, angle: -0.8 },]const points = groups.flatMap((group, g) => Array.from({ length: 80 }, (_, i) => { const radius = Math.sqrt(-2 * Math.log((i + 0.5) / 80)) const theta = i * 2.399963 const u = group.sx * radius * Math.cos(theta) const v = group.sy * radius * Math.sin(theta) return { x: group.x + u * Math.cos(group.angle) - v * Math.sin(group.angle), y: group.y + u * Math.sin(group.angle) + v * Math.cos(group.angle), group: g, }}))
plot.plotArgs = { series: [ scatter<Node>({ data: points, size: 3, color: row => Number(row.group) === 0 ? { light: '#0d9488', dark: '#5eead4' } : { light: '#7c3aed', dark: '#c4b5fd' }, tooltip: ({ x, y }) => document.createTextNode(Number(x).toFixed(1) + ', ' + Number(y).toFixed(1)), }), custom<Node>({ data: groups, x: 'x', y: 'y', color: (_, i) => i === 0 ? { light: '#0f766e', dark: '#5eead4' } : { light: '#6d28d9', dark: '#c4b5fd' }, hoverable: false, renderer: (_, { ctx, x_scale, y_scale, color_at }) => { ctx.save() ctx.lineWidth = 2 groups.forEach((group, i) => { ctx.strokeStyle = color_at(i) ctx.beginPath() // The 95% contour of the specified bivariate Gaussian model. for (let step = 0; step <= 100; step++) { const theta = step / 100 * Math.PI * 2 const u = Math.sqrt(5.991) * group.sx * Math.cos(theta) const v = Math.sqrt(5.991) * group.sy * Math.sin(theta) const x = Number(x_scale(group.x + u * Math.cos(group.angle) - v * Math.sin(group.angle))) const y = Number(y_scale(group.y + u * Math.sin(group.angle) + v * Math.cos(group.angle))) if (step === 0) ctx.moveTo(x, y) else ctx.lineTo(x, y) } ctx.closePath() ctx.stroke() }) ctx.restore() }, }), ], height: 260, margin: { top: 8, right: 8, bottom: 24, left: 30 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot)Chord diagram
Connect five groups with ribbons whose widths represent shared volume. The outer arcs use the same weights, showing each group’s total connections. Hover an outer arc to inspect its total.
import { custom, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const names = ['A', 'B', 'C', 'D', 'E']const matrix = [ [0, 12, 8, 4, 16], [12, 0, 18, 6, 3], [8, 18, 0, 14, 7], [4, 6, 14, 0, 11], [16, 3, 7, 11, 0],]const gap = 0.08const total = matrix.flat().reduce((sum, value) => sum + value, 0)const unit = (Math.PI * 2 - gap * names.length) / totallet angle = -Math.PI / 2const groups = matrix.map((row, i) => { const start = angle const segments = row.map(value => { const from = angle angle += value * unit return { start: from, end: angle } }) const end = angle angle += gap return { name: names[i], total: row.reduce((sum, value) => sum + value, 0), start, end, segments }})
plot.plotArgs = { series: [custom<Node>({ data: groups, color: (_, i) => [ { light: '#0d9488', dark: '#5eead4' }, { light: '#7c3aed', dark: '#c4b5fd' }, { light: '#db2777', dark: '#f9a8d4' }, { light: '#d97706', dark: '#fcd34d' }, { light: '#2563eb', dark: '#93c5fd' }, ][i], hit_test: (_, { cursor, inner }) => { const cx = (inner.left + inner.right) / 2 const cy = (inner.top + inner.bottom) / 2 const radius = Math.min(inner.right - inner.left, inner.bottom - inner.top) / 2 - 32 const distance = Math.hypot(cursor.x - cx, cursor.y - cy) if (Math.abs(distance - (radius + 7)) > 8) return null let angle = Math.atan2(cursor.y - cy, cursor.x - cx) if (angle < -Math.PI / 2) angle += Math.PI * 2 const index = groups.findIndex(group => angle >= group.start && angle <= group.end) return index < 0 ? null : index }, tooltip: ({ row }) => document.createTextNode('Group ' + row?.name + ': ' + row?.total + ' connections'), renderer: (_, { ctx, inner, color_at }) => { const cx = (inner.left + inner.right) / 2 const cy = (inner.top + inner.bottom) / 2 const radius = Math.min(inner.right - inner.left, inner.bottom - inner.top) / 2 - 32 const point = (a: number) => [cx + radius * Math.cos(a), cy + radius * Math.sin(a)] ctx.save() // Allocate ribbon endpoints from the same weights as the outer arcs. groups.forEach((group, i) => { for (let j = i + 1; j < groups.length; j++) { const source = group.segments[j] const target = groups[j].segments[i] const [sx, sy] = point(source.start) const [tx, ty] = point(target.start) ctx.fillStyle = color_at(i) ctx.globalAlpha = 0.45 ctx.beginPath() ctx.moveTo(sx, sy) ctx.arc(cx, cy, radius, source.start, source.end) ctx.quadraticCurveTo(cx, cy, tx, ty) ctx.arc(cx, cy, radius, target.start, target.end) ctx.quadraticCurveTo(cx, cy, sx, sy) ctx.closePath() ctx.fill() } }) ctx.globalAlpha = 1 ctx.font = '12px sans-serif' ctx.textAlign = 'center' ctx.textBaseline = 'middle' groups.forEach((group, i) => { ctx.strokeStyle = color_at(i) ctx.fillStyle = color_at(i) ctx.lineWidth = 9 ctx.beginPath() ctx.arc(cx, cy, radius + 7, group.start, group.end) ctx.stroke() const mid = (group.start + group.end) / 2 ctx.fillText(group.name, cx + (radius + 22) * Math.cos(mid), cy + (radius + 22) * Math.sin(mid)) }) ctx.restore() }, })], height: 260, margin: 0, axis: { x: { hidden: true, min: 0, max: 1 }, y: { hidden: true, min: 0, max: 1 } }, background: { light: '#ffffff', dark: '#151b28' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot)Ridgeline distributions
Compare six weekly distributions with overlapping density curves. Each ridge uses the same horizontal scale and density normalization, revealing shifts in location and spread. Hover a ridge to see its sample count and mean.
import { custom, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
// Test the same closed polygon used for drawing, including the spaces between sampled points.function containsPoint(points: number[][], x: number, y: number): boolean { let inside = false for (let i = 0, j = points.length - 1; i < points.length; j = i++) { const [xi, yi] = points[i] const [xj, yj] = points[j] if ((yi > y) !== (yj > y) && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside } return inside}
const data = Array.from({ length: 6 }, (_, i) => { const values = Array.from({ length: 80 }, (_, j) => { const z = Math.sqrt(-2 * Math.log((j + 0.5) / 80)) * Math.cos(j * 2.399963) return 25 + i * 8 + z * [8, 5, 11, 4, 9, 6][i] + (j % 3 === 0 ? 14 : 0) }) return { group: 'Week ' + (i + 1), mean: values.reduce((sum, value) => sum + value, 0) / values.length, density: Array.from({ length: 101 }, (_, value) => ({ value, density: values.reduce((sum, sample) => sum + Math.exp(-0.5 * ((value - sample) / 3) ** 2), 0) / (values.length * 3 * Math.sqrt(2 * Math.PI)), })), }})const peak = Math.max(...data.flatMap(row => row.density.map(point => point.density)))
plot.plotArgs = { series: [custom<Node>({ data, y: 'group', color: (_, i) => ({ light: 'hsl(' + (175 + i * 15) + ', 55%, 55%)', dark: 'hsl(' + (175 + i * 15) + ', 50%, 45%)' }), hit_test: (_, { cursor, x_scale, resolve_y, inner }) => { const step = (inner.bottom - inner.top) / data.length // Reverse paint order selects the foreground ridge where shapes overlap. for (let i = data.length - 1; i >= 0; i--) { const center = resolve_y(i) if (center === undefined) continue const baseline = center + step * 0.45 const points = [ [Number(x_scale(0)), baseline], ...data[i].density.map(point => [ Number(x_scale(point.value)), baseline - point.density / peak * step * 1.9, ]), [Number(x_scale(100)), baseline], ] if (containsPoint(points, cursor.x, cursor.y)) return i } return null }, tooltip: ({ row }) => document.createTextNode(row?.group + ' · 80 samples · mean ' + Number(row?.mean).toFixed(1)), renderer: (_, { ctx, x_scale, resolve_y, color_at, inner }) => { const step = (inner.bottom - inner.top) / data.length ctx.save() // Paint from the top row down, so foreground ridges cover the preceding tails. data.forEach((row, i) => { const center = resolve_y(i) if (center === undefined) return const baseline = center + step * 0.45 ctx.fillStyle = color_at(i) ctx.strokeStyle = '#334155' ctx.lineWidth = 1 ctx.beginPath() ctx.moveTo(Number(x_scale(0)), baseline) row.density.forEach(point => { ctx.lineTo(Number(x_scale(point.value)), baseline - point.density / peak * step * 1.9) }) ctx.lineTo(Number(x_scale(100)), baseline) ctx.closePath() ctx.globalAlpha = [1, 0.7, 0.85, 0.65, 0.8, 0.7][i] ctx.fill() ctx.globalAlpha = 1 ctx.stroke() }) ctx.restore() }, })], height: 260, margin: { top: 24, right: 8, bottom: 24, left: 52 }, axis: { x: { min: 0, max: 100, label: '' }, y: { categories: data.map(row => row.group).reverse(), padding_top: 28, label: '' }, }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot)Violin distributions
Mirror a kernel density estimate around each group’s center. Wider sections contain more observations; the central segment shows the interquartile range and the white dot marks the median. Hover a violin to inspect its median and quartiles.
import { custom, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
// Deterministic samples and a Gaussian kernel density estimate, used by the violin example.function distributions() { return ['A', 'B', 'C'].map((group, g) => { const values = Array.from({ length: 60 }, (_, i) => { const z = Math.sqrt(-2 * Math.log((i + 0.5) / 60)) * Math.cos(i * 2.399963) return 36 + g * 10 + z * (6 + g * 2) + (g === 2 && i % 2 === 0 ? 18 : 0) }).sort((a, b) => a - b) const density = Array.from({ length: 101 }, (_, value) => ({ value, density: values.reduce((sum, sample) => sum + Math.exp(-0.5 * ((value - sample) / 4) ** 2), 0) / (values.length * 4 * Math.sqrt(2 * Math.PI)), })) return { group, values, density, q1: values[15], median: (values[29] + values[30]) / 2, q3: values[44] } })}
// Test the same closed polygon used for drawing, including the spaces between sampled points.function containsPoint(points: number[][], x: number, y: number): boolean { let inside = false for (let i = 0, j = points.length - 1; i < points.length; j = i++) { const [xi, yi] = points[i] const [xj, yj] = points[j] if ((yi > y) !== (yj > y) && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside } return inside}
const data = distributions()const peak = Math.max(...data.flatMap(row => row.density.map(point => point.density)))
plot.plotArgs = { series: [custom<Node>({ data, x: 'group', color: (_, i) => [ { light: '#0d9488', dark: '#5eead4' }, { light: '#7c3aed', dark: '#c4b5fd' }, { light: '#c2410c', dark: '#fdba74' }, ][i], hit_test: (_, { cursor, y_scale, resolve_x, inner }) => { const width = Math.min(34, (inner.right - inner.left) / 9) for (let i = data.length - 1; i >= 0; i--) { const x = resolve_x(i) if (x === undefined) continue const points = [ ...data[i].density.map(point => [x + point.density / peak * width, Number(y_scale(point.value))]), ...data[i].density.slice().reverse().map(point => [x - point.density / peak * width, Number(y_scale(point.value))]), ] if (containsPoint(points, cursor.x, cursor.y)) return i } return null }, tooltip: ({ row }) => document.createTextNode('Group ' + row?.group + ' · median ' + Number(row?.median).toFixed(1) + ' · middle 50%: ' + Number(row?.q1).toFixed(1) + '–' + Number(row?.q3).toFixed(1)), renderer: (_, { ctx, y_scale, resolve_x, color_at, inner }) => { const width = Math.min(34, (inner.right - inner.left) / 9) ctx.save() data.forEach((row, i) => { const x = resolve_x(i) if (x === undefined) return ctx.fillStyle = color_at(i) ctx.beginPath() row.density.forEach((point, j) => { const px = x + point.density / peak * width const py = Number(y_scale(point.value)) if (j === 0) ctx.moveTo(px, py) else ctx.lineTo(px, py) }) row.density.slice().reverse().forEach(point => { ctx.lineTo(x - point.density / peak * width, Number(y_scale(point.value))) }) ctx.closePath() ctx.fill() ctx.strokeStyle = '#151b28' ctx.lineWidth = 4 ctx.beginPath() ctx.moveTo(x, Number(y_scale(row.q1))) ctx.lineTo(x, Number(y_scale(row.q3))) ctx.stroke() ctx.fillStyle = '#ffffff' ctx.beginPath() ctx.arc(x, Number(y_scale(row.median)), 3, 0, Math.PI * 2) ctx.fill() }) ctx.restore() }, })], height: 260, margin: { top: 8, right: 8, bottom: 24, left: 30 }, axis: { x: { categories: ['A', 'B', 'C'], label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot)Custom arguments
| Field | Type | Default | Description |
|---|---|---|---|
data? | Record<string, unknown>[] | None | Optional rows; required when using field selectors or hit testing. |
x? | FieldArg | Unclaimed | Horizontal field or accessor; omitted axes do not contribute data bounds. |
y? | FieldArg | Unclaimed | Vertical field or accessor; omitted axes do not contribute data bounds. |
renderer | CustomRendererFn | Required | Draw the series using its data and the supplied canvas context and pixel resolvers. |
hit_test? | CustomHitTestFn | None | Return a row index for a hit, or null. Required for custom tooltips and selection. |
color? | ColorArg | Black | Color made available to the renderer, including optional per-row colors. |
axis_range? | { x?: number[]; y?: number[] } | Data extent | Explicit [low, high] bounds for either axis. |
tooltip? | TooltipArg | None | Use true for the default tooltip, or a callback for custom content. |
on_select? | SelectFn | None | Callback receiving the selected point and its source row. |
hoverable? | boolean | true | Allow the series to participate in hit testing. |
This example draws a cross at each point and finds the nearest cross within eight pixels for tooltips and selection:
import { custom } from '@antadesign/plot'
const series = [custom({ data: [{ x: 1, y: 3 }, { x: 2, y: 5 }, { x: 3, y: 4 }], x: 'x', y: 'y', color: { light: 'steelblue', dark: 'lightskyblue' }, axis_range: { x: [0, 4], y: [0, 6] }, renderer: (series, { ctx, resolve_x, resolve_y, color_at }) => { ctx.save() ctx.lineWidth = 2 for (let i = 0; i < series.x.length; i++) { const x = resolve_x(i) const y = resolve_y(i) if (x === undefined || y === undefined) continue ctx.strokeStyle = color_at(i) ctx.beginPath() ctx.moveTo(x - 4, y) ctx.lineTo(x + 4, y) ctx.moveTo(x, y - 4) ctx.lineTo(x, y + 4) ctx.stroke() } ctx.restore() }, hit_test: (series, { cursor, resolve_x, resolve_y }) => { let nearest: number | null = null let distance = 8 for (let i = 0; i < series.x.length; i++) { const x = resolve_x(i) const y = resolve_y(i) if (x === undefined || y === undefined) continue const candidate = Math.hypot(cursor.x - x, cursor.y - y) if (candidate <= distance) { nearest = i distance = candidate } } return nearest }, tooltip: ({ x, y }) => `(${x}, ${y})`, on_select: ({ row }) => console.log('Selected row:', row), hoverable: true,})]For tooltips or selection, also provide data and a hit_test that returns
the matching row index, or null when there is no hit. Use axis_range
to declare bounds when they cannot be inferred from the data.
Interactions
Hover highlights
Hover bars A through D to see their color intensify. Bar E sets highlight: false, so its appearance stays unchanged. Bar F uses hoverable: false, so it does not participate in hit testing.
Hover feedback is temporary. For custom geometry, supply a hit-testing callback.
import { bar, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = [ { x: 'A', y: 64 }, { x: 'B', y: 82 }, { x: 'C', y: 53 }, { x: 'D', y: 71 }, { x: 'E', y: 44 }, { x: 'F', y: 61 },]
plot.plotArgs = { series: [ bar<Node>({ data: data.slice(0, 4), inset: 7, border_radius: 4, color: { light: 'rgba(13, 148, 136, 0.35)', dark: 'rgba(94, 234, 212, 0.35)' }, }), bar<Node>({ data: data.slice(4, 5), inset: 7, border_radius: 4, color: { light: '#7c3aed', dark: '#c4b5fd' }, highlight: false, }), bar<Node>({ data: data.slice(5), inset: 7, border_radius: 4, color: { light: '#94a3b8', dark: '#64748b' }, hoverable: false, }), ], height: 260, margin: { top: 8, right: 12, bottom: 24, left: 32 }, axis: { x: { categories: data.map(row => row.x), label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot) Custom hit testing
CustomHitTestFn<TooltipContent> receives the composed custom series and a
HitContext. Return the matching row index or null when there is no hit.
Custom series need this callback for tooltips and selection.
import type { ComposedCustom, HitContext } from '@antadesign/plot'
type HitTest = (series: ComposedCustom, context: HitContext) => number | null| Context field | Type | Description |
|---|---|---|
cursor | { x: number; y: number } | Pointer position in the same pixel coordinates as the resolvers. |
inner | Rect | Plot bounds in pixels. |
x_scale | Scale | Composed horizontal scale. |
y_scale | Scale | Composed vertical scale. |
resolve_x | PixelResolver | Resolve a row index to its x position. |
resolve_y | PixelResolver | Resolve a row index to its y position. |
Tooltips
Hover a rectangle to inspect its data. The series with tooltip: true shows the default tooltip. The series with a tooltip callback shows custom content describing its bounds. Where the two series overlap, both tooltip entries appear together, separated by a divider. The tooltip follows the pointer.
Use tooltip: true for default content, or return a DOM node from the standalone host’s callback. React callbacks can return JSX. Both receive the tooltip and selection callback parameters. Omit tooltip to keep hover feedback without a tooltip. See Tooltip content for the callback types.
import { rect, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const data = [ { x: 8, x2: 38, y: 15, y2: 48, name: 'Window A' }, { x: 55, x2: 86, y: 48, y2: 82, name: 'Window B' },]
plot.plotArgs = { series: [ rect<Node>({ data, color: { light: 'rgba(13, 148, 136, 0.45)', dark: 'rgba(94, 234, 212, 0.45)' }, stroke: { color: { light: '#0d9488', dark: '#5eead4' }, width: 2 }, tooltip: true, }), rect<Node>({ data: data.map(row => ({ ...row, x: row.x + 12, x2: row.x2 + 8, y: row.y + 12, y2: row.y2 + 10, })), color: { light: 'rgba(124, 58, 237, 0.45)', dark: 'rgba(196, 181, 253, 0.45)' }, stroke: { color: { light: '#7c3aed', dark: '#c4b5fd' }, width: 2 }, tooltip: ({ row }) => { const content = document.createElement('div') const title = document.createElement('strong') title.textContent = String(row.name) const value = document.createElement('div') value.textContent = 'Bounds: x ' + row.x + '–' + row.x2 + ', y ' + row.y + '–' + row.y2 content.append(title, value) return content }, }), ], height: 260, margin: { top: 8, right: 12, bottom: 24, left: 32 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot) Tooltip content
TooltipArg<Row, TooltipContent> accepts true or a TooltipFn callback.
Omit tooltip to show no tooltip for that series.
| Option | Type | Description |
|---|---|---|
| Default tooltip | true | Enable the host’s default presentation of the point’s axis values. Omit tooltip to disable it; false is not accepted. |
| Custom tooltip | (data: TooltipData<Row>) => TooltipContent | Return content for the hovered point. |
With the React component, TooltipContent is a ReactNode, including strings
and JSX. With the standalone browser host, return a DOM Node. The core
leaves the content type to the host.
Selection
Select a point to display its source data in the preview’s text readout. on_select receives the selected point and its original row, so you can update a detail panel or application state.
The callback does not create persistent selection styling. This preview keeps the last selected value in a text readout. See Selection callbacks for the payload.
import { scatter, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElementconst output = document.createElement('output')output.setAttribute('aria-live', 'polite')output.style.display = 'block'output.textContent = 'Select a point to inspect its data.'
const data = Array.from({ length: 24 }, (_, i) => ({ x: 8 + (i * 19 % 84), y: 12 + (i * 29 % 76), name: 'Sample ' + (i + 1),}))
plot.plotArgs = { series: [ scatter<Node>({ data, size: 13, color: { light: '#0891b2', dark: '#67e8f9' }, tooltip: true, on_select: ({ row, x, y }) => { output.textContent = row.name + ' · x: ' + x + ' · y: ' + y }, }), ], height: 260, margin: { top: 8, right: 12, bottom: 24, left: 32 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: false,}
document.body.append(plot, output) Selection callbacks
SelectFn<Row> is (data: TooltipData<Row>) => unknown. Assign it to
on_select to respond when a point is selected. Its return value is ignored.
import type { SelectFn } from '@antadesign/plot'
const on_select: SelectFn = ({ x, y, row }) => { console.log('Selected point:', x, y, row)} Tooltip and selection callback parameters
Tooltip and selection callbacks receive the same TooltipData<Row> shape:
| Field | Type | Description |
|---|---|---|
x | number | string | Point’s x value, or category label. |
y | number | string | Point’s y value, or category label. |
row | Row | Source data row. May be undefined for rule or custom series without data. |
label? | string | Point label when available, such as the field name of a stacked bar segment. |
Row defaults to Record<string, unknown>. The related PointData type has
the same fields with an optional row.
Zoom and pan
Zoom continuous axes with the scroll wheel and pan by dragging. The reset control restores the full extent after the view changes. Categorical axes do not zoom. See Zoom and pan options.
Zoom with a modifier
Hold Ctrl and scroll to zoom around the pointer, or hold Ctrl and drag to pan. The modifier leaves ordinary scrolling available for navigating the page.
import { line, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElementconst output = document.createElement('output')output.setAttribute('aria-live', 'polite')output.style.display = 'block'output.textContent = 'Hold Ctrl and scroll to zoom, or Ctrl-drag to pan.'
const data = Array.from({ length: 201 }, (_, i) => ({ x: i / 2, y: 45 + 18 * Math.sin(i / 18) + 9 * Math.cos(i / 5) + 4 * Math.sin(i * 1.3),}))
plot.plotArgs = { series: [ line<Node>({ data, width: 2, color: { light: '#6366f1', dark: '#a5b4fc' }, tooltip: true, }), ], height: 260, margin: { top: 8, right: 12, bottom: 24, left: 32 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: { x: true, y: true, modifier: true }, on_viewport_change: ({ x, y }) => { const format = (axis: { window: number[] } | null) => axis ? axis.window.map(value => value.toFixed(1)).join('–') : 'full extent' output.textContent = 'Visible x: ' + format(x) + ' · y: ' + format(y) },}
document.body.append(plot, output)Zoom without a modifier
Scroll over the plot to zoom, or drag to pan. Set modifier: false when the plot should own these gestures without requiring Ctrl.
import { line, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElementconst output = document.createElement('output')output.setAttribute('aria-live', 'polite')output.style.display = 'block'output.textContent = 'Scroll to zoom, or drag to pan.'
const data = Array.from({ length: 201 }, (_, i) => ({ x: i / 2, y: 45 + 18 * Math.sin(i / 18) + 9 * Math.cos(i / 5) + 4 * Math.sin(i * 1.3),}))
plot.plotArgs = { series: [ line<Node>({ data, width: 2, color: { light: '#6366f1', dark: '#a5b4fc' }, tooltip: true, }), ], height: 260, margin: { top: 8, right: 12, bottom: 24, left: 32 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: { x: true, y: true, modifier: false }, on_viewport_change: ({ x, y }) => { const format = (axis: { window: number[] } | null) => axis ? axis.window.map(value => value.toFixed(1)).join('–') : 'full extent' output.textContent = 'Visible x: ' + format(x) + ' · y: ' + format(y) },}
document.body.append(plot, output)Zoom on one axis
Hold Ctrl and scroll or drag. Only the horizontal window changes; the vertical range stays fixed. Set x: false, y: true for vertical-only navigation.
import { line, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElementconst output = document.createElement('output')output.setAttribute('aria-live', 'polite')output.style.display = 'block'output.textContent = 'Hold Ctrl and scroll to zoom, or Ctrl-drag to pan.'
const data = Array.from({ length: 201 }, (_, i) => ({ x: i / 2, y: 45 + 18 * Math.sin(i / 18) + 9 * Math.cos(i / 5) + 4 * Math.sin(i * 1.3),}))
plot.plotArgs = { series: [ line<Node>({ data, width: 2, color: { light: '#6366f1', dark: '#a5b4fc' }, tooltip: true, }), ], height: 260, margin: { top: 8, right: 12, bottom: 24, left: 32 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: { x: true, y: false, modifier: true }, on_viewport_change: ({ x, y }) => { const format = (axis: { window: number[] } | null) => axis ? axis.window.map(value => value.toFixed(1)).join('–') : 'full extent' output.textContent = 'Visible x: ' + format(x) + ' · y: ' + format(y) },}
document.body.append(plot, output) Zoom and pan options
zoom_pan accepts a boolean for both axes or this ZoomPanArg object:
| Field | Type | Default | Description |
|---|---|---|---|
x? | boolean | true | Enable horizontal zoom and pan on a continuous axis. |
y? | boolean | true | Enable vertical zoom and pan on a continuous axis. |
modifier? | boolean | true | Require Ctrl for gestures. Set false to allow gestures without a modifier. |
Setting both axes to false disables zoom and pan. Categorical axes do not zoom.
Controlling the viewport
Use the buttons to focus on a range or restore the full extent. You can still zoom and pan horizontally with Ctrl after a button sets the view.
Set viewport to control the visible range. Increment its key to apply a new request after the user zooms or pans. See Viewport for initialization and reset behavior.
on_viewport_change reports the visible and full ranges. Keep this notification separate from viewport requests to avoid reapplying the view on every gesture. See Viewport changes for the callback parameters.
import { line, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'import '@antadesign/anta/elements/a-button'
const plot = document.createElement('a-plot') as APlotElementconst output = document.createElement('output')output.setAttribute('aria-live', 'polite')output.style.display = 'block'output.textContent = 'Hold Ctrl and scroll to zoom, or Ctrl-drag to pan.'
const data = Array.from({ length: 201 }, (_, i) => ({ x: i / 2, y: 45 + 18 * Math.sin(i / 18) + 9 * Math.cos(i / 5) + 4 * Math.sin(i * 1.3),}))
plot.plotArgs = { series: [ line<Node>({ data, width: 2, color: { light: '#6366f1', dark: '#a5b4fc' }, tooltip: true, }), ], height: 260, margin: { top: 8, right: 12, bottom: 24, left: 32 }, axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } }, background: { light: '#ffffff', dark: '#151b28' }, chrome_color: { light: '#e2e8f0', dark: '#334155' }, grid: false, border: false, zoom_pan: { x: true, y: false, modifier: true }, viewport: { x: [20, 45], y: null, key: 0 }, on_viewport_change: ({ x, y }) => { const format = (axis: { window: number[] } | null) => axis ? axis.window.map(value => value.toFixed(1)).join('–') : 'full extent' output.textContent = 'Visible x: ' + format(x) + ' · y: ' + format(y) },}
const controls = document.createElement('div')let key = 0const windows: [string, number[] | null][] = [ ['Focus 20–45', [20, 45]], ['Focus 55–80', [55, 80]], ['Full extent', null],]for (const [label, x] of windows) { const button = document.createElement('a-button') button.textContent = label button.addEventListener('click', () => { const args = plot.plotArgs if (!args) return plot.plotArgs = { ...args, viewport: { x, y: null, key: ++key } } }) controls.append(button)}document.body.append(plot, controls, output) Viewport
viewport requests a visible window within the full data domain. It applies
at mount and when key changes; user gestures can then move away from it.
Changing only x or y with the same key does not reapply the request.
| Field | Type | Default | Description |
|---|---|---|---|
x? | number[] | null | Leave unchanged | Requested [min, max] on x, clamped to the full domain. null restores its full extent. |
y? | number[] | null | Leave unchanged | Requested [min, max] on y, clamped to the full domain. null restores its full extent. |
key? | string | number | None | Change this value to apply a new viewport request. |
At mount, an omitted axis starts at its full extent. Viewport windows apply to continuous axes.
Viewport changes
on_viewport_change receives a ViewportChange after a gesture or reset:
| Field | Type | Description |
|---|---|---|
x | AxisViewport | null | Current horizontal viewport, or null for a categorical axis. |
y | AxisViewport | null | Current vertical viewport, or null for a categorical axis. |
Each AxisViewport contains:
| Field | Type | Description |
|---|---|---|
window | number[] | Current visible [min, max]. |
full | number[] | Full unzoomed [min, max]. |
Configuration details
Title
Set the title text, size, and purple theme colors.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function title(): PlotArgs<Node> { return { ...base(), margin: { top: 44, right: 24, bottom: 48, left: 76 }, title: { text: 'Plot title', size: 20, color: { light: '#713fff', dark: '#c4b5fd' } } }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = title()document.body.append(plot)title accepts a string or a TitleArg object:
| Field | Type | Default | Description |
|---|---|---|---|
text | string | Required | Plot title text. |
size? | number | 14 | Font size in pixels. |
color? | ThemeColor | Theme default | Title color. |
Margins
The canvas uses a gray CSS background. The plot background fills only the area inside the axes, making the margins visible.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function margins(): PlotArgs<Node> { return { ...base(), margin: { top: 40, right: 40, bottom: 64, left: 88 }, background: { light: 'white', dark: '#202124' } }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = margins()plot.style.background = 'color-mix(in srgb, var(--text-1, #111827) 18%, var(--bg-1, #ffffff))'document.body.append(plot)margin accepts one number for all sides or a SideMargins object.
Margins reserve space outside the plot area for labels and the title;
axis padding adds space inside it.
| Field | Type | Default | Description |
|---|---|---|---|
top? | number | 60 | Top margin in pixels. |
right? | number | 60 | Right margin in pixels. |
bottom? | number | 60 | Bottom margin in pixels. |
left? | number | 60 | Left margin in pixels. |
When both axes are hidden and there is no title, omitted margins use 2
pixels instead. Explicit margins still apply.
Grid
Compare both grid directions, each direction separately, and no grid. Seven categories set the vertical grid positions; tick marks and labels are hidden.
Both grid directions
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function grid(): PlotArgs<Node> { const categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G'] return { ...base(), height: 160, margin: { top: 10, right: 10, bottom: 10, left: 10 }, series: [scatter<Node>({ data: categories.map((x, i) => ({ x, y: 20 + i * 9 })), size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], axis: { x: { categories, label: '', tick_mark: false, tick_label: { format: () => '' } }, y: { min: 0, max: 100, label: '', tick_mark: false, tick_label: { format: () => '' } } }, grid: { x: true, y: true } }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = grid()document.body.append(plot)Vertical grid lines
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function grid(): PlotArgs<Node> { const categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G'] return { ...base(), height: 160, margin: { top: 10, right: 10, bottom: 10, left: 10 }, series: [scatter<Node>({ data: categories.map((x, i) => ({ x, y: 20 + i * 9 })), size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], axis: { x: { categories, label: '', tick_mark: false, tick_label: { format: () => '' } }, y: { min: 0, max: 100, label: '', tick_mark: false, tick_label: { format: () => '' } } }, grid: { x: true, y: true } }}
function gridX(): PlotArgs<Node> { return { ...grid(), grid: { x: true, y: false } }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = gridX()document.body.append(plot)Horizontal grid lines
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function grid(): PlotArgs<Node> { const categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G'] return { ...base(), height: 160, margin: { top: 10, right: 10, bottom: 10, left: 10 }, series: [scatter<Node>({ data: categories.map((x, i) => ({ x, y: 20 + i * 9 })), size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], axis: { x: { categories, label: '', tick_mark: false, tick_label: { format: () => '' } }, y: { min: 0, max: 100, label: '', tick_mark: false, tick_label: { format: () => '' } } }, grid: { x: true, y: true } }}
function gridY(): PlotArgs<Node> { return { ...grid(), grid: { x: false, y: true } }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = gridY()document.body.append(plot)No grid lines
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function grid(): PlotArgs<Node> { const categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G'] return { ...base(), height: 160, margin: { top: 10, right: 10, bottom: 10, left: 10 }, series: [scatter<Node>({ data: categories.map((x, i) => ({ x, y: 20 + i * 9 })), size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], axis: { x: { categories, label: '', tick_mark: false, tick_label: { format: () => '' } }, y: { min: 0, max: 100, label: '', tick_mark: false, tick_label: { format: () => '' } } }, grid: { x: true, y: true } }}
function gridNone(): PlotArgs<Node> { return { ...grid(), grid: false }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = gridNone()document.body.append(plot)grid accepts a boolean for both axes or this GridSpec object:
| Field | Type | Default | Description |
|---|---|---|---|
x? | boolean | false | Draw vertical grid lines at x-axis ticks. |
y? | boolean | false | Draw horizontal grid lines at y-axis ticks. |
Theme colors
The scatter marks use tomato in light mode and lightskyblue in dark mode, with size: 18. Switch the site theme to compare them.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function themeColors(): PlotArgs<Node> { return { ...base(), series: [scatter<Node>({ data: [{ x: 20, y: 30 }, { x: 50, y: 65 }, { x: 80, y: 45 }], size: 18, color: { light: 'tomato', dark: 'lightskyblue' }, hoverable: false, })] }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = themeColors()document.body.append(plot)ThemeColor accepts a CSS color string or a ColorPair. Use a pair to choose
separate colors for light and dark themes.
| Form | Type | Example |
|---|---|---|
| One color | string | 'steelblue', '#4682b4', 'rgb(70, 130, 180)', 'hsl(207, 44%, 49%)' |
| Theme pair | { light: string; dark: string } | { light: 'steelblue', dark: 'lightskyblue' } |
Both fields in a ColorPair are required. ColorTheme is 'light' or 'dark'.
Plain colors remain subject to the plot’s theme_invert setting.
Scales
Compare linear and logarithmic axes, then time and category axes. Each axis label names its scale.
Linear and logarithmic scales
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function scales(): PlotArgs<Node> { return { ...base(), series: [scatter<Node>({ data: [1, 10, 100, 1000, 10000].map((y, i) => ({ x: 10 + i * 20, y })), size: 4, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], axis: { x: { scale: 'linear', min: 0, max: 100, label: 'Linear' }, y: { scale: 'log', min: 1, max: 10000, label: 'Logarithmic', tick_label: { format: value => Number(value) >= 1000 ? Number(value) / 1000 + 'k' : String(value) } } }, grid: { y: true }, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = scales()document.body.append(plot)Time and category scales
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function timeCategory(): PlotArgs<Node> { const start = new Date(2026, 0, 12).getTime() return { ...base(), series: [scatter<Node>({ data: [ { x: start, y: 'Alpha' }, { x: start + 3600000, y: 'Beta' }, { x: start + 7200000, y: 'Alpha' }, { x: start + 10800000, y: 'Gamma' }, { x: start + 14400000, y: 'Beta' }, ], size: 4, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], margin: { top: 20, right: 24, bottom: 48, left: 94 }, axis: { x: { scale: 'time', min: start, max: start + 14400000, label: 'Time', padding: 8, tick_label: { format: value => new Date(Number(value)).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }) } }, y: { scale: 'category', categories: ['Alpha', 'Beta', 'Gamma'], label: 'Category' } }, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = timeCategory()document.body.append(plot)Set axis.x.scale or axis.y.scale to choose how data maps to positions.
Plot uses D3 scales internally.
| Value | D3 scale | Description |
|---|---|---|
'linear' | scaleLinear | Equal differences in value produce equal distances. The default for numeric data. |
'log' | scaleLog | Equal ratios produce equal distances. Use for positive values spanning several orders of magnitude. |
'category' | scaleBand | Place discrete categories in evenly spaced bands. Inferred from string data. |
'time' | scaleTime | Position timestamps on a continuous timeline, with ticks in local time. |
'utc' | scaleUtc | Position timestamps on a continuous timeline, with ticks in UTC. |
Both time scales accept Unix timestamps in milliseconds. Use categories
to set a category order, or min and max to set continuous-domain bounds.
Axis labels
Compare label positions, sizes, and colors: 18px and 12px labels aligned right and top, then 12px and 20px labels aligned left and bottom.
Right and top labels
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function axisLabels(): PlotArgs<Node> { return { ...base(), margin: { top: 44, right: 28, bottom: 62, left: 94 }, axis: { x: { min: 0, max: 100, label: { text: 'Time', position: 'right', size: 18, color: { light: '#713fff', dark: '#c4b5fd' } } }, y: { min: 0, max: 100, label: { text: 'Value', position: 'top', size: 12, color: { light: '#c2410c', dark: '#fdba74' } } }, } }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = axisLabels()document.body.append(plot)Left and bottom labels
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function axisLabelsOpposite(): PlotArgs<Node> { return { ...base(), margin: { top: 44, right: 28, bottom: 62, left: 94 }, axis: { x: { min: 0, max: 100, label: { text: 'Time', position: 'left', size: 12, color: { light: '#c2410c', dark: '#fdba74' } } }, y: { min: 0, max: 100, label: { text: 'Value', position: 'bottom', size: 20, color: { light: '#713fff', dark: '#c4b5fd' } } }, } }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = axisLabelsOpposite()document.body.append(plot)LabelArg accepts a string or this object at axis.x.label or axis.y.label:
| Field | Type | Default | Description |
|---|---|---|---|
text | string | Required | Axis label text. |
size? | number | 12 | Font size in pixels. |
color? | ThemeColor | Theme default | Label color. |
position? | 'center' | 'left' | 'right' | 'top' | 'bottom' | 'center' | Use left or right on x; top or bottom on y. |
Tick labels
Add units to tick labels with formatting callbacks. The horizontal ticks use 10px purple text; the vertical ticks use 15px orange text.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function tickLabels(): PlotArgs<Node> { return { ...base(), margin: { top: 44, right: 28, bottom: 62, left: 94 }, axis: { x: { min: 0, max: 100, label: 'Elapsed time', tick_label: { format: value => value + ' s', size: 10, color: { light: '#713fff', dark: '#c4b5fd' } } }, y: { min: 0, max: 100, label: 'Utilization', tick_label: { format: value => value + '%', size: 15, color: { light: '#c2410c', dark: '#fdba74' } } }, } }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = tickLabels()document.body.append(plot)Use TickLabelArg at axis.x.tick_label or axis.y.tick_label:
| Field | Type | Default | Description |
|---|---|---|---|
format? | (value: number | string, index: number) => string | Scale-dependent | Format each tick value. Return '' for an empty label. |
size? | number | 10 | Font size in pixels. |
color? | ThemeColor | Theme default | Tick label color. |
Series colors
Use a color accessor to distinguish values at or above 60.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function seriesColors(): PlotArgs<Node> { return { ...base(), series: [scatter<Node>({ data: [{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }], size: 8, color: row => Number(row.y) >= 60 ? { light: '#c2410c', dark: '#fdba74' } : { light: '#64748b', dark: '#94a3b8' }, hoverable: false, })] }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = seriesColors()document.body.append(plot)ColorArg adds a per-row accessor to ThemeColor. Scatter, bar, rect, rule,
and custom series accept it; line and area use one ThemeColor per series.
| Form | Type | Description |
|---|---|---|
| Shared color | ThemeColor | Apply one color or theme pair to the series. |
| Accessor | (row: Record<string, unknown>, index: number) => ThemeColor | Choose a color or theme pair for each row. |
import type { ColorArg } from '@antadesign/plot'
const color: ColorArg = (row) => Number(row.value) >= 10 ? 'coral' : 'steelblue'For these series, a valid color on a data row takes precedence over the
series color or accessor. Stacked bars also accept a ThemeColor[], with
one entry per stacked field.
Mark shapes
Set a different mark shape for each category.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function markShapes(): PlotArgs<Node> { const marks = ['circle', 'square', 'diamond', 'triangle'] as const return { ...base(), series: marks.map((mark, i) => scatter<Node>({ data: [{ x: mark, y: 40 + i * 10 }], mark, size: 12, color: [ { light: '#7c3aed', dark: '#c4b5fd' }, { light: '#db2777', dark: '#f9a8d4' }, { light: '#0891b2', dark: '#67e8f9' }, { light: '#ea580c', dark: '#fdba74' }, ][i], hoverable: false, })), axis: { x: { categories: [...marks], label: '' }, y: { min: 0, max: 100, label: '' } }, margin: { top: 20, right: 12, bottom: 32, left: 32 }, }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = markShapes()document.body.append(plot)MarkShape controls scatter.mark and line.mark:
| Value | Description |
|---|---|
'circle' | Circular mark. |
'square' | Square mark. |
'diamond' | Diamond mark. |
'triangle' | Triangular mark. |
Scatter defaults to 'circle'; line draws no marks unless mark is set.
Strokes
Compare outlines with widths of 1, 3, and 5 pixels on equal-sized circles.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function base(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [ { x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }, ], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })], height: 260, margin: { top: 20, right: 24, bottom: 48, left: 76 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: { light: '#ffffff', dark: '#202124' }, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
function strokes(): PlotArgs<Node> { return { ...base(), series: [1, 3, 5].map((width, i) => scatter<Node>({ data: [{ x: 25 + i * 25, y: 50 }], size: 18, color: { light: '#f0abfc', dark: '#c026d3' }, stroke: { color: { light: '#7e22ce', dark: '#f5d0fe' }, width }, hoverable: false, })) }}
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = strokes()document.body.append(plot)StrokeArg accepts a ThemeColor or a Stroke object. Use it for stroke
on scatter, area, and rect series, or mark_stroke on a line series.
| Field | Type | Default | Description |
|---|---|---|---|
color | ThemeColor | Required | Outline color or theme pair. |
width? | number | Series-dependent | Outline width in pixels. Mark outlines scale with mark size when omitted. |
import type { StrokeArg } from '@antadesign/plot'
const stroke: StrokeArg = { color: { light: 'navy', dark: 'lightsteelblue' }, width: 1,} Data fields
FieldArg selects a value from each data row. Use it for series coordinates
such as x and y, or for a second boundary where the series supports one.
| Form | Type | Description |
|---|---|---|
| Field name | string | Read the named property from each row. |
| Accessor | (row: Record<string, unknown>, index: number) => number | string | Compute a value from the row and its zero-based index. Return numbers for continuous axes or strings for categories. |
import type { FieldArg } from '@antadesign/plot'
const x: FieldArg = 'elapsed_ms'const y: FieldArg = (row) => Number(row.duration_ms) / 1000 Custom rendering
CustomRendererFn<TooltipContent> receives the composed custom series and
its drawing context:
import type { ComposedCustom, CustomRenderContext } from '@antadesign/plot'
type Renderer = (series: ComposedCustom, context: CustomRenderContext) => voidThe composed series contains resolved x and y columns, source rows when
provided, and theme-resolved colors. Category coordinates are stored as
indices; use the pixel resolvers to place them on the canvas.
| Context field | Type | Description |
|---|---|---|
ctx | CanvasContext | Main-thread or offscreen 2D canvas context. |
inner | Rect | Plot bounds in pixels: left, right, top, and bottom. |
x_scale | Scale | Composed horizontal D3 scale. |
y_scale | Scale | Composed vertical D3 scale. |
color | string | Resolved fallback series color. |
x_categories? | string[] | Horizontal category labels, when categorical. |
y_categories? | string[] | Vertical category labels, when categorical. |
resolve_x | PixelResolver | Resolve a row index to its x position in pixels. |
resolve_y | PixelResolver | Resolve a row index to its y position in pixels. |
color_at | ColorResolver | Resolve a row index to its color, including the series fallback. |
PixelResolver is (index: number) => number | undefined; skip points whose
position is undefined. ColorResolver is (index: number) => string.
CanvasContext is CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D.
Scale is a D3 linear, logarithmic, band, or time scale.
Web Component
<a-plot> is a light-DOM custom element that takes the same arguments
as a property. Use it directly from plain JavaScript or TypeScript; it owns
controller setup, drawing, and cleanup in the browser.
import { scatter, type APlotElement } from '@antadesign/plot/browser'import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElementplot.plotArgs = { series: [scatter({ data: [{ x: 1, y: 2 }, { x: 2, y: 3 }] })] }document.body.append(plot)plotArgs is a property, not an attribute — assign the object rather
than serializing it. Import @antadesign/plot/elements/a-plot to register the standalone plot and its surface dependency.
Registration skips existing definitions and does nothing when customElements
is unavailable, including during server rendering.
Styling
The JSX Plot component fills its parent with width: 100% and height: 100%.
Give its container a height, or set width and height in plotArgs. Removing
those arguments restores parent-relative sizing. Use className and style to
style the wrapper; inline style props can override its default dimensions.
The standalone <a-plot> element has a 300px fallback height. The preview below
uses that element. Configure canvas colors, lines, and text through plotArgs.
This preview uses CSS for the rounded container, padding, and responsive aspect
ratio. It omits width and height from plotArgs so CSS controls the size.
Set background: false to let the container background show through the canvas.
import { scatter, type PlotArgs } from '@antadesign/plot'import '@antadesign/plot/elements/a-plot'import type { APlotElement } from '@antadesign/plot/browser'
function stylingExample(): PlotArgs<Node> { return { series: [scatter<Node>({ data: [{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 }, { x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false, })], margin: { top: 16, right: 16, bottom: 40, left: 60 }, axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } }, background: false, chrome_color: { light: '#9ca3af', dark: '#d1d5db' }, border: true, grid: false, zoom_pan: false, }}
const container = document.createElement('div')container.className = 'plot-card'const plot = document.createElement('a-plot') as APlotElementplot.className = 'styled-plot'plot.setAttribute('aria-label', 'Scatter plot in a rounded container')plot.plotArgs = stylingExample()container.append(plot)document.body.append(container).plot-card { box-sizing: border-box; width: 100%; max-width: 460px; margin-inline: auto; padding: 16px; border: 1px solid #9ca3af; border-radius: 16px; background: rgb(156 163 175 / 12%);}
.styled-plot { display: block; width: 100%; height: auto; aspect-ratio: 4 / 3;}The a-plot-surface element installs its structural styles automatically,
including canvas stacking and overlay positioning. It does not require a
separate CSS import.
The complete a-plot host installs its base styles automatically too, giving it
a width of 100% and a fallback height of 300px. No plot.css import is needed.
Its sizing rule uses :where() with zero specificity, so your CSS can override
it. Set height: auto when using aspect-ratio to replace the fixed fallback
height, as this example does.
Alternatively, pass width and height to set inline styles on the host.
These override stylesheet rules. Removing an argument restores the previous
inline value, allowing stylesheet rules to apply when no inline value remains.
Inside the canvas, use chrome_color for axis lines, tick marks,
grid lines, and the plot border. Use background for the plot fill and each
series’ color for its marks. These colors accept { light, dark } pairs.
Plain color strings remain subject to the plot’s theme_invert setting.
Set text colors separately with title.color, axis.x.label.color,
axis.y.label.color, axis.x.tick_label.color, and axis.y.tick_label.color.
With the JSX Plot component, return content supported by your renderer from a
series’ tooltip callback and style it like other markup. Anta’s
Tooltip renders that content in the component tree. The reset control is an Anta Button and
follows the application’s theme.