@antadesign/anta 0.3.29 → 0.3.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/anta_helpers.js +1 -1
- package/dist/bundle.js +26 -26
- package/dist/components/Breadcrumbs.d.ts +1 -1
- package/dist/components/Button.d.ts +3 -3
- package/dist/components/ButtonCopy.d.ts +1 -1
- package/dist/elements/a-box.js +2 -0
- package/dist/elements/a-icon.shapes.d.ts +1 -1
- package/dist/elements/a-input-time.js +2 -2
- package/dist/elements/a-input.js +2 -2
- package/dist/elements/shadow-aria.d.ts +1 -1
- package/dist/jsx-runtime.d.ts +10 -0
- package/dist/jsx-runtime.js +12 -0
- package/dist/toaster.d.ts +1 -1
- package/docs/changelog.md +7 -0
- package/docs/index.md +1 -0
- package/docs/packages/plot.md +3771 -0
- package/package.json +14 -10
|
@@ -0,0 +1,3771 @@
|
|
|
1
|
+
# Plot
|
|
2
|
+
|
|
3
|
+
A canvas-based plotting library for React, Preact, and plain JavaScript.
|
|
4
|
+
Compose plots using built-in and custom [series](#series), with configurable [axes](#axes), [scales](#scales), and [styles](#styling).
|
|
5
|
+
Explore your data through [tooltip](#tooltips), [selection](#selection-callbacks), and [zoom](#zoom-and-pan) interactions.
|
|
6
|
+
|
|
7
|
+
## Series types
|
|
8
|
+
|
|
9
|
+
Select a series type below to see its options and examples.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Composed examples
|
|
21
|
+
|
|
22
|
+
Combine multiple series in one plot to compare values, show trends, and add context.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { area, line, rule, scatter, type PlotArgs, type Series } from '@antadesign/plot'
|
|
28
|
+
import '@antadesign/plot/elements/a-plot'
|
|
29
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
30
|
+
|
|
31
|
+
function overviewExample(): PlotArgs<Node> {
|
|
32
|
+
// Synthetic data, sampled every 15 minutes. No randomness: edits keep the same data.
|
|
33
|
+
const forecast = Array.from({ length: 97 }, (_, i) => {
|
|
34
|
+
const hour = i / 4
|
|
35
|
+
const morning = 150 * Math.exp(-(((hour - 9) / 2.8) ** 2))
|
|
36
|
+
const evening = 240 * Math.exp(-(((hour - 17) / 3.4) ** 2))
|
|
37
|
+
const expected = 85 + morning + evening
|
|
38
|
+
const spread = 22 + 12 * Math.sin(hour / 5) ** 2
|
|
39
|
+
return { hour, expected, low: expected - spread, high: expected + spread }
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const observations = forecast.map((point, i) => {
|
|
43
|
+
const burst = i >= 61 && i <= 68 ? 65 * Math.sin((i - 60) / 9 * Math.PI) : 0
|
|
44
|
+
const actual = Math.round(point.expected + 13 * Math.sin(i * 1.8) + 8 * Math.cos(i * 0.7) + burst)
|
|
45
|
+
return { ...point, actual, outside: actual > point.high || actual < point.low }
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const trend = observations.map((point, i) => {
|
|
49
|
+
const window = observations.slice(Math.max(0, i - 3), Math.min(observations.length, i + 4))
|
|
50
|
+
return { hour: point.hour, average: window.reduce((sum, row) => sum + row.actual, 0) / window.length }
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const meanExpected = forecast.reduce((sum, point) => sum + point.expected, 0) / forecast.length
|
|
54
|
+
|
|
55
|
+
const series: Series<Node>[] = [
|
|
56
|
+
area<Node>({
|
|
57
|
+
data: forecast,
|
|
58
|
+
x: 'hour',
|
|
59
|
+
y: 'high',
|
|
60
|
+
y2: 'low',
|
|
61
|
+
color: { light: '#ede9fe', dark: '#30274e' },
|
|
62
|
+
hoverable: false,
|
|
63
|
+
}),
|
|
64
|
+
rule<Node>({
|
|
65
|
+
y: meanExpected,
|
|
66
|
+
color: { light: '#d97706', dark: '#fbbf24' },
|
|
67
|
+
width: 2,
|
|
68
|
+
dash: [8, 4],
|
|
69
|
+
hoverable: false,
|
|
70
|
+
}),
|
|
71
|
+
line<Node>({
|
|
72
|
+
data: forecast,
|
|
73
|
+
x: 'hour',
|
|
74
|
+
y: 'expected',
|
|
75
|
+
color: { light: '#8b5cf6', dark: '#c4b5fd' },
|
|
76
|
+
width: 2,
|
|
77
|
+
dash: [5, 5],
|
|
78
|
+
tooltip: ({ x, y }) => document.createTextNode('Forecast at ' + x + 'h: ' + Math.round(Number(y)) + ' requests/s'),
|
|
79
|
+
}),
|
|
80
|
+
line<Node>({
|
|
81
|
+
data: trend,
|
|
82
|
+
x: 'hour',
|
|
83
|
+
y: 'average',
|
|
84
|
+
color: { light: '#0891b2', dark: '#67e8f9' },
|
|
85
|
+
width: 3,
|
|
86
|
+
tooltip: ({ x, y }) => document.createTextNode('Trend at ' + x + 'h: ' + Math.round(Number(y)) + ' requests/s'),
|
|
87
|
+
}),
|
|
88
|
+
scatter<Node>({
|
|
89
|
+
data: observations.filter(point => !point.outside),
|
|
90
|
+
x: 'hour',
|
|
91
|
+
y: 'actual',
|
|
92
|
+
color: { light: '#0891b2', dark: '#67e8f9' },
|
|
93
|
+
size: 4,
|
|
94
|
+
tooltip: ({ x, y }) => document.createTextNode('Observed at ' + x + 'h: ' + y + ' requests/s'),
|
|
95
|
+
}),
|
|
96
|
+
scatter<Node>({
|
|
97
|
+
data: observations.filter(point => point.outside),
|
|
98
|
+
x: 'hour',
|
|
99
|
+
y: 'actual',
|
|
100
|
+
color: { light: '#e76f51', dark: '#fda489' },
|
|
101
|
+
mark: 'diamond',
|
|
102
|
+
size: 9,
|
|
103
|
+
stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 1 },
|
|
104
|
+
tooltip: ({ x, y }) => document.createTextNode('Outside forecast range: ' + x + 'h · ' + y + ' requests/s'),
|
|
105
|
+
}),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
const options = {
|
|
109
|
+
|
|
110
|
+
height: 400,
|
|
111
|
+
border: false,
|
|
112
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
113
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
114
|
+
theme_invert: false,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const axis = {
|
|
118
|
+
|
|
119
|
+
x: { min: 0, max: 24, label: 'Hour' },
|
|
120
|
+
|
|
121
|
+
y: { min: 0, max: 440, label: 'Requests/s' },
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const margin = 12
|
|
126
|
+
|
|
127
|
+
const grid = false
|
|
128
|
+
|
|
129
|
+
const zoom_pan = { x: true, y: true, modifier: false }
|
|
130
|
+
|
|
131
|
+
// Replace the object to apply a change; Plot does not observe in-place mutation.
|
|
132
|
+
const plotArgs: PlotArgs<Node> = { ...options, series, axis, margin, grid, zoom_pan }
|
|
133
|
+
|
|
134
|
+
return plotArgs
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
138
|
+
const args = overviewExample()
|
|
139
|
+
plot.plotArgs = {
|
|
140
|
+
...args,
|
|
141
|
+
height: 280,
|
|
142
|
+
margin: { top: 12, right: 16, bottom: 42, left: 74 },
|
|
143
|
+
zoom_pan: false,
|
|
144
|
+
}
|
|
145
|
+
document.body.append(plot)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { bar, scatter, type PlotArgs } from '@antadesign/plot'
|
|
152
|
+
import '@antadesign/plot/elements/a-plot'
|
|
153
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
154
|
+
|
|
155
|
+
function energyExample(): PlotArgs<Node> {
|
|
156
|
+
const data = [
|
|
157
|
+
{ x: 'A', solar: 28, wind: 35, demand: 72 },
|
|
158
|
+
{ x: 'B', solar: 48, wind: 18, demand: 54 },
|
|
159
|
+
{ x: 'C', solar: 19, wind: 25, demand: 62 },
|
|
160
|
+
{ x: 'D', solar: 36, wind: 42, demand: 69 },
|
|
161
|
+
{ x: 'E', solar: 52, wind: 12, demand: 78 },
|
|
162
|
+
{ x: 'F', solar: 24, wind: 30, demand: 46 },
|
|
163
|
+
{ x: 'G', solar: 41, wind: 28, demand: 58 },
|
|
164
|
+
{ x: 'H', solar: 16, wind: 22, demand: 51 },
|
|
165
|
+
]
|
|
166
|
+
return {
|
|
167
|
+
series: [
|
|
168
|
+
bar<Node>({ data, y: ['solar', 'wind'],
|
|
169
|
+
color: [{ light: '#fbbf24', dark: '#d97706' }, { light: '#2dd4bf', dark: '#0d9488' }],
|
|
170
|
+
inset: 5, border_radius: 3,
|
|
171
|
+
tooltip: ({ x, y, label }) => document.createTextNode('Site ' + x + ' · ' + label + ': ' + y + ' kW') }),
|
|
172
|
+
scatter<Node>({ data, y: 'demand', mark: 'diamond', size: 7,
|
|
173
|
+
tooltip: ({ x, y }) => document.createTextNode('Site ' + x + ' · Demand: ' + y + ' kW'),
|
|
174
|
+
color: { light: '#7c3aed', dark: '#c4b5fd' },
|
|
175
|
+
stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 1.5 } }),
|
|
176
|
+
],
|
|
177
|
+
axis: { x: { label: 'Site' }, y: { min: 0, max: 90, label: 'Power (kW)' } },
|
|
178
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
179
|
+
border: false,
|
|
180
|
+
grid: false,
|
|
181
|
+
zoom_pan: false,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
186
|
+
const args = energyExample()
|
|
187
|
+
plot.plotArgs = {
|
|
188
|
+
...args,
|
|
189
|
+
height: 280,
|
|
190
|
+
margin: { top: 12, right: 16, bottom: 42, left: 58 },
|
|
191
|
+
zoom_pan: false,
|
|
192
|
+
}
|
|
193
|
+
document.body.append(plot)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { area, rect, line, type PlotArgs } from '@antadesign/plot'
|
|
200
|
+
import '@antadesign/plot/elements/a-plot'
|
|
201
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
202
|
+
|
|
203
|
+
function latencyExample(): PlotArgs<Node> {
|
|
204
|
+
const density = (x: number) => 48 * Math.exp(-(((x - 65) / 24) ** 2))
|
|
205
|
+
+ 19 * Math.exp(-(((x - 135) / 38) ** 2))
|
|
206
|
+
const bins = Array.from({ length: 24 }, (_, i) => {
|
|
207
|
+
const x = i * 10
|
|
208
|
+
return { x: x + 1, x2: x + 9, y: density(x + 5) * (0.8 + 0.3 * Math.sin(i * 2.3) ** 2), y2: 0 }
|
|
209
|
+
})
|
|
210
|
+
const curve = Array.from({ length: 121 }, (_, i) => ({ x: i * 2, y: density(i * 2) }))
|
|
211
|
+
return {
|
|
212
|
+
series: [
|
|
213
|
+
area<Node>({ data: curve, y2: 0, color: { light: '#fce7f3', dark: '#452039' }, hoverable: false }),
|
|
214
|
+
rect<Node>({ data: bins, color: { light: '#f472b6', dark: '#be185d' },
|
|
215
|
+
tooltip: ({ row, y }) => document.createTextNode(
|
|
216
|
+
(Number(row.x) - 1) + '–' + (Number(row.x2) + 1) + ' ms · ' + Math.round(Number(y)) + ' requests',
|
|
217
|
+
) }),
|
|
218
|
+
line<Node>({ data: curve, color: { light: '#9d174d', dark: '#fbcfe8' }, width: 2.5,
|
|
219
|
+
tooltip: ({ x, y }) => document.createTextNode('Distribution at ' + x + ' ms: ' + Number(y).toFixed(1) + ' requests') }),
|
|
220
|
+
|
|
221
|
+
],
|
|
222
|
+
axis: { x: { min: 0, max: 240, label: 'Latency (ms)' }, y: { min: 0, max: 60, label: 'Requests' } },
|
|
223
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
224
|
+
border: false,
|
|
225
|
+
grid: false,
|
|
226
|
+
zoom_pan: false,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
231
|
+
const args = latencyExample()
|
|
232
|
+
plot.plotArgs = {
|
|
233
|
+
...args,
|
|
234
|
+
height: 280,
|
|
235
|
+
margin: { top: 12, right: 16, bottom: 42, left: 58 },
|
|
236
|
+
zoom_pan: false,
|
|
237
|
+
}
|
|
238
|
+
document.body.append(plot)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { area, rect, line, scatter, rule, type PlotArgs } from '@antadesign/plot'
|
|
245
|
+
import '@antadesign/plot/elements/a-plot'
|
|
246
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
247
|
+
|
|
248
|
+
function mixedExample(): PlotArgs<Node> {
|
|
249
|
+
const points = [
|
|
250
|
+
{ x: 12, y: 74 }, { x: 25, y: 57 }, { x: 39, y: 85 }, { x: 48, y: 18 },
|
|
251
|
+
{ x: 62, y: 70 }, { x: 73, y: 37 }, { x: 86.5, y: 86 }, { x: 92, y: 22 },
|
|
252
|
+
{ x: 15, y: 27 }, { x: 36, y: 64 }, { x: 57, y: 88 },
|
|
253
|
+
]
|
|
254
|
+
const paths = [
|
|
255
|
+
[
|
|
256
|
+
{ x: 25, y: 0 }, { x: 36, y: 19 }, { x: 41, y: 15 }, { x: 56, y: 37 },
|
|
257
|
+
{ x: 65, y: 23 }, { x: 72, y: 48 }, { x: 76, y: 44 }, { x: 93, y: 63 }, { x: 100, y: 68 },
|
|
258
|
+
],
|
|
259
|
+
[
|
|
260
|
+
{ x: 0, y: 73 }, { x: 9, y: 87 }, { x: 13, y: 84 }, { x: 20, y: 92 },
|
|
261
|
+
{ x: 29, y: 79 }, { x: 39, y: 96 }, { x: 42, y: 93 }, { x: 48, y: 100 },
|
|
262
|
+
],
|
|
263
|
+
]
|
|
264
|
+
// Sample cosine transitions to round each turn without changing its height.
|
|
265
|
+
const smoothPaths = paths.map(path => path.flatMap((point, i) => {
|
|
266
|
+
const next = path[i + 1]
|
|
267
|
+
if (!next) return [point]
|
|
268
|
+
return Array.from({ length: 16 }, (_, j) => {
|
|
269
|
+
const t = j / 16
|
|
270
|
+
const blend = (1 - Math.cos(Math.PI * t)) / 2
|
|
271
|
+
return { x: point.x + (next.x - point.x) * t, y: point.y + (next.y - point.y) * blend }
|
|
272
|
+
})
|
|
273
|
+
}))
|
|
274
|
+
const colors = [
|
|
275
|
+
{ light: '#7c3aed', dark: '#c4b5fd' },
|
|
276
|
+
{ light: '#e11d48', dark: '#fda4af' },
|
|
277
|
+
{ light: '#0284c7', dark: '#7dd3fc' },
|
|
278
|
+
{ light: '#d97706', dark: '#fcd34d' },
|
|
279
|
+
]
|
|
280
|
+
return {
|
|
281
|
+
series: [
|
|
282
|
+
area<Node>({ data: Array.from({ length: 81 }, (_, i) => {
|
|
283
|
+
const x = i * 1.25
|
|
284
|
+
const center = 50 + 7 * Math.sin(i / 9) + 3 * Math.cos(i / 4)
|
|
285
|
+
const spread = 3 + 1.5 * Math.sin(i / 11) ** 2
|
|
286
|
+
return { x, y: center + spread, y2: center - spread }
|
|
287
|
+
}), color: { light: '#99f6e4', dark: '#115e59' },
|
|
288
|
+
stroke: { color: { light: '#14b8a6', dark: '#5eead4' }, width: 1 } }),
|
|
289
|
+
...points.map((point, i) => scatter<Node>({ data: [point], size: [7, 12, 21, 10, 15, 9, 26, 13, 11, 8, 14][i],
|
|
290
|
+
mark: 'circle', color: colors[i % colors.length],
|
|
291
|
+
tooltip: ({ x, y }) => document.createTextNode('Point ' + (i + 1) + ' · (' + x + ', ' + y + ')'),
|
|
292
|
+
stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 1.5 } })),
|
|
293
|
+
rect<Node>({ data: [{ x: 32, x2: 44, y: 31, y2: 47 }],
|
|
294
|
+
color: { light: '#ddd6fe', dark: '#5b21b6' },
|
|
295
|
+
stroke: { color: { light: '#8b5cf6', dark: '#c4b5fd' }, width: 1.5 } }),
|
|
296
|
+
rect<Node>({ data: [{ x: 76, x2: 97, y: 76, y2: 96 }],
|
|
297
|
+
color: { light: 'rgba(254, 215, 170, 0.55)', dark: 'rgba(154, 52, 18, 0.55)' },
|
|
298
|
+
stroke: { color: { light: '#f97316', dark: '#fdba74' }, width: 1.5 } }),
|
|
299
|
+
...smoothPaths.map((data, i) => line<Node>({ data, width: 2.5, dash: i === 1 ? [7, 5] : [],
|
|
300
|
+
tooltip: ({ x, y }) => document.createTextNode(
|
|
301
|
+
'Line ' + (i + 1) + ' · (' + Number(x).toFixed(1) + ', ' + Number(y).toFixed(1) + ')',
|
|
302
|
+
),
|
|
303
|
+
color: i % 2 === 0 ? { light: '#0d9488', dark: '#5eead4' }
|
|
304
|
+
: { light: '#f97316', dark: '#fdba74' },
|
|
305
|
+
})),
|
|
306
|
+
|
|
307
|
+
rule<Node>({ y: 10, color: { light: '#0284c7', dark: '#7dd3fc' }, width: 1.5, dash: [6, 4] }),
|
|
308
|
+
],
|
|
309
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
310
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
311
|
+
border: false,
|
|
312
|
+
grid: false,
|
|
313
|
+
zoom_pan: false,
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
318
|
+
const args = mixedExample()
|
|
319
|
+
plot.plotArgs = {
|
|
320
|
+
...args,
|
|
321
|
+
height: 280,
|
|
322
|
+
margin: { top: 12, right: 16, bottom: 42, left: 58 },
|
|
323
|
+
zoom_pan: false,
|
|
324
|
+
}
|
|
325
|
+
document.body.append(plot)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Install
|
|
329
|
+
|
|
330
|
+
Install the package to use plots in React, Preact, or plain JavaScript.
|
|
331
|
+
|
|
332
|
+
```sh
|
|
333
|
+
npm install @antadesign/plot # or pnpm / bun
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Import `Plot` from `/components` and series factories from `@antadesign/plot`.
|
|
337
|
+
React 19 is a peer dependency. For Preact, use `preact/compat` aliases.
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
import { Plot } from '@antadesign/plot/components'
|
|
341
|
+
import { line } from '@antadesign/plot'
|
|
342
|
+
import '@antadesign/plot/elements/a-plot-surface'
|
|
343
|
+
import '@antadesign/anta/elements/a-tooltip'
|
|
344
|
+
|
|
345
|
+
const plotArgs = {
|
|
346
|
+
height: 300,
|
|
347
|
+
series: [line({ data: [{ x: 1, y: 3 }, { x: 2, y: 5 }, { x: 3, y: 4 }] })],
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function Example() {
|
|
351
|
+
return <Plot plotArgs={plotArgs} />
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
`Plot` is a function component that uses Anta's configured JSX factory and hooks.
|
|
356
|
+
It needs no DOM registration. Import the surface and tooltip registration modules
|
|
357
|
+
once in your browser entry; their elements install their styles automatically.
|
|
358
|
+
`Plot` handles canvas setup, drawing, interactions, and cleanup.
|
|
359
|
+
|
|
360
|
+
By default, `Plot` fills its parent. Give the parent a height or set `plotArgs.height`,
|
|
361
|
+
as above. Custom renderers supply their hooks through Anta's `configure()`.
|
|
362
|
+
With a worker renderer and a DOM bridge that transfers the surface's canvases,
|
|
363
|
+
`Plot` runs in the worker while `a-plot-surface` stays in the browser.
|
|
364
|
+
|
|
365
|
+
For plain JavaScript or TypeScript, import `@antadesign/plot/elements/a-plot`, then use `<a-plot>` and assign its `plotArgs` property.
|
|
366
|
+
|
|
367
|
+
For custom host integrations, `/components` also exports `PlotSurface`.
|
|
368
|
+
Register it with `@antadesign/plot/elements/a-plot-surface`; your host manages
|
|
369
|
+
controllers, drawing, and tooltip content.
|
|
370
|
+
|
|
371
|
+
## Plot arguments
|
|
372
|
+
|
|
373
|
+
`PlotArgs` configures the whole plot: its series, axes, and presentation. Pass it
|
|
374
|
+
as the required `plotArgs` prop to `<Plot plotArgs={plotArgs} />`, or assign it to
|
|
375
|
+
the `plotArgs` property on `<a-plot>`. Replace the object to apply changes;
|
|
376
|
+
in-place mutation is not observed.
|
|
377
|
+
|
|
378
|
+
Title, background, and grid
|
|
379
|
+
|
|
380
|
+
Set a title, reserve space with `margin`, and style the plot background and horizontal grid lines.
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
384
|
+
import '@antadesign/plot/elements/a-plot'
|
|
385
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
386
|
+
|
|
387
|
+
const examplePoints = [
|
|
388
|
+
{ x: 8, y: 14 }, { x: 36, y: 40 }, { x: 52, y: 32 },
|
|
389
|
+
{ x: 44, y: 59 }, { x: 64, y: 54 }, { x: 92, y: 86 },
|
|
390
|
+
]
|
|
391
|
+
|
|
392
|
+
function presentation(): PlotArgs<Node> {
|
|
393
|
+
return {
|
|
394
|
+
series: [scatter<Node>({ data: examplePoints, size: 3,
|
|
395
|
+
color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
396
|
+
title: { text: 'Plot title', size: 16, color: { light: '#374151', dark: '#e5e7eb' } },
|
|
397
|
+
height: 260,
|
|
398
|
+
margin: { top: 38, right: 16, bottom: 42, left: 52 },
|
|
399
|
+
background: { light: '#f3f4f6', dark: '#202124' },
|
|
400
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
401
|
+
grid: { x: false, y: true },
|
|
402
|
+
border: true,
|
|
403
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
404
|
+
zoom_pan: false,
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
409
|
+
plot.plotArgs = presentation()
|
|
410
|
+
document.body.append(plot)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Border and spacing
|
|
414
|
+
|
|
415
|
+
Set `border: true` and `grid: false` to frame the plot without grid lines. Use `margin` to reserve space around the axes and their labels.
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
419
|
+
import '@antadesign/plot/elements/a-plot'
|
|
420
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
421
|
+
|
|
422
|
+
const examplePoints = [
|
|
423
|
+
{ x: 8, y: 14 }, { x: 36, y: 40 }, { x: 52, y: 32 },
|
|
424
|
+
{ x: 44, y: 59 }, { x: 64, y: 54 }, { x: 92, y: 86 },
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
function spacing(): PlotArgs<Node> {
|
|
428
|
+
return {
|
|
429
|
+
series: [scatter<Node>({ data: examplePoints, size: 3,
|
|
430
|
+
color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
431
|
+
height: 260,
|
|
432
|
+
margin: { top: 40, right: 40, bottom: 68, left: 88 },
|
|
433
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
434
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
435
|
+
border: true,
|
|
436
|
+
grid: false,
|
|
437
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
438
|
+
zoom_pan: false,
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
443
|
+
plot.plotArgs = spacing()
|
|
444
|
+
document.body.append(plot)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
| Field | Type | Default | Description |
|
|
448
|
+
|---|---|---|---|
|
|
449
|
+
| [`series`](#series) | `Series[]` | Required | What to draw, in paint order. Build each one with a factory. |
|
|
450
|
+
| [`axis?`](#axes) | `{ x?: AxisArgs, y?: AxisArgs }` | Inferred from data | Per-axis scale, label, ticks, and domain. |
|
|
451
|
+
| [`title?`](#title) | `string \| { text, size?, color? }` | None | Plot title. |
|
|
452
|
+
| `width?` | `number` | Container width | Canvas width in pixels. Overrides the wrapper's CSS width. |
|
|
453
|
+
| `height?` | `number` | Parent height for `Plot`; `300` for `<a-plot>` | Canvas height in pixels. Overrides the host's CSS height. |
|
|
454
|
+
| [`margin?`](#margins) | `number \| { top?, right?, bottom?, left? }` | `60` per side; `2` without axes or title | Space reserved around the plot area for axes and title. |
|
|
455
|
+
| `border?` | `boolean` | `true` | Draws a border around the plot area. |
|
|
456
|
+
| [`grid?`](#grid) | `boolean \| { x?, y? }` | `false` | Grid lines, per axis. |
|
|
457
|
+
| [`chrome_color?`](#theme-colors) | `string \| { light, dark }` | Theme defaults | Color for axis lines, tick marks, grid lines, and the plot border. Text colors are configured separately. |
|
|
458
|
+
| [`background?`](#theme-colors) | `boolean \| string \| { light, dark }` | White | Plot fill. `false` paints none; `true` and an absent value use `#fff`. |
|
|
459
|
+
| [`theme_invert?`](#theme-colors) | `boolean` | Automatic | Forces dark-mode inversion on or off. Absent means invert unless a series color or the background is a `{ light, dark }` pair. |
|
|
460
|
+
| [`zoom_pan?`](#zoom-and-pan) | `boolean \| { x?, y?, modifier? }` | Both axes, Ctrl required | Require Ctrl for zoom and pan by default. Set `modifier: false` to allow gestures without Ctrl. Set both axes to `false` to disable zoom and pan. |
|
|
461
|
+
| [`viewport?`](#viewport) | `{ x?, y?, key? }` | Full domain | Requested starting window per axis. Applied at mount and on each `key` change, clamped to the full domain. |
|
|
462
|
+
| [`on_viewport_change?`](#viewport-changes) | `(change: ViewportChange) => void` | None | Fires with each axis's current window and full extent after a gesture or reset. |
|
|
463
|
+
|
|
464
|
+
A zoomed plot shows a **Reset zoom** button in the plot area; it
|
|
465
|
+
disappears once the view is back to its full extent.
|
|
466
|
+
|
|
467
|
+
### Axes
|
|
468
|
+
|
|
469
|
+
`axis.x` and `axis.y` both accept `AxisArgs`. Configure them independently.
|
|
470
|
+
Omitted fields are inferred from the series or use the documented defaults.
|
|
471
|
+
[`ThemeColor`](#theme-colors) accepts a CSS color string or a `{ light, dark }` pair.
|
|
472
|
+
|
|
473
|
+
Logarithmic scale
|
|
474
|
+
|
|
475
|
+
Use a logarithmic axis to compare values across orders of magnitude. Format large tick values with a `k` suffix.
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
479
|
+
import '@antadesign/plot/elements/a-plot'
|
|
480
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
481
|
+
|
|
482
|
+
const examplePoints = [
|
|
483
|
+
{ x: 8, y: 14 }, { x: 36, y: 40 }, { x: 52, y: 32 },
|
|
484
|
+
{ x: 44, y: 59 }, { x: 64, y: 54 }, { x: 92, y: 86 },
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
function logarithmic(): PlotArgs<Node> {
|
|
488
|
+
const data = examplePoints.map(({ x, y }) => ({ x, y: 10 ** (y / 20 - 1) }))
|
|
489
|
+
return {
|
|
490
|
+
series: [scatter<Node>({ data, size: 3,
|
|
491
|
+
color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
492
|
+
height: 260,
|
|
493
|
+
margin: { top: 12, right: 14, bottom: 44, left: 80 },
|
|
494
|
+
axis: {
|
|
495
|
+
x: { min: 0, max: 100, label: 'Batch' },
|
|
496
|
+
y: { scale: 'log', min: 0.1, max: 10000, label: 'Duration (ms)',
|
|
497
|
+
tick_label: { format: value => Number(value) >= 1000 ? Number(value) / 1000 + 'k' : String(value) } },
|
|
498
|
+
},
|
|
499
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
500
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
501
|
+
grid: { x: false, y: true },
|
|
502
|
+
border: true,
|
|
503
|
+
zoom_pan: false,
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
508
|
+
plot.plotArgs = logarithmic()
|
|
509
|
+
document.body.append(plot)
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Time and tick formatting
|
|
513
|
+
|
|
514
|
+
Plot timestamps on a UTC axis and format the horizontal ticks as hours and minutes. Add a percent suffix to the vertical ticks.
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
518
|
+
import '@antadesign/plot/elements/a-plot'
|
|
519
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
520
|
+
|
|
521
|
+
const examplePoints = [
|
|
522
|
+
{ x: 8, y: 14 }, { x: 36, y: 40 }, { x: 52, y: 32 },
|
|
523
|
+
{ x: 44, y: 59 }, { x: 64, y: 54 }, { x: 92, y: 86 },
|
|
524
|
+
]
|
|
525
|
+
|
|
526
|
+
function timeAxis(): PlotArgs<Node> {
|
|
527
|
+
const start = Date.UTC(2026, 0, 12)
|
|
528
|
+
const data = examplePoints.map(({ x, y }) => ({ x: start + x / 100 * 86400000, y }))
|
|
529
|
+
return {
|
|
530
|
+
series: [scatter<Node>({ data, size: 3,
|
|
531
|
+
color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
532
|
+
height: 260,
|
|
533
|
+
margin: { top: 12, right: 16, bottom: 44, left: 76 },
|
|
534
|
+
axis: {
|
|
535
|
+
x: { scale: 'utc', min: start, max: start + 86400000, label: 'Time (UTC)', padding: 8,
|
|
536
|
+
tick_label: { format: value => new Date(Number(value)).toISOString().slice(11, 16) } },
|
|
537
|
+
y: { min: 0, max: 100, label: 'Utilization', tick_mark: false,
|
|
538
|
+
tick_label: { format: value => value + '%' } },
|
|
539
|
+
},
|
|
540
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
541
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
542
|
+
grid: { x: true, y: false },
|
|
543
|
+
border: true,
|
|
544
|
+
zoom_pan: false,
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
549
|
+
plot.plotArgs = timeAxis()
|
|
550
|
+
document.body.append(plot)
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
| Field | Type | Default | Description |
|
|
554
|
+
|---|---|---|---|
|
|
555
|
+
| [`scale?`](#scales) | `'linear' \| 'log' \| 'category' \| 'time' \| 'utc'` | Inferred | Numeric data uses a linear scale; string data uses categories. Time scales take timestamps in milliseconds; `time` formats in local time and `utc` in UTC. |
|
|
556
|
+
| [`label?`](#axis-labels) | `LabelArg` | Shared field name, if available | Axis name, as text or an object with presentation options. Use `''` to omit it. |
|
|
557
|
+
| `min?` | `number` | Inferred from data | Lower continuous-domain bound. |
|
|
558
|
+
| `max?` | `number` | Inferred from data | Upper continuous-domain bound. |
|
|
559
|
+
| `categories?` | `string[]` | Inferred from data | Explicit category list and order; also declares a categorical axis. |
|
|
560
|
+
| `padding?` | `number` | `0` | Pixel padding at both ends of the axis, inside the plot area. |
|
|
561
|
+
| `padding_left?` | `number` | `padding` | Left-end padding for `axis.x`. |
|
|
562
|
+
| `padding_right?` | `number` | `padding` | Right-end padding for `axis.x`. |
|
|
563
|
+
| `padding_top?` | `number` | `padding` | Top-end padding for `axis.y`. |
|
|
564
|
+
| `padding_bottom?` | `number` | `padding` | Bottom-end padding for `axis.y`. |
|
|
565
|
+
| `grid_align?` | `'center' \| 'edge'` | `'center'` | Position grid lines at category centers or boundaries. Categorical axes only. |
|
|
566
|
+
| `line?` | `boolean` | `true` | Draw the axis line. |
|
|
567
|
+
| `hidden?` | `boolean` | `false` | Hide the axis presentation while retaining its scale. |
|
|
568
|
+
| [`tick_label?`](#tick-labels) | `TickLabelArg` | Automatic formatting | Tick text formatting, size, and color. |
|
|
569
|
+
| `tick_mark?` | `boolean` | `true` | Draw tick marks. Does not hide tick labels. |
|
|
570
|
+
|
|
571
|
+
### Series
|
|
572
|
+
|
|
573
|
+
A series describes a set of data and how to draw it. Pass one or more series
|
|
574
|
+
in `plotArgs.series`; they are drawn in array order. Import the series
|
|
575
|
+
functions from `@antadesign/plot`.
|
|
576
|
+
|
|
577
|
+
The default column describes omitted fields. Examples may override these defaults.
|
|
578
|
+
See [Data fields](#data-fields), [Series colors](#series-colors), [Strokes](#strokes),
|
|
579
|
+
and [Mark shapes](#mark-shapes) for shared configuration types.
|
|
580
|
+
For callbacks, see [Tooltips](#tooltips) and [Selection callbacks](#selection-callbacks).
|
|
581
|
+
|
|
582
|
+
#### Scatter
|
|
583
|
+
|
|
584
|
+
Draw a mark at each data point. Use `size`, [`mark`](#mark-shapes), and [`stroke`](#strokes) to control
|
|
585
|
+
its appearance. Set `tooltip: true` for a tooltip showing the point's values.
|
|
586
|
+
|
|
587
|
+
Size and color from data
|
|
588
|
+
|
|
589
|
+
Use a size accessor to vary mark diameter and a color accessor to highlight larger values. An outline keeps overlapping bubbles distinct.
|
|
590
|
+
|
|
591
|
+
```ts
|
|
592
|
+
import { scatter, type APlotElement } from '@antadesign/plot/browser'
|
|
593
|
+
import '@antadesign/plot/elements/a-plot'
|
|
594
|
+
|
|
595
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
596
|
+
|
|
597
|
+
const data = Array.from({ length: 24 }, (_, i) => ({
|
|
598
|
+
x: 12 + (i * 17 % 83),
|
|
599
|
+
y: 15 + (i * 31 % 72),
|
|
600
|
+
volume: 20 + (i * 43 % 180),
|
|
601
|
+
}))
|
|
602
|
+
|
|
603
|
+
plot.plotArgs = {
|
|
604
|
+
series: [scatter({
|
|
605
|
+
data,
|
|
606
|
+
size: row => (5 + Math.sqrt(Number(row.volume)) * 1.8) * 1.3,
|
|
607
|
+
color: row => Number(row.volume) > 120
|
|
608
|
+
? { light: '#e76f51', dark: '#fda489' }
|
|
609
|
+
: { light: '#0891b2', dark: '#67e8f9' },
|
|
610
|
+
stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 2 },
|
|
611
|
+
tooltip: ({ row }) => document.createTextNode('Volume: ' + row.volume),
|
|
612
|
+
})],
|
|
613
|
+
height: 260,
|
|
614
|
+
margin: { top: 12, right: 12, bottom: 28, left: 36 },
|
|
615
|
+
axis: { x: { min: 0, max: 110, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
616
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
617
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
618
|
+
border: false,
|
|
619
|
+
zoom_pan: false,
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
document.body.append(plot)
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Mark shapes and outlines
|
|
626
|
+
|
|
627
|
+
Use different marks to distinguish groups without relying on color alone. Each series sets its own shape, size, and stroke.
|
|
628
|
+
|
|
629
|
+
```ts
|
|
630
|
+
import { scatter, type APlotElement } from '@antadesign/plot/browser'
|
|
631
|
+
import '@antadesign/plot/elements/a-plot'
|
|
632
|
+
|
|
633
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
634
|
+
|
|
635
|
+
const data = Array.from({ length: 18 }, (_, i) => ({
|
|
636
|
+
x: 10 + i * 4.5,
|
|
637
|
+
y: 18 + i * 2.5 + Math.sin(i * 2.1) * 12,
|
|
638
|
+
}))
|
|
639
|
+
|
|
640
|
+
plot.plotArgs = {
|
|
641
|
+
series: [
|
|
642
|
+
scatter({
|
|
643
|
+
data,
|
|
644
|
+
mark: 'square',
|
|
645
|
+
size: 11,
|
|
646
|
+
color: { light: '#c4b5fd', dark: '#8b5cf6' },
|
|
647
|
+
stroke: { color: { light: '#6d28d9', dark: '#ddd6fe' }, width: 2 },
|
|
648
|
+
tooltip: ({ x, y }) => document.createTextNode('Squares: ' + x + ', ' + Math.round(Number(y))),
|
|
649
|
+
}),
|
|
650
|
+
scatter({
|
|
651
|
+
data: data.map(point => ({ x: point.x + 5, y: 95 - point.y })),
|
|
652
|
+
mark: 'diamond',
|
|
653
|
+
size: 13,
|
|
654
|
+
color: { light: '#99f6e4', dark: '#0d9488' },
|
|
655
|
+
stroke: { color: { light: '#0f766e', dark: '#5eead4' }, width: 2 },
|
|
656
|
+
tooltip: ({ x, y }) => document.createTextNode('Diamonds: ' + x + ', ' + Math.round(Number(y))),
|
|
657
|
+
}),
|
|
658
|
+
],
|
|
659
|
+
height: 260,
|
|
660
|
+
margin: { top: 12, right: 12, bottom: 28, left: 36 },
|
|
661
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
662
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
663
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
664
|
+
border: false,
|
|
665
|
+
zoom_pan: false,
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
document.body.append(plot)
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Scatter arguments
|
|
672
|
+
|
|
673
|
+
| Field | Type | Default | Description |
|
|
674
|
+
|---|---|---|---|
|
|
675
|
+
| `data` | `Record<string, unknown>[]` | Required | Rows used to build the series. |
|
|
676
|
+
| `x?` | [`FieldArg`](#data-fields) | `'x'` | Horizontal field name or accessor. |
|
|
677
|
+
| `y?` | [`FieldArg`](#data-fields) | `'y'` | Vertical field name or accessor. |
|
|
678
|
+
| `color?` | [`ColorArg`](#series-colors) | Black | Series color; theme pairs provide separate light and dark colors. |
|
|
679
|
+
| `size?` | `number \| ((row, index) => number)` | `5` | Mark diameter in pixels, or a per-row accessor. A row’s `size` takes precedence. |
|
|
680
|
+
| [`mark?`](#mark-shapes) | `MarkShape` | `'circle'` | Mark shape: circle, square, diamond, or triangle. |
|
|
681
|
+
| [`stroke?`](#strokes) | `StrokeArg` | None | Outline color and optional width. |
|
|
682
|
+
| [`tooltip?`](#tooltips) | `TooltipArg` | None | Use `true` for the default tooltip, or a callback for custom content. |
|
|
683
|
+
| [`on_select?`](#selection-callbacks) | `SelectFn` | None | Callback receiving the selected point and its source row. |
|
|
684
|
+
| `hoverable?` | `boolean` | `true` | Allow the series to participate in hit testing. |
|
|
685
|
+
| `highlight?` | `boolean` | `true` | Draw hover feedback for a hit point. |
|
|
686
|
+
|
|
687
|
+
```ts
|
|
688
|
+
import { scatter } from '@antadesign/plot'
|
|
689
|
+
|
|
690
|
+
const series = [scatter({
|
|
691
|
+
data: [{ time: 1, value: 3 }, { time: 2, value: 5 }, { time: 3, value: 4 }],
|
|
692
|
+
x: 'time',
|
|
693
|
+
y: 'value',
|
|
694
|
+
color: (row) => Number(row.value) >= 5 ? 'coral' : 'steelblue',
|
|
695
|
+
size: (row) => Number(row.value) * 2,
|
|
696
|
+
mark: 'diamond',
|
|
697
|
+
stroke: { color: 'navy', width: 1 },
|
|
698
|
+
tooltip: ({ x, y }) => `(${x}, ${y})`,
|
|
699
|
+
on_select: ({ row }) => console.log('Selected row:', row),
|
|
700
|
+
hoverable: true,
|
|
701
|
+
highlight: true,
|
|
702
|
+
})]
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
#### Line
|
|
706
|
+
|
|
707
|
+
Connect points in data order. Set `width` and `dash` for the line, or add
|
|
708
|
+
[`mark`](#mark-shapes) and `mark_size` to show the individual points.
|
|
709
|
+
|
|
710
|
+
Solid and dashed lines
|
|
711
|
+
|
|
712
|
+
Compare measured values with a forecast. A solid line follows each fluctuation, while a dashed line distinguishes the expected trend.
|
|
713
|
+
|
|
714
|
+
```ts
|
|
715
|
+
import { line, type APlotElement } from '@antadesign/plot/browser'
|
|
716
|
+
import '@antadesign/plot/elements/a-plot'
|
|
717
|
+
|
|
718
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
719
|
+
|
|
720
|
+
const data = Array.from({ length: 97 }, (_, i) => ({
|
|
721
|
+
x: i / 4,
|
|
722
|
+
expected: 45 + 22 * Math.sin(i / 18) + 12 * Math.cos(i / 7),
|
|
723
|
+
measured: 45 + 22 * Math.sin(i / 18) + 12 * Math.cos(i / 7)
|
|
724
|
+
+ 7 * Math.sin(i * 1.1) + 4 * Math.cos(i * 2.3),
|
|
725
|
+
}))
|
|
726
|
+
|
|
727
|
+
plot.plotArgs = {
|
|
728
|
+
series: [
|
|
729
|
+
line<Node>({
|
|
730
|
+
data,
|
|
731
|
+
y: 'expected',
|
|
732
|
+
color: { light: '#c2410c', dark: '#fdba74' },
|
|
733
|
+
width: 2,
|
|
734
|
+
dash: [7, 5],
|
|
735
|
+
tooltip: ({ y }) => document.createTextNode('Expected: ' + Math.round(Number(y))),
|
|
736
|
+
}),
|
|
737
|
+
line<Node>({
|
|
738
|
+
data,
|
|
739
|
+
y: 'measured',
|
|
740
|
+
color: { light: '#0891b2', dark: '#67e8f9' },
|
|
741
|
+
width: 2,
|
|
742
|
+
tooltip: ({ y }) => document.createTextNode('Measured: ' + Math.round(Number(y))),
|
|
743
|
+
}),
|
|
744
|
+
],
|
|
745
|
+
height: 260,
|
|
746
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
747
|
+
axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
748
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
749
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
750
|
+
grid: { x: false, y: true },
|
|
751
|
+
border: false,
|
|
752
|
+
zoom_pan: false,
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
document.body.append(plot)
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
Point marks and outlines
|
|
759
|
+
|
|
760
|
+
Show individual samples with diamond marks. Set the line width, mark size, and mark outline independently to keep each measurement visible.
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
import { line, type APlotElement } from '@antadesign/plot/browser'
|
|
764
|
+
import '@antadesign/plot/elements/a-plot'
|
|
765
|
+
|
|
766
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
767
|
+
|
|
768
|
+
const data = Array.from({ length: 13 }, (_, i) => ({
|
|
769
|
+
x: i,
|
|
770
|
+
y: 35 + i * 2 + 18 * Math.sin(i * 0.8) + 8 * Math.cos(i * 1.7),
|
|
771
|
+
}))
|
|
772
|
+
|
|
773
|
+
plot.plotArgs = {
|
|
774
|
+
series: [line<Node>({
|
|
775
|
+
data,
|
|
776
|
+
color: { light: '#7c3aed', dark: '#c4b5fd' },
|
|
777
|
+
width: 3,
|
|
778
|
+
mark: 'diamond',
|
|
779
|
+
mark_size: 10,
|
|
780
|
+
mark_stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 2 },
|
|
781
|
+
tooltip: ({ x, y }) => document.createTextNode('Sample ' + x + ': ' + Math.round(Number(y))),
|
|
782
|
+
})],
|
|
783
|
+
height: 260,
|
|
784
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
785
|
+
axis: { x: { min: -0.5, max: 12.5, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
786
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
787
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
788
|
+
grid: { x: false, y: true },
|
|
789
|
+
border: false,
|
|
790
|
+
zoom_pan: false,
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
document.body.append(plot)
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
Line arguments
|
|
797
|
+
|
|
798
|
+
| Field | Type | Default | Description |
|
|
799
|
+
|---|---|---|---|
|
|
800
|
+
| `data` | `Record<string, unknown>[]` | Required | Rows used to build the series. |
|
|
801
|
+
| `x?` | [`FieldArg`](#data-fields) | `'x'` | Horizontal field name or accessor. |
|
|
802
|
+
| `y?` | [`FieldArg`](#data-fields) | `'y'` | Vertical field name or accessor. |
|
|
803
|
+
| `color?` | [`ThemeColor`](#theme-colors) | Black | Series color; theme pairs provide separate light and dark colors. |
|
|
804
|
+
| `width?` | `number` | `2` | Line width in pixels. |
|
|
805
|
+
| `dash?` | `number[]` | Solid | Alternating dash and gap lengths in pixels. |
|
|
806
|
+
| [`mark?`](#mark-shapes) | `MarkShape` | None | Shape drawn at each point. |
|
|
807
|
+
| `mark_size?` | `number` | `6` | Point marker diameter in pixels. |
|
|
808
|
+
| [`mark_stroke?`](#strokes) | `StrokeArg` | None | Outline for the point markers. |
|
|
809
|
+
| [`tooltip?`](#tooltips) | `TooltipArg` | None | Use `true` for the default tooltip, or a callback for custom content. |
|
|
810
|
+
| [`on_select?`](#selection-callbacks) | `SelectFn` | None | Callback receiving the selected point and its source row. |
|
|
811
|
+
| `hoverable?` | `boolean` | `true` | Allow the series to participate in hit testing. |
|
|
812
|
+
| `highlight?` | `boolean` | `true` | Draw hover feedback for a hit point. |
|
|
813
|
+
|
|
814
|
+
```ts
|
|
815
|
+
import { line } from '@antadesign/plot'
|
|
816
|
+
|
|
817
|
+
const series = [line({
|
|
818
|
+
data: [{ time: 1, value: 3 }, { time: 2, value: 5 }, { time: 3, value: 4 }],
|
|
819
|
+
x: 'time',
|
|
820
|
+
y: 'value',
|
|
821
|
+
color: { light: 'steelblue', dark: 'lightskyblue' },
|
|
822
|
+
width: 2,
|
|
823
|
+
dash: [6, 3],
|
|
824
|
+
mark: 'circle',
|
|
825
|
+
mark_size: 8,
|
|
826
|
+
mark_stroke: { color: 'navy', width: 1 },
|
|
827
|
+
tooltip: ({ x, y }) => `(${x}, ${y})`,
|
|
828
|
+
on_select: ({ row }) => console.log('Selected row:', row),
|
|
829
|
+
hoverable: true,
|
|
830
|
+
highlight: true,
|
|
831
|
+
})]
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
#### Bar
|
|
835
|
+
|
|
836
|
+
Draw one bar per category. Use a categorical field on one axis and a numeric
|
|
837
|
+
field on the other. Categories on `x` produce vertical bars; categories on
|
|
838
|
+
`y` produce horizontal bars.
|
|
839
|
+
|
|
840
|
+
Rounded bars and data colors
|
|
841
|
+
|
|
842
|
+
Set corner rounding and spacing independently. A color accessor highlights days with more than 80 requests per second.
|
|
843
|
+
|
|
844
|
+
```ts
|
|
845
|
+
import { bar, type APlotElement } from '@antadesign/plot/browser'
|
|
846
|
+
import '@antadesign/plot/elements/a-plot'
|
|
847
|
+
|
|
848
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
849
|
+
|
|
850
|
+
const data = [
|
|
851
|
+
{ day: 'Mon', requests: 42 },
|
|
852
|
+
{ day: 'Tue', requests: 68 },
|
|
853
|
+
{ day: 'Wed', requests: 91 },
|
|
854
|
+
{ day: 'Thu', requests: 57 },
|
|
855
|
+
{ day: 'Fri', requests: 84 },
|
|
856
|
+
{ day: 'Sat', requests: 36 },
|
|
857
|
+
{ day: 'Sun', requests: 24 },
|
|
858
|
+
]
|
|
859
|
+
|
|
860
|
+
plot.plotArgs = {
|
|
861
|
+
series: [bar<Node>({
|
|
862
|
+
data,
|
|
863
|
+
x: 'day',
|
|
864
|
+
y: 'requests',
|
|
865
|
+
border_radius: 6,
|
|
866
|
+
inset: 5,
|
|
867
|
+
color: row => Number(row.requests) > 80
|
|
868
|
+
? { light: '#c2410c', dark: '#fdba74' }
|
|
869
|
+
: { light: '#0891b2', dark: '#67e8f9' },
|
|
870
|
+
hover_span_x: true,
|
|
871
|
+
tooltip: ({ x, y }) => document.createTextNode(x + ': ' + y + ' requests/s'),
|
|
872
|
+
})],
|
|
873
|
+
height: 260,
|
|
874
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
875
|
+
axis: { x: { label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
876
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
877
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
878
|
+
grid: { x: false, y: true },
|
|
879
|
+
border: false,
|
|
880
|
+
zoom_pan: false,
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
document.body.append(plot)
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
Horizontal bars
|
|
887
|
+
|
|
888
|
+
Place categories on the y-axis to leave room for their names. Hover anywhere along a category’s row to inspect its value.
|
|
889
|
+
|
|
890
|
+
```ts
|
|
891
|
+
import { bar, type APlotElement } from '@antadesign/plot/browser'
|
|
892
|
+
import '@antadesign/plot/elements/a-plot'
|
|
893
|
+
|
|
894
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
895
|
+
|
|
896
|
+
const data = [
|
|
897
|
+
{ service: 'Search', requests: 92 },
|
|
898
|
+
{ service: 'Feed', requests: 78 },
|
|
899
|
+
{ service: 'Checkout', requests: 64 },
|
|
900
|
+
{ service: 'Profile', requests: 47 },
|
|
901
|
+
{ service: 'Upload', requests: 31 },
|
|
902
|
+
]
|
|
903
|
+
|
|
904
|
+
plot.plotArgs = {
|
|
905
|
+
series: [bar<Node>({
|
|
906
|
+
data,
|
|
907
|
+
x: 'requests',
|
|
908
|
+
y: 'service',
|
|
909
|
+
border_radius: 4,
|
|
910
|
+
inset: 8,
|
|
911
|
+
color: { light: '#7c3aed', dark: '#c4b5fd' },
|
|
912
|
+
hover_span_y: true,
|
|
913
|
+
tooltip: ({ x, y }) => document.createTextNode(y + ': ' + x + ' requests/s'),
|
|
914
|
+
})],
|
|
915
|
+
height: 260,
|
|
916
|
+
margin: { top: 5, right: 8, bottom: 20, left: 78 },
|
|
917
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { label: '' } },
|
|
918
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
919
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
920
|
+
grid: { x: true, y: false },
|
|
921
|
+
border: false,
|
|
922
|
+
zoom_pan: false,
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
document.body.append(plot)
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
Positive and negative values
|
|
929
|
+
|
|
930
|
+
Bars extend from zero in either direction. Use a color accessor to distinguish monthly gains from losses.
|
|
931
|
+
|
|
932
|
+
```ts
|
|
933
|
+
import { bar, type APlotElement } from '@antadesign/plot/browser'
|
|
934
|
+
import '@antadesign/plot/elements/a-plot'
|
|
935
|
+
|
|
936
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
937
|
+
|
|
938
|
+
const data = [
|
|
939
|
+
{ month: 'Jan', change: 24 },
|
|
940
|
+
{ month: 'Feb', change: -16 },
|
|
941
|
+
{ month: 'Mar', change: 38 },
|
|
942
|
+
{ month: 'Apr', change: 12 },
|
|
943
|
+
{ month: 'May', change: -28 },
|
|
944
|
+
{ month: 'Jun', change: -9 },
|
|
945
|
+
{ month: 'Jul', change: 31 },
|
|
946
|
+
]
|
|
947
|
+
|
|
948
|
+
plot.plotArgs = {
|
|
949
|
+
series: [bar<Node>({
|
|
950
|
+
data,
|
|
951
|
+
x: 'month',
|
|
952
|
+
y: 'change',
|
|
953
|
+
inset: 6,
|
|
954
|
+
color: row => Number(row.change) >= 0
|
|
955
|
+
? { light: '#0d9488', dark: '#5eead4' }
|
|
956
|
+
: { light: '#c2410c', dark: '#fdba74' },
|
|
957
|
+
hover_span_x: true,
|
|
958
|
+
tooltip: ({ x, y }) => document.createTextNode(x + ': ' + (Number(y) > 0 ? '+' : '') + y + '%'),
|
|
959
|
+
})],
|
|
960
|
+
height: 260,
|
|
961
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
962
|
+
axis: { x: { label: '' }, y: { min: -40, max: 40, label: '' } },
|
|
963
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
964
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
965
|
+
grid: { x: false, y: true },
|
|
966
|
+
border: false,
|
|
967
|
+
zoom_pan: false,
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
document.body.append(plot)
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
Custom category order
|
|
974
|
+
|
|
975
|
+
Set `axis.x.categories` to order bars by severity, independently of the source row order.
|
|
976
|
+
|
|
977
|
+
```ts
|
|
978
|
+
import { bar, type PlotArgs } from '@antadesign/plot'
|
|
979
|
+
import '@antadesign/plot/elements/a-plot'
|
|
980
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
981
|
+
|
|
982
|
+
function categoryOrder(): PlotArgs<Node> {
|
|
983
|
+
const data = [
|
|
984
|
+
{ severity: 'High', count: 28 }, { severity: 'Low', count: 46 },
|
|
985
|
+
{ severity: 'Critical', count: 12 }, { severity: 'Medium', count: 35 },
|
|
986
|
+
]
|
|
987
|
+
return {
|
|
988
|
+
series: [bar<Node>({ data, x: 'severity', y: 'count', inset: 10, border_radius: 3,
|
|
989
|
+
color: { light: '#64748b', dark: '#94a3b8' },
|
|
990
|
+
tooltip: ({ x, y }) => document.createTextNode(x + ': ' + y + ' issues'),
|
|
991
|
+
})],
|
|
992
|
+
height: 260,
|
|
993
|
+
margin: { top: 5, right: 12, bottom: 24, left: 30 },
|
|
994
|
+
axis: {
|
|
995
|
+
x: { categories: ['Critical', 'High', 'Medium', 'Low'], label: '' },
|
|
996
|
+
y: { min: 0, max: 50, label: '' },
|
|
997
|
+
},
|
|
998
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
999
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1000
|
+
grid: { x: false, y: true },
|
|
1001
|
+
border: false,
|
|
1002
|
+
zoom_pan: false,
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1007
|
+
plot.plotArgs = categoryOrder()
|
|
1008
|
+
document.body.append(plot)
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
Bar arguments
|
|
1012
|
+
|
|
1013
|
+
| Field | Type | Default | Description |
|
|
1014
|
+
|---|---|---|---|
|
|
1015
|
+
| `data` | `Record<string, unknown>[]` | Required | Rows used to build the series. |
|
|
1016
|
+
| `x?` | [`FieldArg \| string[]`](#data-fields) | `'x'` | Category or value field. An array of value fields creates horizontal stacks. |
|
|
1017
|
+
| `y?` | [`FieldArg \| string[]`](#data-fields) | `'y'` | Category or value field. An array of value fields creates vertical stacks. |
|
|
1018
|
+
| `color?` | [`ColorArg`](#series-colors)` \| ``ThemeColor``[]` | Black | Uniform or per-row color; a color array maps to stacked fields in order. |
|
|
1019
|
+
| `border_radius?` | `number` | `0` | Bar corner radius in pixels. |
|
|
1020
|
+
| `inset?` | `number` | One sixth of band width | Inset on each side of a categorical band, in pixels. |
|
|
1021
|
+
| `min_size?` | `number` | `2` | Minimum rendered value length in pixels. |
|
|
1022
|
+
| `hover_span_x?` | `boolean` | `false` | Extend the horizontal hit region across the categorical band. |
|
|
1023
|
+
| `hover_span_y?` | `boolean` | `false` | Extend the vertical hit region across the categorical band. |
|
|
1024
|
+
| [`tooltip?`](#tooltips) | `TooltipArg` | None | Use `true` for the default tooltip, or a callback for custom content. |
|
|
1025
|
+
| [`on_select?`](#selection-callbacks) | `SelectFn` | None | Callback receiving the selected point and its source row. |
|
|
1026
|
+
| `hoverable?` | `boolean` | `true` | Allow the series to participate in hit testing. |
|
|
1027
|
+
| `highlight?` | `boolean` | `true` | Draw hover feedback for a hit point. |
|
|
1028
|
+
|
|
1029
|
+
```ts
|
|
1030
|
+
import { bar } from '@antadesign/plot'
|
|
1031
|
+
|
|
1032
|
+
const series = [bar({
|
|
1033
|
+
data: [{ group: 'A', count: 8 }, { group: 'B', count: 12 }],
|
|
1034
|
+
x: 'group',
|
|
1035
|
+
y: 'count',
|
|
1036
|
+
color: { light: 'steelblue', dark: 'lightskyblue' },
|
|
1037
|
+
border_radius: 3,
|
|
1038
|
+
inset: 4,
|
|
1039
|
+
min_size: 2,
|
|
1040
|
+
hover_span_x: true,
|
|
1041
|
+
hover_span_y: false,
|
|
1042
|
+
tooltip: ({ x, y }) => `(${x}, ${y})`,
|
|
1043
|
+
on_select: ({ row }) => console.log('Selected row:', row),
|
|
1044
|
+
hoverable: true,
|
|
1045
|
+
highlight: true,
|
|
1046
|
+
})]
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
##### Stacked bars
|
|
1050
|
+
|
|
1051
|
+
Pass an array of value-field names to stack them within each category.
|
|
1052
|
+
Use `y` for vertical stacks or `x` for horizontal stacks. An array of
|
|
1053
|
+
colors assigns one color to each field, in the same order.
|
|
1054
|
+
|
|
1055
|
+
Stacked values
|
|
1056
|
+
|
|
1057
|
+
Pass multiple value fields to stack them within each category. Colors follow the field order; hover a segment to see its contribution.
|
|
1058
|
+
|
|
1059
|
+
```ts
|
|
1060
|
+
import { bar, type APlotElement } from '@antadesign/plot/browser'
|
|
1061
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1062
|
+
|
|
1063
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1064
|
+
|
|
1065
|
+
const data = [
|
|
1066
|
+
{ day: 'Mon', network: 18, compute: 42, storage: 24 },
|
|
1067
|
+
{ day: 'Tue', network: 24, compute: 32, storage: 18 },
|
|
1068
|
+
{ day: 'Wed', network: 12, compute: 56, storage: 28 },
|
|
1069
|
+
{ day: 'Thu', network: 28, compute: 24, storage: 16 },
|
|
1070
|
+
{ day: 'Fri', network: 16, compute: 48, storage: 22 },
|
|
1071
|
+
]
|
|
1072
|
+
|
|
1073
|
+
plot.plotArgs = {
|
|
1074
|
+
series: [bar<Node>({
|
|
1075
|
+
data,
|
|
1076
|
+
x: 'day',
|
|
1077
|
+
y: ['network', 'compute', 'storage'],
|
|
1078
|
+
color: [
|
|
1079
|
+
{ light: '#0d9488', dark: '#5eead4' },
|
|
1080
|
+
{ light: '#6366f1', dark: '#a5b4fc' },
|
|
1081
|
+
{ light: '#d97706', dark: '#fcd34d' },
|
|
1082
|
+
],
|
|
1083
|
+
inset: 8,
|
|
1084
|
+
tooltip: ({ label, y }) => document.createTextNode(label + ': ' + y + ' ms'),
|
|
1085
|
+
})],
|
|
1086
|
+
height: 260,
|
|
1087
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
1088
|
+
axis: { x: { label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
1089
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1090
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1091
|
+
grid: { x: false, y: true },
|
|
1092
|
+
border: false,
|
|
1093
|
+
zoom_pan: false,
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
document.body.append(plot)
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
Horizontal stacks
|
|
1100
|
+
|
|
1101
|
+
Pass multiple fields to `x` and categories to `y` to compare contributions across services.
|
|
1102
|
+
|
|
1103
|
+
```ts
|
|
1104
|
+
import { bar, type PlotArgs } from '@antadesign/plot'
|
|
1105
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1106
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
1107
|
+
|
|
1108
|
+
function stackedHorizontal(): PlotArgs<Node> {
|
|
1109
|
+
const data = [
|
|
1110
|
+
{ service: 'Search', network: 18, compute: 46, storage: 14 },
|
|
1111
|
+
{ service: 'Feed', network: 26, compute: 28, storage: 22 },
|
|
1112
|
+
{ service: 'Upload', network: 42, compute: 16, storage: 32 },
|
|
1113
|
+
{ service: 'Export', network: 12, compute: 36, storage: 18 },
|
|
1114
|
+
]
|
|
1115
|
+
return {
|
|
1116
|
+
series: [bar<Node>({ data, x: ['network', 'compute', 'storage'], y: 'service',
|
|
1117
|
+
color: [
|
|
1118
|
+
{ light: '#0d9488', dark: '#5eead4' },
|
|
1119
|
+
{ light: '#6366f1', dark: '#a5b4fc' },
|
|
1120
|
+
{ light: '#d97706', dark: '#fcd34d' },
|
|
1121
|
+
],
|
|
1122
|
+
inset: 8, border_radius: 3,
|
|
1123
|
+
tooltip: ({ label, x }) => document.createTextNode(label + ': ' + x + ' ms'),
|
|
1124
|
+
})],
|
|
1125
|
+
height: 260,
|
|
1126
|
+
margin: { top: 5, right: 8, bottom: 20, left: 64 },
|
|
1127
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { label: '' } },
|
|
1128
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1129
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1130
|
+
grid: { x: true, y: false },
|
|
1131
|
+
border: false,
|
|
1132
|
+
zoom_pan: false,
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1137
|
+
plot.plotArgs = stackedHorizontal()
|
|
1138
|
+
document.body.append(plot)
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
Positive and negative stacks
|
|
1142
|
+
|
|
1143
|
+
Positive and negative values stack separately from zero. Each segment retains its sign and field label.
|
|
1144
|
+
|
|
1145
|
+
```ts
|
|
1146
|
+
import { bar, type PlotArgs } from '@antadesign/plot'
|
|
1147
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1148
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
1149
|
+
|
|
1150
|
+
function stackedDiverging(): PlotArgs<Node> {
|
|
1151
|
+
const data = [
|
|
1152
|
+
{ month: 'Jan', new: 28, expansion: 12, churn: -14, contraction: -6 },
|
|
1153
|
+
{ month: 'Feb', new: 18, expansion: 8, churn: -22, contraction: -10 },
|
|
1154
|
+
{ month: 'Mar', new: 36, expansion: 16, churn: -8, contraction: -4 },
|
|
1155
|
+
{ month: 'Apr', new: 24, expansion: 18, churn: -18, contraction: -12 },
|
|
1156
|
+
{ month: 'May', new: 32, expansion: 10, churn: -12, contraction: -8 },
|
|
1157
|
+
]
|
|
1158
|
+
return {
|
|
1159
|
+
series: [bar<Node>({ data, x: 'month', y: ['new', 'expansion', 'churn', 'contraction'],
|
|
1160
|
+
color: [
|
|
1161
|
+
{ light: '#0d9488', dark: '#2dd4bf' },
|
|
1162
|
+
{ light: '#5eead4', dark: '#99f6e4' },
|
|
1163
|
+
{ light: '#ea580c', dark: '#fb923c' },
|
|
1164
|
+
{ light: '#fdba74', dark: '#fed7aa' },
|
|
1165
|
+
],
|
|
1166
|
+
inset: 7,
|
|
1167
|
+
tooltip: ({ label, y }) => document.createTextNode(label + ': ' + (Number(y) > 0 ? '+' : '') + y),
|
|
1168
|
+
})],
|
|
1169
|
+
height: 260,
|
|
1170
|
+
margin: { top: 5, right: 8, bottom: 20, left: 36 },
|
|
1171
|
+
axis: { x: { label: '' }, y: { min: -40, max: 60, label: '' } },
|
|
1172
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1173
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1174
|
+
grid: { x: false, y: true },
|
|
1175
|
+
border: false,
|
|
1176
|
+
zoom_pan: false,
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1181
|
+
plot.plotArgs = stackedDiverging()
|
|
1182
|
+
document.body.append(plot)
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
Full-stack tooltips
|
|
1186
|
+
|
|
1187
|
+
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.
|
|
1188
|
+
|
|
1189
|
+
```ts
|
|
1190
|
+
import { bar, type PlotArgs } from '@antadesign/plot'
|
|
1191
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1192
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
1193
|
+
|
|
1194
|
+
function stackedTooltip(): PlotArgs<Node> {
|
|
1195
|
+
const data = [
|
|
1196
|
+
{ day: 'Mon', network: 18, compute: 42, storage: 24 },
|
|
1197
|
+
{ day: 'Tue', network: 24, compute: 32, storage: 18 },
|
|
1198
|
+
{ day: 'Wed', network: 12, compute: 56, storage: 28 },
|
|
1199
|
+
{ day: 'Thu', network: 28, compute: 24, storage: 16 },
|
|
1200
|
+
{ day: 'Fri', network: 16, compute: 48, storage: 22 },
|
|
1201
|
+
]
|
|
1202
|
+
|
|
1203
|
+
return {
|
|
1204
|
+
series: [bar<Node>({
|
|
1205
|
+
data,
|
|
1206
|
+
x: 'day',
|
|
1207
|
+
y: ['network', 'compute', 'storage'],
|
|
1208
|
+
color: [
|
|
1209
|
+
{ light: '#0d9488', dark: '#5eead4' },
|
|
1210
|
+
{ light: '#6366f1', dark: '#a5b4fc' },
|
|
1211
|
+
{ light: '#d97706', dark: '#fcd34d' },
|
|
1212
|
+
],
|
|
1213
|
+
inset: 10,
|
|
1214
|
+
hover_span_x: false,
|
|
1215
|
+
hover_span_y: false,
|
|
1216
|
+
tooltip: ({ label, row }) => {
|
|
1217
|
+
const content = document.createElement('div')
|
|
1218
|
+
for (const field of ['network', 'compute', 'storage']) {
|
|
1219
|
+
const entry = document.createElement('div')
|
|
1220
|
+
const value = document.createElement(field === label ? 'strong' : 'span')
|
|
1221
|
+
value.textContent = field + ': ' + row[field] + ' ms'
|
|
1222
|
+
entry.append(value)
|
|
1223
|
+
content.append(entry)
|
|
1224
|
+
}
|
|
1225
|
+
return content
|
|
1226
|
+
},
|
|
1227
|
+
})],
|
|
1228
|
+
height: 260,
|
|
1229
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
1230
|
+
axis: { x: { label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
1231
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1232
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1233
|
+
grid: { x: false, y: true },
|
|
1234
|
+
border: false,
|
|
1235
|
+
zoom_pan: false,
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1240
|
+
plot.plotArgs = stackedTooltip()
|
|
1241
|
+
document.body.append(plot)
|
|
1242
|
+
```
|
|
1243
|
+
|
|
1244
|
+
```ts
|
|
1245
|
+
import { bar } from '@antadesign/plot'
|
|
1246
|
+
|
|
1247
|
+
const series = [bar({
|
|
1248
|
+
data: [
|
|
1249
|
+
{ group: 'A', passed: 8, failed: 2 },
|
|
1250
|
+
{ group: 'B', passed: 12, failed: 3 },
|
|
1251
|
+
],
|
|
1252
|
+
x: 'group',
|
|
1253
|
+
y: ['passed', 'failed'],
|
|
1254
|
+
color: ['seagreen', 'coral'],
|
|
1255
|
+
border_radius: 3,
|
|
1256
|
+
inset: 4,
|
|
1257
|
+
min_size: 2,
|
|
1258
|
+
hover_span_x: true,
|
|
1259
|
+
hover_span_y: false,
|
|
1260
|
+
tooltip: ({ label, y }) => `${label}: ${y}`,
|
|
1261
|
+
on_select: ({ label, row }) => console.log('Selected segment:', label, row),
|
|
1262
|
+
hoverable: true,
|
|
1263
|
+
highlight: true,
|
|
1264
|
+
})]
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
#### Area
|
|
1268
|
+
|
|
1269
|
+
Fill the space between two boundaries. Set `y2` for a second vertical value
|
|
1270
|
+
or `x2` for a second horizontal value. The second boundary can be a field
|
|
1271
|
+
name or a constant, such as a zero baseline.
|
|
1272
|
+
|
|
1273
|
+
Fill to a baseline
|
|
1274
|
+
|
|
1275
|
+
Fill beneath a signal with a constant zero baseline. A stroke traces the area’s boundary.
|
|
1276
|
+
|
|
1277
|
+
```ts
|
|
1278
|
+
import { area, type APlotElement } from '@antadesign/plot/browser'
|
|
1279
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1280
|
+
|
|
1281
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1282
|
+
|
|
1283
|
+
const data = Array.from({ length: 97 }, (_, i) => ({
|
|
1284
|
+
x: i / 4,
|
|
1285
|
+
y: 28 + 42 * Math.exp(-(((i - 55) / 24) ** 2))
|
|
1286
|
+
+ 9 * Math.sin(i / 5) + 4 * Math.cos(i * 1.3),
|
|
1287
|
+
}))
|
|
1288
|
+
|
|
1289
|
+
plot.plotArgs = {
|
|
1290
|
+
series: [
|
|
1291
|
+
area<Node>({
|
|
1292
|
+
data,
|
|
1293
|
+
y2: 0,
|
|
1294
|
+
color: { light: '#99f6e4', dark: '#134e4a' },
|
|
1295
|
+
stroke: { color: { light: '#0d9488', dark: '#5eead4' }, width: 2 },
|
|
1296
|
+
tooltip: ({ y }) => document.createTextNode('Load: ' + Math.round(Number(y))),
|
|
1297
|
+
}),
|
|
1298
|
+
],
|
|
1299
|
+
height: 260,
|
|
1300
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
1301
|
+
axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
1302
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1303
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1304
|
+
grid: { x: false, y: true },
|
|
1305
|
+
border: false,
|
|
1306
|
+
zoom_pan: false,
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
document.body.append(plot)
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
A range between fields
|
|
1313
|
+
|
|
1314
|
+
Use two value fields to draw a changing interval. A dashed outline distinguishes its boundaries.
|
|
1315
|
+
|
|
1316
|
+
```ts
|
|
1317
|
+
import { area, type APlotElement } from '@antadesign/plot/browser'
|
|
1318
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1319
|
+
|
|
1320
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1321
|
+
|
|
1322
|
+
const data = Array.from({ length: 81 }, (_, i) => {
|
|
1323
|
+
const center = 48 + 16 * Math.sin(i / 12) + 5 * Math.cos(i / 3)
|
|
1324
|
+
const spread = 8 + 10 * Math.sin(i / 17) ** 2
|
|
1325
|
+
return { x: i * 0.3, low: center - spread, high: center + spread }
|
|
1326
|
+
})
|
|
1327
|
+
|
|
1328
|
+
plot.plotArgs = {
|
|
1329
|
+
series: [
|
|
1330
|
+
area<Node>({
|
|
1331
|
+
data,
|
|
1332
|
+
y: 'high',
|
|
1333
|
+
y2: 'low',
|
|
1334
|
+
color: { light: '#ddd6fe', dark: '#4c1d95' },
|
|
1335
|
+
stroke: { color: { light: '#7c3aed', dark: '#c4b5fd' }, width: 2 },
|
|
1336
|
+
dash: [5, 3],
|
|
1337
|
+
tooltip: ({ row }) => document.createTextNode(Math.round(Number(row.low)) + '–' + Math.round(Number(row.high))),
|
|
1338
|
+
}),
|
|
1339
|
+
],
|
|
1340
|
+
height: 260,
|
|
1341
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
1342
|
+
axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
1343
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1344
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1345
|
+
grid: { x: false, y: true },
|
|
1346
|
+
border: false,
|
|
1347
|
+
zoom_pan: false,
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
document.body.append(plot)
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
Layered contributions
|
|
1354
|
+
|
|
1355
|
+
Compose adjacent areas with explicit cumulative bounds. Each band shows one contribution to the total.
|
|
1356
|
+
|
|
1357
|
+
```ts
|
|
1358
|
+
import { area, type APlotElement } from '@antadesign/plot/browser'
|
|
1359
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1360
|
+
|
|
1361
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1362
|
+
|
|
1363
|
+
const data = Array.from({ length: 97 }, (_, i) => {
|
|
1364
|
+
const first = 18 + 7 * Math.sin(i / 13) + 3 * Math.cos(i / 4)
|
|
1365
|
+
const second = first + 22 + 9 * Math.sin(i / 18 + 1)
|
|
1366
|
+
const total = second + 16 + 6 * Math.cos(i / 9)
|
|
1367
|
+
return { x: i / 4, first, second, total }
|
|
1368
|
+
})
|
|
1369
|
+
|
|
1370
|
+
plot.plotArgs = {
|
|
1371
|
+
series: [
|
|
1372
|
+
area<Node>({
|
|
1373
|
+
data,
|
|
1374
|
+
y: 'first',
|
|
1375
|
+
y2: 0,
|
|
1376
|
+
color: { light: '#2dd4bf', dark: '#0d9488' },
|
|
1377
|
+
tooltip: ({ row }) => document.createTextNode('Network: ' + Math.round(Number(row.first))),
|
|
1378
|
+
}),
|
|
1379
|
+
area<Node>({
|
|
1380
|
+
data,
|
|
1381
|
+
y: 'second',
|
|
1382
|
+
y2: 'first',
|
|
1383
|
+
color: { light: '#a5b4fc', dark: '#6366f1' },
|
|
1384
|
+
tooltip: ({ row }) => document.createTextNode('Compute: ' + Math.round(Number(row.second) - Number(row.first))),
|
|
1385
|
+
}),
|
|
1386
|
+
area<Node>({
|
|
1387
|
+
data,
|
|
1388
|
+
y: 'total',
|
|
1389
|
+
y2: 'second',
|
|
1390
|
+
color: { light: '#fcd34d', dark: '#b45309' },
|
|
1391
|
+
tooltip: ({ row }) => document.createTextNode('Storage: ' + Math.round(Number(row.total) - Number(row.second))),
|
|
1392
|
+
}),
|
|
1393
|
+
],
|
|
1394
|
+
height: 260,
|
|
1395
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
1396
|
+
axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
1397
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1398
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1399
|
+
grid: { x: false, y: true },
|
|
1400
|
+
border: false,
|
|
1401
|
+
zoom_pan: false,
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
document.body.append(plot)
|
|
1405
|
+
```
|
|
1406
|
+
|
|
1407
|
+
Horizontal intervals
|
|
1408
|
+
|
|
1409
|
+
Use `x` and `x2` for horizontal bounds. The filled band shows how an interval shifts along the vertical axis.
|
|
1410
|
+
|
|
1411
|
+
```ts
|
|
1412
|
+
import { area, type APlotElement } from '@antadesign/plot/browser'
|
|
1413
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1414
|
+
|
|
1415
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1416
|
+
|
|
1417
|
+
const data = Array.from({ length: 81 }, (_, i) => {
|
|
1418
|
+
const center = 46 + 18 * Math.sin(i / 16) + 4 * Math.cos(i / 4)
|
|
1419
|
+
const spread = 9 + 6 * Math.cos(i / 11) ** 2
|
|
1420
|
+
return { y: i * 0.3, low: center - spread, high: center + spread }
|
|
1421
|
+
})
|
|
1422
|
+
|
|
1423
|
+
plot.plotArgs = {
|
|
1424
|
+
series: [
|
|
1425
|
+
area<Node>({
|
|
1426
|
+
data,
|
|
1427
|
+
x: 'low',
|
|
1428
|
+
x2: 'high',
|
|
1429
|
+
color: { light: '#fed7aa', dark: '#7c2d12' },
|
|
1430
|
+
stroke: { color: { light: '#ea580c', dark: '#fdba74' }, width: 2 },
|
|
1431
|
+
tooltip: ({ row }) => document.createTextNode(Math.round(Number(row.low)) + '–' + Math.round(Number(row.high))),
|
|
1432
|
+
}),
|
|
1433
|
+
],
|
|
1434
|
+
height: 260,
|
|
1435
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
1436
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 24, label: '' } },
|
|
1437
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1438
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1439
|
+
grid: { x: true, y: false },
|
|
1440
|
+
border: false,
|
|
1441
|
+
zoom_pan: false,
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
document.body.append(plot)
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
Area arguments
|
|
1448
|
+
|
|
1449
|
+
| Field | Type | Default | Description |
|
|
1450
|
+
|---|---|---|---|
|
|
1451
|
+
| `data` | `Record<string, unknown>[]` | Required | Rows used to build the series. |
|
|
1452
|
+
| `x?` | [`FieldArg`](#data-fields) | `'x'` | Horizontal field name or accessor. |
|
|
1453
|
+
| `x2?` | [`FieldArg \| number`](#data-fields) | None | Second horizontal boundary, as a field, accessor, or constant. Use either `x2` or `y2`. |
|
|
1454
|
+
| `y?` | [`FieldArg`](#data-fields) | `'y'` | Vertical field name or accessor. |
|
|
1455
|
+
| `y2?` | [`FieldArg \| number`](#data-fields) | Zero baseline if neither bound is set | Second vertical boundary, as a field, accessor, or constant. |
|
|
1456
|
+
| `color?` | [`ThemeColor`](#theme-colors) | Black | Series color; theme pairs provide separate light and dark colors. |
|
|
1457
|
+
| [`stroke?`](#strokes) | `StrokeArg` | None | Outline color and optional width. |
|
|
1458
|
+
| `dash?` | `number[]` | Solid | Alternating dash and gap lengths in pixels. |
|
|
1459
|
+
| [`tooltip?`](#tooltips) | `TooltipArg` | None | Use `true` for the default tooltip, or a callback for custom content. |
|
|
1460
|
+
| [`on_select?`](#selection-callbacks) | `SelectFn` | None | Callback receiving the selected point and its source row. |
|
|
1461
|
+
| `hoverable?` | `boolean` | `true` | Allow the series to participate in hit testing. |
|
|
1462
|
+
| `highlight?` | `boolean` | `true` | Draw hover feedback for a hit point. |
|
|
1463
|
+
|
|
1464
|
+
```ts
|
|
1465
|
+
import { area } from '@antadesign/plot'
|
|
1466
|
+
|
|
1467
|
+
const series = [area({
|
|
1468
|
+
data: [
|
|
1469
|
+
{ time: 1, low: 2, high: 4 },
|
|
1470
|
+
{ time: 2, low: 3, high: 6 },
|
|
1471
|
+
{ time: 3, low: 2, high: 5 },
|
|
1472
|
+
],
|
|
1473
|
+
x: 'time',
|
|
1474
|
+
y: 'high',
|
|
1475
|
+
y2: 'low',
|
|
1476
|
+
color: { light: 'lightsteelblue', dark: 'midnightblue' },
|
|
1477
|
+
stroke: { color: 'steelblue', width: 2 },
|
|
1478
|
+
dash: [6, 3],
|
|
1479
|
+
tooltip: ({ x, y }) => `(${x}, ${y})`,
|
|
1480
|
+
on_select: ({ row }) => console.log('Selected row:', row),
|
|
1481
|
+
hoverable: true,
|
|
1482
|
+
highlight: true,
|
|
1483
|
+
})]
|
|
1484
|
+
```
|
|
1485
|
+
For a horizontal area, use `x2` instead of `y2`:
|
|
1486
|
+
|
|
1487
|
+
```ts
|
|
1488
|
+
import { area } from '@antadesign/plot'
|
|
1489
|
+
|
|
1490
|
+
const series = [area({
|
|
1491
|
+
data: [{ low: 2, high: 4, time: 1 }, { low: 3, high: 6, time: 2 }],
|
|
1492
|
+
x: 'high',
|
|
1493
|
+
x2: 'low',
|
|
1494
|
+
y: 'time',
|
|
1495
|
+
color: 'lightsteelblue',
|
|
1496
|
+
})]
|
|
1497
|
+
```
|
|
1498
|
+
|
|
1499
|
+
#### Rect
|
|
1500
|
+
|
|
1501
|
+
Draw rectangles with explicit bounds. Use `x`, `x2`, `y`, and `y2` to select
|
|
1502
|
+
the fields defining each rectangle's corners. Rectangles can represent
|
|
1503
|
+
intervals, regions, or cells in a grid.
|
|
1504
|
+
|
|
1505
|
+
Categorical cells
|
|
1506
|
+
|
|
1507
|
+
Combine two categorical axes to form a heatmap. Color each cell from its value and use `inset` to separate neighboring cells.
|
|
1508
|
+
|
|
1509
|
+
```ts
|
|
1510
|
+
import { rect, type APlotElement } from '@antadesign/plot/browser'
|
|
1511
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1512
|
+
|
|
1513
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1514
|
+
|
|
1515
|
+
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
|
|
1516
|
+
const hours = ['09', '11', '13', '15', '17', '19']
|
|
1517
|
+
const data = days.flatMap((day, d) => hours.map((hour, h) => ({
|
|
1518
|
+
day, hour, activity: Math.round(20 + 75 * Math.sin(h * 0.8 + d * 0.5) ** 2),
|
|
1519
|
+
})))
|
|
1520
|
+
|
|
1521
|
+
plot.plotArgs = {
|
|
1522
|
+
series: [
|
|
1523
|
+
rect<Node>({
|
|
1524
|
+
data,
|
|
1525
|
+
x: 'hour',
|
|
1526
|
+
y: 'day',
|
|
1527
|
+
inset: 2,
|
|
1528
|
+
color: row => ({
|
|
1529
|
+
light: 'hsl(174, 65%, ' + (94 - Number(row.activity) * 0.6) + '%)',
|
|
1530
|
+
dark: 'hsl(174, 55%, ' + (16 + Number(row.activity) * 0.4) + '%)',
|
|
1531
|
+
}),
|
|
1532
|
+
tooltip: ({ row }) => document.createTextNode(row.day + ' ' + row.hour + ':00 · ' + row.activity + '%'),
|
|
1533
|
+
}),
|
|
1534
|
+
],
|
|
1535
|
+
height: 260,
|
|
1536
|
+
margin: { top: 5, right: 8, bottom: 20, left: 38 },
|
|
1537
|
+
axis: { x: { categories: hours, label: '' }, y: { categories: days, label: '' } },
|
|
1538
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1539
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1540
|
+
grid: false,
|
|
1541
|
+
border: false,
|
|
1542
|
+
zoom_pan: false,
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
document.body.append(plot)
|
|
1546
|
+
```
|
|
1547
|
+
|
|
1548
|
+
Intervals with explicit bounds
|
|
1549
|
+
|
|
1550
|
+
Set `x` and `x2` to show durations within categorical rows. Each rectangle retains its start and end values as the plot resizes.
|
|
1551
|
+
|
|
1552
|
+
```ts
|
|
1553
|
+
import { rect, type APlotElement } from '@antadesign/plot/browser'
|
|
1554
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1555
|
+
|
|
1556
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1557
|
+
|
|
1558
|
+
const data = [
|
|
1559
|
+
{ task: 'Fetch', start: 0.5, end: 3.2 },
|
|
1560
|
+
{ task: 'Parse', start: 2.8, end: 5.1 },
|
|
1561
|
+
{ task: 'Build', start: 4.5, end: 9.4 },
|
|
1562
|
+
{ task: 'Test', start: 7.8, end: 11.2 },
|
|
1563
|
+
{ task: 'Ship', start: 10.8, end: 12.5 },
|
|
1564
|
+
]
|
|
1565
|
+
|
|
1566
|
+
plot.plotArgs = {
|
|
1567
|
+
series: [
|
|
1568
|
+
rect<Node>({
|
|
1569
|
+
data,
|
|
1570
|
+
x: 'start',
|
|
1571
|
+
x2: 'end',
|
|
1572
|
+
y: 'task',
|
|
1573
|
+
inset: 9,
|
|
1574
|
+
color: { light: '#c4b5fd', dark: '#6d28d9' },
|
|
1575
|
+
stroke: { color: { light: '#7c3aed', dark: '#c4b5fd' }, width: 1 },
|
|
1576
|
+
hover_span_y: true,
|
|
1577
|
+
tooltip: ({ row }) => document.createTextNode(row.task + ': ' + row.start + '–' + row.end + ' s'),
|
|
1578
|
+
}),
|
|
1579
|
+
],
|
|
1580
|
+
height: 260,
|
|
1581
|
+
margin: { top: 5, right: 8, bottom: 20, left: 44 },
|
|
1582
|
+
axis: { x: { min: 0, max: 14, label: '' }, y: { label: '' } },
|
|
1583
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1584
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1585
|
+
grid: false,
|
|
1586
|
+
border: false,
|
|
1587
|
+
zoom_pan: false,
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
document.body.append(plot)
|
|
1591
|
+
```
|
|
1592
|
+
|
|
1593
|
+
Fixed pixel dimensions
|
|
1594
|
+
|
|
1595
|
+
Set `size` to draw rectangles with a consistent width and height at numeric coordinates. An outline keeps nearby marks distinct.
|
|
1596
|
+
|
|
1597
|
+
```ts
|
|
1598
|
+
import { rect, type APlotElement } from '@antadesign/plot/browser'
|
|
1599
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1600
|
+
|
|
1601
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1602
|
+
|
|
1603
|
+
const data = Array.from({ length: 32 }, (_, i) => ({
|
|
1604
|
+
x: 8 + (i * 23 % 84),
|
|
1605
|
+
y: 18 + (i * 13 % 65),
|
|
1606
|
+
value: 20 + (i * 17 % 80),
|
|
1607
|
+
}))
|
|
1608
|
+
|
|
1609
|
+
plot.plotArgs = {
|
|
1610
|
+
series: [
|
|
1611
|
+
rect<Node>({
|
|
1612
|
+
data,
|
|
1613
|
+
size: { width: 16, height: 9 },
|
|
1614
|
+
color: row => Number(row.value) > 65
|
|
1615
|
+
? { light: '#c2410c', dark: '#fdba74' }
|
|
1616
|
+
: { light: '#0891b2', dark: '#67e8f9' },
|
|
1617
|
+
stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 1 },
|
|
1618
|
+
tooltip: ({ row }) => document.createTextNode('Value: ' + row.value),
|
|
1619
|
+
}),
|
|
1620
|
+
],
|
|
1621
|
+
height: 260,
|
|
1622
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
1623
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
1624
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1625
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1626
|
+
grid: false,
|
|
1627
|
+
border: false,
|
|
1628
|
+
zoom_pan: false,
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
document.body.append(plot)
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
Run history
|
|
1635
|
+
|
|
1636
|
+
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.
|
|
1637
|
+
|
|
1638
|
+
```ts
|
|
1639
|
+
import { rect, type APlotElement } from '@antadesign/plot/browser'
|
|
1640
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1641
|
+
|
|
1642
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1643
|
+
|
|
1644
|
+
const start = Date.UTC(2026, 0, 1, 9)
|
|
1645
|
+
const data = Array.from({ length: 36 }, (_, i) => ({
|
|
1646
|
+
run: i + 1,
|
|
1647
|
+
end_ms: start + (i * 3 + (i % 3)) * 60_000,
|
|
1648
|
+
lane: [5, 6, 14, 23, 24, 25, 32].includes(i) ? 'Failed' : 'Passed',
|
|
1649
|
+
}))
|
|
1650
|
+
|
|
1651
|
+
plot.plotArgs = {
|
|
1652
|
+
series: [
|
|
1653
|
+
rect<Node>({
|
|
1654
|
+
data: data.filter(row => row.lane === 'Passed'),
|
|
1655
|
+
x: 'end_ms',
|
|
1656
|
+
y: 'lane',
|
|
1657
|
+
size: { width: 10, height: 40 },
|
|
1658
|
+
band_align: 'end',
|
|
1659
|
+
inset: 0,
|
|
1660
|
+
color: { light: '#189e3e', dark: '#4ade80' },
|
|
1661
|
+
stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 0.5 },
|
|
1662
|
+
tooltip: ({ row }) => document.createTextNode('Run ' + row.run + ': ' + row.lane),
|
|
1663
|
+
}),
|
|
1664
|
+
rect<Node>({
|
|
1665
|
+
data: data.filter(row => row.lane === 'Failed'),
|
|
1666
|
+
x: 'end_ms',
|
|
1667
|
+
y: 'lane',
|
|
1668
|
+
size: { width: 10, height: 40 },
|
|
1669
|
+
band_align: 'start',
|
|
1670
|
+
inset: 0,
|
|
1671
|
+
color: { light: '#c41e5e', dark: '#fb7185' },
|
|
1672
|
+
stroke: { color: { light: '#ffffff', dark: '#151b28' }, width: 0.5 },
|
|
1673
|
+
tooltip: ({ row }) => document.createTextNode('Run ' + row.run + ': ' + row.lane),
|
|
1674
|
+
}),
|
|
1675
|
+
],
|
|
1676
|
+
height: 260,
|
|
1677
|
+
margin: { top: 5, right: 8, bottom: 24, left: 56 },
|
|
1678
|
+
axis: {
|
|
1679
|
+
x: {
|
|
1680
|
+
scale: 'utc',
|
|
1681
|
+
min: start - 5 * 60_000,
|
|
1682
|
+
max: start + 115 * 60_000,
|
|
1683
|
+
label: '',
|
|
1684
|
+
line: false,
|
|
1685
|
+
tick_label: { format: value => new Date(Number(value)).toISOString().slice(11, 16) },
|
|
1686
|
+
},
|
|
1687
|
+
y: { categories: ['Failed', 'Passed'], grid_align: 'edge', label: '', line: false },
|
|
1688
|
+
},
|
|
1689
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1690
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1691
|
+
grid: true,
|
|
1692
|
+
border: false,
|
|
1693
|
+
theme_invert: false,
|
|
1694
|
+
zoom_pan: false,
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
document.body.append(plot)
|
|
1698
|
+
```
|
|
1699
|
+
|
|
1700
|
+
Rect arguments
|
|
1701
|
+
|
|
1702
|
+
| Field | Type | Default | Description |
|
|
1703
|
+
|---|---|---|---|
|
|
1704
|
+
| `data` | `Record<string, unknown>[]` | Required | Rows used to build the series. |
|
|
1705
|
+
| `x?` | [`FieldArg`](#data-fields) | `'x'` | Horizontal field name or accessor. |
|
|
1706
|
+
| `x2?` | [`FieldArg`](#data-fields) | `'x2'` when present in data | Opposite horizontal corner. Numeric axes need a second corner or pixel size. |
|
|
1707
|
+
| `y?` | [`FieldArg`](#data-fields) | `'y'` | Vertical field name or accessor. |
|
|
1708
|
+
| `y2?` | [`FieldArg`](#data-fields) | `'y2'` when present in data | Opposite vertical corner. Numeric axes need a second corner or pixel size. |
|
|
1709
|
+
| `span?` | `'x' \| 'y'` | None | Stretch the rectangle across the full plot on this axis. |
|
|
1710
|
+
| `size?` | `number \| { width?: number; height?: number }` | Derived from bounds or band | Fixed pixel size, shared or per dimension. |
|
|
1711
|
+
| `band_align?` | `'center' \| 'start' \| 'end'` | `'center'` | Alignment of a fixed-size rectangle within a categorical band. |
|
|
1712
|
+
| `hover_span_x?` | `boolean` | `false` | Extend the horizontal hit region across the categorical band. |
|
|
1713
|
+
| `hover_span_y?` | `boolean` | `false` | Extend the vertical hit region across the categorical band. |
|
|
1714
|
+
| `inset?` | `number` | One sixth of band width | Inset on each side of a categorical band, in pixels. |
|
|
1715
|
+
| `min_size?` | `number` | `0` | Minimum pixel length for a rectangle defined by data bounds. |
|
|
1716
|
+
| `offset?` | `{ x?: number; y?: number }` | `{ x: 0, y: 0 }` | Pixel offsets applied to each rectangle. |
|
|
1717
|
+
| `color?` | [`ColorArg`](#series-colors) | Black | Series color; theme pairs provide separate light and dark colors. |
|
|
1718
|
+
| [`stroke?`](#strokes) | `StrokeArg` | None | Outline color and optional width. |
|
|
1719
|
+
| [`tooltip?`](#tooltips) | `TooltipArg` | None | Use `true` for the default tooltip, or a callback for custom content. |
|
|
1720
|
+
| [`on_select?`](#selection-callbacks) | `SelectFn` | None | Callback receiving the selected point and its source row. |
|
|
1721
|
+
| `hoverable?` | `boolean` | `true` | Allow the series to participate in hit testing. |
|
|
1722
|
+
| `highlight?` | `boolean` | `true` | Draw hover feedback for a hit point. |
|
|
1723
|
+
|
|
1724
|
+
```ts
|
|
1725
|
+
import { rect } from '@antadesign/plot'
|
|
1726
|
+
|
|
1727
|
+
const series = [rect({
|
|
1728
|
+
data: [
|
|
1729
|
+
{ start: 1, end: 3, low: 2, high: 5 },
|
|
1730
|
+
{ start: 4, end: 6, low: 1, high: 4 },
|
|
1731
|
+
],
|
|
1732
|
+
x: 'start',
|
|
1733
|
+
x2: 'end',
|
|
1734
|
+
y: 'low',
|
|
1735
|
+
y2: 'high',
|
|
1736
|
+
min_size: 2,
|
|
1737
|
+
offset: { x: 0, y: 0 },
|
|
1738
|
+
color: 'lightsteelblue',
|
|
1739
|
+
stroke: { color: 'steelblue', width: 1 },
|
|
1740
|
+
tooltip: ({ x, y }) => `(${x}, ${y})`,
|
|
1741
|
+
on_select: ({ row }) => console.log('Selected row:', row),
|
|
1742
|
+
hoverable: true,
|
|
1743
|
+
highlight: true,
|
|
1744
|
+
})]
|
|
1745
|
+
```
|
|
1746
|
+
For fixed-size marks in categorical bands, use `size` and `band_align`.
|
|
1747
|
+
`inset` controls the space around a band when its size is not fixed:
|
|
1748
|
+
|
|
1749
|
+
```ts
|
|
1750
|
+
import { rect } from '@antadesign/plot'
|
|
1751
|
+
|
|
1752
|
+
const series = [rect({
|
|
1753
|
+
data: [{ group: 'A', row: 'First' }, { group: 'B', row: 'Second' }],
|
|
1754
|
+
x: 'group',
|
|
1755
|
+
y: 'row',
|
|
1756
|
+
size: { width: 12 },
|
|
1757
|
+
band_align: 'start',
|
|
1758
|
+
inset: 4,
|
|
1759
|
+
offset: { x: 2, y: 0 },
|
|
1760
|
+
hover_span_x: true,
|
|
1761
|
+
hover_span_y: true,
|
|
1762
|
+
color: 'steelblue',
|
|
1763
|
+
tooltip: true,
|
|
1764
|
+
})]
|
|
1765
|
+
```
|
|
1766
|
+
|
|
1767
|
+
Use `span` to fill one axis, leaving its coordinates unspecified. Add this
|
|
1768
|
+
series before the data series to draw a background region:
|
|
1769
|
+
|
|
1770
|
+
```ts
|
|
1771
|
+
import { rect } from '@antadesign/plot'
|
|
1772
|
+
|
|
1773
|
+
const series = [rect({
|
|
1774
|
+
data: [{ low: 2, high: 5 }],
|
|
1775
|
+
span: 'x',
|
|
1776
|
+
y: 'low',
|
|
1777
|
+
y2: 'high',
|
|
1778
|
+
color: 'aliceblue',
|
|
1779
|
+
hoverable: false,
|
|
1780
|
+
highlight: false,
|
|
1781
|
+
})]
|
|
1782
|
+
```
|
|
1783
|
+
|
|
1784
|
+
#### Rule
|
|
1785
|
+
|
|
1786
|
+
Draw a reference line across the plot. Supply `x` for a vertical line or
|
|
1787
|
+
`y` for a horizontal line. A single rule needs no data array; add it to the
|
|
1788
|
+
same series array as the data it annotates.
|
|
1789
|
+
|
|
1790
|
+
Horizontal reference values
|
|
1791
|
+
|
|
1792
|
+
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.
|
|
1793
|
+
|
|
1794
|
+
```ts
|
|
1795
|
+
import { rule, line, type APlotElement } from '@antadesign/plot/browser'
|
|
1796
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1797
|
+
|
|
1798
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1799
|
+
|
|
1800
|
+
const data = Array.from({ length: 97 }, (_, i) => ({
|
|
1801
|
+
x: i / 4,
|
|
1802
|
+
y: 44 + 15 * Math.sin(i / 9) + 5 * Math.cos(i * 0.8)
|
|
1803
|
+
+ 38 * Math.exp(-(((i - 62) / 10) ** 2)),
|
|
1804
|
+
}))
|
|
1805
|
+
|
|
1806
|
+
plot.plotArgs = {
|
|
1807
|
+
series: [
|
|
1808
|
+
line<Node>({
|
|
1809
|
+
data,
|
|
1810
|
+
color: { light: '#0891b2', dark: '#67e8f9' },
|
|
1811
|
+
width: 2,
|
|
1812
|
+
}),
|
|
1813
|
+
rule<Node>({
|
|
1814
|
+
y: 60,
|
|
1815
|
+
color: { light: '#7c3aed', dark: '#c4b5fd' },
|
|
1816
|
+
width: 1,
|
|
1817
|
+
dash: [7, 4],
|
|
1818
|
+
tooltip: () => document.createTextNode('Target: 60 ms'),
|
|
1819
|
+
}),
|
|
1820
|
+
rule<Node>({
|
|
1821
|
+
y: 90,
|
|
1822
|
+
color: { light: '#c2410c', dark: '#fdba74' },
|
|
1823
|
+
width: 4,
|
|
1824
|
+
tooltip: () => document.createTextNode('Upper limit: 90 ms'),
|
|
1825
|
+
}),
|
|
1826
|
+
],
|
|
1827
|
+
height: 260,
|
|
1828
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
1829
|
+
axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 110, label: '' } },
|
|
1830
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1831
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1832
|
+
grid: false,
|
|
1833
|
+
border: false,
|
|
1834
|
+
zoom_pan: false,
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
document.body.append(plot)
|
|
1838
|
+
```
|
|
1839
|
+
|
|
1840
|
+
Vertical event markers
|
|
1841
|
+
|
|
1842
|
+
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.
|
|
1843
|
+
|
|
1844
|
+
```ts
|
|
1845
|
+
import { rule, line, type APlotElement } from '@antadesign/plot/browser'
|
|
1846
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1847
|
+
|
|
1848
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1849
|
+
|
|
1850
|
+
const data = Array.from({ length: 97 }, (_, i) => ({
|
|
1851
|
+
x: i / 4,
|
|
1852
|
+
y: 35 + 12 * Math.sin(i / 11) + 4 * Math.cos(i * 0.7)
|
|
1853
|
+
+ (i >= 28 && i < 64 ? 30 : 0),
|
|
1854
|
+
}))
|
|
1855
|
+
const events = [
|
|
1856
|
+
{ hour: 7, name: 'Deploy', recovery: false },
|
|
1857
|
+
{ hour: 16, name: 'Rollback', recovery: true },
|
|
1858
|
+
{ hour: 21, name: 'Verified', recovery: true },
|
|
1859
|
+
]
|
|
1860
|
+
|
|
1861
|
+
plot.plotArgs = {
|
|
1862
|
+
series: [
|
|
1863
|
+
line<Node>({
|
|
1864
|
+
data,
|
|
1865
|
+
color: { light: '#6366f1', dark: '#a5b4fc' },
|
|
1866
|
+
width: 2,
|
|
1867
|
+
}),
|
|
1868
|
+
rule<Node>({
|
|
1869
|
+
data: events.filter(event => !event.recovery),
|
|
1870
|
+
x: 'hour',
|
|
1871
|
+
color: { light: '#c2410c', dark: '#fdba74' },
|
|
1872
|
+
width: 3,
|
|
1873
|
+
tooltip: ({ row }) => document.createTextNode(row?.name + ' at hour ' + row?.hour),
|
|
1874
|
+
}),
|
|
1875
|
+
rule<Node>({
|
|
1876
|
+
data: events.filter(event => event.recovery),
|
|
1877
|
+
x: 'hour',
|
|
1878
|
+
color: { light: '#0d9488', dark: '#5eead4' },
|
|
1879
|
+
width: 2,
|
|
1880
|
+
dash: [5, 4],
|
|
1881
|
+
tooltip: ({ row }) => document.createTextNode(row?.name + ' at hour ' + row?.hour),
|
|
1882
|
+
}),
|
|
1883
|
+
],
|
|
1884
|
+
height: 260,
|
|
1885
|
+
margin: { top: 5, right: 8, bottom: 20, left: 30 },
|
|
1886
|
+
axis: { x: { min: 0, max: 24, label: '' }, y: { min: 0, max: 110, label: '' } },
|
|
1887
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
1888
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
1889
|
+
grid: false,
|
|
1890
|
+
border: false,
|
|
1891
|
+
zoom_pan: false,
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
document.body.append(plot)
|
|
1895
|
+
```
|
|
1896
|
+
|
|
1897
|
+
Rule arguments
|
|
1898
|
+
|
|
1899
|
+
| Field | Type | Default | Description |
|
|
1900
|
+
|---|---|---|---|
|
|
1901
|
+
| `data?` | `Record<string, unknown>[]` | None | Optional rows for multiple reference lines. |
|
|
1902
|
+
| `x?` | [`number \| FieldArg`](#data-fields) | None | Vertical rule position, or a field/accessor when `data` is supplied. Set exactly one of `x` and `y`. |
|
|
1903
|
+
| `y?` | [`number \| FieldArg`](#data-fields) | None | Horizontal rule position, or a field/accessor when `data` is supplied. |
|
|
1904
|
+
| `color?` | [`ColorArg`](#series-colors) | Black | Series color; theme pairs provide separate light and dark colors. |
|
|
1905
|
+
| `width?` | `number` | `1` | Rule width in pixels. |
|
|
1906
|
+
| `dash?` | `number[]` | Solid | Alternating dash and gap lengths in pixels. |
|
|
1907
|
+
| [`tooltip?`](#tooltips) | `TooltipArg` | None | Use `true` for the default tooltip, or a callback for custom content. |
|
|
1908
|
+
| [`on_select?`](#selection-callbacks) | `SelectFn` | None | Callback receiving the selected point and its source row. |
|
|
1909
|
+
| `hoverable?` | `boolean` | `true` | Allow the series to participate in hit testing. |
|
|
1910
|
+
| `highlight?` | `boolean` | `true` | Draw hover feedback for a hit point. |
|
|
1911
|
+
|
|
1912
|
+
```ts
|
|
1913
|
+
import { rule } from '@antadesign/plot'
|
|
1914
|
+
|
|
1915
|
+
const series = [rule({
|
|
1916
|
+
data: [{ threshold: 5 }, { threshold: 8 }],
|
|
1917
|
+
y: 'threshold',
|
|
1918
|
+
color: (row) => Number(row.threshold) >= 8 ? 'tomato' : 'steelblue',
|
|
1919
|
+
width: 2,
|
|
1920
|
+
dash: [6, 4],
|
|
1921
|
+
tooltip: ({ x, y }) => `(${x}, ${y})`,
|
|
1922
|
+
on_select: ({ row }) => console.log('Selected row:', row),
|
|
1923
|
+
hoverable: true,
|
|
1924
|
+
highlight: true,
|
|
1925
|
+
})]
|
|
1926
|
+
```
|
|
1927
|
+
For a single vertical reference line, supply a numeric `x` without `data`:
|
|
1928
|
+
|
|
1929
|
+
```ts
|
|
1930
|
+
import { rule } from '@antadesign/plot'
|
|
1931
|
+
|
|
1932
|
+
const series = [rule({
|
|
1933
|
+
x: 3,
|
|
1934
|
+
color: 'tomato',
|
|
1935
|
+
width: 2,
|
|
1936
|
+
hoverable: false,
|
|
1937
|
+
highlight: false,
|
|
1938
|
+
})]
|
|
1939
|
+
```
|
|
1940
|
+
|
|
1941
|
+
#### Custom
|
|
1942
|
+
|
|
1943
|
+
Provide a [`renderer`](#custom-rendering) to draw directly on the canvas. Its second argument
|
|
1944
|
+
includes the canvas context, plot bounds, scales, and helpers that convert
|
|
1945
|
+
data positions into pixel coordinates.
|
|
1946
|
+
|
|
1947
|
+
Confidence ellipses
|
|
1948
|
+
|
|
1949
|
+
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.
|
|
1950
|
+
|
|
1951
|
+
```ts
|
|
1952
|
+
import { custom, scatter, type APlotElement } from '@antadesign/plot/browser'
|
|
1953
|
+
import '@antadesign/plot/elements/a-plot'
|
|
1954
|
+
|
|
1955
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
1956
|
+
|
|
1957
|
+
const groups = [
|
|
1958
|
+
{ x: 32, y: 40, sx: 10, sy: 5, angle: 0.6 },
|
|
1959
|
+
{ x: 68, y: 62, sx: 8, sy: 5, angle: -0.8 },
|
|
1960
|
+
]
|
|
1961
|
+
const points = groups.flatMap((group, g) => Array.from({ length: 80 }, (_, i) => {
|
|
1962
|
+
const radius = Math.sqrt(-2 * Math.log((i + 0.5) / 80))
|
|
1963
|
+
const theta = i * 2.399963
|
|
1964
|
+
const u = group.sx * radius * Math.cos(theta)
|
|
1965
|
+
const v = group.sy * radius * Math.sin(theta)
|
|
1966
|
+
return {
|
|
1967
|
+
x: group.x + u * Math.cos(group.angle) - v * Math.sin(group.angle),
|
|
1968
|
+
y: group.y + u * Math.sin(group.angle) + v * Math.cos(group.angle),
|
|
1969
|
+
group: g,
|
|
1970
|
+
}
|
|
1971
|
+
}))
|
|
1972
|
+
|
|
1973
|
+
plot.plotArgs = {
|
|
1974
|
+
series: [
|
|
1975
|
+
scatter<Node>({
|
|
1976
|
+
data: points,
|
|
1977
|
+
size: 3,
|
|
1978
|
+
color: row => Number(row.group) === 0
|
|
1979
|
+
? { light: '#0d9488', dark: '#5eead4' }
|
|
1980
|
+
: { light: '#7c3aed', dark: '#c4b5fd' },
|
|
1981
|
+
tooltip: ({ x, y }) => document.createTextNode(Number(x).toFixed(1) + ', ' + Number(y).toFixed(1)),
|
|
1982
|
+
}),
|
|
1983
|
+
custom<Node>({
|
|
1984
|
+
data: groups,
|
|
1985
|
+
x: 'x', y: 'y',
|
|
1986
|
+
color: (_, i) => i === 0
|
|
1987
|
+
? { light: '#0f766e', dark: '#5eead4' }
|
|
1988
|
+
: { light: '#6d28d9', dark: '#c4b5fd' },
|
|
1989
|
+
hoverable: false,
|
|
1990
|
+
renderer: (_, { ctx, x_scale, y_scale, color_at }) => {
|
|
1991
|
+
ctx.save()
|
|
1992
|
+
ctx.lineWidth = 2
|
|
1993
|
+
groups.forEach((group, i) => {
|
|
1994
|
+
ctx.strokeStyle = color_at(i)
|
|
1995
|
+
ctx.beginPath()
|
|
1996
|
+
// The 95% contour of the specified bivariate Gaussian model.
|
|
1997
|
+
for (let step = 0; step <= 100; step++) {
|
|
1998
|
+
const theta = step / 100 * Math.PI * 2
|
|
1999
|
+
const u = Math.sqrt(5.991) * group.sx * Math.cos(theta)
|
|
2000
|
+
const v = Math.sqrt(5.991) * group.sy * Math.sin(theta)
|
|
2001
|
+
const x = Number(x_scale(group.x + u * Math.cos(group.angle) - v * Math.sin(group.angle)))
|
|
2002
|
+
const y = Number(y_scale(group.y + u * Math.sin(group.angle) + v * Math.cos(group.angle)))
|
|
2003
|
+
if (step === 0) ctx.moveTo(x, y)
|
|
2004
|
+
else ctx.lineTo(x, y)
|
|
2005
|
+
}
|
|
2006
|
+
ctx.closePath()
|
|
2007
|
+
ctx.stroke()
|
|
2008
|
+
})
|
|
2009
|
+
ctx.restore()
|
|
2010
|
+
},
|
|
2011
|
+
}),
|
|
2012
|
+
],
|
|
2013
|
+
height: 260,
|
|
2014
|
+
margin: { top: 8, right: 8, bottom: 24, left: 30 },
|
|
2015
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
2016
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2017
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2018
|
+
grid: false,
|
|
2019
|
+
border: false,
|
|
2020
|
+
zoom_pan: false,
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
document.body.append(plot)
|
|
2024
|
+
```
|
|
2025
|
+
|
|
2026
|
+
Chord diagram
|
|
2027
|
+
|
|
2028
|
+
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.
|
|
2029
|
+
|
|
2030
|
+
```ts
|
|
2031
|
+
import { custom, type APlotElement } from '@antadesign/plot/browser'
|
|
2032
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2033
|
+
|
|
2034
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2035
|
+
|
|
2036
|
+
const names = ['A', 'B', 'C', 'D', 'E']
|
|
2037
|
+
const matrix = [
|
|
2038
|
+
[0, 12, 8, 4, 16],
|
|
2039
|
+
[12, 0, 18, 6, 3],
|
|
2040
|
+
[8, 18, 0, 14, 7],
|
|
2041
|
+
[4, 6, 14, 0, 11],
|
|
2042
|
+
[16, 3, 7, 11, 0],
|
|
2043
|
+
]
|
|
2044
|
+
const gap = 0.08
|
|
2045
|
+
const total = matrix.flat().reduce((sum, value) => sum + value, 0)
|
|
2046
|
+
const unit = (Math.PI * 2 - gap * names.length) / total
|
|
2047
|
+
let angle = -Math.PI / 2
|
|
2048
|
+
const groups = matrix.map((row, i) => {
|
|
2049
|
+
const start = angle
|
|
2050
|
+
const segments = row.map(value => {
|
|
2051
|
+
const from = angle
|
|
2052
|
+
angle += value * unit
|
|
2053
|
+
return { start: from, end: angle }
|
|
2054
|
+
})
|
|
2055
|
+
const end = angle
|
|
2056
|
+
angle += gap
|
|
2057
|
+
return { name: names[i], total: row.reduce((sum, value) => sum + value, 0), start, end, segments }
|
|
2058
|
+
})
|
|
2059
|
+
|
|
2060
|
+
plot.plotArgs = {
|
|
2061
|
+
series: [custom<Node>({
|
|
2062
|
+
data: groups,
|
|
2063
|
+
color: (_, i) => [
|
|
2064
|
+
{ light: '#0d9488', dark: '#5eead4' },
|
|
2065
|
+
{ light: '#7c3aed', dark: '#c4b5fd' },
|
|
2066
|
+
{ light: '#db2777', dark: '#f9a8d4' },
|
|
2067
|
+
{ light: '#d97706', dark: '#fcd34d' },
|
|
2068
|
+
{ light: '#2563eb', dark: '#93c5fd' },
|
|
2069
|
+
][i],
|
|
2070
|
+
hit_test: (_, { cursor, inner }) => {
|
|
2071
|
+
const cx = (inner.left + inner.right) / 2
|
|
2072
|
+
const cy = (inner.top + inner.bottom) / 2
|
|
2073
|
+
const radius = Math.min(inner.right - inner.left, inner.bottom - inner.top) / 2 - 32
|
|
2074
|
+
const distance = Math.hypot(cursor.x - cx, cursor.y - cy)
|
|
2075
|
+
if (Math.abs(distance - (radius + 7)) > 8) return null
|
|
2076
|
+
let angle = Math.atan2(cursor.y - cy, cursor.x - cx)
|
|
2077
|
+
if (angle < -Math.PI / 2) angle += Math.PI * 2
|
|
2078
|
+
const index = groups.findIndex(group => angle >= group.start && angle <= group.end)
|
|
2079
|
+
return index < 0 ? null : index
|
|
2080
|
+
},
|
|
2081
|
+
tooltip: ({ row }) => document.createTextNode('Group ' + row?.name + ': ' + row?.total + ' connections'),
|
|
2082
|
+
renderer: (_, { ctx, inner, color_at }) => {
|
|
2083
|
+
const cx = (inner.left + inner.right) / 2
|
|
2084
|
+
const cy = (inner.top + inner.bottom) / 2
|
|
2085
|
+
const radius = Math.min(inner.right - inner.left, inner.bottom - inner.top) / 2 - 32
|
|
2086
|
+
const point = (a: number) => [cx + radius * Math.cos(a), cy + radius * Math.sin(a)]
|
|
2087
|
+
ctx.save()
|
|
2088
|
+
// Allocate ribbon endpoints from the same weights as the outer arcs.
|
|
2089
|
+
groups.forEach((group, i) => {
|
|
2090
|
+
for (let j = i + 1; j < groups.length; j++) {
|
|
2091
|
+
const source = group.segments[j]
|
|
2092
|
+
const target = groups[j].segments[i]
|
|
2093
|
+
const [sx, sy] = point(source.start)
|
|
2094
|
+
const [tx, ty] = point(target.start)
|
|
2095
|
+
ctx.fillStyle = color_at(i)
|
|
2096
|
+
ctx.globalAlpha = 0.45
|
|
2097
|
+
ctx.beginPath()
|
|
2098
|
+
ctx.moveTo(sx, sy)
|
|
2099
|
+
ctx.arc(cx, cy, radius, source.start, source.end)
|
|
2100
|
+
ctx.quadraticCurveTo(cx, cy, tx, ty)
|
|
2101
|
+
ctx.arc(cx, cy, radius, target.start, target.end)
|
|
2102
|
+
ctx.quadraticCurveTo(cx, cy, sx, sy)
|
|
2103
|
+
ctx.closePath()
|
|
2104
|
+
ctx.fill()
|
|
2105
|
+
}
|
|
2106
|
+
})
|
|
2107
|
+
ctx.globalAlpha = 1
|
|
2108
|
+
ctx.font = '12px sans-serif'
|
|
2109
|
+
ctx.textAlign = 'center'
|
|
2110
|
+
ctx.textBaseline = 'middle'
|
|
2111
|
+
groups.forEach((group, i) => {
|
|
2112
|
+
ctx.strokeStyle = color_at(i)
|
|
2113
|
+
ctx.fillStyle = color_at(i)
|
|
2114
|
+
ctx.lineWidth = 9
|
|
2115
|
+
ctx.beginPath()
|
|
2116
|
+
ctx.arc(cx, cy, radius + 7, group.start, group.end)
|
|
2117
|
+
ctx.stroke()
|
|
2118
|
+
const mid = (group.start + group.end) / 2
|
|
2119
|
+
ctx.fillText(group.name, cx + (radius + 22) * Math.cos(mid), cy + (radius + 22) * Math.sin(mid))
|
|
2120
|
+
})
|
|
2121
|
+
ctx.restore()
|
|
2122
|
+
},
|
|
2123
|
+
})],
|
|
2124
|
+
height: 260,
|
|
2125
|
+
margin: 0,
|
|
2126
|
+
axis: { x: { hidden: true, min: 0, max: 1 }, y: { hidden: true, min: 0, max: 1 } },
|
|
2127
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2128
|
+
grid: false,
|
|
2129
|
+
border: false,
|
|
2130
|
+
zoom_pan: false,
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
document.body.append(plot)
|
|
2134
|
+
```
|
|
2135
|
+
|
|
2136
|
+
Ridgeline distributions
|
|
2137
|
+
|
|
2138
|
+
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.
|
|
2139
|
+
|
|
2140
|
+
```ts
|
|
2141
|
+
import { custom, type APlotElement } from '@antadesign/plot/browser'
|
|
2142
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2143
|
+
|
|
2144
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2145
|
+
|
|
2146
|
+
// Test the same closed polygon used for drawing, including the spaces between sampled points.
|
|
2147
|
+
function containsPoint(points: number[][], x: number, y: number): boolean {
|
|
2148
|
+
let inside = false
|
|
2149
|
+
for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
|
|
2150
|
+
const [xi, yi] = points[i]
|
|
2151
|
+
const [xj, yj] = points[j]
|
|
2152
|
+
if ((yi > y) !== (yj > y) && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside
|
|
2153
|
+
}
|
|
2154
|
+
return inside
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
const data = Array.from({ length: 6 }, (_, i) => {
|
|
2158
|
+
const values = Array.from({ length: 80 }, (_, j) => {
|
|
2159
|
+
const z = Math.sqrt(-2 * Math.log((j + 0.5) / 80)) * Math.cos(j * 2.399963)
|
|
2160
|
+
return 25 + i * 8 + z * [8, 5, 11, 4, 9, 6][i] + (j % 3 === 0 ? 14 : 0)
|
|
2161
|
+
})
|
|
2162
|
+
return {
|
|
2163
|
+
group: 'Week ' + (i + 1),
|
|
2164
|
+
mean: values.reduce((sum, value) => sum + value, 0) / values.length,
|
|
2165
|
+
density: Array.from({ length: 101 }, (_, value) => ({
|
|
2166
|
+
value,
|
|
2167
|
+
density: values.reduce((sum, sample) => sum + Math.exp(-0.5 * ((value - sample) / 3) ** 2), 0)
|
|
2168
|
+
/ (values.length * 3 * Math.sqrt(2 * Math.PI)),
|
|
2169
|
+
})),
|
|
2170
|
+
}
|
|
2171
|
+
})
|
|
2172
|
+
const peak = Math.max(...data.flatMap(row => row.density.map(point => point.density)))
|
|
2173
|
+
|
|
2174
|
+
plot.plotArgs = {
|
|
2175
|
+
series: [custom<Node>({
|
|
2176
|
+
data,
|
|
2177
|
+
y: 'group',
|
|
2178
|
+
color: (_, i) => ({ light: 'hsl(' + (175 + i * 15) + ', 55%, 55%)', dark: 'hsl(' + (175 + i * 15) + ', 50%, 45%)' }),
|
|
2179
|
+
hit_test: (_, { cursor, x_scale, resolve_y, inner }) => {
|
|
2180
|
+
const step = (inner.bottom - inner.top) / data.length
|
|
2181
|
+
// Reverse paint order selects the foreground ridge where shapes overlap.
|
|
2182
|
+
for (let i = data.length - 1; i >= 0; i--) {
|
|
2183
|
+
const center = resolve_y(i)
|
|
2184
|
+
if (center === undefined) continue
|
|
2185
|
+
const baseline = center + step * 0.45
|
|
2186
|
+
const points = [
|
|
2187
|
+
[Number(x_scale(0)), baseline],
|
|
2188
|
+
...data[i].density.map(point => [
|
|
2189
|
+
Number(x_scale(point.value)), baseline - point.density / peak * step * 1.9,
|
|
2190
|
+
]),
|
|
2191
|
+
[Number(x_scale(100)), baseline],
|
|
2192
|
+
]
|
|
2193
|
+
if (containsPoint(points, cursor.x, cursor.y)) return i
|
|
2194
|
+
}
|
|
2195
|
+
return null
|
|
2196
|
+
},
|
|
2197
|
+
tooltip: ({ row }) => document.createTextNode(row?.group + ' · 80 samples · mean ' + Number(row?.mean).toFixed(1)),
|
|
2198
|
+
renderer: (_, { ctx, x_scale, resolve_y, color_at, inner }) => {
|
|
2199
|
+
const step = (inner.bottom - inner.top) / data.length
|
|
2200
|
+
ctx.save()
|
|
2201
|
+
// Paint from the top row down, so foreground ridges cover the preceding tails.
|
|
2202
|
+
data.forEach((row, i) => {
|
|
2203
|
+
const center = resolve_y(i)
|
|
2204
|
+
if (center === undefined) return
|
|
2205
|
+
const baseline = center + step * 0.45
|
|
2206
|
+
ctx.fillStyle = color_at(i)
|
|
2207
|
+
ctx.strokeStyle = '#334155'
|
|
2208
|
+
ctx.lineWidth = 1
|
|
2209
|
+
ctx.beginPath()
|
|
2210
|
+
ctx.moveTo(Number(x_scale(0)), baseline)
|
|
2211
|
+
row.density.forEach(point => {
|
|
2212
|
+
ctx.lineTo(Number(x_scale(point.value)), baseline - point.density / peak * step * 1.9)
|
|
2213
|
+
})
|
|
2214
|
+
ctx.lineTo(Number(x_scale(100)), baseline)
|
|
2215
|
+
ctx.closePath()
|
|
2216
|
+
ctx.globalAlpha = [1, 0.7, 0.85, 0.65, 0.8, 0.7][i]
|
|
2217
|
+
ctx.fill()
|
|
2218
|
+
ctx.globalAlpha = 1
|
|
2219
|
+
ctx.stroke()
|
|
2220
|
+
})
|
|
2221
|
+
ctx.restore()
|
|
2222
|
+
},
|
|
2223
|
+
})],
|
|
2224
|
+
height: 260,
|
|
2225
|
+
margin: { top: 24, right: 8, bottom: 24, left: 52 },
|
|
2226
|
+
axis: {
|
|
2227
|
+
x: { min: 0, max: 100, label: '' },
|
|
2228
|
+
y: { categories: data.map(row => row.group).reverse(), padding_top: 28, label: '' },
|
|
2229
|
+
},
|
|
2230
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2231
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2232
|
+
grid: false,
|
|
2233
|
+
border: false,
|
|
2234
|
+
zoom_pan: false,
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
document.body.append(plot)
|
|
2238
|
+
```
|
|
2239
|
+
|
|
2240
|
+
Violin distributions
|
|
2241
|
+
|
|
2242
|
+
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.
|
|
2243
|
+
|
|
2244
|
+
```ts
|
|
2245
|
+
import { custom, type APlotElement } from '@antadesign/plot/browser'
|
|
2246
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2247
|
+
|
|
2248
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2249
|
+
|
|
2250
|
+
// Deterministic samples and a Gaussian kernel density estimate, used by the violin example.
|
|
2251
|
+
function distributions() {
|
|
2252
|
+
return ['A', 'B', 'C'].map((group, g) => {
|
|
2253
|
+
const values = Array.from({ length: 60 }, (_, i) => {
|
|
2254
|
+
const z = Math.sqrt(-2 * Math.log((i + 0.5) / 60)) * Math.cos(i * 2.399963)
|
|
2255
|
+
return 36 + g * 10 + z * (6 + g * 2) + (g === 2 && i % 2 === 0 ? 18 : 0)
|
|
2256
|
+
}).sort((a, b) => a - b)
|
|
2257
|
+
const density = Array.from({ length: 101 }, (_, value) => ({
|
|
2258
|
+
value,
|
|
2259
|
+
density: values.reduce((sum, sample) => sum + Math.exp(-0.5 * ((value - sample) / 4) ** 2), 0)
|
|
2260
|
+
/ (values.length * 4 * Math.sqrt(2 * Math.PI)),
|
|
2261
|
+
}))
|
|
2262
|
+
return { group, values, density, q1: values[15], median: (values[29] + values[30]) / 2, q3: values[44] }
|
|
2263
|
+
})
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
// Test the same closed polygon used for drawing, including the spaces between sampled points.
|
|
2267
|
+
function containsPoint(points: number[][], x: number, y: number): boolean {
|
|
2268
|
+
let inside = false
|
|
2269
|
+
for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
|
|
2270
|
+
const [xi, yi] = points[i]
|
|
2271
|
+
const [xj, yj] = points[j]
|
|
2272
|
+
if ((yi > y) !== (yj > y) && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside
|
|
2273
|
+
}
|
|
2274
|
+
return inside
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
const data = distributions()
|
|
2278
|
+
const peak = Math.max(...data.flatMap(row => row.density.map(point => point.density)))
|
|
2279
|
+
|
|
2280
|
+
plot.plotArgs = {
|
|
2281
|
+
series: [custom<Node>({
|
|
2282
|
+
data,
|
|
2283
|
+
x: 'group',
|
|
2284
|
+
color: (_, i) => [
|
|
2285
|
+
{ light: '#0d9488', dark: '#5eead4' },
|
|
2286
|
+
{ light: '#7c3aed', dark: '#c4b5fd' },
|
|
2287
|
+
{ light: '#c2410c', dark: '#fdba74' },
|
|
2288
|
+
][i],
|
|
2289
|
+
hit_test: (_, { cursor, y_scale, resolve_x, inner }) => {
|
|
2290
|
+
const width = Math.min(34, (inner.right - inner.left) / 9)
|
|
2291
|
+
for (let i = data.length - 1; i >= 0; i--) {
|
|
2292
|
+
const x = resolve_x(i)
|
|
2293
|
+
if (x === undefined) continue
|
|
2294
|
+
const points = [
|
|
2295
|
+
...data[i].density.map(point => [x + point.density / peak * width, Number(y_scale(point.value))]),
|
|
2296
|
+
...data[i].density.slice().reverse().map(point => [x - point.density / peak * width, Number(y_scale(point.value))]),
|
|
2297
|
+
]
|
|
2298
|
+
if (containsPoint(points, cursor.x, cursor.y)) return i
|
|
2299
|
+
}
|
|
2300
|
+
return null
|
|
2301
|
+
},
|
|
2302
|
+
tooltip: ({ row }) => document.createTextNode('Group ' + row?.group
|
|
2303
|
+
+ ' · median ' + Number(row?.median).toFixed(1)
|
|
2304
|
+
+ ' · middle 50%: ' + Number(row?.q1).toFixed(1) + '–' + Number(row?.q3).toFixed(1)),
|
|
2305
|
+
renderer: (_, { ctx, y_scale, resolve_x, color_at, inner }) => {
|
|
2306
|
+
const width = Math.min(34, (inner.right - inner.left) / 9)
|
|
2307
|
+
ctx.save()
|
|
2308
|
+
data.forEach((row, i) => {
|
|
2309
|
+
const x = resolve_x(i)
|
|
2310
|
+
if (x === undefined) return
|
|
2311
|
+
ctx.fillStyle = color_at(i)
|
|
2312
|
+
ctx.beginPath()
|
|
2313
|
+
row.density.forEach((point, j) => {
|
|
2314
|
+
const px = x + point.density / peak * width
|
|
2315
|
+
const py = Number(y_scale(point.value))
|
|
2316
|
+
if (j === 0) ctx.moveTo(px, py)
|
|
2317
|
+
else ctx.lineTo(px, py)
|
|
2318
|
+
})
|
|
2319
|
+
row.density.slice().reverse().forEach(point => {
|
|
2320
|
+
ctx.lineTo(x - point.density / peak * width, Number(y_scale(point.value)))
|
|
2321
|
+
})
|
|
2322
|
+
ctx.closePath()
|
|
2323
|
+
ctx.fill()
|
|
2324
|
+
ctx.strokeStyle = '#151b28'
|
|
2325
|
+
ctx.lineWidth = 4
|
|
2326
|
+
ctx.beginPath()
|
|
2327
|
+
ctx.moveTo(x, Number(y_scale(row.q1)))
|
|
2328
|
+
ctx.lineTo(x, Number(y_scale(row.q3)))
|
|
2329
|
+
ctx.stroke()
|
|
2330
|
+
ctx.fillStyle = '#ffffff'
|
|
2331
|
+
ctx.beginPath()
|
|
2332
|
+
ctx.arc(x, Number(y_scale(row.median)), 3, 0, Math.PI * 2)
|
|
2333
|
+
ctx.fill()
|
|
2334
|
+
})
|
|
2335
|
+
ctx.restore()
|
|
2336
|
+
},
|
|
2337
|
+
})],
|
|
2338
|
+
height: 260,
|
|
2339
|
+
margin: { top: 8, right: 8, bottom: 24, left: 30 },
|
|
2340
|
+
axis: { x: { categories: ['A', 'B', 'C'], label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
2341
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2342
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2343
|
+
grid: false,
|
|
2344
|
+
border: false,
|
|
2345
|
+
zoom_pan: false,
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
document.body.append(plot)
|
|
2349
|
+
```
|
|
2350
|
+
|
|
2351
|
+
Custom arguments
|
|
2352
|
+
|
|
2353
|
+
| Field | Type | Default | Description |
|
|
2354
|
+
|---|---|---|---|
|
|
2355
|
+
| `data?` | `Record<string, unknown>[]` | None | Optional rows; required when using field selectors or hit testing. |
|
|
2356
|
+
| `x?` | [`FieldArg`](#data-fields) | Unclaimed | Horizontal field or accessor; omitted axes do not contribute data bounds. |
|
|
2357
|
+
| `y?` | [`FieldArg`](#data-fields) | Unclaimed | Vertical field or accessor; omitted axes do not contribute data bounds. |
|
|
2358
|
+
| [`renderer`](#custom-rendering) | `CustomRendererFn` | Required | Draw the series using its data and the supplied canvas context and pixel resolvers. |
|
|
2359
|
+
| [`hit_test?`](#custom-hit-testing) | `CustomHitTestFn` | None | Return a row index for a hit, or `null`. Required for custom tooltips and selection. |
|
|
2360
|
+
| `color?` | [`ColorArg`](#series-colors) | Black | Color made available to the renderer, including optional per-row colors. |
|
|
2361
|
+
| `axis_range?` | `{ x?: number[]; y?: number[] }` | Data extent | Explicit `[low, high]` bounds for either axis. |
|
|
2362
|
+
| [`tooltip?`](#tooltips) | `TooltipArg` | None | Use `true` for the default tooltip, or a callback for custom content. |
|
|
2363
|
+
| [`on_select?`](#selection-callbacks) | `SelectFn` | None | Callback receiving the selected point and its source row. |
|
|
2364
|
+
| `hoverable?` | `boolean` | `true` | Allow the series to participate in hit testing. |
|
|
2365
|
+
|
|
2366
|
+
This example draws a cross at each point and finds the nearest cross within
|
|
2367
|
+
eight pixels for tooltips and selection:
|
|
2368
|
+
|
|
2369
|
+
```ts
|
|
2370
|
+
import { custom } from '@antadesign/plot'
|
|
2371
|
+
|
|
2372
|
+
const series = [custom({
|
|
2373
|
+
data: [{ x: 1, y: 3 }, { x: 2, y: 5 }, { x: 3, y: 4 }],
|
|
2374
|
+
x: 'x',
|
|
2375
|
+
y: 'y',
|
|
2376
|
+
color: { light: 'steelblue', dark: 'lightskyblue' },
|
|
2377
|
+
axis_range: { x: [0, 4], y: [0, 6] },
|
|
2378
|
+
renderer: (series, { ctx, resolve_x, resolve_y, color_at }) => {
|
|
2379
|
+
ctx.save()
|
|
2380
|
+
ctx.lineWidth = 2
|
|
2381
|
+
for (let i = 0; i < series.x.length; i++) {
|
|
2382
|
+
const x = resolve_x(i)
|
|
2383
|
+
const y = resolve_y(i)
|
|
2384
|
+
if (x === undefined || y === undefined) continue
|
|
2385
|
+
ctx.strokeStyle = color_at(i)
|
|
2386
|
+
ctx.beginPath()
|
|
2387
|
+
ctx.moveTo(x - 4, y)
|
|
2388
|
+
ctx.lineTo(x + 4, y)
|
|
2389
|
+
ctx.moveTo(x, y - 4)
|
|
2390
|
+
ctx.lineTo(x, y + 4)
|
|
2391
|
+
ctx.stroke()
|
|
2392
|
+
}
|
|
2393
|
+
ctx.restore()
|
|
2394
|
+
},
|
|
2395
|
+
hit_test: (series, { cursor, resolve_x, resolve_y }) => {
|
|
2396
|
+
let nearest: number | null = null
|
|
2397
|
+
let distance = 8
|
|
2398
|
+
for (let i = 0; i < series.x.length; i++) {
|
|
2399
|
+
const x = resolve_x(i)
|
|
2400
|
+
const y = resolve_y(i)
|
|
2401
|
+
if (x === undefined || y === undefined) continue
|
|
2402
|
+
const candidate = Math.hypot(cursor.x - x, cursor.y - y)
|
|
2403
|
+
if (candidate <= distance) {
|
|
2404
|
+
nearest = i
|
|
2405
|
+
distance = candidate
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
return nearest
|
|
2409
|
+
},
|
|
2410
|
+
tooltip: ({ x, y }) => `(${x}, ${y})`,
|
|
2411
|
+
on_select: ({ row }) => console.log('Selected row:', row),
|
|
2412
|
+
hoverable: true,
|
|
2413
|
+
})]
|
|
2414
|
+
```
|
|
2415
|
+
|
|
2416
|
+
For tooltips or selection, also provide `data` and a [`hit_test`](#custom-hit-testing) that returns
|
|
2417
|
+
the matching row index, or `null` when there is no hit. Use `axis_range`
|
|
2418
|
+
to declare bounds when they cannot be inferred from the data.
|
|
2419
|
+
|
|
2420
|
+
## Interactions
|
|
2421
|
+
|
|
2422
|
+
### Hover highlights
|
|
2423
|
+
|
|
2424
|
+
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.
|
|
2425
|
+
|
|
2426
|
+
Hover feedback is temporary. For custom geometry, supply a [hit-testing callback](#custom-hit-testing).
|
|
2427
|
+
|
|
2428
|
+
```ts
|
|
2429
|
+
import { bar, type APlotElement } from '@antadesign/plot/browser'
|
|
2430
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2431
|
+
|
|
2432
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2433
|
+
|
|
2434
|
+
const data = [
|
|
2435
|
+
{ x: 'A', y: 64 }, { x: 'B', y: 82 },
|
|
2436
|
+
{ x: 'C', y: 53 }, { x: 'D', y: 71 },
|
|
2437
|
+
{ x: 'E', y: 44 }, { x: 'F', y: 61 },
|
|
2438
|
+
]
|
|
2439
|
+
|
|
2440
|
+
plot.plotArgs = {
|
|
2441
|
+
series: [
|
|
2442
|
+
bar<Node>({
|
|
2443
|
+
data: data.slice(0, 4), inset: 7, border_radius: 4,
|
|
2444
|
+
color: { light: 'rgba(13, 148, 136, 0.35)', dark: 'rgba(94, 234, 212, 0.35)' },
|
|
2445
|
+
}),
|
|
2446
|
+
bar<Node>({
|
|
2447
|
+
data: data.slice(4, 5), inset: 7, border_radius: 4,
|
|
2448
|
+
color: { light: '#7c3aed', dark: '#c4b5fd' },
|
|
2449
|
+
highlight: false,
|
|
2450
|
+
}),
|
|
2451
|
+
bar<Node>({
|
|
2452
|
+
data: data.slice(5), inset: 7, border_radius: 4,
|
|
2453
|
+
color: { light: '#94a3b8', dark: '#64748b' },
|
|
2454
|
+
hoverable: false,
|
|
2455
|
+
}),
|
|
2456
|
+
],
|
|
2457
|
+
height: 260,
|
|
2458
|
+
margin: { top: 8, right: 12, bottom: 24, left: 32 },
|
|
2459
|
+
axis: { x: { categories: data.map(row => row.x), label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
2460
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2461
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2462
|
+
grid: false,
|
|
2463
|
+
border: false,
|
|
2464
|
+
zoom_pan: false,
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
document.body.append(plot)
|
|
2468
|
+
```
|
|
2469
|
+
|
|
2470
|
+
#### Custom hit testing
|
|
2471
|
+
|
|
2472
|
+
`CustomHitTestFn<TooltipContent>` receives the composed custom series and a
|
|
2473
|
+
`HitContext`. Return the matching row index or `null` when there is no hit.
|
|
2474
|
+
Custom series need this callback for tooltips and selection.
|
|
2475
|
+
|
|
2476
|
+
```ts
|
|
2477
|
+
import type { ComposedCustom, HitContext } from '@antadesign/plot'
|
|
2478
|
+
|
|
2479
|
+
type HitTest = (series: ComposedCustom, context: HitContext) => number | null
|
|
2480
|
+
```
|
|
2481
|
+
|
|
2482
|
+
| Context field | Type | Description |
|
|
2483
|
+
|---|---|---|
|
|
2484
|
+
| `cursor` | `{ x: number; y: number }` | Pointer position in the same pixel coordinates as the resolvers. |
|
|
2485
|
+
| `inner` | [`Rect`](#custom-rendering) | Plot bounds in pixels. |
|
|
2486
|
+
| `x_scale` | [`Scale`](#custom-rendering) | Composed horizontal scale. |
|
|
2487
|
+
| `y_scale` | [`Scale`](#custom-rendering) | Composed vertical scale. |
|
|
2488
|
+
| `resolve_x` | [`PixelResolver`](#custom-rendering) | Resolve a row index to its x position. |
|
|
2489
|
+
| `resolve_y` | [`PixelResolver`](#custom-rendering) | Resolve a row index to its y position. |
|
|
2490
|
+
|
|
2491
|
+
### Tooltips
|
|
2492
|
+
|
|
2493
|
+
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.
|
|
2494
|
+
|
|
2495
|
+
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](#tooltip-and-selection-callback-parameters). Omit `tooltip` to keep hover feedback without a tooltip. See [Tooltip content](#tooltip-content) for the callback types.
|
|
2496
|
+
|
|
2497
|
+
```ts
|
|
2498
|
+
import { rect, type APlotElement } from '@antadesign/plot/browser'
|
|
2499
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2500
|
+
|
|
2501
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2502
|
+
|
|
2503
|
+
const data = [
|
|
2504
|
+
{ x: 8, x2: 38, y: 15, y2: 48, name: 'Window A' },
|
|
2505
|
+
{ x: 55, x2: 86, y: 48, y2: 82, name: 'Window B' },
|
|
2506
|
+
]
|
|
2507
|
+
|
|
2508
|
+
plot.plotArgs = {
|
|
2509
|
+
series: [
|
|
2510
|
+
rect<Node>({
|
|
2511
|
+
data,
|
|
2512
|
+
color: { light: 'rgba(13, 148, 136, 0.45)', dark: 'rgba(94, 234, 212, 0.45)' },
|
|
2513
|
+
stroke: { color: { light: '#0d9488', dark: '#5eead4' }, width: 2 },
|
|
2514
|
+
tooltip: true,
|
|
2515
|
+
}),
|
|
2516
|
+
rect<Node>({
|
|
2517
|
+
data: data.map(row => ({
|
|
2518
|
+
...row, x: row.x + 12, x2: row.x2 + 8, y: row.y + 12, y2: row.y2 + 10,
|
|
2519
|
+
})),
|
|
2520
|
+
color: { light: 'rgba(124, 58, 237, 0.45)', dark: 'rgba(196, 181, 253, 0.45)' },
|
|
2521
|
+
stroke: { color: { light: '#7c3aed', dark: '#c4b5fd' }, width: 2 },
|
|
2522
|
+
tooltip: ({ row }) => {
|
|
2523
|
+
const content = document.createElement('div')
|
|
2524
|
+
const title = document.createElement('strong')
|
|
2525
|
+
title.textContent = String(row.name)
|
|
2526
|
+
const value = document.createElement('div')
|
|
2527
|
+
value.textContent = 'Bounds: x ' + row.x + '–' + row.x2 + ', y ' + row.y + '–' + row.y2
|
|
2528
|
+
content.append(title, value)
|
|
2529
|
+
return content
|
|
2530
|
+
},
|
|
2531
|
+
}),
|
|
2532
|
+
],
|
|
2533
|
+
height: 260,
|
|
2534
|
+
margin: { top: 8, right: 12, bottom: 24, left: 32 },
|
|
2535
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
2536
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2537
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2538
|
+
grid: false,
|
|
2539
|
+
border: false,
|
|
2540
|
+
zoom_pan: false,
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
document.body.append(plot)
|
|
2544
|
+
```
|
|
2545
|
+
|
|
2546
|
+
#### Tooltip content
|
|
2547
|
+
|
|
2548
|
+
`TooltipArg<Row, TooltipContent>` accepts `true` or a `TooltipFn` callback.
|
|
2549
|
+
Omit `tooltip` to show no tooltip for that series.
|
|
2550
|
+
|
|
2551
|
+
| Option | Type | Description |
|
|
2552
|
+
|---|---|---|
|
|
2553
|
+
| Default tooltip | `true` | Enable the host's default presentation of the point's axis values. Omit `tooltip` to disable it; `false` is not accepted. |
|
|
2554
|
+
| Custom tooltip | [`(data: TooltipData<Row>) => TooltipContent`](#tooltip-and-selection-callback-parameters) | Return content for the hovered point. |
|
|
2555
|
+
|
|
2556
|
+
With the React component, `TooltipContent` is a `ReactNode`, including strings
|
|
2557
|
+
and JSX. With the standalone browser host, return a DOM `Node`. The core
|
|
2558
|
+
leaves the content type to the host.
|
|
2559
|
+
|
|
2560
|
+
### Selection
|
|
2561
|
+
|
|
2562
|
+
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.
|
|
2563
|
+
|
|
2564
|
+
The callback does not create persistent selection styling. This preview keeps the last selected value in a text readout. See [Selection callbacks](#selection-callbacks) for the payload.
|
|
2565
|
+
|
|
2566
|
+
```ts
|
|
2567
|
+
import { scatter, type APlotElement } from '@antadesign/plot/browser'
|
|
2568
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2569
|
+
|
|
2570
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2571
|
+
const output = document.createElement('output')
|
|
2572
|
+
output.setAttribute('aria-live', 'polite')
|
|
2573
|
+
output.style.display = 'block'
|
|
2574
|
+
output.textContent = 'Select a point to inspect its data.'
|
|
2575
|
+
|
|
2576
|
+
const data = Array.from({ length: 24 }, (_, i) => ({
|
|
2577
|
+
x: 8 + (i * 19 % 84),
|
|
2578
|
+
y: 12 + (i * 29 % 76),
|
|
2579
|
+
name: 'Sample ' + (i + 1),
|
|
2580
|
+
}))
|
|
2581
|
+
|
|
2582
|
+
plot.plotArgs = {
|
|
2583
|
+
series: [
|
|
2584
|
+
scatter<Node>({
|
|
2585
|
+
data, size: 13,
|
|
2586
|
+
color: { light: '#0891b2', dark: '#67e8f9' },
|
|
2587
|
+
tooltip: true,
|
|
2588
|
+
on_select: ({ row, x, y }) => {
|
|
2589
|
+
output.textContent = row.name + ' · x: ' + x + ' · y: ' + y
|
|
2590
|
+
},
|
|
2591
|
+
}),
|
|
2592
|
+
],
|
|
2593
|
+
height: 260,
|
|
2594
|
+
margin: { top: 8, right: 12, bottom: 24, left: 32 },
|
|
2595
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
2596
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2597
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2598
|
+
grid: false,
|
|
2599
|
+
border: false,
|
|
2600
|
+
zoom_pan: false,
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
document.body.append(plot, output)
|
|
2604
|
+
```
|
|
2605
|
+
|
|
2606
|
+
#### Selection callbacks
|
|
2607
|
+
|
|
2608
|
+
`SelectFn<Row>` is [`(data: TooltipData<Row>) => unknown`](#tooltip-and-selection-callback-parameters). Assign it to
|
|
2609
|
+
`on_select` to respond when a point is selected. Its return value is ignored.
|
|
2610
|
+
|
|
2611
|
+
```ts
|
|
2612
|
+
import type { SelectFn } from '@antadesign/plot'
|
|
2613
|
+
|
|
2614
|
+
const on_select: SelectFn = ({ x, y, row }) => {
|
|
2615
|
+
console.log('Selected point:', x, y, row)
|
|
2616
|
+
}
|
|
2617
|
+
```
|
|
2618
|
+
|
|
2619
|
+
##### Tooltip and selection callback parameters
|
|
2620
|
+
|
|
2621
|
+
Tooltip and selection callbacks receive the same `TooltipData<Row>` shape:
|
|
2622
|
+
|
|
2623
|
+
| Field | Type | Description |
|
|
2624
|
+
|---|---|---|
|
|
2625
|
+
| `x` | `number \| string` | Point's x value, or category label. |
|
|
2626
|
+
| `y` | `number \| string` | Point's y value, or category label. |
|
|
2627
|
+
| `row` | `Row` | Source data row. May be `undefined` for rule or custom series without data. |
|
|
2628
|
+
| `label?` | `string` | Point label when available, such as the field name of a stacked bar segment. |
|
|
2629
|
+
|
|
2630
|
+
`Row` defaults to `Record<string, unknown>`. The related `PointData` type has
|
|
2631
|
+
the same fields with an optional `row`.
|
|
2632
|
+
|
|
2633
|
+
### Zoom and pan
|
|
2634
|
+
|
|
2635
|
+
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-and-pan-options).
|
|
2636
|
+
|
|
2637
|
+
Zoom with a modifier
|
|
2638
|
+
|
|
2639
|
+
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.
|
|
2640
|
+
|
|
2641
|
+
```ts
|
|
2642
|
+
import { line, type APlotElement } from '@antadesign/plot/browser'
|
|
2643
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2644
|
+
|
|
2645
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2646
|
+
const output = document.createElement('output')
|
|
2647
|
+
output.setAttribute('aria-live', 'polite')
|
|
2648
|
+
output.style.display = 'block'
|
|
2649
|
+
output.textContent = 'Hold Ctrl and scroll to zoom, or Ctrl-drag to pan.'
|
|
2650
|
+
|
|
2651
|
+
const data = Array.from({ length: 201 }, (_, i) => ({
|
|
2652
|
+
x: i / 2,
|
|
2653
|
+
y: 45 + 18 * Math.sin(i / 18) + 9 * Math.cos(i / 5) + 4 * Math.sin(i * 1.3),
|
|
2654
|
+
}))
|
|
2655
|
+
|
|
2656
|
+
plot.plotArgs = {
|
|
2657
|
+
series: [
|
|
2658
|
+
line<Node>({
|
|
2659
|
+
data, width: 2,
|
|
2660
|
+
color: { light: '#6366f1', dark: '#a5b4fc' },
|
|
2661
|
+
tooltip: true,
|
|
2662
|
+
}),
|
|
2663
|
+
],
|
|
2664
|
+
height: 260,
|
|
2665
|
+
margin: { top: 8, right: 12, bottom: 24, left: 32 },
|
|
2666
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
2667
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2668
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2669
|
+
grid: false,
|
|
2670
|
+
border: false,
|
|
2671
|
+
zoom_pan: { x: true, y: true, modifier: true },
|
|
2672
|
+
on_viewport_change: ({ x, y }) => {
|
|
2673
|
+
const format = (axis: { window: number[] } | null) => axis
|
|
2674
|
+
? axis.window.map(value => value.toFixed(1)).join('–') : 'full extent'
|
|
2675
|
+
output.textContent = 'Visible x: ' + format(x) + ' · y: ' + format(y)
|
|
2676
|
+
},
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
document.body.append(plot, output)
|
|
2680
|
+
```
|
|
2681
|
+
|
|
2682
|
+
Zoom without a modifier
|
|
2683
|
+
|
|
2684
|
+
Scroll over the plot to zoom, or drag to pan. Set `modifier: false` when the plot should own these gestures without requiring Ctrl.
|
|
2685
|
+
|
|
2686
|
+
```ts
|
|
2687
|
+
import { line, type APlotElement } from '@antadesign/plot/browser'
|
|
2688
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2689
|
+
|
|
2690
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2691
|
+
const output = document.createElement('output')
|
|
2692
|
+
output.setAttribute('aria-live', 'polite')
|
|
2693
|
+
output.style.display = 'block'
|
|
2694
|
+
output.textContent = 'Scroll to zoom, or drag to pan.'
|
|
2695
|
+
|
|
2696
|
+
const data = Array.from({ length: 201 }, (_, i) => ({
|
|
2697
|
+
x: i / 2,
|
|
2698
|
+
y: 45 + 18 * Math.sin(i / 18) + 9 * Math.cos(i / 5) + 4 * Math.sin(i * 1.3),
|
|
2699
|
+
}))
|
|
2700
|
+
|
|
2701
|
+
plot.plotArgs = {
|
|
2702
|
+
series: [
|
|
2703
|
+
line<Node>({
|
|
2704
|
+
data, width: 2,
|
|
2705
|
+
color: { light: '#6366f1', dark: '#a5b4fc' },
|
|
2706
|
+
tooltip: true,
|
|
2707
|
+
}),
|
|
2708
|
+
],
|
|
2709
|
+
height: 260,
|
|
2710
|
+
margin: { top: 8, right: 12, bottom: 24, left: 32 },
|
|
2711
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
2712
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2713
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2714
|
+
grid: false,
|
|
2715
|
+
border: false,
|
|
2716
|
+
zoom_pan: { x: true, y: true, modifier: false },
|
|
2717
|
+
on_viewport_change: ({ x, y }) => {
|
|
2718
|
+
const format = (axis: { window: number[] } | null) => axis
|
|
2719
|
+
? axis.window.map(value => value.toFixed(1)).join('–') : 'full extent'
|
|
2720
|
+
output.textContent = 'Visible x: ' + format(x) + ' · y: ' + format(y)
|
|
2721
|
+
},
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
document.body.append(plot, output)
|
|
2725
|
+
```
|
|
2726
|
+
|
|
2727
|
+
Zoom on one axis
|
|
2728
|
+
|
|
2729
|
+
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.
|
|
2730
|
+
|
|
2731
|
+
```ts
|
|
2732
|
+
import { line, type APlotElement } from '@antadesign/plot/browser'
|
|
2733
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2734
|
+
|
|
2735
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2736
|
+
const output = document.createElement('output')
|
|
2737
|
+
output.setAttribute('aria-live', 'polite')
|
|
2738
|
+
output.style.display = 'block'
|
|
2739
|
+
output.textContent = 'Hold Ctrl and scroll to zoom, or Ctrl-drag to pan.'
|
|
2740
|
+
|
|
2741
|
+
const data = Array.from({ length: 201 }, (_, i) => ({
|
|
2742
|
+
x: i / 2,
|
|
2743
|
+
y: 45 + 18 * Math.sin(i / 18) + 9 * Math.cos(i / 5) + 4 * Math.sin(i * 1.3),
|
|
2744
|
+
}))
|
|
2745
|
+
|
|
2746
|
+
plot.plotArgs = {
|
|
2747
|
+
series: [
|
|
2748
|
+
line<Node>({
|
|
2749
|
+
data, width: 2,
|
|
2750
|
+
color: { light: '#6366f1', dark: '#a5b4fc' },
|
|
2751
|
+
tooltip: true,
|
|
2752
|
+
}),
|
|
2753
|
+
],
|
|
2754
|
+
height: 260,
|
|
2755
|
+
margin: { top: 8, right: 12, bottom: 24, left: 32 },
|
|
2756
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
2757
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2758
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2759
|
+
grid: false,
|
|
2760
|
+
border: false,
|
|
2761
|
+
zoom_pan: { x: true, y: false, modifier: true },
|
|
2762
|
+
on_viewport_change: ({ x, y }) => {
|
|
2763
|
+
const format = (axis: { window: number[] } | null) => axis
|
|
2764
|
+
? axis.window.map(value => value.toFixed(1)).join('–') : 'full extent'
|
|
2765
|
+
output.textContent = 'Visible x: ' + format(x) + ' · y: ' + format(y)
|
|
2766
|
+
},
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
document.body.append(plot, output)
|
|
2770
|
+
```
|
|
2771
|
+
|
|
2772
|
+
#### Zoom and pan options
|
|
2773
|
+
|
|
2774
|
+
`zoom_pan` accepts a boolean for both axes or this `ZoomPanArg` object:
|
|
2775
|
+
|
|
2776
|
+
| Field | Type | Default | Description |
|
|
2777
|
+
|---|---|---|---|
|
|
2778
|
+
| `x?` | `boolean` | `true` | Enable horizontal zoom and pan on a continuous axis. |
|
|
2779
|
+
| `y?` | `boolean` | `true` | Enable vertical zoom and pan on a continuous axis. |
|
|
2780
|
+
| `modifier?` | `boolean` | `true` | Require Ctrl for gestures. Set `false` to allow gestures without a modifier. |
|
|
2781
|
+
|
|
2782
|
+
Setting both axes to `false` disables zoom and pan. Categorical axes do not zoom.
|
|
2783
|
+
|
|
2784
|
+
### Controlling the viewport
|
|
2785
|
+
|
|
2786
|
+
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.
|
|
2787
|
+
|
|
2788
|
+
Set `viewport` to control the visible range. Increment its `key` to apply a new request after the user zooms or pans. See [Viewport](#viewport) for initialization and reset behavior.
|
|
2789
|
+
|
|
2790
|
+
`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](#viewport-changes) for the callback parameters.
|
|
2791
|
+
|
|
2792
|
+
```ts
|
|
2793
|
+
import { line, type APlotElement } from '@antadesign/plot/browser'
|
|
2794
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2795
|
+
import '@antadesign/anta/elements/a-button'
|
|
2796
|
+
|
|
2797
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2798
|
+
const output = document.createElement('output')
|
|
2799
|
+
output.setAttribute('aria-live', 'polite')
|
|
2800
|
+
output.style.display = 'block'
|
|
2801
|
+
output.textContent = 'Hold Ctrl and scroll to zoom, or Ctrl-drag to pan.'
|
|
2802
|
+
|
|
2803
|
+
const data = Array.from({ length: 201 }, (_, i) => ({
|
|
2804
|
+
x: i / 2,
|
|
2805
|
+
y: 45 + 18 * Math.sin(i / 18) + 9 * Math.cos(i / 5) + 4 * Math.sin(i * 1.3),
|
|
2806
|
+
}))
|
|
2807
|
+
|
|
2808
|
+
plot.plotArgs = {
|
|
2809
|
+
series: [
|
|
2810
|
+
line<Node>({
|
|
2811
|
+
data, width: 2,
|
|
2812
|
+
color: { light: '#6366f1', dark: '#a5b4fc' },
|
|
2813
|
+
tooltip: true,
|
|
2814
|
+
}),
|
|
2815
|
+
],
|
|
2816
|
+
height: 260,
|
|
2817
|
+
margin: { top: 8, right: 12, bottom: 24, left: 32 },
|
|
2818
|
+
axis: { x: { min: 0, max: 100, label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
2819
|
+
background: { light: '#ffffff', dark: '#151b28' },
|
|
2820
|
+
chrome_color: { light: '#e2e8f0', dark: '#334155' },
|
|
2821
|
+
grid: false,
|
|
2822
|
+
border: false,
|
|
2823
|
+
zoom_pan: { x: true, y: false, modifier: true },
|
|
2824
|
+
viewport: { x: [20, 45], y: null, key: 0 },
|
|
2825
|
+
on_viewport_change: ({ x, y }) => {
|
|
2826
|
+
const format = (axis: { window: number[] } | null) => axis
|
|
2827
|
+
? axis.window.map(value => value.toFixed(1)).join('–') : 'full extent'
|
|
2828
|
+
output.textContent = 'Visible x: ' + format(x) + ' · y: ' + format(y)
|
|
2829
|
+
},
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
const controls = document.createElement('div')
|
|
2833
|
+
let key = 0
|
|
2834
|
+
const windows: [string, number[] | null][] = [
|
|
2835
|
+
['Focus 20–45', [20, 45]], ['Focus 55–80', [55, 80]], ['Full extent', null],
|
|
2836
|
+
]
|
|
2837
|
+
for (const [label, x] of windows) {
|
|
2838
|
+
const button = document.createElement('a-button')
|
|
2839
|
+
button.textContent = label
|
|
2840
|
+
button.addEventListener('click', () => {
|
|
2841
|
+
const args = plot.plotArgs
|
|
2842
|
+
if (!args) return
|
|
2843
|
+
plot.plotArgs = { ...args, viewport: { x, y: null, key: ++key } }
|
|
2844
|
+
})
|
|
2845
|
+
controls.append(button)
|
|
2846
|
+
}
|
|
2847
|
+
document.body.append(plot, controls, output)
|
|
2848
|
+
```
|
|
2849
|
+
|
|
2850
|
+
#### Viewport
|
|
2851
|
+
|
|
2852
|
+
`viewport` requests a visible window within the full data domain. It applies
|
|
2853
|
+
at mount and when `key` changes; user gestures can then move away from it.
|
|
2854
|
+
Changing only `x` or `y` with the same key does not reapply the request.
|
|
2855
|
+
|
|
2856
|
+
| Field | Type | Default | Description |
|
|
2857
|
+
|---|---|---|---|
|
|
2858
|
+
| `x?` | `number[] \| null` | Leave unchanged | Requested `[min, max]` on x, clamped to the full domain. `null` restores its full extent. |
|
|
2859
|
+
| `y?` | `number[] \| null` | Leave unchanged | Requested `[min, max]` on y, clamped to the full domain. `null` restores its full extent. |
|
|
2860
|
+
| `key?` | `string \| number` | None | Change this value to apply a new viewport request. |
|
|
2861
|
+
|
|
2862
|
+
At mount, an omitted axis starts at its full extent. Viewport windows apply
|
|
2863
|
+
to continuous axes.
|
|
2864
|
+
|
|
2865
|
+
##### Viewport changes
|
|
2866
|
+
|
|
2867
|
+
`on_viewport_change` receives a `ViewportChange` after a gesture or reset:
|
|
2868
|
+
|
|
2869
|
+
| Field | Type | Description |
|
|
2870
|
+
|---|---|---|
|
|
2871
|
+
| `x` | `AxisViewport \| null` | Current horizontal viewport, or `null` for a categorical axis. |
|
|
2872
|
+
| `y` | `AxisViewport \| null` | Current vertical viewport, or `null` for a categorical axis. |
|
|
2873
|
+
|
|
2874
|
+
Each `AxisViewport` contains:
|
|
2875
|
+
|
|
2876
|
+
| Field | Type | Description |
|
|
2877
|
+
|---|---|---|
|
|
2878
|
+
| `window` | `number[]` | Current visible `[min, max]`. |
|
|
2879
|
+
| `full` | `number[]` | Full unzoomed `[min, max]`. |
|
|
2880
|
+
|
|
2881
|
+
## Configuration details
|
|
2882
|
+
|
|
2883
|
+
### Title
|
|
2884
|
+
|
|
2885
|
+
Set the title text, size, and purple theme colors.
|
|
2886
|
+
|
|
2887
|
+
```ts
|
|
2888
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
2889
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2890
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
2891
|
+
|
|
2892
|
+
function base(): PlotArgs<Node> {
|
|
2893
|
+
return {
|
|
2894
|
+
series: [scatter<Node>({ data: [
|
|
2895
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
2896
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
2897
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
2898
|
+
height: 260,
|
|
2899
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
2900
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
2901
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
2902
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
2903
|
+
border: true,
|
|
2904
|
+
grid: false,
|
|
2905
|
+
zoom_pan: false,
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
function title(): PlotArgs<Node> {
|
|
2910
|
+
return { ...base(), margin: { top: 44, right: 24, bottom: 48, left: 76 },
|
|
2911
|
+
title: { text: 'Plot title', size: 20, color: { light: '#713fff', dark: '#c4b5fd' } } }
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2915
|
+
plot.plotArgs = title()
|
|
2916
|
+
document.body.append(plot)
|
|
2917
|
+
```
|
|
2918
|
+
|
|
2919
|
+
`title` accepts a string or a `TitleArg` object:
|
|
2920
|
+
|
|
2921
|
+
| Field | Type | Default | Description |
|
|
2922
|
+
|---|---|---|---|
|
|
2923
|
+
| `text` | `string` | Required | Plot title text. |
|
|
2924
|
+
| `size?` | `number` | `14` | Font size in pixels. |
|
|
2925
|
+
| `color?` | [`ThemeColor`](#theme-colors) | Theme default | Title color. |
|
|
2926
|
+
|
|
2927
|
+
### Margins
|
|
2928
|
+
|
|
2929
|
+
The canvas uses a gray CSS background. The plot background fills only the area inside the axes, making the margins visible.
|
|
2930
|
+
|
|
2931
|
+
```ts
|
|
2932
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
2933
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2934
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
2935
|
+
|
|
2936
|
+
function base(): PlotArgs<Node> {
|
|
2937
|
+
return {
|
|
2938
|
+
series: [scatter<Node>({ data: [
|
|
2939
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
2940
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
2941
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
2942
|
+
height: 260,
|
|
2943
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
2944
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
2945
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
2946
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
2947
|
+
border: true,
|
|
2948
|
+
grid: false,
|
|
2949
|
+
zoom_pan: false,
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
function margins(): PlotArgs<Node> {
|
|
2954
|
+
return { ...base(), margin: { top: 40, right: 40, bottom: 64, left: 88 },
|
|
2955
|
+
background: { light: 'white', dark: '#202124' } }
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
2959
|
+
plot.plotArgs = margins()
|
|
2960
|
+
plot.style.background = 'color-mix(in srgb, var(--text-1, #111827) 18%, var(--bg-1, #ffffff))'
|
|
2961
|
+
document.body.append(plot)
|
|
2962
|
+
```
|
|
2963
|
+
|
|
2964
|
+
`margin` accepts one number for all sides or a `SideMargins` object.
|
|
2965
|
+
Margins reserve space outside the plot area for labels and the title;
|
|
2966
|
+
axis padding adds space inside it.
|
|
2967
|
+
|
|
2968
|
+
| Field | Type | Default | Description |
|
|
2969
|
+
|---|---|---|---|
|
|
2970
|
+
| `top?` | `number` | `60` | Top margin in pixels. |
|
|
2971
|
+
| `right?` | `number` | `60` | Right margin in pixels. |
|
|
2972
|
+
| `bottom?` | `number` | `60` | Bottom margin in pixels. |
|
|
2973
|
+
| `left?` | `number` | `60` | Left margin in pixels. |
|
|
2974
|
+
|
|
2975
|
+
When both axes are hidden and there is no title, omitted margins use `2`
|
|
2976
|
+
pixels instead. Explicit margins still apply.
|
|
2977
|
+
|
|
2978
|
+
### Grid
|
|
2979
|
+
|
|
2980
|
+
Compare both grid directions, each direction separately, and no grid. Seven categories set the vertical grid positions; tick marks and labels are hidden.
|
|
2981
|
+
|
|
2982
|
+
Both grid directions
|
|
2983
|
+
|
|
2984
|
+
```ts
|
|
2985
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
2986
|
+
import '@antadesign/plot/elements/a-plot'
|
|
2987
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
2988
|
+
|
|
2989
|
+
function base(): PlotArgs<Node> {
|
|
2990
|
+
return {
|
|
2991
|
+
series: [scatter<Node>({ data: [
|
|
2992
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
2993
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
2994
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
2995
|
+
height: 260,
|
|
2996
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
2997
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
2998
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
2999
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3000
|
+
border: true,
|
|
3001
|
+
grid: false,
|
|
3002
|
+
zoom_pan: false,
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
function grid(): PlotArgs<Node> {
|
|
3007
|
+
const categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
|
3008
|
+
return { ...base(), height: 160, margin: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
3009
|
+
series: [scatter<Node>({ data: categories.map((x, i) => ({ x, y: 20 + i * 9 })),
|
|
3010
|
+
size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3011
|
+
axis: { x: { categories, label: '', tick_mark: false, tick_label: { format: () => '' } },
|
|
3012
|
+
y: { min: 0, max: 100, label: '', tick_mark: false, tick_label: { format: () => '' } } },
|
|
3013
|
+
grid: { x: true, y: true } }
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3017
|
+
plot.plotArgs = grid()
|
|
3018
|
+
document.body.append(plot)
|
|
3019
|
+
```
|
|
3020
|
+
|
|
3021
|
+
Vertical grid lines
|
|
3022
|
+
|
|
3023
|
+
```ts
|
|
3024
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3025
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3026
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3027
|
+
|
|
3028
|
+
function base(): PlotArgs<Node> {
|
|
3029
|
+
return {
|
|
3030
|
+
series: [scatter<Node>({ data: [
|
|
3031
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3032
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3033
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3034
|
+
height: 260,
|
|
3035
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3036
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3037
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3038
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3039
|
+
border: true,
|
|
3040
|
+
grid: false,
|
|
3041
|
+
zoom_pan: false,
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
function grid(): PlotArgs<Node> {
|
|
3046
|
+
const categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
|
3047
|
+
return { ...base(), height: 160, margin: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
3048
|
+
series: [scatter<Node>({ data: categories.map((x, i) => ({ x, y: 20 + i * 9 })),
|
|
3049
|
+
size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3050
|
+
axis: { x: { categories, label: '', tick_mark: false, tick_label: { format: () => '' } },
|
|
3051
|
+
y: { min: 0, max: 100, label: '', tick_mark: false, tick_label: { format: () => '' } } },
|
|
3052
|
+
grid: { x: true, y: true } }
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
function gridX(): PlotArgs<Node> {
|
|
3056
|
+
return { ...grid(), grid: { x: true, y: false } }
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3060
|
+
plot.plotArgs = gridX()
|
|
3061
|
+
document.body.append(plot)
|
|
3062
|
+
```
|
|
3063
|
+
|
|
3064
|
+
Horizontal grid lines
|
|
3065
|
+
|
|
3066
|
+
```ts
|
|
3067
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3068
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3069
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3070
|
+
|
|
3071
|
+
function base(): PlotArgs<Node> {
|
|
3072
|
+
return {
|
|
3073
|
+
series: [scatter<Node>({ data: [
|
|
3074
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3075
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3076
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3077
|
+
height: 260,
|
|
3078
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3079
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3080
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3081
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3082
|
+
border: true,
|
|
3083
|
+
grid: false,
|
|
3084
|
+
zoom_pan: false,
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
function grid(): PlotArgs<Node> {
|
|
3089
|
+
const categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
|
3090
|
+
return { ...base(), height: 160, margin: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
3091
|
+
series: [scatter<Node>({ data: categories.map((x, i) => ({ x, y: 20 + i * 9 })),
|
|
3092
|
+
size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3093
|
+
axis: { x: { categories, label: '', tick_mark: false, tick_label: { format: () => '' } },
|
|
3094
|
+
y: { min: 0, max: 100, label: '', tick_mark: false, tick_label: { format: () => '' } } },
|
|
3095
|
+
grid: { x: true, y: true } }
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
function gridY(): PlotArgs<Node> {
|
|
3099
|
+
return { ...grid(), grid: { x: false, y: true } }
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3103
|
+
plot.plotArgs = gridY()
|
|
3104
|
+
document.body.append(plot)
|
|
3105
|
+
```
|
|
3106
|
+
|
|
3107
|
+
No grid lines
|
|
3108
|
+
|
|
3109
|
+
```ts
|
|
3110
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3111
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3112
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3113
|
+
|
|
3114
|
+
function base(): PlotArgs<Node> {
|
|
3115
|
+
return {
|
|
3116
|
+
series: [scatter<Node>({ data: [
|
|
3117
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3118
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3119
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3120
|
+
height: 260,
|
|
3121
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3122
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3123
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3124
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3125
|
+
border: true,
|
|
3126
|
+
grid: false,
|
|
3127
|
+
zoom_pan: false,
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
function grid(): PlotArgs<Node> {
|
|
3132
|
+
const categories = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
|
|
3133
|
+
return { ...base(), height: 160, margin: { top: 10, right: 10, bottom: 10, left: 10 },
|
|
3134
|
+
series: [scatter<Node>({ data: categories.map((x, i) => ({ x, y: 20 + i * 9 })),
|
|
3135
|
+
size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3136
|
+
axis: { x: { categories, label: '', tick_mark: false, tick_label: { format: () => '' } },
|
|
3137
|
+
y: { min: 0, max: 100, label: '', tick_mark: false, tick_label: { format: () => '' } } },
|
|
3138
|
+
grid: { x: true, y: true } }
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
function gridNone(): PlotArgs<Node> {
|
|
3142
|
+
return { ...grid(), grid: false }
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3146
|
+
plot.plotArgs = gridNone()
|
|
3147
|
+
document.body.append(plot)
|
|
3148
|
+
```
|
|
3149
|
+
|
|
3150
|
+
`grid` accepts a boolean for both axes or this `GridSpec` object:
|
|
3151
|
+
|
|
3152
|
+
| Field | Type | Default | Description |
|
|
3153
|
+
|---|---|---|---|
|
|
3154
|
+
| `x?` | `boolean` | `false` | Draw vertical grid lines at x-axis ticks. |
|
|
3155
|
+
| `y?` | `boolean` | `false` | Draw horizontal grid lines at y-axis ticks. |
|
|
3156
|
+
|
|
3157
|
+
### Theme colors
|
|
3158
|
+
|
|
3159
|
+
The scatter marks use `tomato` in light mode and `lightskyblue` in dark mode, with `size: 18`. Switch the site theme to compare them.
|
|
3160
|
+
|
|
3161
|
+
```ts
|
|
3162
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3163
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3164
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3165
|
+
|
|
3166
|
+
function base(): PlotArgs<Node> {
|
|
3167
|
+
return {
|
|
3168
|
+
series: [scatter<Node>({ data: [
|
|
3169
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3170
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3171
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3172
|
+
height: 260,
|
|
3173
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3174
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3175
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3176
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3177
|
+
border: true,
|
|
3178
|
+
grid: false,
|
|
3179
|
+
zoom_pan: false,
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
function themeColors(): PlotArgs<Node> {
|
|
3184
|
+
return { ...base(), series: [scatter<Node>({
|
|
3185
|
+
data: [{ x: 20, y: 30 }, { x: 50, y: 65 }, { x: 80, y: 45 }],
|
|
3186
|
+
size: 18, color: { light: 'tomato', dark: 'lightskyblue' }, hoverable: false,
|
|
3187
|
+
})] }
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3191
|
+
plot.plotArgs = themeColors()
|
|
3192
|
+
document.body.append(plot)
|
|
3193
|
+
```
|
|
3194
|
+
|
|
3195
|
+
`ThemeColor` accepts a CSS color string or a `ColorPair`. Use a pair to choose
|
|
3196
|
+
separate colors for light and dark themes.
|
|
3197
|
+
|
|
3198
|
+
| Form | Type | Example |
|
|
3199
|
+
|---|---|---|
|
|
3200
|
+
| One color | `string` | `'steelblue'`, `'#4682b4'`, `'rgb(70, 130, 180)'`, `'hsl(207, 44%, 49%)'` |
|
|
3201
|
+
| Theme pair | `{ light: string; dark: string }` | `{ light: 'steelblue', dark: 'lightskyblue' }` |
|
|
3202
|
+
|
|
3203
|
+
Both fields in a `ColorPair` are required. `ColorTheme` is `'light'` or `'dark'`.
|
|
3204
|
+
Plain colors remain subject to the plot's `theme_invert` setting.
|
|
3205
|
+
|
|
3206
|
+
### Scales
|
|
3207
|
+
|
|
3208
|
+
Compare linear and logarithmic axes, then time and category axes. Each axis label names its scale.
|
|
3209
|
+
|
|
3210
|
+
Linear and logarithmic scales
|
|
3211
|
+
|
|
3212
|
+
```ts
|
|
3213
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3214
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3215
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3216
|
+
|
|
3217
|
+
function base(): PlotArgs<Node> {
|
|
3218
|
+
return {
|
|
3219
|
+
series: [scatter<Node>({ data: [
|
|
3220
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3221
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3222
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3223
|
+
height: 260,
|
|
3224
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3225
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3226
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3227
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3228
|
+
border: true,
|
|
3229
|
+
grid: false,
|
|
3230
|
+
zoom_pan: false,
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
function scales(): PlotArgs<Node> {
|
|
3235
|
+
return { ...base(),
|
|
3236
|
+
series: [scatter<Node>({ data: [1, 10, 100, 1000, 10000].map((y, i) => ({ x: 10 + i * 20, y })),
|
|
3237
|
+
size: 4, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3238
|
+
axis: { x: { scale: 'linear', min: 0, max: 100, label: 'Linear' },
|
|
3239
|
+
y: { scale: 'log', min: 1, max: 10000, label: 'Logarithmic',
|
|
3240
|
+
tick_label: { format: value => Number(value) >= 1000 ? Number(value) / 1000 + 'k' : String(value) } } },
|
|
3241
|
+
grid: { y: true },
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3246
|
+
plot.plotArgs = scales()
|
|
3247
|
+
document.body.append(plot)
|
|
3248
|
+
```
|
|
3249
|
+
|
|
3250
|
+
Time and category scales
|
|
3251
|
+
|
|
3252
|
+
```ts
|
|
3253
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3254
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3255
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3256
|
+
|
|
3257
|
+
function base(): PlotArgs<Node> {
|
|
3258
|
+
return {
|
|
3259
|
+
series: [scatter<Node>({ data: [
|
|
3260
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3261
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3262
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3263
|
+
height: 260,
|
|
3264
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3265
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3266
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3267
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3268
|
+
border: true,
|
|
3269
|
+
grid: false,
|
|
3270
|
+
zoom_pan: false,
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
function timeCategory(): PlotArgs<Node> {
|
|
3275
|
+
const start = new Date(2026, 0, 12).getTime()
|
|
3276
|
+
return { ...base(),
|
|
3277
|
+
series: [scatter<Node>({ data: [
|
|
3278
|
+
{ x: start, y: 'Alpha' }, { x: start + 3600000, y: 'Beta' },
|
|
3279
|
+
{ x: start + 7200000, y: 'Alpha' }, { x: start + 10800000, y: 'Gamma' },
|
|
3280
|
+
{ x: start + 14400000, y: 'Beta' },
|
|
3281
|
+
], size: 4, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3282
|
+
margin: { top: 20, right: 24, bottom: 48, left: 94 },
|
|
3283
|
+
axis: { x: { scale: 'time', min: start, max: start + 14400000, label: 'Time', padding: 8,
|
|
3284
|
+
tick_label: { format: value => new Date(Number(value)).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }) } },
|
|
3285
|
+
y: { scale: 'category', categories: ['Alpha', 'Beta', 'Gamma'], label: 'Category' } },
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3290
|
+
plot.plotArgs = timeCategory()
|
|
3291
|
+
document.body.append(plot)
|
|
3292
|
+
```
|
|
3293
|
+
|
|
3294
|
+
Set `axis.x.scale` or `axis.y.scale` to choose how data maps to positions.
|
|
3295
|
+
Plot uses [D3 scales](https://d3js.org/d3-scale) internally.
|
|
3296
|
+
|
|
3297
|
+
| Value | D3 scale | Description |
|
|
3298
|
+
|---|---|---|
|
|
3299
|
+
| `'linear'` | `scaleLinear` | Equal differences in value produce equal distances. The default for numeric data. |
|
|
3300
|
+
| `'log'` | `scaleLog` | Equal ratios produce equal distances. Use for positive values spanning several orders of magnitude. |
|
|
3301
|
+
| `'category'` | `scaleBand` | Place discrete categories in evenly spaced bands. Inferred from string data. |
|
|
3302
|
+
| `'time'` | `scaleTime` | Position timestamps on a continuous timeline, with ticks in local time. |
|
|
3303
|
+
| `'utc'` | `scaleUtc` | Position timestamps on a continuous timeline, with ticks in UTC. |
|
|
3304
|
+
|
|
3305
|
+
Both time scales accept Unix timestamps in milliseconds. Use `categories`
|
|
3306
|
+
to set a category order, or `min` and `max` to set continuous-domain bounds.
|
|
3307
|
+
|
|
3308
|
+
### Axis labels
|
|
3309
|
+
|
|
3310
|
+
Compare label positions, sizes, and colors: 18px and 12px labels aligned right and top, then 12px and 20px labels aligned left and bottom.
|
|
3311
|
+
|
|
3312
|
+
Right and top labels
|
|
3313
|
+
|
|
3314
|
+
```ts
|
|
3315
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3316
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3317
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3318
|
+
|
|
3319
|
+
function base(): PlotArgs<Node> {
|
|
3320
|
+
return {
|
|
3321
|
+
series: [scatter<Node>({ data: [
|
|
3322
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3323
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3324
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3325
|
+
height: 260,
|
|
3326
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3327
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3328
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3329
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3330
|
+
border: true,
|
|
3331
|
+
grid: false,
|
|
3332
|
+
zoom_pan: false,
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
function axisLabels(): PlotArgs<Node> {
|
|
3337
|
+
return { ...base(), margin: { top: 44, right: 28, bottom: 62, left: 94 }, axis: {
|
|
3338
|
+
x: { min: 0, max: 100, label: { text: 'Time', position: 'right', size: 18, color: { light: '#713fff', dark: '#c4b5fd' } } },
|
|
3339
|
+
y: { min: 0, max: 100, label: { text: 'Value', position: 'top', size: 12, color: { light: '#c2410c', dark: '#fdba74' } } },
|
|
3340
|
+
} }
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3344
|
+
plot.plotArgs = axisLabels()
|
|
3345
|
+
document.body.append(plot)
|
|
3346
|
+
```
|
|
3347
|
+
|
|
3348
|
+
Left and bottom labels
|
|
3349
|
+
|
|
3350
|
+
```ts
|
|
3351
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3352
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3353
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3354
|
+
|
|
3355
|
+
function base(): PlotArgs<Node> {
|
|
3356
|
+
return {
|
|
3357
|
+
series: [scatter<Node>({ data: [
|
|
3358
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3359
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3360
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3361
|
+
height: 260,
|
|
3362
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3363
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3364
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3365
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3366
|
+
border: true,
|
|
3367
|
+
grid: false,
|
|
3368
|
+
zoom_pan: false,
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
function axisLabelsOpposite(): PlotArgs<Node> {
|
|
3373
|
+
return { ...base(), margin: { top: 44, right: 28, bottom: 62, left: 94 }, axis: {
|
|
3374
|
+
x: { min: 0, max: 100, label: { text: 'Time', position: 'left', size: 12,
|
|
3375
|
+
color: { light: '#c2410c', dark: '#fdba74' } } },
|
|
3376
|
+
y: { min: 0, max: 100, label: { text: 'Value', position: 'bottom', size: 20,
|
|
3377
|
+
color: { light: '#713fff', dark: '#c4b5fd' } } },
|
|
3378
|
+
} }
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3382
|
+
plot.plotArgs = axisLabelsOpposite()
|
|
3383
|
+
document.body.append(plot)
|
|
3384
|
+
```
|
|
3385
|
+
|
|
3386
|
+
`LabelArg` accepts a string or this object at `axis.x.label` or `axis.y.label`:
|
|
3387
|
+
|
|
3388
|
+
| Field | Type | Default | Description |
|
|
3389
|
+
|---|---|---|---|
|
|
3390
|
+
| `text` | `string` | Required | Axis label text. |
|
|
3391
|
+
| `size?` | `number` | `12` | Font size in pixels. |
|
|
3392
|
+
| `color?` | [`ThemeColor`](#theme-colors) | Theme default | Label color. |
|
|
3393
|
+
| `position?` | `'center' \| 'left' \| 'right' \| 'top' \| 'bottom'` | `'center'` | Use `left` or `right` on x; `top` or `bottom` on y. |
|
|
3394
|
+
|
|
3395
|
+
### Tick labels
|
|
3396
|
+
|
|
3397
|
+
Add units to tick labels with formatting callbacks. The horizontal ticks use 10px purple text; the vertical ticks use 15px orange text.
|
|
3398
|
+
|
|
3399
|
+
```ts
|
|
3400
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3401
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3402
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3403
|
+
|
|
3404
|
+
function base(): PlotArgs<Node> {
|
|
3405
|
+
return {
|
|
3406
|
+
series: [scatter<Node>({ data: [
|
|
3407
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3408
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3409
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3410
|
+
height: 260,
|
|
3411
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3412
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3413
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3414
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3415
|
+
border: true,
|
|
3416
|
+
grid: false,
|
|
3417
|
+
zoom_pan: false,
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
function tickLabels(): PlotArgs<Node> {
|
|
3422
|
+
return { ...base(), margin: { top: 44, right: 28, bottom: 62, left: 94 }, axis: {
|
|
3423
|
+
x: { min: 0, max: 100, label: 'Elapsed time', tick_label: { format: value => value + ' s', size: 10, color: { light: '#713fff', dark: '#c4b5fd' } } },
|
|
3424
|
+
y: { min: 0, max: 100, label: 'Utilization', tick_label: { format: value => value + '%', size: 15, color: { light: '#c2410c', dark: '#fdba74' } } },
|
|
3425
|
+
} }
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3429
|
+
plot.plotArgs = tickLabels()
|
|
3430
|
+
document.body.append(plot)
|
|
3431
|
+
```
|
|
3432
|
+
|
|
3433
|
+
Use `TickLabelArg` at `axis.x.tick_label` or `axis.y.tick_label`:
|
|
3434
|
+
|
|
3435
|
+
| Field | Type | Default | Description |
|
|
3436
|
+
|---|---|---|---|
|
|
3437
|
+
| `format?` | `(value: number \| string, index: number) => string` | Scale-dependent | Format each tick value. Return `''` for an empty label. |
|
|
3438
|
+
| `size?` | `number` | `10` | Font size in pixels. |
|
|
3439
|
+
| `color?` | [`ThemeColor`](#theme-colors) | Theme default | Tick label color. |
|
|
3440
|
+
|
|
3441
|
+
### Series colors
|
|
3442
|
+
|
|
3443
|
+
Use a color accessor to distinguish values at or above 60.
|
|
3444
|
+
|
|
3445
|
+
```ts
|
|
3446
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3447
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3448
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3449
|
+
|
|
3450
|
+
function base(): PlotArgs<Node> {
|
|
3451
|
+
return {
|
|
3452
|
+
series: [scatter<Node>({ data: [
|
|
3453
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3454
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3455
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3456
|
+
height: 260,
|
|
3457
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3458
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3459
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3460
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3461
|
+
border: true,
|
|
3462
|
+
grid: false,
|
|
3463
|
+
zoom_pan: false,
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
function seriesColors(): PlotArgs<Node> {
|
|
3468
|
+
return { ...base(), series: [scatter<Node>({
|
|
3469
|
+
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 }],
|
|
3470
|
+
size: 8, color: row => Number(row.y) >= 60
|
|
3471
|
+
? { light: '#c2410c', dark: '#fdba74' } : { light: '#64748b', dark: '#94a3b8' },
|
|
3472
|
+
hoverable: false,
|
|
3473
|
+
})] }
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3477
|
+
plot.plotArgs = seriesColors()
|
|
3478
|
+
document.body.append(plot)
|
|
3479
|
+
```
|
|
3480
|
+
|
|
3481
|
+
`ColorArg` adds a per-row accessor to [`ThemeColor`](#theme-colors). Scatter, bar, rect, rule,
|
|
3482
|
+
and custom series accept it; line and area use one `ThemeColor` per series.
|
|
3483
|
+
|
|
3484
|
+
| Form | Type | Description |
|
|
3485
|
+
|---|---|---|
|
|
3486
|
+
| Shared color | [`ThemeColor`](#theme-colors) | Apply one color or theme pair to the series. |
|
|
3487
|
+
| Accessor | [`(row: Record<string, unknown>, index: number) => ThemeColor`](#theme-colors) | Choose a color or theme pair for each row. |
|
|
3488
|
+
|
|
3489
|
+
```ts
|
|
3490
|
+
import type { ColorArg } from '@antadesign/plot'
|
|
3491
|
+
|
|
3492
|
+
const color: ColorArg = (row) => Number(row.value) >= 10 ? 'coral' : 'steelblue'
|
|
3493
|
+
```
|
|
3494
|
+
|
|
3495
|
+
For these series, a valid `color` on a data row takes precedence over the
|
|
3496
|
+
series color or accessor. Stacked bars also accept a [`ThemeColor[]`](#theme-colors), with
|
|
3497
|
+
one entry per stacked field.
|
|
3498
|
+
|
|
3499
|
+
### Mark shapes
|
|
3500
|
+
|
|
3501
|
+
Set a different mark shape for each category.
|
|
3502
|
+
|
|
3503
|
+
```ts
|
|
3504
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3505
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3506
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3507
|
+
|
|
3508
|
+
function base(): PlotArgs<Node> {
|
|
3509
|
+
return {
|
|
3510
|
+
series: [scatter<Node>({ data: [
|
|
3511
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3512
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3513
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3514
|
+
height: 260,
|
|
3515
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3516
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3517
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3518
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3519
|
+
border: true,
|
|
3520
|
+
grid: false,
|
|
3521
|
+
zoom_pan: false,
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
function markShapes(): PlotArgs<Node> {
|
|
3526
|
+
const marks = ['circle', 'square', 'diamond', 'triangle'] as const
|
|
3527
|
+
return { ...base(), series: marks.map((mark, i) => scatter<Node>({
|
|
3528
|
+
data: [{ x: mark, y: 40 + i * 10 }], mark, size: 12,
|
|
3529
|
+
color: [
|
|
3530
|
+
{ light: '#7c3aed', dark: '#c4b5fd' }, { light: '#db2777', dark: '#f9a8d4' },
|
|
3531
|
+
{ light: '#0891b2', dark: '#67e8f9' }, { light: '#ea580c', dark: '#fdba74' },
|
|
3532
|
+
][i], hoverable: false,
|
|
3533
|
+
})), axis: { x: { categories: [...marks], label: '' }, y: { min: 0, max: 100, label: '' } },
|
|
3534
|
+
margin: { top: 20, right: 12, bottom: 32, left: 32 },
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3539
|
+
plot.plotArgs = markShapes()
|
|
3540
|
+
document.body.append(plot)
|
|
3541
|
+
```
|
|
3542
|
+
|
|
3543
|
+
`MarkShape` controls [`scatter.mark`](#scatter) and [`line.mark`](#line):
|
|
3544
|
+
|
|
3545
|
+
| Value | Description |
|
|
3546
|
+
|---|---|
|
|
3547
|
+
| `'circle'` | Circular mark. |
|
|
3548
|
+
| `'square'` | Square mark. |
|
|
3549
|
+
| `'diamond'` | Diamond mark. |
|
|
3550
|
+
| `'triangle'` | Triangular mark. |
|
|
3551
|
+
|
|
3552
|
+
Scatter defaults to `'circle'`; line draws no marks unless `mark` is set.
|
|
3553
|
+
|
|
3554
|
+
### Strokes
|
|
3555
|
+
|
|
3556
|
+
Compare outlines with widths of 1, 3, and 5 pixels on equal-sized circles.
|
|
3557
|
+
|
|
3558
|
+
```ts
|
|
3559
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3560
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3561
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3562
|
+
|
|
3563
|
+
function base(): PlotArgs<Node> {
|
|
3564
|
+
return {
|
|
3565
|
+
series: [scatter<Node>({ data: [
|
|
3566
|
+
{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3567
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 },
|
|
3568
|
+
], size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false })],
|
|
3569
|
+
height: 260,
|
|
3570
|
+
margin: { top: 20, right: 24, bottom: 48, left: 76 },
|
|
3571
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3572
|
+
background: { light: '#ffffff', dark: '#202124' },
|
|
3573
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3574
|
+
border: true,
|
|
3575
|
+
grid: false,
|
|
3576
|
+
zoom_pan: false,
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
function strokes(): PlotArgs<Node> {
|
|
3581
|
+
return { ...base(), series: [1, 3, 5].map((width, i) => scatter<Node>({
|
|
3582
|
+
data: [{ x: 25 + i * 25, y: 50 }], size: 18,
|
|
3583
|
+
color: { light: '#f0abfc', dark: '#c026d3' },
|
|
3584
|
+
stroke: { color: { light: '#7e22ce', dark: '#f5d0fe' }, width }, hoverable: false,
|
|
3585
|
+
})) }
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3589
|
+
plot.plotArgs = strokes()
|
|
3590
|
+
document.body.append(plot)
|
|
3591
|
+
```
|
|
3592
|
+
|
|
3593
|
+
`StrokeArg` accepts a [`ThemeColor`](#theme-colors) or a `Stroke` object. Use it for `stroke`
|
|
3594
|
+
on scatter, area, and rect series, or `mark_stroke` on a line series.
|
|
3595
|
+
|
|
3596
|
+
| Field | Type | Default | Description |
|
|
3597
|
+
|---|---|---|---|
|
|
3598
|
+
| `color` | [`ThemeColor`](#theme-colors) | Required | Outline color or theme pair. |
|
|
3599
|
+
| `width?` | `number` | Series-dependent | Outline width in pixels. Mark outlines scale with mark size when omitted. |
|
|
3600
|
+
|
|
3601
|
+
```ts
|
|
3602
|
+
import type { StrokeArg } from '@antadesign/plot'
|
|
3603
|
+
|
|
3604
|
+
const stroke: StrokeArg = {
|
|
3605
|
+
color: { light: 'navy', dark: 'lightsteelblue' },
|
|
3606
|
+
width: 1,
|
|
3607
|
+
}
|
|
3608
|
+
```
|
|
3609
|
+
|
|
3610
|
+
### Data fields
|
|
3611
|
+
|
|
3612
|
+
`FieldArg` selects a value from each data row. Use it for series coordinates
|
|
3613
|
+
such as `x` and `y`, or for a second boundary where the series supports one.
|
|
3614
|
+
|
|
3615
|
+
| Form | Type | Description |
|
|
3616
|
+
|---|---|---|
|
|
3617
|
+
| Field name | `string` | Read the named property from each row. |
|
|
3618
|
+
| Accessor | `(row: Record<string, unknown>, index: number) => number \| string` | Compute a value from the row and its zero-based index. Return numbers for continuous axes or strings for categories. |
|
|
3619
|
+
|
|
3620
|
+
```ts
|
|
3621
|
+
import type { FieldArg } from '@antadesign/plot'
|
|
3622
|
+
|
|
3623
|
+
const x: FieldArg = 'elapsed_ms'
|
|
3624
|
+
const y: FieldArg = (row) => Number(row.duration_ms) / 1000
|
|
3625
|
+
```
|
|
3626
|
+
|
|
3627
|
+
### Custom rendering
|
|
3628
|
+
|
|
3629
|
+
`CustomRendererFn<TooltipContent>` receives the composed custom series and
|
|
3630
|
+
its drawing context:
|
|
3631
|
+
|
|
3632
|
+
```ts
|
|
3633
|
+
import type { ComposedCustom, CustomRenderContext } from '@antadesign/plot'
|
|
3634
|
+
|
|
3635
|
+
type Renderer = (series: ComposedCustom, context: CustomRenderContext) => void
|
|
3636
|
+
```
|
|
3637
|
+
|
|
3638
|
+
The composed series contains resolved `x` and `y` columns, source `rows` when
|
|
3639
|
+
provided, and theme-resolved colors. Category coordinates are stored as
|
|
3640
|
+
indices; use the pixel resolvers to place them on the canvas.
|
|
3641
|
+
|
|
3642
|
+
| Context field | Type | Description |
|
|
3643
|
+
|---|---|---|
|
|
3644
|
+
| `ctx` | `CanvasContext` | Main-thread or offscreen 2D canvas context. |
|
|
3645
|
+
| `inner` | `Rect` | Plot bounds in pixels: `left`, `right`, `top`, and `bottom`. |
|
|
3646
|
+
| `x_scale` | `Scale` | Composed horizontal D3 scale. |
|
|
3647
|
+
| `y_scale` | `Scale` | Composed vertical D3 scale. |
|
|
3648
|
+
| `color` | `string` | Resolved fallback series color. |
|
|
3649
|
+
| `x_categories?` | `string[]` | Horizontal category labels, when categorical. |
|
|
3650
|
+
| `y_categories?` | `string[]` | Vertical category labels, when categorical. |
|
|
3651
|
+
| `resolve_x` | `PixelResolver` | Resolve a row index to its x position in pixels. |
|
|
3652
|
+
| `resolve_y` | `PixelResolver` | Resolve a row index to its y position in pixels. |
|
|
3653
|
+
| `color_at` | `ColorResolver` | Resolve a row index to its color, including the series fallback. |
|
|
3654
|
+
|
|
3655
|
+
`PixelResolver` is `(index: number) => number | undefined`; skip points whose
|
|
3656
|
+
position is `undefined`. `ColorResolver` is `(index: number) => string`.
|
|
3657
|
+
`CanvasContext` is `CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D`.
|
|
3658
|
+
`Scale` is a D3 linear, logarithmic, band, or time scale.
|
|
3659
|
+
|
|
3660
|
+
## Web Component
|
|
3661
|
+
|
|
3662
|
+
`<a-plot>` is a light-DOM custom element that takes the same arguments
|
|
3663
|
+
as a property. Use it directly from plain JavaScript or TypeScript; it owns
|
|
3664
|
+
controller setup, drawing, and cleanup in the browser.
|
|
3665
|
+
|
|
3666
|
+
```ts
|
|
3667
|
+
import { scatter, type APlotElement } from '@antadesign/plot/browser'
|
|
3668
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3669
|
+
|
|
3670
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3671
|
+
plot.plotArgs = { series: [scatter({ data: [{ x: 1, y: 2 }, { x: 2, y: 3 }] })] }
|
|
3672
|
+
document.body.append(plot)
|
|
3673
|
+
```
|
|
3674
|
+
|
|
3675
|
+
[`plotArgs`](#plot-arguments) is a property, not an attribute — assign the object rather
|
|
3676
|
+
than serializing it. Import `@antadesign/plot/elements/a-plot` to register the standalone plot and its surface dependency.
|
|
3677
|
+
Registration skips existing definitions and does nothing when `customElements`
|
|
3678
|
+
is unavailable, including during server rendering.
|
|
3679
|
+
|
|
3680
|
+
## Styling
|
|
3681
|
+
|
|
3682
|
+
The JSX `Plot` component fills its parent with `width: 100%` and `height: 100%`.
|
|
3683
|
+
Give its container a height, or set `width` and `height` in `plotArgs`. Removing
|
|
3684
|
+
those arguments restores parent-relative sizing. Use `className` and `style` to
|
|
3685
|
+
style the wrapper; inline `style` props can override its default dimensions.
|
|
3686
|
+
|
|
3687
|
+
The standalone `<a-plot>` element has a 300px fallback height. The preview below
|
|
3688
|
+
uses that element. Configure canvas colors, lines, and text through `plotArgs`.
|
|
3689
|
+
|
|
3690
|
+
This preview uses CSS for the rounded container, padding, and responsive aspect
|
|
3691
|
+
ratio. It omits `width` and `height` from `plotArgs` so CSS controls the size.
|
|
3692
|
+
Set `background: false` to let the container background show through the canvas.
|
|
3693
|
+
|
|
3694
|
+
```ts
|
|
3695
|
+
import { scatter, type PlotArgs } from '@antadesign/plot'
|
|
3696
|
+
import '@antadesign/plot/elements/a-plot'
|
|
3697
|
+
import type { APlotElement } from '@antadesign/plot/browser'
|
|
3698
|
+
|
|
3699
|
+
function stylingExample(): PlotArgs<Node> {
|
|
3700
|
+
return {
|
|
3701
|
+
series: [scatter<Node>({
|
|
3702
|
+
data: [{ x: 12, y: 18 }, { x: 30, y: 42 }, { x: 48, y: 35 },
|
|
3703
|
+
{ x: 58, y: 64 }, { x: 76, y: 58 }, { x: 88, y: 82 }],
|
|
3704
|
+
size: 3, color: { light: '#9ca3af', dark: '#6b7280' }, hoverable: false,
|
|
3705
|
+
})],
|
|
3706
|
+
margin: { top: 16, right: 16, bottom: 40, left: 60 },
|
|
3707
|
+
axis: { x: { min: 0, max: 100, label: 'Time' }, y: { min: 0, max: 100, label: 'Value' } },
|
|
3708
|
+
background: false,
|
|
3709
|
+
chrome_color: { light: '#9ca3af', dark: '#d1d5db' },
|
|
3710
|
+
border: true,
|
|
3711
|
+
grid: false,
|
|
3712
|
+
zoom_pan: false,
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
const container = document.createElement('div')
|
|
3717
|
+
container.className = 'plot-card'
|
|
3718
|
+
const plot = document.createElement('a-plot') as APlotElement
|
|
3719
|
+
plot.className = 'styled-plot'
|
|
3720
|
+
plot.setAttribute('aria-label', 'Scatter plot in a rounded container')
|
|
3721
|
+
plot.plotArgs = stylingExample()
|
|
3722
|
+
container.append(plot)
|
|
3723
|
+
document.body.append(container)
|
|
3724
|
+
```
|
|
3725
|
+
|
|
3726
|
+
```css
|
|
3727
|
+
.plot-card {
|
|
3728
|
+
box-sizing: border-box;
|
|
3729
|
+
width: 100%;
|
|
3730
|
+
max-width: 460px;
|
|
3731
|
+
margin-inline: auto;
|
|
3732
|
+
padding: 16px;
|
|
3733
|
+
border: 1px solid #9ca3af;
|
|
3734
|
+
border-radius: 16px;
|
|
3735
|
+
background: rgb(156 163 175 / 12%);
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
.styled-plot {
|
|
3739
|
+
display: block;
|
|
3740
|
+
width: 100%;
|
|
3741
|
+
height: auto;
|
|
3742
|
+
aspect-ratio: 4 / 3;
|
|
3743
|
+
}
|
|
3744
|
+
```
|
|
3745
|
+
|
|
3746
|
+
The `a-plot-surface` element installs its structural styles automatically,
|
|
3747
|
+
including canvas stacking and overlay positioning. It does not require a
|
|
3748
|
+
separate CSS import.
|
|
3749
|
+
|
|
3750
|
+
The complete `a-plot` host installs its base styles automatically too, giving it
|
|
3751
|
+
a width of `100%` and a fallback height of `300px`. No `plot.css` import is needed.
|
|
3752
|
+
Its sizing rule uses `:where()` with zero specificity, so your CSS can override
|
|
3753
|
+
it. Set `height: auto` when using `aspect-ratio` to replace the fixed fallback
|
|
3754
|
+
height, as this example does.
|
|
3755
|
+
|
|
3756
|
+
Alternatively, pass `width` and `height` to set inline styles on the host.
|
|
3757
|
+
These override stylesheet rules. Removing an argument restores the previous
|
|
3758
|
+
inline value, allowing stylesheet rules to apply when no inline value remains.
|
|
3759
|
+
|
|
3760
|
+
Inside the canvas, use [`chrome_color`](#theme-colors) for axis lines, tick marks,
|
|
3761
|
+
grid lines, and the plot border. Use `background` for the plot fill and each
|
|
3762
|
+
series’ `color` for its marks. These colors accept `{ light, dark }` pairs.
|
|
3763
|
+
Plain color strings remain subject to the plot’s `theme_invert` setting.
|
|
3764
|
+
|
|
3765
|
+
Set text colors separately with `title.color`, `axis.x.label.color`,
|
|
3766
|
+
`axis.y.label.color`, `axis.x.tick_label.color`, and `axis.y.tick_label.color`.
|
|
3767
|
+
|
|
3768
|
+
With the JSX `Plot` component, return content supported by your renderer from a
|
|
3769
|
+
series’ [`tooltip`](#tooltips) callback and style it like other markup. Anta’s
|
|
3770
|
+
`Tooltip` renders that content in the component tree. The reset control is an Anta `Button` and
|
|
3771
|
+
follows the application’s theme.
|