@rokkit/chart 1.3.7 → 1.3.9
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/AnimatedPlot.svelte +26 -8
- package/dist/AnimatedPlot.svelte.d.ts +5 -0
- package/dist/Chart.svelte +51 -65
- package/dist/FacetPlot.svelte +1 -1
- package/dist/Plot/Axis.svelte +19 -10
- package/dist/Plot/Root.svelte +67 -46
- package/dist/Plot/Root.svelte.d.ts +29 -2
- package/dist/Plot/Timeline.svelte +39 -13
- package/dist/Plot/index.d.ts +5 -5
- package/dist/Plot/index.js +5 -5
- package/dist/Plot.svelte +95 -178
- package/dist/Plot.svelte.d.ts +4 -0
- package/dist/PlotState.svelte.d.ts +29 -1
- package/dist/PlotState.svelte.js +163 -19
- package/dist/PlotSurface.svelte +162 -0
- package/dist/PlotSurface.svelte.d.ts +25 -0
- package/dist/charts/AreaChart.svelte +12 -2
- package/dist/charts/AreaChart.svelte.d.ts +6 -0
- package/dist/charts/BarChart.svelte +40 -4
- package/dist/charts/BarChart.svelte.d.ts +15 -1
- package/dist/charts/BoxPlot.svelte +7 -3
- package/dist/charts/BoxPlot.svelte.d.ts +2 -0
- package/dist/charts/BubbleChart.svelte +37 -4
- package/dist/charts/BubbleChart.svelte.d.ts +12 -1
- package/dist/charts/LineChart.svelte +4 -2
- package/dist/charts/LineChart.svelte.d.ts +1 -0
- package/dist/charts/PieChart.svelte +5 -3
- package/dist/charts/PieChart.svelte.d.ts +1 -0
- package/dist/charts/ScatterPlot.svelte +35 -4
- package/dist/charts/ScatterPlot.svelte.d.ts +11 -1
- package/dist/charts/ViolinPlot.svelte +7 -3
- package/dist/charts/ViolinPlot.svelte.d.ts +2 -0
- package/dist/examples/BarChartExample.svelte +1 -1
- package/dist/geoms/Arc.svelte +21 -54
- package/dist/geoms/Arc.svelte.d.ts +4 -4
- package/dist/geoms/Area.svelte +45 -44
- package/dist/geoms/Area.svelte.d.ts +8 -1
- package/dist/geoms/Bar.svelte +33 -45
- package/dist/geoms/Bar.svelte.d.ts +9 -0
- package/dist/geoms/Box.svelte +103 -61
- package/dist/geoms/Box.svelte.d.ts +12 -3
- package/dist/geoms/Candlestick.svelte +26 -75
- package/dist/geoms/Candlestick.svelte.d.ts +2 -0
- package/dist/geoms/Heatmap.svelte +21 -66
- package/dist/geoms/Heatmap.svelte.d.ts +2 -0
- package/dist/geoms/Hexbin.svelte +20 -100
- package/dist/geoms/Hexbin.svelte.d.ts +2 -0
- package/dist/geoms/Jitter.svelte +67 -0
- package/dist/geoms/Jitter.svelte.d.ts +17 -0
- package/dist/geoms/Line.svelte +27 -31
- package/dist/geoms/Line.svelte.d.ts +5 -0
- package/dist/geoms/Point.svelte +31 -60
- package/dist/geoms/Point.svelte.d.ts +5 -1
- package/dist/geoms/Ribbon.svelte +30 -124
- package/dist/geoms/Ribbon.svelte.d.ts +2 -0
- package/dist/geoms/Rule.svelte +117 -0
- package/dist/geoms/Rule.svelte.d.ts +19 -0
- package/dist/geoms/Violin.svelte +36 -31
- package/dist/geoms/Violin.svelte.d.ts +10 -3
- package/dist/geoms/Waterfall.svelte +25 -79
- package/dist/geoms/Waterfall.svelte.d.ts +2 -0
- package/dist/geoms/lib/GeomState.svelte.d.ts +51 -0
- package/dist/geoms/lib/GeomState.svelte.js +79 -0
- package/dist/geoms/lib/aesthetics.d.ts +36 -0
- package/dist/geoms/lib/aesthetics.js +45 -0
- package/dist/geoms/lib/areas.d.ts +6 -2
- package/dist/geoms/lib/areas.js +20 -12
- package/dist/geoms/lib/bars.d.ts +18 -3
- package/dist/geoms/lib/bars.js +80 -101
- package/dist/geoms/lib/marks/arc.d.ts +24 -0
- package/dist/geoms/lib/marks/arc.js +28 -0
- package/dist/geoms/lib/marks/area.d.ts +23 -0
- package/dist/geoms/lib/marks/area.js +38 -0
- package/dist/geoms/lib/marks/bar.d.ts +16 -0
- package/dist/geoms/lib/marks/bar.js +43 -0
- package/dist/geoms/lib/marks/box.d.ts +14 -0
- package/dist/geoms/lib/marks/box.js +30 -0
- package/dist/geoms/lib/marks/candlestick.d.ts +27 -0
- package/dist/geoms/lib/marks/candlestick.js +52 -0
- package/dist/geoms/lib/marks/heatmap.d.ts +24 -0
- package/dist/geoms/lib/marks/heatmap.js +41 -0
- package/dist/geoms/lib/marks/hexbin.d.ts +29 -0
- package/dist/geoms/lib/marks/hexbin.js +62 -0
- package/dist/geoms/lib/marks/jitter.d.ts +21 -0
- package/dist/geoms/lib/marks/jitter.js +36 -0
- package/dist/geoms/lib/marks/line.d.ts +26 -0
- package/dist/geoms/lib/marks/line.js +25 -0
- package/dist/geoms/lib/marks/point.d.ts +25 -0
- package/dist/geoms/lib/marks/point.js +58 -0
- package/dist/geoms/lib/marks/ribbon.d.ts +31 -0
- package/dist/geoms/lib/marks/ribbon.js +77 -0
- package/dist/geoms/lib/marks/violin.d.ts +13 -0
- package/dist/geoms/lib/marks/violin.js +31 -0
- package/dist/geoms/lib/marks/waterfall.d.ts +25 -0
- package/dist/geoms/lib/marks/waterfall.js +58 -0
- package/dist/index.d.ts +25 -12
- package/dist/index.js +26 -13
- package/dist/lib/brewing/marks/arcs.js +7 -1
- package/dist/lib/brewing/marks/boxes.d.ts +11 -2
- package/dist/lib/brewing/marks/boxes.js +32 -11
- package/dist/lib/brewing/marks/lines.d.ts +4 -1
- package/dist/lib/brewing/marks/lines.js +18 -15
- package/dist/lib/brewing/marks/swarm.d.ts +32 -0
- package/dist/lib/brewing/marks/swarm.js +125 -0
- package/dist/lib/brewing/marks/violins.d.ts +4 -20
- package/dist/lib/brewing/marks/violins.js +41 -25
- package/dist/lib/brewing/stats.d.ts +3 -1
- package/dist/lib/brewing/stats.js +28 -11
- package/dist/lib/plot/scales.js +12 -7
- package/dist/lib/plot/types.d.ts +9 -1
- package/dist/lib/plot/types.js +3 -1
- package/dist/lib/preset.d.ts +11 -1
- package/dist/lib/preset.js +16 -4
- package/package.json +1 -1
- package/dist/Plot/Arc.svelte +0 -30
- package/dist/Plot/Arc.svelte.d.ts +0 -18
- package/dist/Plot/Area.svelte +0 -54
- package/dist/Plot/Area.svelte.d.ts +0 -14
- package/dist/Plot/Bar.svelte +0 -69
- package/dist/Plot/Bar.svelte.d.ts +0 -11
- package/dist/Plot/Line.svelte +0 -54
- package/dist/Plot/Line.svelte.d.ts +0 -13
- package/dist/Plot/Point.svelte +0 -58
- package/dist/Plot/Point.svelte.d.ts +0 -13
- package/dist/lib/brewer.d.ts +0 -9
- package/dist/lib/brewer.js +0 -25
- package/dist/lib/brewing/BoxBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/BoxBrewer.svelte.js +0 -14
- package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/CartesianBrewer.svelte.js +0 -21
- package/dist/lib/brewing/PieBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/PieBrewer.svelte.js +0 -14
- package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +0 -9
- package/dist/lib/brewing/QuartileBrewer.svelte.js +0 -51
- package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/ViolinBrewer.svelte.js +0 -14
- package/dist/lib/brewing/axes.svelte.d.ts +0 -66
- package/dist/lib/brewing/axes.svelte.js +0 -270
- package/dist/lib/brewing/bars.svelte.d.ts +0 -56
- package/dist/lib/brewing/bars.svelte.js +0 -201
- package/dist/lib/brewing/brewer.svelte.d.ts +0 -124
- package/dist/lib/brewing/brewer.svelte.js +0 -277
- package/dist/lib/brewing/dimensions.svelte.d.ts +0 -35
- package/dist/lib/brewing/dimensions.svelte.js +0 -56
- package/dist/lib/brewing/index.svelte.d.ts +0 -118
- package/dist/lib/brewing/index.svelte.js +0 -205
- package/dist/lib/brewing/legends.svelte.d.ts +0 -48
- package/dist/lib/brewing/legends.svelte.js +0 -137
- package/dist/lib/brewing/marks/areas.d.ts +0 -31
- package/dist/lib/brewing/marks/areas.js +0 -72
- package/dist/lib/brewing/marks/bars.d.ts +0 -1
- package/dist/lib/brewing/marks/bars.js +0 -49
- package/dist/lib/brewing/scales.svelte.d.ts +0 -24
- package/dist/lib/brewing/scales.svelte.js +0 -82
- package/dist/lib/context.d.ts +0 -13
- package/dist/lib/context.js +0 -131
- package/dist/lib/plot/chartProps.d.ts +0 -177
- package/dist/lib/plot/chartProps.js +0 -76
- package/dist/lib/scales.svelte.d.ts +0 -35
- package/dist/lib/scales.svelte.js +0 -151
- package/dist/lib/ticks.d.ts +0 -36
- package/dist/lib/ticks.js +0 -46
package/dist/PlotState.svelte.js
CHANGED
|
@@ -17,6 +17,9 @@ import { assignSymbols } from './lib/brewing/marks/points.js'
|
|
|
17
17
|
|
|
18
18
|
let nextId = 0
|
|
19
19
|
|
|
20
|
+
// Geoms that treat the x channel as categorical (band scale), even for numeric x.
|
|
21
|
+
const CATEGORICAL_X = new Set(['bar', 'box', 'violin', 'jitter'])
|
|
22
|
+
|
|
20
23
|
export class PlotState {
|
|
21
24
|
#data = $state([])
|
|
22
25
|
#rawData = $state([])
|
|
@@ -43,6 +46,13 @@ export class PlotState {
|
|
|
43
46
|
#onselect = $state(undefined)
|
|
44
47
|
#selectable = $state(false)
|
|
45
48
|
#orientationOverride = $state(undefined)
|
|
49
|
+
// When true, the category (x) axis is a CONTINUOUS position scale (kept linear, not
|
|
50
|
+
// band-forced) — e.g. AnimatedPlot's bar-chart race, whose tweened `_rank` must position
|
|
51
|
+
// smoothly. It still flips like any category-x chart; buildBars uses continuous positioning.
|
|
52
|
+
#continuousCategory = $state(false)
|
|
53
|
+
// Order the category (band) axis by aggregated value instead of by label: 'desc' | 'asc'.
|
|
54
|
+
// Sorts bars by size (histogram-style); undefined keeps data/label order.
|
|
55
|
+
#sort = $state(undefined)
|
|
46
56
|
|
|
47
57
|
axisOrigin = $state([undefined, undefined])
|
|
48
58
|
#axisOffset = $state(0)
|
|
@@ -53,29 +63,78 @@ export class PlotState {
|
|
|
53
63
|
#innerWidth = $derived(this.#width - this.#effectiveMargin.left - this.#effectiveMargin.right)
|
|
54
64
|
#innerHeight = $derived(this.#height - this.#effectiveMargin.top - this.#effectiveMargin.bottom)
|
|
55
65
|
|
|
56
|
-
// Effective channels: prefer top-level channels
|
|
57
|
-
//
|
|
66
|
+
// Effective channels: prefer top-level channels PER FIELD, falling back to the
|
|
67
|
+
// first geom's channels. Merged per-field (not all-or-nothing) so a composable
|
|
68
|
+
// <Plot.Root x y> that omits `color` still picks up a geom's color channel
|
|
69
|
+
// (e.g. <Plot.Box color=x>) — otherwise `colors` has no per-category entries and
|
|
70
|
+
// every mark falls back to gray.
|
|
58
71
|
#mergeGeomChannels(tc, geom) {
|
|
59
72
|
return {
|
|
60
73
|
x: tc.x ?? geom.channels?.x,
|
|
61
74
|
y: tc.y ?? geom.channels?.y,
|
|
62
75
|
color: tc.color ?? geom.channels?.color,
|
|
76
|
+
fill: tc.fill ?? geom.channels?.fill,
|
|
63
77
|
pattern: tc.pattern ?? geom.channels?.pattern,
|
|
64
78
|
symbol: tc.symbol ?? geom.channels?.symbol
|
|
65
79
|
}
|
|
66
80
|
}
|
|
67
81
|
|
|
82
|
+
// Field names feeding the shared categorical color scale: the `color` AND `fill` channels,
|
|
83
|
+
// from the top-level channels and EVERY geom (union, de-duped, order-preserving). Literal
|
|
84
|
+
// CSS colors are excluded. One palette/legend spans both aesthetics — the "shared scale"
|
|
85
|
+
// model (see docs/backlog/2026-08-17-chart-aesthetics-unification.md §2).
|
|
86
|
+
#sharedColorValues() {
|
|
87
|
+
const fields = []
|
|
88
|
+
const addField = (f) => {
|
|
89
|
+
if (f && !isLiteralColor(f) && !fields.includes(f)) fields.push(f)
|
|
90
|
+
}
|
|
91
|
+
addField(this.#channels.color)
|
|
92
|
+
addField(this.#channels.fill)
|
|
93
|
+
for (const g of this.#geoms) {
|
|
94
|
+
addField(g.channels?.color)
|
|
95
|
+
addField(g.channels?.fill)
|
|
96
|
+
}
|
|
97
|
+
const seen = new Set()
|
|
98
|
+
const values = []
|
|
99
|
+
for (const f of fields) {
|
|
100
|
+
for (const v of distinct(this.#data, f)) {
|
|
101
|
+
if (!seen.has(v)) {
|
|
102
|
+
seen.add(v)
|
|
103
|
+
values.push(v)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return values
|
|
108
|
+
}
|
|
109
|
+
|
|
68
110
|
#effectiveChannels = $derived.by(() => {
|
|
69
111
|
const tc = this.#channels
|
|
70
|
-
if (tc.x && tc.y) return tc
|
|
71
112
|
const firstGeom = this.#geoms[0]
|
|
72
113
|
if (!firstGeom) return tc
|
|
73
114
|
return this.#mergeGeomChannels(tc, firstGeom)
|
|
74
115
|
})
|
|
75
116
|
|
|
76
117
|
#resolveXType(rawXType, yType) {
|
|
77
|
-
const
|
|
78
|
-
return
|
|
118
|
+
const hasBandGeom = this.#geoms.some((g) => CATEGORICAL_X.has(g.type))
|
|
119
|
+
return hasBandGeom && rawXType === 'continuous' && yType === 'continuous' ? 'band' : rawXType
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Category order sorted by aggregated value (sum of the value channel per category).
|
|
123
|
+
// Drives "sort bars by size" for the band axis + its ticks. Returns null when not sorting.
|
|
124
|
+
#sortedBandDomain(datasets) {
|
|
125
|
+
if (!this.#sort) return null
|
|
126
|
+
const xf = this.#effectiveChannels.x
|
|
127
|
+
const yf = this.#effectiveChannels.y
|
|
128
|
+
if (!xf || !yf) return null
|
|
129
|
+
const totals = new Map()
|
|
130
|
+
for (const ds of datasets) {
|
|
131
|
+
for (const d of ds) {
|
|
132
|
+
const key = d[xf]
|
|
133
|
+
totals.set(key, (totals.get(key) ?? 0) + (Number(d[yf]) || 0))
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const dir = this.#sort === 'asc' ? 1 : -1
|
|
137
|
+
return [...totals.keys()].sort((a, b) => dir * ((totals.get(a) ?? 0) - (totals.get(b) ?? 0)))
|
|
79
138
|
}
|
|
80
139
|
|
|
81
140
|
orientation = $derived.by(() => {
|
|
@@ -89,6 +148,24 @@ export class PlotState {
|
|
|
89
148
|
return inferOrientation(this.#resolveXType(rawXType, yType), yType)
|
|
90
149
|
})
|
|
91
150
|
|
|
151
|
+
// True when the x-channel is the categorical (band) axis — a string category, or a numeric
|
|
152
|
+
// category a bar/box/violin/jitter geom bands (incl. a race's rank via continuousCategory).
|
|
153
|
+
// Only such charts flip; a plain scatter (both continuous, no banding geom) is false → the
|
|
154
|
+
// orientation is a no-op for the scales.
|
|
155
|
+
#bandIsX = $derived.by(() => {
|
|
156
|
+
const x = this.#effectiveChannels.x
|
|
157
|
+
const y = this.#effectiveChannels.y
|
|
158
|
+
if (!x || !y) return false
|
|
159
|
+
// Resolved type: a bar/box/violin/jitter geom bands a numeric x (e.g. year, week),
|
|
160
|
+
// so those NUMERIC categories are flippable too — not just string categories.
|
|
161
|
+
return this.#resolveXType(inferFieldType(this.#data, x), inferFieldType(this.#data, y)) === 'band'
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Flip = render a category-on-x chart horizontally: the category (x) axis stands up on the
|
|
165
|
+
// vertical screen and the value (y) axis runs along the horizontal screen. x/y channels are
|
|
166
|
+
// unchanged — only the screen mapping rotates.
|
|
167
|
+
#flipped = $derived(this.orientation === 'horizontal' && this.#bandIsX)
|
|
168
|
+
|
|
92
169
|
colorScaleType = $derived.by(() => {
|
|
93
170
|
const field = this.#effectiveChannels.color
|
|
94
171
|
if (!field) return 'categorical'
|
|
@@ -118,15 +195,30 @@ export class PlotState {
|
|
|
118
195
|
if (!field) return null
|
|
119
196
|
const datasets =
|
|
120
197
|
this.#geoms.length > 0 ? this.#geoms.map((g) => this.geomData(g.id)) : [this.#rawData]
|
|
121
|
-
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
198
|
+
// includeZero applies to the VALUE axis. When flipped, x is the category axis, not
|
|
199
|
+
// the value axis, so it must NOT include zero.
|
|
200
|
+
const includeZero = this.orientation === 'horizontal' && !this.#flipped
|
|
201
|
+
// Force scaleBand when x is the categorical axis (incl. flipped, where numeric categories
|
|
202
|
+
// must stay a band). Exception: a CONTINUOUS category axis (a bar race's tweened rank) is
|
|
203
|
+
// kept LINEAR so fractional positions tween smoothly — buildBars positions it directly.
|
|
204
|
+
const hasBandGeom = this.#geoms.some((g) => CATEGORICAL_X.has(g.type))
|
|
205
|
+
const bandX =
|
|
206
|
+
hasBandGeom &&
|
|
207
|
+
!this.#continuousCategory &&
|
|
208
|
+
(this.orientation !== 'horizontal' || this.#flipped)
|
|
209
|
+
// Flip: the category (x) axis stands up on the vertical screen → range over height.
|
|
210
|
+
const range = this.#flipped ? [this.#innerHeight, 0] : undefined
|
|
211
|
+
// Sort the band domain by aggregated value when requested (bars by size); an explicit
|
|
212
|
+
// xDomain override wins.
|
|
213
|
+
const domain = this.#xDomain ?? (bandX ? this.#sortedBandDomain(datasets) : null) ?? undefined
|
|
126
214
|
const base = buildUnifiedXScale(datasets, field, this.#innerWidth, {
|
|
127
|
-
domain
|
|
215
|
+
domain,
|
|
128
216
|
includeZero,
|
|
129
|
-
band: bandX
|
|
217
|
+
band: bandX,
|
|
218
|
+
range,
|
|
219
|
+
// A continuous category axis (bar-race rank) uses a deliberately padded domain —
|
|
220
|
+
// don't nice() it back to whole numbers.
|
|
221
|
+
nice: !this.#continuousCategory
|
|
130
222
|
})
|
|
131
223
|
return this.#zoomTransform && typeof base?.bandwidth !== 'function'
|
|
132
224
|
? this.#zoomTransform.rescaleX(base)
|
|
@@ -139,15 +231,20 @@ export class PlotState {
|
|
|
139
231
|
if (!boxGeom) return null
|
|
140
232
|
const boxData = this.geomData(boxGeom.id)
|
|
141
233
|
const isValid = (v) => v !== null && v !== undefined && !isNaN(v)
|
|
142
|
-
const
|
|
143
|
-
const
|
|
234
|
+
const flatOutliers = boxData.flatMap((d) => d.outliers ?? []).filter(isValid)
|
|
235
|
+
const mins = [...boxData.map((d) => d.iqr_min).filter(isValid), ...flatOutliers]
|
|
236
|
+
const maxs = [...boxData.map((d) => d.iqr_max).filter(isValid), ...flatOutliers]
|
|
144
237
|
return mins.length > 0 && maxs.length > 0 ? [Math.min(...mins), Math.max(...maxs)] : null
|
|
145
238
|
}
|
|
146
239
|
|
|
147
240
|
// For stacked bars, compute y domain from per-x column totals.
|
|
148
241
|
#resolveStackDomain(field) {
|
|
149
|
-
const stackGeom = this.#geoms.find(
|
|
242
|
+
const stackGeom = this.#geoms.find(
|
|
243
|
+
(g) => g.options?.stack || g.options?.position === 'stack' || g.options?.position === 'fill'
|
|
244
|
+
)
|
|
150
245
|
if (!stackGeom) return null
|
|
246
|
+
// position='fill' normalizes each column to 100% → the value domain is [0,1].
|
|
247
|
+
if (stackGeom.options?.position === 'fill') return [0, 1]
|
|
151
248
|
const xField = this.#effectiveChannels.x
|
|
152
249
|
const stackData = this.geomData(stackGeom.id)
|
|
153
250
|
if (!xField || stackData.length === 0) return null
|
|
@@ -157,8 +254,13 @@ export class PlotState {
|
|
|
157
254
|
const colorField = isLiteralColor(this.#effectiveChannels.color)
|
|
158
255
|
? null
|
|
159
256
|
: this.#effectiveChannels.color
|
|
257
|
+
const fillField = isLiteralColor(this.#effectiveChannels.fill)
|
|
258
|
+
? null
|
|
259
|
+
: this.#effectiveChannels.fill
|
|
160
260
|
const patternField = this.#effectiveChannels.pattern
|
|
161
|
-
|
|
261
|
+
// Mirror buildStackedBars/subBandFields (group=fill first, then color, then pattern).
|
|
262
|
+
const stackField =
|
|
263
|
+
[fillField, colorField, patternField].find((f) => f && f !== xField) ?? (fillField ?? colorField)
|
|
162
264
|
const lookup = new SvelteMap()
|
|
163
265
|
for (const d of stackData) {
|
|
164
266
|
const xVal = d[xField]
|
|
@@ -181,9 +283,13 @@ export class PlotState {
|
|
|
181
283
|
if (!field) return null
|
|
182
284
|
const datasets =
|
|
183
285
|
this.#geoms.length > 0 ? this.#geoms.map((g) => this.geomData(g.id)) : [this.#rawData]
|
|
184
|
-
|
|
286
|
+
// includeZero applies to the VALUE axis: vertical charts (value on y) and flipped
|
|
287
|
+
// charts (value still y, but now the horizontal screen axis) both want a 0 baseline.
|
|
288
|
+
const includeZero = this.orientation === 'vertical' || this.#flipped
|
|
185
289
|
const yDomain = this.#yDomain ?? this.#resolveBoxDomain() ?? this.#resolveStackDomain(field)
|
|
186
|
-
|
|
290
|
+
// Flip: the value (y) axis runs along the horizontal screen → range over width.
|
|
291
|
+
const range = this.#flipped ? [0, this.#innerWidth] : undefined
|
|
292
|
+
const base = buildUnifiedYScale(datasets, field, this.#innerHeight, { domain: yDomain, includeZero, range })
|
|
187
293
|
return this.#zoomTransform ? this.#zoomTransform.rescaleY(base) : base
|
|
188
294
|
})
|
|
189
295
|
|
|
@@ -200,7 +306,7 @@ export class PlotState {
|
|
|
200
306
|
const literal = new Map([[null, { fill: field, stroke: field }]])
|
|
201
307
|
return literal
|
|
202
308
|
}
|
|
203
|
-
const values = this.#colorDomain ??
|
|
309
|
+
const values = this.#colorDomain ?? this.#sharedColorValues()
|
|
204
310
|
// No color channel but data exists → use first preset color for single-series rendering.
|
|
205
311
|
// This prevents geoms from falling back to gray (#888) on charts with no fill channel.
|
|
206
312
|
if (values.length === 0 && this.#data.length > 0) return assignColors([null], this.#mode, this.#chartPreset)
|
|
@@ -228,6 +334,10 @@ export class PlotState {
|
|
|
228
334
|
colorField = $derived(
|
|
229
335
|
isLiteralColor(this.#effectiveChannels.color) ? null : this.#effectiveChannels.color
|
|
230
336
|
)
|
|
337
|
+
// Fill field (interior aesthetic), parallel to colorField. Null for a literal CSS color.
|
|
338
|
+
fillField = $derived(
|
|
339
|
+
isLiteralColor(this.#effectiveChannels.fill) ? null : this.#effectiveChannels.fill
|
|
340
|
+
)
|
|
231
341
|
patternField = $derived(this.#effectiveChannels.pattern)
|
|
232
342
|
symbolField = $derived(this.#effectiveChannels.symbol)
|
|
233
343
|
|
|
@@ -283,6 +393,8 @@ export class PlotState {
|
|
|
283
393
|
this.#axisOffset = config.axisOffset ?? 0
|
|
284
394
|
this.#marginOverride = config.margin ?? undefined
|
|
285
395
|
this.#orientationOverride = config.orientation ?? undefined
|
|
396
|
+
this.#continuousCategory = config.continuousCategory ?? false
|
|
397
|
+
this.#sort = config.sort ?? undefined
|
|
286
398
|
}
|
|
287
399
|
|
|
288
400
|
update(config) {
|
|
@@ -309,6 +421,8 @@ export class PlotState {
|
|
|
309
421
|
if (config.axisOffset !== undefined) this.#axisOffset = config.axisOffset
|
|
310
422
|
this.#marginOverride = config.margin ?? undefined
|
|
311
423
|
this.#orientationOverride = config.orientation ?? undefined
|
|
424
|
+
this.#continuousCategory = config.continuousCategory ?? false
|
|
425
|
+
this.#sort = config.sort ?? undefined
|
|
312
426
|
}
|
|
313
427
|
|
|
314
428
|
registerGeom(config) {
|
|
@@ -360,6 +474,36 @@ export class PlotState {
|
|
|
360
474
|
// array separately, so their elements are not ===. Same content, aligned identity.
|
|
361
475
|
return this.#rawData
|
|
362
476
|
}
|
|
477
|
+
/** @returns {{ x?: string, y?: string, color?: string, fill?: string, pattern?: string, symbol?: string }} */
|
|
478
|
+
get channels() {
|
|
479
|
+
return this.#channels
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ─── Orientation helpers (horizontal / axis-flip) ──────────────────────────
|
|
483
|
+
// True when the chart is rendered horizontally (category axis stood up on the
|
|
484
|
+
// vertical screen, value axis along the horizontal screen). x/y channels unchanged.
|
|
485
|
+
get isFlipped() {
|
|
486
|
+
return this.#flipped
|
|
487
|
+
}
|
|
488
|
+
// True when the category (x) axis is a continuous position scale (kept linear) rather than a
|
|
489
|
+
// band — a bar-chart race's tweened rank. buildBars uses continuous positioning for it.
|
|
490
|
+
get continuousCategory() {
|
|
491
|
+
return this.#continuousCategory
|
|
492
|
+
}
|
|
493
|
+
// Map abstract (x-channel, y-channel) scale outputs to screen coords. When flipped,
|
|
494
|
+
// the two screen axes swap. Geoms compute u = xScale(d[x]), v = yScale(d[y]) then
|
|
495
|
+
// `const { x, y } = plotState.place(u, v)`.
|
|
496
|
+
place(u, v) {
|
|
497
|
+
return this.#flipped ? { x: v, y: u } : { x: u, y: v }
|
|
498
|
+
}
|
|
499
|
+
// The categorical (band) scale and the continuous (value) scale, regardless of
|
|
500
|
+
// orientation — their ranges are already oriented to the correct screen axis.
|
|
501
|
+
get bandScale() {
|
|
502
|
+
return this.#bandIsX ? this.xScale : this.yScale
|
|
503
|
+
}
|
|
504
|
+
get valueScale() {
|
|
505
|
+
return this.#bandIsX ? this.yScale : this.xScale
|
|
506
|
+
}
|
|
363
507
|
get margin() {
|
|
364
508
|
return this.#effectiveMargin
|
|
365
509
|
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setContext, untrack } from 'svelte'
|
|
3
|
+
import type { Snippet } from 'svelte'
|
|
4
|
+
import { PlotState } from './PlotState.svelte.js'
|
|
5
|
+
import DefinePatterns from './patterns/DefinePatterns.svelte'
|
|
6
|
+
|
|
7
|
+
type Row = Record<string, unknown>
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
/** Full PlotState config. `config.width` is the initial/fallback width — PlotSurface
|
|
11
|
+
* owns the responsive width via a ResizeObserver and overrides it. */
|
|
12
|
+
config?: Record<string, unknown>
|
|
13
|
+
/** Animate marks on data/flip changes (data-plot-animate). Enabled one frame after the
|
|
14
|
+
* width settles so the initial layout paints un-animated. */
|
|
15
|
+
animate?: boolean
|
|
16
|
+
/** Enable d3-zoom pan/zoom on the svg. */
|
|
17
|
+
zoom?: boolean
|
|
18
|
+
ariaLabel?: string
|
|
19
|
+
/** SVG <desc> for screen readers. */
|
|
20
|
+
summary?: string
|
|
21
|
+
/** Two-way selection (row references), synced with the PlotState selection. */
|
|
22
|
+
selected?: Row[]
|
|
23
|
+
/** Canvas content (geoms/grid/axes) — rendered inside the svg. */
|
|
24
|
+
children?: Snippet
|
|
25
|
+
/** HTML overlays (legend/tooltip/a11y table) — rendered outside the svg but INSIDE the
|
|
26
|
+
* plot-state context, so they can read plotState. */
|
|
27
|
+
overlay?: Snippet
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
config = {},
|
|
32
|
+
animate = true,
|
|
33
|
+
zoom = false,
|
|
34
|
+
ariaLabel = 'Chart visualization',
|
|
35
|
+
summary = '',
|
|
36
|
+
selected = $bindable([]),
|
|
37
|
+
children,
|
|
38
|
+
overlay
|
|
39
|
+
}: Props = $props()
|
|
40
|
+
|
|
41
|
+
// ── Responsive width: observe the container, use its pixel width so the chart fills the stage.
|
|
42
|
+
let containerEl = $state<HTMLDivElement | null>(null)
|
|
43
|
+
let svgEl = $state<SVGSVGElement | null>(null)
|
|
44
|
+
let observedWidth = $state(0)
|
|
45
|
+
$effect(() => {
|
|
46
|
+
if (!containerEl) return
|
|
47
|
+
const ro = new ResizeObserver((entries) => {
|
|
48
|
+
const w = Math.floor(entries[0].contentRect.width)
|
|
49
|
+
if (w > 0) observedWidth = w
|
|
50
|
+
})
|
|
51
|
+
ro.observe(containerEl)
|
|
52
|
+
return () => ro.disconnect()
|
|
53
|
+
})
|
|
54
|
+
const fallbackWidth = $derived(Number(config.width) || 600)
|
|
55
|
+
const effectiveWidth = $derived(observedWidth > 0 ? observedWidth : fallbackWidth)
|
|
56
|
+
const svgHeight = $derived(Number(config.height) || 400)
|
|
57
|
+
const mode = $derived(config.mode as 'light' | 'dark' | undefined)
|
|
58
|
+
|
|
59
|
+
// ── The single PlotState + context. config.width is overridden with the observed width.
|
|
60
|
+
const fullConfig = () => ({ ...config, width: effectiveWidth })
|
|
61
|
+
const plotState = untrack(() => new PlotState(fullConfig()))
|
|
62
|
+
setContext('plot-state', plotState)
|
|
63
|
+
$effect(() => {
|
|
64
|
+
plotState.update(fullConfig())
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// ── Selection sync (external prop ↔ PlotState). Guarded to avoid an update loop.
|
|
68
|
+
$effect(() => {
|
|
69
|
+
plotState.setSelected(selected)
|
|
70
|
+
})
|
|
71
|
+
$effect(() => {
|
|
72
|
+
const rows = plotState.selectedRows
|
|
73
|
+
const same = rows.length === selected.length && rows.every((r, i) => r === selected[i])
|
|
74
|
+
if (!same) selected = rows
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// ── Enable data-change / flip animation one frame after the width first settles, so the
|
|
78
|
+
// initial (possibly reflowed) layout paints without animating.
|
|
79
|
+
let animateReady = $state(false)
|
|
80
|
+
$effect(() => {
|
|
81
|
+
if (!animate || animateReady || observedWidth <= 0) return
|
|
82
|
+
const raf = requestAnimationFrame(() => {
|
|
83
|
+
animateReady = true
|
|
84
|
+
})
|
|
85
|
+
return () => cancelAnimationFrame(raf)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// ── Pan/zoom (d3-zoom/d3-selection are client-only → dynamic import).
|
|
89
|
+
$effect(() => {
|
|
90
|
+
if (!zoom || !svgEl) return
|
|
91
|
+
let cancelled = false
|
|
92
|
+
let detach: (() => void) | undefined
|
|
93
|
+
Promise.all([import('d3-zoom'), import('d3-selection')]).then(
|
|
94
|
+
([{ zoom: d3Zoom }, { select }]) => {
|
|
95
|
+
if (cancelled || !svgEl) return
|
|
96
|
+
const zoomBehavior = d3Zoom<SVGSVGElement, unknown>()
|
|
97
|
+
.scaleExtent([1, 8])
|
|
98
|
+
.on('zoom', (event) => plotState.applyZoom(event.transform))
|
|
99
|
+
const sel = select(svgEl)
|
|
100
|
+
sel.call(zoomBehavior)
|
|
101
|
+
detach = () => sel.on('.zoom', null)
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
return () => {
|
|
105
|
+
cancelled = true
|
|
106
|
+
detach?.()
|
|
107
|
+
plotState.resetZoom()
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<div class="plot-surface" bind:this={containerEl}>
|
|
113
|
+
<svg
|
|
114
|
+
bind:this={svgEl}
|
|
115
|
+
width={effectiveWidth}
|
|
116
|
+
height={svgHeight}
|
|
117
|
+
viewBox="0 0 {effectiveWidth} {svgHeight}"
|
|
118
|
+
role="img"
|
|
119
|
+
aria-label={ariaLabel}
|
|
120
|
+
data-plot-root
|
|
121
|
+
data-mode={mode}
|
|
122
|
+
data-plot-animate={animate && animateReady ? '' : undefined}
|
|
123
|
+
style:cursor={zoom ? 'grab' : undefined}
|
|
124
|
+
>
|
|
125
|
+
{#if summary}
|
|
126
|
+
<desc>{summary}</desc>
|
|
127
|
+
{/if}
|
|
128
|
+
<!-- SVG <pattern> defs for texture fills (Bar/Area/Arc/Box/Violin `pattern` channel) -->
|
|
129
|
+
<DefinePatterns />
|
|
130
|
+
<g
|
|
131
|
+
class="plot-canvas"
|
|
132
|
+
transform="translate({plotState.margin.left}, {plotState.margin.top})"
|
|
133
|
+
data-plot-canvas
|
|
134
|
+
>
|
|
135
|
+
{@render children?.()}
|
|
136
|
+
</g>
|
|
137
|
+
</svg>
|
|
138
|
+
<!-- HTML overlays (legend/tooltip/a11y table) — inside the context, outside the svg. -->
|
|
139
|
+
{@render overlay?.()}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<style>
|
|
143
|
+
.plot-surface {
|
|
144
|
+
width: 100%;
|
|
145
|
+
}
|
|
146
|
+
.plot-surface svg {
|
|
147
|
+
display: block;
|
|
148
|
+
width: 100%;
|
|
149
|
+
height: auto;
|
|
150
|
+
overflow: visible;
|
|
151
|
+
}
|
|
152
|
+
.plot-canvas {
|
|
153
|
+
pointer-events: all;
|
|
154
|
+
}
|
|
155
|
+
/* Mode token-lock colour (only when data-mode is set explicitly). */
|
|
156
|
+
[data-plot-root][data-mode='light'] {
|
|
157
|
+
color: rgba(var(--color-surface-700, 59, 65, 77), 1);
|
|
158
|
+
}
|
|
159
|
+
[data-plot-root][data-mode='dark'] {
|
|
160
|
+
color: rgba(var(--color-surface-200, 224, 224, 224), 1);
|
|
161
|
+
}
|
|
162
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Row = Record<string, unknown>;
|
|
3
|
+
type Props = {
|
|
4
|
+
/** Full PlotState config. `config.width` is the initial/fallback width — PlotSurface
|
|
5
|
+
* owns the responsive width via a ResizeObserver and overrides it. */
|
|
6
|
+
config?: Record<string, unknown>;
|
|
7
|
+
/** Animate marks on data/flip changes (data-plot-animate). Enabled one frame after the
|
|
8
|
+
* width settles so the initial layout paints un-animated. */
|
|
9
|
+
animate?: boolean;
|
|
10
|
+
/** Enable d3-zoom pan/zoom on the svg. */
|
|
11
|
+
zoom?: boolean;
|
|
12
|
+
ariaLabel?: string;
|
|
13
|
+
/** SVG <desc> for screen readers. */
|
|
14
|
+
summary?: string;
|
|
15
|
+
/** Two-way selection (row references), synced with the PlotState selection. */
|
|
16
|
+
selected?: Row[];
|
|
17
|
+
/** Canvas content (geoms/grid/axes) — rendered inside the svg. */
|
|
18
|
+
children?: Snippet;
|
|
19
|
+
/** HTML overlays (legend/tooltip/a11y table) — rendered outside the svg but INSIDE the
|
|
20
|
+
* plot-state context, so they can read plotState. */
|
|
21
|
+
overlay?: Snippet;
|
|
22
|
+
};
|
|
23
|
+
declare const PlotSurface: import("svelte").Component<Props, {}, "selected">;
|
|
24
|
+
type PlotSurface = ReturnType<typeof PlotSurface>;
|
|
25
|
+
export default PlotSurface;
|
|
@@ -14,12 +14,18 @@
|
|
|
14
14
|
stat?: string
|
|
15
15
|
curve?: 'linear' | 'smooth' | 'step'
|
|
16
16
|
pattern?: string
|
|
17
|
+
/** @deprecated use `position="stack"`. */
|
|
17
18
|
stack?: boolean
|
|
19
|
+
/** Multi-series arrangement: 'stack' | 'fill' | 'identity' (default). */
|
|
20
|
+
position?: 'stack' | 'fill' | 'identity'
|
|
21
|
+
/** Fixed area opacity 0–1. */
|
|
22
|
+
alpha?: number
|
|
18
23
|
width?: number
|
|
19
24
|
height?: number
|
|
20
25
|
mode?: 'light' | 'dark'
|
|
21
26
|
grid?: boolean | 'x' | 'y' | 'both'
|
|
22
27
|
legend?: boolean
|
|
28
|
+
tooltip?: boolean | ((data: Row) => string)
|
|
23
29
|
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean)
|
|
24
30
|
trend?: Method | Method[]
|
|
25
31
|
xFormat?: Format
|
|
@@ -41,11 +47,14 @@
|
|
|
41
47
|
curve = undefined,
|
|
42
48
|
pattern = undefined,
|
|
43
49
|
stack = false,
|
|
50
|
+
position = undefined,
|
|
51
|
+
alpha = undefined,
|
|
44
52
|
width = 600,
|
|
45
53
|
height = 400,
|
|
46
|
-
mode =
|
|
54
|
+
mode = undefined,
|
|
47
55
|
grid = true,
|
|
48
56
|
legend = false,
|
|
57
|
+
tooltip = false,
|
|
49
58
|
highlight = undefined,
|
|
50
59
|
trend = undefined,
|
|
51
60
|
xFormat = undefined,
|
|
@@ -68,6 +77,7 @@
|
|
|
68
77
|
{mode}
|
|
69
78
|
{grid}
|
|
70
79
|
{legend}
|
|
80
|
+
{tooltip}
|
|
71
81
|
{highlight}
|
|
72
82
|
{trend}
|
|
73
83
|
{xFormat}
|
|
@@ -79,5 +89,5 @@
|
|
|
79
89
|
{selectable}
|
|
80
90
|
bind:selected
|
|
81
91
|
>
|
|
82
|
-
<Area {x} {y} color={fill} {pattern} {stat} options={{ curve, stack }} />
|
|
92
|
+
<Area {x} {y} color={fill} {pattern} {stat} {position} {alpha} options={{ curve, stack }} />
|
|
83
93
|
</Plot>
|
|
@@ -12,12 +12,18 @@ type Props = {
|
|
|
12
12
|
stat?: string;
|
|
13
13
|
curve?: 'linear' | 'smooth' | 'step';
|
|
14
14
|
pattern?: string;
|
|
15
|
+
/** @deprecated use `position="stack"`. */
|
|
15
16
|
stack?: boolean;
|
|
17
|
+
/** Multi-series arrangement: 'stack' | 'fill' | 'identity' (default). */
|
|
18
|
+
position?: 'stack' | 'fill' | 'identity';
|
|
19
|
+
/** Fixed area opacity 0–1. */
|
|
20
|
+
alpha?: number;
|
|
16
21
|
width?: number;
|
|
17
22
|
height?: number;
|
|
18
23
|
mode?: 'light' | 'dark';
|
|
19
24
|
grid?: boolean | 'x' | 'y' | 'both';
|
|
20
25
|
legend?: boolean;
|
|
26
|
+
tooltip?: boolean | ((data: Row) => string);
|
|
21
27
|
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean);
|
|
22
28
|
trend?: Method | Method[];
|
|
23
29
|
xFormat?: Format;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
type Row = Record<string, unknown>
|
|
6
6
|
type Format = (v: unknown) => string
|
|
7
|
+
type Method = string | number | { type: string; [k: string]: unknown }
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
9
10
|
data?: Row[]
|
|
@@ -17,14 +18,24 @@
|
|
|
17
18
|
grid?: boolean
|
|
18
19
|
legend?: boolean
|
|
19
20
|
stat?: string
|
|
21
|
+
/** @deprecated use `position="stack"`. */
|
|
20
22
|
stack?: boolean
|
|
23
|
+
/** Multi-series arrangement: 'stack' | 'dodge' (default) | 'fill' | 'identity'. */
|
|
24
|
+
position?: 'stack' | 'dodge' | 'fill' | 'identity'
|
|
25
|
+
/** Fixed bar opacity 0–1. */
|
|
26
|
+
alpha?: number
|
|
21
27
|
label?: boolean | string | ((data: Row) => unknown)
|
|
22
28
|
tooltip?: boolean | ((data: Row) => string)
|
|
29
|
+
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean)
|
|
30
|
+
trend?: Method | Method[]
|
|
23
31
|
xFormat?: Format
|
|
24
32
|
yFormat?: Format
|
|
25
33
|
xTicks?: number
|
|
26
34
|
yTicks?: number
|
|
27
35
|
minorTicks?: boolean
|
|
36
|
+
onselect?: (detail: unknown) => void
|
|
37
|
+
selectable?: boolean
|
|
38
|
+
selected?: Row[]
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
let {
|
|
@@ -35,21 +46,46 @@
|
|
|
35
46
|
pattern = undefined,
|
|
36
47
|
width = 600,
|
|
37
48
|
height = 400,
|
|
38
|
-
mode =
|
|
49
|
+
mode = undefined,
|
|
39
50
|
grid = true,
|
|
40
51
|
legend = false,
|
|
41
52
|
stat = 'identity',
|
|
42
53
|
stack = false,
|
|
54
|
+
position = undefined,
|
|
55
|
+
alpha = undefined,
|
|
43
56
|
label = false,
|
|
44
57
|
tooltip = false,
|
|
58
|
+
highlight = undefined,
|
|
59
|
+
trend = undefined,
|
|
45
60
|
xFormat = undefined,
|
|
46
61
|
yFormat = undefined,
|
|
47
62
|
xTicks = undefined,
|
|
48
63
|
yTicks = undefined,
|
|
49
|
-
minorTicks = false
|
|
64
|
+
minorTicks = false,
|
|
65
|
+
onselect = undefined,
|
|
66
|
+
selectable = false,
|
|
67
|
+
selected = $bindable([])
|
|
50
68
|
}: Props = $props()
|
|
51
69
|
</script>
|
|
52
70
|
|
|
53
|
-
<Plot
|
|
54
|
-
|
|
71
|
+
<Plot
|
|
72
|
+
{data}
|
|
73
|
+
{width}
|
|
74
|
+
{height}
|
|
75
|
+
{mode}
|
|
76
|
+
{grid}
|
|
77
|
+
{legend}
|
|
78
|
+
{tooltip}
|
|
79
|
+
{highlight}
|
|
80
|
+
{trend}
|
|
81
|
+
{xFormat}
|
|
82
|
+
{yFormat}
|
|
83
|
+
{xTicks}
|
|
84
|
+
{yTicks}
|
|
85
|
+
{minorTicks}
|
|
86
|
+
{onselect}
|
|
87
|
+
{selectable}
|
|
88
|
+
bind:selected
|
|
89
|
+
>
|
|
90
|
+
<Bar {x} {y} color={fill} {pattern} {label} {stat} {position} {alpha} options={{ stack }} />
|
|
55
91
|
</Plot>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
type Row = Record<string, unknown>;
|
|
2
2
|
type Format = (v: unknown) => string;
|
|
3
|
+
type Method = string | number | {
|
|
4
|
+
type: string;
|
|
5
|
+
[k: string]: unknown;
|
|
6
|
+
};
|
|
3
7
|
type Props = {
|
|
4
8
|
data?: Row[];
|
|
5
9
|
x?: string;
|
|
@@ -12,15 +16,25 @@ type Props = {
|
|
|
12
16
|
grid?: boolean;
|
|
13
17
|
legend?: boolean;
|
|
14
18
|
stat?: string;
|
|
19
|
+
/** @deprecated use `position="stack"`. */
|
|
15
20
|
stack?: boolean;
|
|
21
|
+
/** Multi-series arrangement: 'stack' | 'dodge' (default) | 'fill' | 'identity'. */
|
|
22
|
+
position?: 'stack' | 'dodge' | 'fill' | 'identity';
|
|
23
|
+
/** Fixed bar opacity 0–1. */
|
|
24
|
+
alpha?: number;
|
|
16
25
|
label?: boolean | string | ((data: Row) => unknown);
|
|
17
26
|
tooltip?: boolean | ((data: Row) => string);
|
|
27
|
+
highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean);
|
|
28
|
+
trend?: Method | Method[];
|
|
18
29
|
xFormat?: Format;
|
|
19
30
|
yFormat?: Format;
|
|
20
31
|
xTicks?: number;
|
|
21
32
|
yTicks?: number;
|
|
22
33
|
minorTicks?: boolean;
|
|
34
|
+
onselect?: (detail: unknown) => void;
|
|
35
|
+
selectable?: boolean;
|
|
36
|
+
selected?: Row[];
|
|
23
37
|
};
|
|
24
|
-
declare const BarChart: import("svelte").Component<Props, {}, "">;
|
|
38
|
+
declare const BarChart: import("svelte").Component<Props, {}, "selected">;
|
|
25
39
|
type BarChart = ReturnType<typeof BarChart>;
|
|
26
40
|
export default BarChart;
|