AntaPlot
Switch to dark theme
Search documentation
On this page

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.

Install

Install the package to use plots in React, Preact, or plain JavaScript.

Terminal window
npm install @antadesign/plot # or pnpm / bun

Import 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 APlotElement
plot.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 APlotElement
plot.plotArgs = spacing()
document.body.append(plot)
FieldTypeDefaultDescription
seriesSeries[]RequiredWhat to draw, in paint order. Build each one with a factory.
axis?{ x?: AxisArgs, y?: AxisArgs }Inferred from dataPer-axis scale, label, ticks, and domain.
title?string | { text, size?, color? }NonePlot title.
width?numberContainer widthCanvas width in pixels. Overrides the wrapper’s CSS width.
height?numberParent 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 titleSpace reserved around the plot area for axes and title.
border?booleantrueDraws a border around the plot area.
grid?boolean | { x?, y? }falseGrid lines, per axis.
chrome_color?string | { light, dark }Theme defaultsColor for axis lines, tick marks, grid lines, and the plot border. Text colors are configured separately.
background?boolean | string | { light, dark }WhitePlot fill. false paints none; true and an absent value use #fff.
theme_invert?booleanAutomaticForces 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 requiredRequire 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 domainRequested starting window per axis. Applied at mount and on each key change, clamped to the full domain.
on_viewport_change?(change: ViewportChange) => voidNoneFires 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 APlotElement
plot.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 APlotElement
plot.plotArgs = timeAxis()
document.body.append(plot)
FieldTypeDefaultDescription
scale?'linear' | 'log' | 'category' | 'time' | 'utc'InferredNumeric 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?LabelArgShared field name, if availableAxis name, as text or an object with presentation options. Use '' to omit it.
min?numberInferred from dataLower continuous-domain bound.
max?numberInferred from dataUpper continuous-domain bound.
categories?string[]Inferred from dataExplicit category list and order; also declares a categorical axis.
padding?number0Pixel padding at both ends of the axis, inside the plot area.
padding_left?numberpaddingLeft-end padding for axis.x.
padding_right?numberpaddingRight-end padding for axis.x.
padding_top?numberpaddingTop-end padding for axis.y.
padding_bottom?numberpaddingBottom-end padding for axis.y.
grid_align?'center' | 'edge''center'Position grid lines at category centers or boundaries. Categorical axes only.
line?booleantrueDraw the axis line.
hidden?booleanfalseHide the axis presentation while retaining its scale.
tick_label?TickLabelArgAutomatic formattingTick text formatting, size, and color.
tick_mark?booleantrueDraw 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
FieldTypeDefaultDescription
dataRecord<string, unknown>[]RequiredRows used to build the series.
x?FieldArg'x'Horizontal field name or accessor.
y?FieldArg'y'Vertical field name or accessor.
color?ColorArgBlackSeries color; theme pairs provide separate light and dark colors.
size?number | ((row, index) => number)5Mark 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?StrokeArgNoneOutline color and optional width.
tooltip?TooltipArgNoneUse true for the default tooltip, or a callback for custom content.
on_select?SelectFnNoneCallback receiving the selected point and its source row.
hoverable?booleantrueAllow the series to participate in hit testing.
highlight?booleantrueDraw 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
FieldTypeDefaultDescription
dataRecord<string, unknown>[]RequiredRows used to build the series.
x?FieldArg'x'Horizontal field name or accessor.
y?FieldArg'y'Vertical field name or accessor.
color?ThemeColorBlackSeries color; theme pairs provide separate light and dark colors.
width?number2Line width in pixels.
dash?number[]SolidAlternating dash and gap lengths in pixels.
mark?MarkShapeNoneShape drawn at each point.
mark_size?number6Point marker diameter in pixels.
mark_stroke?StrokeArgNoneOutline for the point markers.
tooltip?TooltipArgNoneUse true for the default tooltip, or a callback for custom content.
on_select?SelectFnNoneCallback receiving the selected point and its source row.
hoverable?booleantrueAllow the series to participate in hit testing.
highlight?booleantrueDraw 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 APlotElement
plot.plotArgs = categoryOrder()
document.body.append(plot)
Bar arguments
FieldTypeDefaultDescription
dataRecord<string, unknown>[]RequiredRows 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``[]BlackUniform or per-row color; a color array maps to stacked fields in order.
border_radius?number0Bar corner radius in pixels.
inset?numberOne sixth of band widthInset on each side of a categorical band, in pixels.
min_size?number2Minimum rendered value length in pixels.
hover_span_x?booleanfalseExtend the horizontal hit region across the categorical band.
hover_span_y?booleanfalseExtend the vertical hit region across the categorical band.
tooltip?TooltipArgNoneUse true for the default tooltip, or a callback for custom content.
on_select?SelectFnNoneCallback receiving the selected point and its source row.
hoverable?booleantrueAllow the series to participate in hit testing.
highlight?booleantrueDraw 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 APlotElement
plot.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 APlotElement
plot.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 APlotElement
plot.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
FieldTypeDefaultDescription
dataRecord<string, unknown>[]RequiredRows used to build the series.
x?FieldArg'x'Horizontal field name or accessor.
x2?FieldArg | numberNoneSecond horizontal boundary, as a field, accessor, or constant. Use either x2 or y2.
y?FieldArg'y'Vertical field name or accessor.
y2?FieldArg | numberZero baseline if neither bound is setSecond vertical boundary, as a field, accessor, or constant.
color?ThemeColorBlackSeries color; theme pairs provide separate light and dark colors.
stroke?StrokeArgNoneOutline color and optional width.
dash?number[]SolidAlternating dash and gap lengths in pixels.
tooltip?TooltipArgNoneUse true for the default tooltip, or a callback for custom content.
on_select?SelectFnNoneCallback receiving the selected point and its source row.
hoverable?booleantrueAllow the series to participate in hit testing.
highlight?booleantrueDraw 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
FieldTypeDefaultDescription
dataRecord<string, unknown>[]RequiredRows used to build the series.
x?FieldArg'x'Horizontal field name or accessor.
x2?FieldArg'x2' when present in dataOpposite 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 dataOpposite vertical corner. Numeric axes need a second corner or pixel size.
span?'x' | 'y'NoneStretch the rectangle across the full plot on this axis.
size?number | { width?: number; height?: number }Derived from bounds or bandFixed 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?booleanfalseExtend the horizontal hit region across the categorical band.
hover_span_y?booleanfalseExtend the vertical hit region across the categorical band.
inset?numberOne sixth of band widthInset on each side of a categorical band, in pixels.
min_size?number0Minimum 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?ColorArgBlackSeries color; theme pairs provide separate light and dark colors.
stroke?StrokeArgNoneOutline color and optional width.
tooltip?TooltipArgNoneUse true for the default tooltip, or a callback for custom content.
on_select?SelectFnNoneCallback receiving the selected point and its source row.
hoverable?booleantrueAllow the series to participate in hit testing.
highlight?booleantrueDraw 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
FieldTypeDefaultDescription
data?Record<string, unknown>[]NoneOptional rows for multiple reference lines.
x?number | FieldArgNoneVertical rule position, or a field/accessor when data is supplied. Set exactly one of x and y.
y?number | FieldArgNoneHorizontal rule position, or a field/accessor when data is supplied.
color?ColorArgBlackSeries color; theme pairs provide separate light and dark colors.
width?number1Rule width in pixels.
dash?number[]SolidAlternating dash and gap lengths in pixels.
tooltip?TooltipArgNoneUse true for the default tooltip, or a callback for custom content.
on_select?SelectFnNoneCallback receiving the selected point and its source row.
hoverable?booleantrueAllow the series to participate in hit testing.
highlight?booleantrueDraw 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.08
const total = matrix.flat().reduce((sum, value) => sum + value, 0)
const unit = (Math.PI * 2 - gap * names.length) / total
let angle = -Math.PI / 2
const 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
FieldTypeDefaultDescription
data?Record<string, unknown>[]NoneOptional rows; required when using field selectors or hit testing.
x?FieldArgUnclaimedHorizontal field or accessor; omitted axes do not contribute data bounds.
y?FieldArgUnclaimedVertical field or accessor; omitted axes do not contribute data bounds.
rendererCustomRendererFnRequiredDraw the series using its data and the supplied canvas context and pixel resolvers.
hit_test?CustomHitTestFnNoneReturn a row index for a hit, or null. Required for custom tooltips and selection.
color?ColorArgBlackColor made available to the renderer, including optional per-row colors.
axis_range?{ x?: number[]; y?: number[] }Data extentExplicit [low, high] bounds for either axis.
tooltip?TooltipArgNoneUse true for the default tooltip, or a callback for custom content.
on_select?SelectFnNoneCallback receiving the selected point and its source row.
hoverable?booleantrueAllow 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 fieldTypeDescription
cursor{ x: number; y: number }Pointer position in the same pixel coordinates as the resolvers.
innerRectPlot bounds in pixels.
x_scaleScaleComposed horizontal scale.
y_scaleScaleComposed vertical scale.
resolve_xPixelResolverResolve a row index to its x position.
resolve_yPixelResolverResolve 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.

OptionTypeDescription
Default tooltiptrueEnable 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>) => TooltipContentReturn 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.

Select a point to inspect its data.
import { scatter, type APlotElement } from '@antadesign/plot/browser'
import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const 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:

FieldTypeDescription
xnumber | stringPoint’s x value, or category label.
ynumber | stringPoint’s y value, or category label.
rowRowSource data row. May be undefined for rule or custom series without data.
label?stringPoint 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.

Hold Ctrl and scroll to zoom, or Ctrl-drag to pan.
import { line, type APlotElement } from '@antadesign/plot/browser'
import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const 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.

Scroll to zoom, or drag to pan.
import { line, type APlotElement } from '@antadesign/plot/browser'
import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const 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.

Hold Ctrl and scroll to zoom, or Ctrl-drag to pan.
import { line, type APlotElement } from '@antadesign/plot/browser'
import '@antadesign/plot/elements/a-plot'
const plot = document.createElement('a-plot') as APlotElement
const 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:

FieldTypeDefaultDescription
x?booleantrueEnable horizontal zoom and pan on a continuous axis.
y?booleantrueEnable vertical zoom and pan on a continuous axis.
modifier?booleantrueRequire 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.

Focus 20–45 Focus 55–80 Full extent
Hold Ctrl and scroll to zoom, or Ctrl-drag to pan.
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 APlotElement
const 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 = 0
const 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.

FieldTypeDefaultDescription
x?number[] | nullLeave unchangedRequested [min, max] on x, clamped to the full domain. null restores its full extent.
y?number[] | nullLeave unchangedRequested [min, max] on y, clamped to the full domain. null restores its full extent.
key?string | numberNoneChange 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:

FieldTypeDescription
xAxisViewport | nullCurrent horizontal viewport, or null for a categorical axis.
yAxisViewport | nullCurrent vertical viewport, or null for a categorical axis.

Each AxisViewport contains:

FieldTypeDescription
windownumber[]Current visible [min, max].
fullnumber[]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 APlotElement
plot.plotArgs = title()
document.body.append(plot)

title accepts a string or a TitleArg object:

FieldTypeDefaultDescription
textstringRequiredPlot title text.
size?number14Font size in pixels.
color?ThemeColorTheme defaultTitle 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 APlotElement
plot.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.

FieldTypeDefaultDescription
top?number60Top margin in pixels.
right?number60Right margin in pixels.
bottom?number60Bottom margin in pixels.
left?number60Left 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 APlotElement
plot.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 APlotElement
plot.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 APlotElement
plot.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 APlotElement
plot.plotArgs = gridNone()
document.body.append(plot)

grid accepts a boolean for both axes or this GridSpec object:

FieldTypeDefaultDescription
x?booleanfalseDraw vertical grid lines at x-axis ticks.
y?booleanfalseDraw 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 APlotElement
plot.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.

FormTypeExample
One colorstring'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 APlotElement
plot.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 APlotElement
plot.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.

ValueD3 scaleDescription
'linear'scaleLinearEqual differences in value produce equal distances. The default for numeric data.
'log'scaleLogEqual ratios produce equal distances. Use for positive values spanning several orders of magnitude.
'category'scaleBandPlace discrete categories in evenly spaced bands. Inferred from string data.
'time'scaleTimePosition timestamps on a continuous timeline, with ticks in local time.
'utc'scaleUtcPosition 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 APlotElement
plot.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 APlotElement
plot.plotArgs = axisLabelsOpposite()
document.body.append(plot)

LabelArg accepts a string or this object at axis.x.label or axis.y.label:

FieldTypeDefaultDescription
textstringRequiredAxis label text.
size?number12Font size in pixels.
color?ThemeColorTheme defaultLabel 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 APlotElement
plot.plotArgs = tickLabels()
document.body.append(plot)

Use TickLabelArg at axis.x.tick_label or axis.y.tick_label:

FieldTypeDefaultDescription
format?(value: number | string, index: number) => stringScale-dependentFormat each tick value. Return '' for an empty label.
size?number10Font size in pixels.
color?ThemeColorTheme defaultTick 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 APlotElement
plot.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.

FormTypeDescription
Shared colorThemeColorApply one color or theme pair to the series.
Accessor(row: Record<string, unknown>, index: number) => ThemeColorChoose 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 APlotElement
plot.plotArgs = markShapes()
document.body.append(plot)

MarkShape controls scatter.mark and line.mark:

ValueDescription
'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 APlotElement
plot.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.

FieldTypeDefaultDescription
colorThemeColorRequiredOutline color or theme pair.
width?numberSeries-dependentOutline 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.

FormTypeDescription
Field namestringRead the named property from each row.
Accessor(row: Record<string, unknown>, index: number) => number | stringCompute 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) => void

The 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 fieldTypeDescription
ctxCanvasContextMain-thread or offscreen 2D canvas context.
innerRectPlot bounds in pixels: left, right, top, and bottom.
x_scaleScaleComposed horizontal D3 scale.
y_scaleScaleComposed vertical D3 scale.
colorstringResolved fallback series color.
x_categories?string[]Horizontal category labels, when categorical.
y_categories?string[]Vertical category labels, when categorical.
resolve_xPixelResolverResolve a row index to its x position in pixels.
resolve_yPixelResolverResolve a row index to its y position in pixels.
color_atColorResolverResolve 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 APlotElement
plot.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 APlotElement
plot.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.