@zuilib/charts 0.3.0
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/README.md +140 -0
- package/dist/authoring.d.ts +47 -0
- package/dist/authoring.js +11 -0
- package/dist/chart-defaults.d.ts +60 -0
- package/dist/chart-defaults.js +24 -0
- package/dist/chart-frame-B9fxUxGG.d.ts +90 -0
- package/dist/chart-frame.d.ts +4 -0
- package/dist/chart-frame.js +14 -0
- package/dist/chart-legend.d.ts +25 -0
- package/dist/chart-legend.js +13 -0
- package/dist/chart-spec-CH0tAkLG.d.ts +226 -0
- package/dist/chart-tooltip.d.ts +44 -0
- package/dist/chart-tooltip.js +11 -0
- package/dist/chart.d.ts +78 -0
- package/dist/chart.js +24 -0
- package/dist/chunk-3VGLTPGL.js +19 -0
- package/dist/chunk-6QTIAMA3.js +169 -0
- package/dist/chunk-7WABBHMT.js +88 -0
- package/dist/chunk-7WJ26NHR.js +48 -0
- package/dist/chunk-BFTWWI46.js +148 -0
- package/dist/chunk-D7K23UFP.js +43 -0
- package/dist/chunk-G4FXJJ66.js +169 -0
- package/dist/chunk-HNJKYOPE.js +45 -0
- package/dist/chunk-IPOBQIYU.js +13 -0
- package/dist/chunk-MJDIQVJX.js +148 -0
- package/dist/chunk-PV27RZSV.js +61 -0
- package/dist/chunk-SHQZRND7.js +83 -0
- package/dist/chunk-UN3XPBZ5.js +17 -0
- package/dist/chunk-WVWGHGZE.js +90 -0
- package/dist/chunk-YMPZODMC.js +1084 -0
- package/dist/cross-filter.d.ts +62 -0
- package/dist/cross-filter.js +13 -0
- package/dist/format.d.ts +17 -0
- package/dist/format.js +7 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +93 -0
- package/dist/interop.d.ts +46 -0
- package/dist/interop.js +12 -0
- package/dist/registry.d.ts +111 -0
- package/dist/registry.js +25 -0
- package/package.json +98 -0
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
import {
|
|
2
|
+
activeCategoryValues,
|
|
3
|
+
categoryOf,
|
|
4
|
+
markDimOpacity,
|
|
5
|
+
reportMarkClick
|
|
6
|
+
} from "./chunk-HNJKYOPE.js";
|
|
7
|
+
import {
|
|
8
|
+
warnOnce
|
|
9
|
+
} from "./chunk-3VGLTPGL.js";
|
|
10
|
+
import {
|
|
11
|
+
ChartTooltipPanel
|
|
12
|
+
} from "./chunk-PV27RZSV.js";
|
|
13
|
+
import {
|
|
14
|
+
ChartLegendList
|
|
15
|
+
} from "./chunk-SHQZRND7.js";
|
|
16
|
+
import {
|
|
17
|
+
cartesianGridDefaults,
|
|
18
|
+
chartColorVar,
|
|
19
|
+
seriesColorVar,
|
|
20
|
+
tooltipCrosshairDefaults,
|
|
21
|
+
tooltipCursorDefaults,
|
|
22
|
+
xAxisDefaults,
|
|
23
|
+
yAxisDefaults
|
|
24
|
+
} from "./chunk-7WJ26NHR.js";
|
|
25
|
+
|
|
26
|
+
// src/charts/area.tsx
|
|
27
|
+
import { Area, AreaChart } from "recharts";
|
|
28
|
+
|
|
29
|
+
// src/charts/cartesian.tsx
|
|
30
|
+
import { Brush, CartesianGrid, Legend, ReferenceLine, Tooltip, XAxis, YAxis } from "recharts";
|
|
31
|
+
import { jsx } from "react/jsx-runtime";
|
|
32
|
+
function stackIdOf(series, stacked) {
|
|
33
|
+
return series.stackId ?? (stacked ? "stack" : void 0);
|
|
34
|
+
}
|
|
35
|
+
function valueFormatOf(spec, stacked) {
|
|
36
|
+
return stacked === "percent" ? "percent" : spec.yAxis?.format ?? "number";
|
|
37
|
+
}
|
|
38
|
+
function renderXAxis(xKey, spec, formats, options) {
|
|
39
|
+
const axis = spec.xAxis ?? {};
|
|
40
|
+
const numeric = options?.numeric || axis.scale !== void 0 && axis.scale !== "band";
|
|
41
|
+
return /* @__PURE__ */ jsx(
|
|
42
|
+
XAxis,
|
|
43
|
+
{
|
|
44
|
+
dataKey: xKey,
|
|
45
|
+
...xAxisDefaults,
|
|
46
|
+
type: numeric ? "number" : "category",
|
|
47
|
+
...axis.scale === "log" ? { scale: "log", domain: ["auto", "auto"] } : {},
|
|
48
|
+
...axis.scale === "time" ? { domain: ["dataMin", "dataMax"] } : {},
|
|
49
|
+
hide: axis.hidden,
|
|
50
|
+
tickCount: axis.tickCount,
|
|
51
|
+
tickFormatter: formats.resolve(axis.format, "none"),
|
|
52
|
+
label: axis.label ? { value: axis.label, position: "insideBottom", offset: -4, fill: "var(--muted-foreground)" } : void 0
|
|
53
|
+
},
|
|
54
|
+
"x"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function renderYAxes(spec, formats, valueFormat, hasSecondary) {
|
|
58
|
+
const y = spec.yAxis ?? {};
|
|
59
|
+
const axes = [
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
YAxis,
|
|
62
|
+
{
|
|
63
|
+
yAxisId: "primary",
|
|
64
|
+
...yAxisDefaults,
|
|
65
|
+
hide: y.hidden,
|
|
66
|
+
domain: y.domain,
|
|
67
|
+
tickCount: y.tickCount,
|
|
68
|
+
tickFormatter: formats.resolve(y.format, valueFormat),
|
|
69
|
+
label: y.label ? { value: y.label, angle: -90, position: "insideLeft", fill: "var(--muted-foreground)" } : void 0
|
|
70
|
+
},
|
|
71
|
+
"y"
|
|
72
|
+
)
|
|
73
|
+
];
|
|
74
|
+
if (hasSecondary) {
|
|
75
|
+
const secondary = spec.secondaryAxis ?? {};
|
|
76
|
+
axes.push(
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
YAxis,
|
|
79
|
+
{
|
|
80
|
+
yAxisId: "secondary",
|
|
81
|
+
orientation: "right",
|
|
82
|
+
...yAxisDefaults,
|
|
83
|
+
hide: secondary.hidden,
|
|
84
|
+
domain: secondary.domain,
|
|
85
|
+
tickCount: secondary.tickCount,
|
|
86
|
+
tickFormatter: formats.resolve(secondary.format, "number"),
|
|
87
|
+
label: secondary.label ? { value: secondary.label, angle: 90, position: "insideRight", fill: "var(--muted-foreground)" } : void 0
|
|
88
|
+
},
|
|
89
|
+
"y-secondary"
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return axes;
|
|
94
|
+
}
|
|
95
|
+
function yAxisIdOf(series) {
|
|
96
|
+
return series.axis === "secondary" ? "secondary" : "primary";
|
|
97
|
+
}
|
|
98
|
+
function renderGrid(spec) {
|
|
99
|
+
if (spec.grid === false) return null;
|
|
100
|
+
const grid = typeof spec.grid === "object" ? spec.grid : {};
|
|
101
|
+
return /* @__PURE__ */ jsx(
|
|
102
|
+
CartesianGrid,
|
|
103
|
+
{
|
|
104
|
+
...cartesianGridDefaults,
|
|
105
|
+
horizontal: grid.horizontal ?? true,
|
|
106
|
+
vertical: grid.vertical ?? false
|
|
107
|
+
},
|
|
108
|
+
"grid"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
function renderReferenceLines(spec) {
|
|
112
|
+
return (spec.referenceLines ?? []).map((line, index) => /* @__PURE__ */ jsx(
|
|
113
|
+
ReferenceLine,
|
|
114
|
+
{
|
|
115
|
+
...line.axis === "x" ? { x: line.value } : { y: line.value },
|
|
116
|
+
yAxisId: line.axis === "y2" ? "secondary" : "primary",
|
|
117
|
+
stroke: line.paletteSlot ? chartColorVar(line.paletteSlot) : "var(--muted-foreground)",
|
|
118
|
+
strokeDasharray: line.dashed === false ? void 0 : "4 4",
|
|
119
|
+
label: line.label ? { value: line.label, position: line.axis === "x" ? "top" : "insideTopRight", fill: "var(--muted-foreground)", fontSize: 12 } : void 0
|
|
120
|
+
},
|
|
121
|
+
`ref-${line.axis}-${String(line.value)}-${index}`
|
|
122
|
+
));
|
|
123
|
+
}
|
|
124
|
+
function renderBrush(xKey, spec) {
|
|
125
|
+
if (!spec.brush) return null;
|
|
126
|
+
return /* @__PURE__ */ jsx(
|
|
127
|
+
Brush,
|
|
128
|
+
{
|
|
129
|
+
dataKey: xKey,
|
|
130
|
+
height: 24,
|
|
131
|
+
stroke: "var(--muted-foreground)",
|
|
132
|
+
fill: "var(--muted)",
|
|
133
|
+
travellerWidth: 8
|
|
134
|
+
},
|
|
135
|
+
"brush"
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
function legendEnabled(spec, entryCount) {
|
|
139
|
+
if (spec.legend === void 0) return entryCount >= 2;
|
|
140
|
+
return spec.legend !== false;
|
|
141
|
+
}
|
|
142
|
+
function renderLegend(spec, entryCount) {
|
|
143
|
+
if (!legendEnabled(spec, entryCount)) return null;
|
|
144
|
+
const placement = typeof spec.legend === "object" ? spec.legend.placement : "bottom";
|
|
145
|
+
return /* @__PURE__ */ jsx(Legend, { verticalAlign: placement, content: /* @__PURE__ */ jsx(ChartLegendList, {}) }, "legend");
|
|
146
|
+
}
|
|
147
|
+
function renderTooltip(spec, valueFormat, cursor) {
|
|
148
|
+
if (spec.tooltip === false) return null;
|
|
149
|
+
return /* @__PURE__ */ jsx(
|
|
150
|
+
Tooltip,
|
|
151
|
+
{
|
|
152
|
+
cursor,
|
|
153
|
+
content: /* @__PURE__ */ jsx(ChartTooltipPanel, { labelFormat: spec.xAxis?.format, valueFormat })
|
|
154
|
+
},
|
|
155
|
+
"tooltip"
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
function chartClickHandler(spec, rows, interaction) {
|
|
159
|
+
return (state, event) => {
|
|
160
|
+
const value = state?.activeLabel;
|
|
161
|
+
if (value === void 0 || value === null || value === "") return;
|
|
162
|
+
const row = rows.find((r) => r[spec.xKey] === value) ?? {};
|
|
163
|
+
const additive = Boolean(event?.shiftKey);
|
|
164
|
+
reportMarkClick(interaction, value, void 0, row, additive);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function warnOnSpecIssues(spec) {
|
|
168
|
+
const count = spec.series?.length ?? 0;
|
|
169
|
+
if (count > 8) {
|
|
170
|
+
warnOnce(
|
|
171
|
+
`series-${spec.type}-${count}`,
|
|
172
|
+
`@zuilib/charts: ${spec.type} spec declares ${count} series; the palette has 8 slots and never cycles \u2014 fold the rest into "Other" or use small multiples.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/charts/area.tsx
|
|
178
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
179
|
+
function AreaChartRenderer({
|
|
180
|
+
spec,
|
|
181
|
+
rows,
|
|
182
|
+
formats,
|
|
183
|
+
hiddenSeries,
|
|
184
|
+
markInteraction,
|
|
185
|
+
width,
|
|
186
|
+
height,
|
|
187
|
+
animate
|
|
188
|
+
}) {
|
|
189
|
+
warnOnSpecIssues(spec);
|
|
190
|
+
const hasSecondary = spec.series.some((s) => s.axis === "secondary");
|
|
191
|
+
const valueFormat = valueFormatOf(spec, spec.stacked);
|
|
192
|
+
return /* @__PURE__ */ jsxs(
|
|
193
|
+
AreaChart,
|
|
194
|
+
{
|
|
195
|
+
data: rows,
|
|
196
|
+
width,
|
|
197
|
+
height,
|
|
198
|
+
onClick: chartClickHandler(spec, rows, markInteraction),
|
|
199
|
+
stackOffset: spec.stacked === "percent" ? "expand" : void 0,
|
|
200
|
+
margin: { top: 8, bottom: 4 },
|
|
201
|
+
children: [
|
|
202
|
+
renderGrid(spec),
|
|
203
|
+
renderXAxis(spec.xKey, spec, formats),
|
|
204
|
+
renderYAxes(spec, formats, valueFormat, hasSecondary),
|
|
205
|
+
renderTooltip(spec, valueFormat, tooltipCrosshairDefaults),
|
|
206
|
+
renderLegend(spec, spec.series.length),
|
|
207
|
+
renderReferenceLines(spec),
|
|
208
|
+
spec.series.map((series) => /* @__PURE__ */ jsx2(
|
|
209
|
+
Area,
|
|
210
|
+
{
|
|
211
|
+
yAxisId: yAxisIdOf(series),
|
|
212
|
+
dataKey: series.field,
|
|
213
|
+
name: series.label ?? series.field,
|
|
214
|
+
type: spec.curve ?? "monotone",
|
|
215
|
+
stackId: stackIdOf(series, spec.stacked),
|
|
216
|
+
stroke: seriesColorVar(series.field),
|
|
217
|
+
strokeWidth: 2,
|
|
218
|
+
fill: seriesColorVar(series.field),
|
|
219
|
+
fillOpacity: 0.2,
|
|
220
|
+
dot: false,
|
|
221
|
+
activeDot: { r: 4, strokeWidth: 2, stroke: "var(--background)" },
|
|
222
|
+
hide: hiddenSeries.includes(series.field),
|
|
223
|
+
isAnimationActive: animate
|
|
224
|
+
},
|
|
225
|
+
series.field
|
|
226
|
+
)),
|
|
227
|
+
renderBrush(spec.xKey, spec)
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/charts/bar.tsx
|
|
234
|
+
import { Bar, BarChart, Cell, XAxis as XAxis2, YAxis as YAxis2 } from "recharts";
|
|
235
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
236
|
+
function BarChartRenderer({
|
|
237
|
+
spec,
|
|
238
|
+
rows,
|
|
239
|
+
formats,
|
|
240
|
+
hiddenSeries,
|
|
241
|
+
markInteraction,
|
|
242
|
+
width,
|
|
243
|
+
height,
|
|
244
|
+
animate
|
|
245
|
+
}) {
|
|
246
|
+
warnOnSpecIssues(spec);
|
|
247
|
+
const horizontal = spec.orientation === "horizontal";
|
|
248
|
+
const hasSecondary = !horizontal && spec.series.some((s) => s.axis === "secondary");
|
|
249
|
+
const valueFormat = valueFormatOf(spec, spec.stacked);
|
|
250
|
+
const active = activeCategoryValues(markInteraction);
|
|
251
|
+
const radius = spec.stacked ? [0, 0, 0, 0] : horizontal ? [0, 4, 4, 0] : [4, 4, 0, 0];
|
|
252
|
+
const bars = spec.series.map((series) => /* @__PURE__ */ jsx3(
|
|
253
|
+
Bar,
|
|
254
|
+
{
|
|
255
|
+
yAxisId: horizontal ? void 0 : yAxisIdOf(series),
|
|
256
|
+
dataKey: series.field,
|
|
257
|
+
name: series.label ?? series.field,
|
|
258
|
+
stackId: stackIdOf(series, spec.stacked),
|
|
259
|
+
fill: seriesColorVar(series.field),
|
|
260
|
+
radius,
|
|
261
|
+
stroke: spec.stacked ? "var(--background)" : void 0,
|
|
262
|
+
strokeWidth: spec.stacked ? 2 : 0,
|
|
263
|
+
hide: hiddenSeries.includes(series.field),
|
|
264
|
+
isAnimationActive: animate,
|
|
265
|
+
children: rows.map((row, index) => /* @__PURE__ */ jsx3(Cell, { opacity: markDimOpacity(active, row[spec.xKey]) }, `${String(row[spec.xKey])}-${index}`))
|
|
266
|
+
},
|
|
267
|
+
series.field
|
|
268
|
+
));
|
|
269
|
+
if (horizontal) {
|
|
270
|
+
return /* @__PURE__ */ jsxs2(
|
|
271
|
+
BarChart,
|
|
272
|
+
{
|
|
273
|
+
data: rows,
|
|
274
|
+
width,
|
|
275
|
+
height,
|
|
276
|
+
layout: "vertical",
|
|
277
|
+
onClick: chartClickHandler(spec, rows, markInteraction),
|
|
278
|
+
margin: { top: 8, bottom: 4 },
|
|
279
|
+
children: [
|
|
280
|
+
renderGrid(spec),
|
|
281
|
+
/* @__PURE__ */ jsx3(
|
|
282
|
+
XAxis2,
|
|
283
|
+
{
|
|
284
|
+
type: "number",
|
|
285
|
+
...xAxisDefaults,
|
|
286
|
+
hide: spec.yAxis?.hidden,
|
|
287
|
+
domain: spec.yAxis?.domain,
|
|
288
|
+
tickFormatter: formats.resolve(spec.yAxis?.format, valueFormat)
|
|
289
|
+
},
|
|
290
|
+
"x"
|
|
291
|
+
),
|
|
292
|
+
/* @__PURE__ */ jsx3(
|
|
293
|
+
YAxis2,
|
|
294
|
+
{
|
|
295
|
+
type: "category",
|
|
296
|
+
dataKey: spec.xKey,
|
|
297
|
+
...yAxisDefaults,
|
|
298
|
+
hide: spec.xAxis?.hidden,
|
|
299
|
+
tickFormatter: formats.resolve(spec.xAxis?.format, "none")
|
|
300
|
+
},
|
|
301
|
+
"y"
|
|
302
|
+
),
|
|
303
|
+
renderTooltip(spec, valueFormat, tooltipCursorDefaults),
|
|
304
|
+
renderLegend(spec, spec.series.length),
|
|
305
|
+
bars
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
return /* @__PURE__ */ jsxs2(
|
|
311
|
+
BarChart,
|
|
312
|
+
{
|
|
313
|
+
data: rows,
|
|
314
|
+
width,
|
|
315
|
+
height,
|
|
316
|
+
onClick: chartClickHandler(spec, rows, markInteraction),
|
|
317
|
+
stackOffset: spec.stacked === "percent" ? "expand" : void 0,
|
|
318
|
+
margin: { top: 8, bottom: 4 },
|
|
319
|
+
children: [
|
|
320
|
+
renderGrid(spec),
|
|
321
|
+
renderXAxis(spec.xKey, spec, formats),
|
|
322
|
+
renderYAxes(spec, formats, valueFormat, hasSecondary),
|
|
323
|
+
renderTooltip(spec, valueFormat, tooltipCursorDefaults),
|
|
324
|
+
renderLegend(spec, spec.series.length),
|
|
325
|
+
renderReferenceLines(spec),
|
|
326
|
+
bars,
|
|
327
|
+
renderBrush(spec.xKey, spec)
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/charts/composed.tsx
|
|
334
|
+
import { Area as Area2, Bar as Bar2, ComposedChart, Line } from "recharts";
|
|
335
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
336
|
+
function renderSeries(series, spec, hidden, animate) {
|
|
337
|
+
const shared = {
|
|
338
|
+
key: series.field,
|
|
339
|
+
yAxisId: yAxisIdOf(series),
|
|
340
|
+
dataKey: series.field,
|
|
341
|
+
name: series.label ?? series.field,
|
|
342
|
+
hide: hidden,
|
|
343
|
+
isAnimationActive: animate
|
|
344
|
+
};
|
|
345
|
+
const color = seriesColorVar(series.field);
|
|
346
|
+
switch (series.mark ?? "bar") {
|
|
347
|
+
case "line":
|
|
348
|
+
return /* @__PURE__ */ jsx4(
|
|
349
|
+
Line,
|
|
350
|
+
{
|
|
351
|
+
...shared,
|
|
352
|
+
type: "monotone",
|
|
353
|
+
stroke: color,
|
|
354
|
+
strokeWidth: 2,
|
|
355
|
+
dot: false,
|
|
356
|
+
activeDot: { r: 4, strokeWidth: 2, stroke: "var(--background)" }
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
case "area":
|
|
360
|
+
return /* @__PURE__ */ jsx4(Area2, { ...shared, type: "monotone", stroke: color, strokeWidth: 2, fill: color, fillOpacity: 0.2, dot: false });
|
|
361
|
+
default:
|
|
362
|
+
return /* @__PURE__ */ jsx4(
|
|
363
|
+
Bar2,
|
|
364
|
+
{
|
|
365
|
+
...shared,
|
|
366
|
+
stackId: stackIdOf(series, spec.stacked),
|
|
367
|
+
fill: color,
|
|
368
|
+
radius: spec.stacked ? [0, 0, 0, 0] : [4, 4, 0, 0]
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function ComposedChartRenderer({
|
|
374
|
+
spec,
|
|
375
|
+
rows,
|
|
376
|
+
formats,
|
|
377
|
+
hiddenSeries,
|
|
378
|
+
markInteraction,
|
|
379
|
+
width,
|
|
380
|
+
height,
|
|
381
|
+
animate
|
|
382
|
+
}) {
|
|
383
|
+
warnOnSpecIssues(spec);
|
|
384
|
+
const hasSecondary = spec.series.some((s) => s.axis === "secondary");
|
|
385
|
+
const valueFormat = valueFormatOf(spec);
|
|
386
|
+
return /* @__PURE__ */ jsxs3(
|
|
387
|
+
ComposedChart,
|
|
388
|
+
{
|
|
389
|
+
data: rows,
|
|
390
|
+
width,
|
|
391
|
+
height,
|
|
392
|
+
onClick: chartClickHandler(spec, rows, markInteraction),
|
|
393
|
+
margin: { top: 8, bottom: 4 },
|
|
394
|
+
children: [
|
|
395
|
+
renderGrid(spec),
|
|
396
|
+
renderXAxis(spec.xKey, spec, formats),
|
|
397
|
+
renderYAxes(spec, formats, valueFormat, hasSecondary),
|
|
398
|
+
renderTooltip(spec, valueFormat, tooltipCursorDefaults),
|
|
399
|
+
renderLegend(spec, spec.series.length),
|
|
400
|
+
renderReferenceLines(spec),
|
|
401
|
+
spec.series.map((series) => renderSeries(series, spec, hiddenSeries.includes(series.field), animate)),
|
|
402
|
+
renderBrush(spec.xKey, spec)
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/charts/funnel.tsx
|
|
409
|
+
import { Cell as Cell2, Funnel, FunnelChart, LabelList, Tooltip as Tooltip2 } from "recharts";
|
|
410
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
411
|
+
function FunnelChartRenderer({
|
|
412
|
+
spec,
|
|
413
|
+
rows,
|
|
414
|
+
markInteraction,
|
|
415
|
+
width,
|
|
416
|
+
height,
|
|
417
|
+
animate
|
|
418
|
+
}) {
|
|
419
|
+
const active = activeCategoryValues(markInteraction);
|
|
420
|
+
return /* @__PURE__ */ jsxs4(FunnelChart, { width, height, margin: { top: 8, bottom: 4 }, children: [
|
|
421
|
+
spec.tooltip !== false && /* @__PURE__ */ jsx5(Tooltip2, { content: /* @__PURE__ */ jsx5(ChartTooltipPanel, { showLabel: false, valueFormat: spec.format ?? "number" }) }),
|
|
422
|
+
renderLegend(spec, rows.length),
|
|
423
|
+
/* @__PURE__ */ jsxs4(
|
|
424
|
+
Funnel,
|
|
425
|
+
{
|
|
426
|
+
data: rows,
|
|
427
|
+
dataKey: spec.valueKey,
|
|
428
|
+
nameKey: spec.nameKey,
|
|
429
|
+
stroke: "var(--background)",
|
|
430
|
+
strokeWidth: 2,
|
|
431
|
+
isAnimationActive: animate,
|
|
432
|
+
onClick: (entry, index, event) => {
|
|
433
|
+
const row = entry?.payload ?? rows[index] ?? {};
|
|
434
|
+
const value = categoryOf(row, spec.nameKey);
|
|
435
|
+
if (value === null) return;
|
|
436
|
+
reportMarkClick(markInteraction, value, void 0, row, Boolean(event?.shiftKey));
|
|
437
|
+
},
|
|
438
|
+
children: [
|
|
439
|
+
/* @__PURE__ */ jsx5(LabelList, { dataKey: spec.nameKey, position: "right", fill: "var(--muted-foreground)", stroke: "none", fontSize: 12 }),
|
|
440
|
+
rows.map((row, index) => /* @__PURE__ */ jsx5(
|
|
441
|
+
Cell2,
|
|
442
|
+
{
|
|
443
|
+
fill: chartColorVar(index + 1),
|
|
444
|
+
opacity: markDimOpacity(active, row[spec.nameKey])
|
|
445
|
+
},
|
|
446
|
+
`${String(row[spec.nameKey])}-${index}`
|
|
447
|
+
))
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
)
|
|
451
|
+
] });
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/charts/line.tsx
|
|
455
|
+
import { Line as Line2, LineChart } from "recharts";
|
|
456
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
457
|
+
function LineChartRenderer({
|
|
458
|
+
spec,
|
|
459
|
+
rows,
|
|
460
|
+
formats,
|
|
461
|
+
hiddenSeries,
|
|
462
|
+
markInteraction,
|
|
463
|
+
width,
|
|
464
|
+
height,
|
|
465
|
+
animate
|
|
466
|
+
}) {
|
|
467
|
+
warnOnSpecIssues(spec);
|
|
468
|
+
const hasSecondary = spec.series.some((s) => s.axis === "secondary");
|
|
469
|
+
const valueFormat = valueFormatOf(spec);
|
|
470
|
+
return /* @__PURE__ */ jsxs5(
|
|
471
|
+
LineChart,
|
|
472
|
+
{
|
|
473
|
+
data: rows,
|
|
474
|
+
width,
|
|
475
|
+
height,
|
|
476
|
+
onClick: chartClickHandler(spec, rows, markInteraction),
|
|
477
|
+
margin: { top: 8, bottom: 4 },
|
|
478
|
+
children: [
|
|
479
|
+
renderGrid(spec),
|
|
480
|
+
renderXAxis(spec.xKey, spec, formats),
|
|
481
|
+
renderYAxes(spec, formats, valueFormat, hasSecondary),
|
|
482
|
+
renderTooltip(spec, valueFormat, tooltipCrosshairDefaults),
|
|
483
|
+
renderLegend(spec, spec.series.length),
|
|
484
|
+
renderReferenceLines(spec),
|
|
485
|
+
spec.series.map((series) => /* @__PURE__ */ jsx6(
|
|
486
|
+
Line2,
|
|
487
|
+
{
|
|
488
|
+
yAxisId: yAxisIdOf(series),
|
|
489
|
+
dataKey: series.field,
|
|
490
|
+
name: series.label ?? series.field,
|
|
491
|
+
type: spec.curve ?? "monotone",
|
|
492
|
+
stroke: seriesColorVar(series.field),
|
|
493
|
+
strokeWidth: 2,
|
|
494
|
+
dot: spec.dots ? { r: 3, fill: seriesColorVar(series.field), strokeWidth: 0 } : false,
|
|
495
|
+
activeDot: { r: 4, strokeWidth: 2, stroke: "var(--background)" },
|
|
496
|
+
hide: hiddenSeries.includes(series.field),
|
|
497
|
+
isAnimationActive: animate
|
|
498
|
+
},
|
|
499
|
+
series.field
|
|
500
|
+
)),
|
|
501
|
+
renderBrush(spec.xKey, spec)
|
|
502
|
+
]
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/charts/pie.tsx
|
|
508
|
+
import { Cell as Cell3, Pie, PieChart, Tooltip as Tooltip3 } from "recharts";
|
|
509
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
510
|
+
function PieChartRenderer({ spec, rows, markInteraction, width, height, animate }) {
|
|
511
|
+
const active = activeCategoryValues(markInteraction);
|
|
512
|
+
return /* @__PURE__ */ jsxs6(PieChart, { width, height, margin: { top: 8, bottom: 4 }, children: [
|
|
513
|
+
spec.tooltip !== false && /* @__PURE__ */ jsx7(Tooltip3, { content: /* @__PURE__ */ jsx7(ChartTooltipPanel, { showLabel: false, valueFormat: spec.format ?? "number" }) }),
|
|
514
|
+
renderLegend(spec, rows.length),
|
|
515
|
+
/* @__PURE__ */ jsx7(
|
|
516
|
+
Pie,
|
|
517
|
+
{
|
|
518
|
+
data: rows,
|
|
519
|
+
dataKey: spec.valueKey,
|
|
520
|
+
nameKey: spec.nameKey,
|
|
521
|
+
innerRadius: spec.type === "donut" ? "55%" : 0,
|
|
522
|
+
stroke: "var(--background)",
|
|
523
|
+
strokeWidth: 2,
|
|
524
|
+
paddingAngle: spec.type === "donut" ? 1 : 0,
|
|
525
|
+
isAnimationActive: animate,
|
|
526
|
+
label: spec.labels ? { fill: "var(--muted-foreground)", fontSize: 12 } : false,
|
|
527
|
+
labelLine: spec.labels ? { stroke: "var(--border)" } : false,
|
|
528
|
+
onClick: (entry, index, event) => {
|
|
529
|
+
const row = entry?.payload ?? rows[index] ?? {};
|
|
530
|
+
const value = categoryOf(row, spec.nameKey);
|
|
531
|
+
if (value === null) return;
|
|
532
|
+
reportMarkClick(markInteraction, value, void 0, row, Boolean(event?.shiftKey));
|
|
533
|
+
},
|
|
534
|
+
children: rows.map((row, index) => /* @__PURE__ */ jsx7(
|
|
535
|
+
Cell3,
|
|
536
|
+
{
|
|
537
|
+
fill: chartColorVar(index + 1),
|
|
538
|
+
opacity: markDimOpacity(active, row[spec.nameKey])
|
|
539
|
+
},
|
|
540
|
+
`${String(row[spec.nameKey])}-${index}`
|
|
541
|
+
))
|
|
542
|
+
}
|
|
543
|
+
)
|
|
544
|
+
] });
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/charts/radar.tsx
|
|
548
|
+
import { PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, Tooltip as Tooltip4 } from "recharts";
|
|
549
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
550
|
+
function RadarChartRenderer({
|
|
551
|
+
spec,
|
|
552
|
+
rows,
|
|
553
|
+
formats,
|
|
554
|
+
hiddenSeries,
|
|
555
|
+
width,
|
|
556
|
+
height,
|
|
557
|
+
animate
|
|
558
|
+
}) {
|
|
559
|
+
warnOnSpecIssues(spec);
|
|
560
|
+
return /* @__PURE__ */ jsxs7(RadarChart, { data: rows, width, height, margin: { top: 8, bottom: 4 }, children: [
|
|
561
|
+
/* @__PURE__ */ jsx8(PolarGrid, { stroke: "var(--border)" }),
|
|
562
|
+
/* @__PURE__ */ jsx8(
|
|
563
|
+
PolarAngleAxis,
|
|
564
|
+
{
|
|
565
|
+
dataKey: spec.xKey,
|
|
566
|
+
tick: { fill: "var(--muted-foreground)", fontSize: 12 },
|
|
567
|
+
tickFormatter: formats.resolve(void 0, "none")
|
|
568
|
+
}
|
|
569
|
+
),
|
|
570
|
+
/* @__PURE__ */ jsx8(PolarRadiusAxis, { tick: false, axisLine: false }),
|
|
571
|
+
spec.tooltip !== false && /* @__PURE__ */ jsx8(Tooltip4, { content: /* @__PURE__ */ jsx8(ChartTooltipPanel, { valueFormat: "number" }) }),
|
|
572
|
+
renderLegend(spec, spec.series.length),
|
|
573
|
+
spec.series.map((series) => /* @__PURE__ */ jsx8(
|
|
574
|
+
Radar,
|
|
575
|
+
{
|
|
576
|
+
dataKey: series.field,
|
|
577
|
+
name: series.label ?? series.field,
|
|
578
|
+
stroke: seriesColorVar(series.field),
|
|
579
|
+
strokeWidth: 2,
|
|
580
|
+
fill: seriesColorVar(series.field),
|
|
581
|
+
fillOpacity: spec.filled === false ? 0 : 0.2,
|
|
582
|
+
hide: hiddenSeries.includes(series.field),
|
|
583
|
+
isAnimationActive: animate
|
|
584
|
+
},
|
|
585
|
+
series.field
|
|
586
|
+
))
|
|
587
|
+
] });
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/charts/radial-bar.tsx
|
|
591
|
+
import { Cell as Cell4, PolarAngleAxis as PolarAngleAxis2, RadialBar, RadialBarChart, Tooltip as Tooltip5 } from "recharts";
|
|
592
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
593
|
+
function RadialBarChartRenderer({
|
|
594
|
+
spec,
|
|
595
|
+
rows,
|
|
596
|
+
markInteraction,
|
|
597
|
+
width,
|
|
598
|
+
height,
|
|
599
|
+
animate
|
|
600
|
+
}) {
|
|
601
|
+
const active = activeCategoryValues(markInteraction);
|
|
602
|
+
const max = spec.max ?? rows.reduce((top, row) => {
|
|
603
|
+
const value = row[spec.valueKey];
|
|
604
|
+
return typeof value === "number" && value > top ? value : top;
|
|
605
|
+
}, 0);
|
|
606
|
+
return /* @__PURE__ */ jsxs8(
|
|
607
|
+
RadialBarChart,
|
|
608
|
+
{
|
|
609
|
+
data: rows,
|
|
610
|
+
width,
|
|
611
|
+
height,
|
|
612
|
+
innerRadius: "25%",
|
|
613
|
+
outerRadius: "100%",
|
|
614
|
+
startAngle: 90,
|
|
615
|
+
endAngle: -270,
|
|
616
|
+
margin: { top: 8, bottom: 4 },
|
|
617
|
+
children: [
|
|
618
|
+
/* @__PURE__ */ jsx9(PolarAngleAxis2, { type: "number", domain: [spec.min ?? 0, max], tick: false }),
|
|
619
|
+
spec.tooltip !== false && /* @__PURE__ */ jsx9(Tooltip5, { content: /* @__PURE__ */ jsx9(ChartTooltipPanel, { showLabel: false, valueFormat: spec.format ?? "number" }) }),
|
|
620
|
+
renderLegend(spec, rows.length),
|
|
621
|
+
/* @__PURE__ */ jsx9(
|
|
622
|
+
RadialBar,
|
|
623
|
+
{
|
|
624
|
+
dataKey: spec.valueKey,
|
|
625
|
+
background: { fill: "var(--muted)" },
|
|
626
|
+
cornerRadius: 4,
|
|
627
|
+
isAnimationActive: animate,
|
|
628
|
+
onClick: (entry, index, event) => {
|
|
629
|
+
const row = entry?.payload ?? rows[index] ?? {};
|
|
630
|
+
const value = categoryOf(row, spec.nameKey);
|
|
631
|
+
if (value === null) return;
|
|
632
|
+
reportMarkClick(markInteraction, value, void 0, row, Boolean(event?.shiftKey));
|
|
633
|
+
},
|
|
634
|
+
children: rows.map((row, index) => /* @__PURE__ */ jsx9(
|
|
635
|
+
Cell4,
|
|
636
|
+
{
|
|
637
|
+
fill: chartColorVar(index + 1),
|
|
638
|
+
opacity: markDimOpacity(active, row[spec.nameKey])
|
|
639
|
+
},
|
|
640
|
+
`${String(row[spec.nameKey])}-${index}`
|
|
641
|
+
))
|
|
642
|
+
}
|
|
643
|
+
)
|
|
644
|
+
]
|
|
645
|
+
}
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// src/charts/scatter.tsx
|
|
650
|
+
import { Scatter, ScatterChart, ZAxis } from "recharts";
|
|
651
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
652
|
+
var SCATTER_MAX_GROUPS = 3;
|
|
653
|
+
function ScatterChartRenderer({
|
|
654
|
+
spec,
|
|
655
|
+
rows,
|
|
656
|
+
formats,
|
|
657
|
+
markInteraction,
|
|
658
|
+
width,
|
|
659
|
+
height,
|
|
660
|
+
animate
|
|
661
|
+
}) {
|
|
662
|
+
const groups = spec.groupKey ? [...new Set(rows.map((row) => categoryOf(row, spec.groupKey)).filter((v) => v !== null))] : [null];
|
|
663
|
+
if (groups.length > SCATTER_MAX_GROUPS) {
|
|
664
|
+
warnOnce(
|
|
665
|
+
`scatter-${spec.groupKey}`,
|
|
666
|
+
`@zuilib/charts: scatter groups by "${spec.groupKey}" into ${groups.length} groups; only the first ${SCATTER_MAX_GROUPS} palette slots are validated for all-pairs discrimination \u2014 facet or filter instead.`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
const active = activeCategoryValues(markInteraction);
|
|
670
|
+
const valueFormat = spec.yAxis?.format ?? "number";
|
|
671
|
+
return /* @__PURE__ */ jsxs9(ScatterChart, { width, height, margin: { top: 8, bottom: 4 }, children: [
|
|
672
|
+
renderGrid(spec),
|
|
673
|
+
renderXAxis(spec.xKey, spec, formats, { numeric: true }),
|
|
674
|
+
renderYAxes(spec, formats, valueFormat, false),
|
|
675
|
+
spec.sizeKey && /* @__PURE__ */ jsx10(ZAxis, { dataKey: spec.sizeKey, range: spec.sizeRange ?? [64, 400] }),
|
|
676
|
+
renderTooltip(spec, valueFormat, tooltipCrosshairDefaults),
|
|
677
|
+
spec.groupKey ? renderLegend(spec, groups.length) : null,
|
|
678
|
+
renderReferenceLines(spec),
|
|
679
|
+
groups.map((group, index) => {
|
|
680
|
+
const groupRows = group === null ? rows : rows.filter((row) => row[spec.groupKey] === group);
|
|
681
|
+
return /* @__PURE__ */ jsx10(
|
|
682
|
+
Scatter,
|
|
683
|
+
{
|
|
684
|
+
yAxisId: "primary",
|
|
685
|
+
name: group === null ? void 0 : String(group),
|
|
686
|
+
data: groupRows,
|
|
687
|
+
dataKey: spec.yKey,
|
|
688
|
+
fill: chartColorVar(index + 1),
|
|
689
|
+
stroke: "var(--background)",
|
|
690
|
+
strokeWidth: 2,
|
|
691
|
+
isAnimationActive: animate,
|
|
692
|
+
opacity: group === null ? void 0 : markDimOpacity(active, group),
|
|
693
|
+
onClick: (entry, _index, event) => {
|
|
694
|
+
const row = entry?.payload ?? {};
|
|
695
|
+
const value = group ?? categoryOf(row, spec.xKey);
|
|
696
|
+
if (value === null) return;
|
|
697
|
+
reportMarkClick(markInteraction, value, void 0, row, Boolean(event?.shiftKey));
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
group === null ? "all" : String(group)
|
|
701
|
+
);
|
|
702
|
+
})
|
|
703
|
+
] });
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// src/charts/treemap.tsx
|
|
707
|
+
import { Tooltip as Tooltip6, Treemap } from "recharts";
|
|
708
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
709
|
+
function TreemapNode({
|
|
710
|
+
x = 0,
|
|
711
|
+
y = 0,
|
|
712
|
+
width = 0,
|
|
713
|
+
height = 0,
|
|
714
|
+
depth = 0,
|
|
715
|
+
name,
|
|
716
|
+
root,
|
|
717
|
+
colorOf,
|
|
718
|
+
opacityOf,
|
|
719
|
+
onNodeClick
|
|
720
|
+
}) {
|
|
721
|
+
if (depth === 0) return null;
|
|
722
|
+
const groupName = depth === 1 ? name : root?.name;
|
|
723
|
+
const leaf = depth >= 1;
|
|
724
|
+
const showLabel = leaf && width > 48 && height > 24;
|
|
725
|
+
return /* @__PURE__ */ jsxs10("g", { onClick: (event) => onNodeClick(name, event), style: { cursor: "pointer" }, children: [
|
|
726
|
+
/* @__PURE__ */ jsx11(
|
|
727
|
+
"rect",
|
|
728
|
+
{
|
|
729
|
+
x,
|
|
730
|
+
y,
|
|
731
|
+
width,
|
|
732
|
+
height,
|
|
733
|
+
fill: colorOf(groupName),
|
|
734
|
+
opacity: opacityOf(name),
|
|
735
|
+
stroke: "var(--background)",
|
|
736
|
+
strokeWidth: 2
|
|
737
|
+
}
|
|
738
|
+
),
|
|
739
|
+
showLabel && /* @__PURE__ */ jsx11("text", { x: x + 6, y: y + 16, fill: "var(--background)", fontSize: 12, pointerEvents: "none", children: name })
|
|
740
|
+
] });
|
|
741
|
+
}
|
|
742
|
+
function TreemapChartRenderer({ spec, rows, markInteraction, width, height }) {
|
|
743
|
+
const active = activeCategoryValues(markInteraction);
|
|
744
|
+
const groups = [];
|
|
745
|
+
let treeRows = rows;
|
|
746
|
+
if (spec.groupKey) {
|
|
747
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
748
|
+
for (const row of rows) {
|
|
749
|
+
const group = String(row[spec.groupKey] ?? "");
|
|
750
|
+
if (!byGroup.has(group)) byGroup.set(group, []);
|
|
751
|
+
byGroup.get(group)?.push(row);
|
|
752
|
+
}
|
|
753
|
+
groups.push(...byGroup.keys());
|
|
754
|
+
treeRows = groups.map((group) => ({
|
|
755
|
+
[spec.nameKey]: group,
|
|
756
|
+
children: byGroup.get(group)
|
|
757
|
+
}));
|
|
758
|
+
} else {
|
|
759
|
+
groups.push(...rows.map((row) => String(row[spec.nameKey] ?? "")));
|
|
760
|
+
}
|
|
761
|
+
const colorOf = (name) => {
|
|
762
|
+
const index = groups.indexOf(name ?? "");
|
|
763
|
+
return chartColorVar((index === -1 ? 0 : index) + 1);
|
|
764
|
+
};
|
|
765
|
+
const opacityOf = (name) => name === void 0 ? void 0 : markDimOpacity(active, name);
|
|
766
|
+
const onNodeClick = (name, event) => {
|
|
767
|
+
if (name === void 0) return;
|
|
768
|
+
const row = rows.find((r) => categoryOf(r, spec.nameKey) === name) ?? { [spec.nameKey]: name };
|
|
769
|
+
reportMarkClick(markInteraction, name, void 0, row, event.shiftKey);
|
|
770
|
+
};
|
|
771
|
+
return /* @__PURE__ */ jsx11(
|
|
772
|
+
Treemap,
|
|
773
|
+
{
|
|
774
|
+
data: treeRows,
|
|
775
|
+
width,
|
|
776
|
+
height,
|
|
777
|
+
dataKey: spec.sizeKey,
|
|
778
|
+
nameKey: spec.nameKey,
|
|
779
|
+
isAnimationActive: false,
|
|
780
|
+
content: /* @__PURE__ */ jsx11(TreemapNode, { colorOf, opacityOf, onNodeClick }),
|
|
781
|
+
children: spec.tooltip !== false && /* @__PURE__ */ jsx11(Tooltip6, { content: /* @__PURE__ */ jsx11(ChartTooltipPanel, { showLabel: false, valueFormat: spec.format ?? "number" }) })
|
|
782
|
+
}
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/registry.ts
|
|
787
|
+
function defineChartType(def) {
|
|
788
|
+
return def;
|
|
789
|
+
}
|
|
790
|
+
function createChartRegistry(...sources) {
|
|
791
|
+
const types = /* @__PURE__ */ new Map();
|
|
792
|
+
for (const source of sources) {
|
|
793
|
+
const defs = Array.isArray(source) ? source : source.types.values();
|
|
794
|
+
for (const def of defs) types.set(def.type, def);
|
|
795
|
+
}
|
|
796
|
+
return { types };
|
|
797
|
+
}
|
|
798
|
+
var FORMAT_TOKEN = {
|
|
799
|
+
type: "string",
|
|
800
|
+
description: 'Format token, e.g. "number", "compact", "percent", "currency:USD", "date:short".'
|
|
801
|
+
};
|
|
802
|
+
var PALETTE_SLOT = { type: "number", minimum: 1, maximum: 8, description: "Palette slot (--chart-N)." };
|
|
803
|
+
var COMMON_PROPERTIES = {
|
|
804
|
+
version: { const: 1, description: "Contract version of this spec." },
|
|
805
|
+
title: { type: "string", description: "Figure title, also the default accessible name." },
|
|
806
|
+
description: { type: "string", description: "Longer description for assistive tech." },
|
|
807
|
+
legend: {
|
|
808
|
+
anyOf: [
|
|
809
|
+
{ type: "boolean" },
|
|
810
|
+
{
|
|
811
|
+
type: "object",
|
|
812
|
+
properties: { placement: { enum: ["top", "bottom"] } },
|
|
813
|
+
required: ["placement"],
|
|
814
|
+
additionalProperties: false
|
|
815
|
+
}
|
|
816
|
+
],
|
|
817
|
+
description: "Legend visibility/placement. Defaults to true for two or more series."
|
|
818
|
+
},
|
|
819
|
+
tooltip: { type: "boolean" },
|
|
820
|
+
interaction: {
|
|
821
|
+
enum: ["filter", "highlight", "none"],
|
|
822
|
+
description: 'What a mark click does inside a CrossFilterProvider. Defaults to "filter".'
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
var AXIS_SCHEMA = {
|
|
826
|
+
type: "object",
|
|
827
|
+
properties: {
|
|
828
|
+
label: { type: "string", description: "Axis title." },
|
|
829
|
+
format: FORMAT_TOKEN,
|
|
830
|
+
domain: {
|
|
831
|
+
type: "array",
|
|
832
|
+
prefixItems: [
|
|
833
|
+
{ anyOf: [{ type: "number" }, { enum: ["auto", "dataMin"] }] },
|
|
834
|
+
{ anyOf: [{ type: "number" }, { enum: ["auto", "dataMax"] }] }
|
|
835
|
+
],
|
|
836
|
+
items: false,
|
|
837
|
+
minItems: 2,
|
|
838
|
+
description: 'Value-axis domain. Defaults to ["auto", "auto"].'
|
|
839
|
+
},
|
|
840
|
+
tickCount: { type: "number", description: "Suggested tick count." },
|
|
841
|
+
hidden: { type: "boolean", description: "Hides the axis entirely." },
|
|
842
|
+
scale: { enum: ["band", "number", "time", "log"], description: 'X axis only. Defaults to "band".' }
|
|
843
|
+
},
|
|
844
|
+
additionalProperties: false
|
|
845
|
+
};
|
|
846
|
+
var SERIES_ITEM_SCHEMA = {
|
|
847
|
+
type: "object",
|
|
848
|
+
properties: {
|
|
849
|
+
field: { type: "string", description: "Row field holding this series' value." },
|
|
850
|
+
label: { type: "string", description: "Legend / tooltip name. Defaults to the field." },
|
|
851
|
+
paletteSlot: PALETTE_SLOT,
|
|
852
|
+
axis: { enum: ["primary", "secondary"], description: 'Value axis; "secondary" enables the secondary axis.' },
|
|
853
|
+
stackId: { type: "string", description: "Series sharing a stackId stack." },
|
|
854
|
+
mark: { enum: ["line", "area", "bar"], description: "Composed charts only: how this series is drawn." },
|
|
855
|
+
format: FORMAT_TOKEN
|
|
856
|
+
},
|
|
857
|
+
required: ["field"],
|
|
858
|
+
additionalProperties: false
|
|
859
|
+
};
|
|
860
|
+
var REFERENCE_LINE_SCHEMA = {
|
|
861
|
+
type: "object",
|
|
862
|
+
properties: {
|
|
863
|
+
axis: { enum: ["x", "y", "y2"], description: '"y"/"y2" draw horizontal lines ("y2" on the secondary axis), "x" vertical.' },
|
|
864
|
+
value: { anyOf: [{ type: "number" }, { type: "string" }], description: "Position on that axis." },
|
|
865
|
+
label: { type: "string", description: "Text beside the line." },
|
|
866
|
+
paletteSlot: PALETTE_SLOT,
|
|
867
|
+
dashed: { type: "boolean" }
|
|
868
|
+
},
|
|
869
|
+
required: ["axis", "value"],
|
|
870
|
+
additionalProperties: false
|
|
871
|
+
};
|
|
872
|
+
var FRAME_PROPERTIES = {
|
|
873
|
+
xAxis: AXIS_SCHEMA,
|
|
874
|
+
yAxis: AXIS_SCHEMA,
|
|
875
|
+
secondaryAxis: { ...AXIS_SCHEMA, description: 'Secondary value axis; used by series with axis: "secondary".' },
|
|
876
|
+
grid: {
|
|
877
|
+
anyOf: [
|
|
878
|
+
{ type: "boolean" },
|
|
879
|
+
{
|
|
880
|
+
type: "object",
|
|
881
|
+
properties: { horizontal: { type: "boolean" }, vertical: { type: "boolean" } },
|
|
882
|
+
additionalProperties: false
|
|
883
|
+
}
|
|
884
|
+
],
|
|
885
|
+
description: "Grid lines. Defaults to horizontal only."
|
|
886
|
+
},
|
|
887
|
+
referenceLines: { type: "array", items: REFERENCE_LINE_SCHEMA },
|
|
888
|
+
brush: { type: "boolean", description: "Renders a range brush under the plot." }
|
|
889
|
+
};
|
|
890
|
+
var CARTESIAN_PROPERTIES = {
|
|
891
|
+
...FRAME_PROPERTIES,
|
|
892
|
+
xKey: { type: "string", description: "Category / x field. Also the cross-filter field." },
|
|
893
|
+
series: {
|
|
894
|
+
type: "array",
|
|
895
|
+
items: SERIES_ITEM_SCHEMA,
|
|
896
|
+
minItems: 1,
|
|
897
|
+
description: "The plotted series, in palette-slot order."
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
var STACKED_SCHEMA = {
|
|
901
|
+
anyOf: [{ type: "boolean" }, { const: "percent" }],
|
|
902
|
+
description: 'true stacks the series; "percent" normalizes each stack to 100%.'
|
|
903
|
+
};
|
|
904
|
+
function chartTypeSchema(type, properties, required) {
|
|
905
|
+
return {
|
|
906
|
+
type: "object",
|
|
907
|
+
properties: { ...COMMON_PROPERTIES, type: { const: type }, ...properties },
|
|
908
|
+
required: ["version", "type", ...required],
|
|
909
|
+
additionalProperties: false
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
function namedConfig(spec) {
|
|
913
|
+
return { [spec.valueKey]: { label: spec.title ?? spec.valueKey, format: spec.format } };
|
|
914
|
+
}
|
|
915
|
+
function pieDef(type) {
|
|
916
|
+
return defineChartType({
|
|
917
|
+
type,
|
|
918
|
+
family: "named",
|
|
919
|
+
render: PieChartRenderer,
|
|
920
|
+
crossFilterField: (spec) => spec.nameKey,
|
|
921
|
+
categoryField: (spec) => spec.nameKey,
|
|
922
|
+
configFrom: namedConfig,
|
|
923
|
+
schema: chartTypeSchema(type, {
|
|
924
|
+
nameKey: { type: "string", description: "Row field of the slice name; also the cross-filter field." },
|
|
925
|
+
valueKey: { type: "string", description: "Row field of the slice value." },
|
|
926
|
+
format: FORMAT_TOKEN,
|
|
927
|
+
labels: { type: "boolean", description: "Direct labels beside the slices." }
|
|
928
|
+
}, ["nameKey", "valueKey"])
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
var defaultChartTypes = [
|
|
932
|
+
defineChartType({
|
|
933
|
+
type: "line",
|
|
934
|
+
family: "cartesian",
|
|
935
|
+
render: LineChartRenderer,
|
|
936
|
+
crossFilterField: (spec) => spec.xKey,
|
|
937
|
+
schema: chartTypeSchema("line", {
|
|
938
|
+
...CARTESIAN_PROPERTIES,
|
|
939
|
+
curve: { enum: ["linear", "monotone", "step"] },
|
|
940
|
+
dots: { type: "boolean", description: "Dots on every point (dots always show on hover)." }
|
|
941
|
+
}, ["xKey", "series"])
|
|
942
|
+
}),
|
|
943
|
+
defineChartType({
|
|
944
|
+
type: "area",
|
|
945
|
+
family: "cartesian",
|
|
946
|
+
render: AreaChartRenderer,
|
|
947
|
+
crossFilterField: (spec) => spec.xKey,
|
|
948
|
+
schema: chartTypeSchema("area", {
|
|
949
|
+
...CARTESIAN_PROPERTIES,
|
|
950
|
+
curve: { enum: ["linear", "monotone", "step"] },
|
|
951
|
+
stacked: STACKED_SCHEMA
|
|
952
|
+
}, ["xKey", "series"])
|
|
953
|
+
}),
|
|
954
|
+
defineChartType({
|
|
955
|
+
type: "bar",
|
|
956
|
+
family: "cartesian",
|
|
957
|
+
render: BarChartRenderer,
|
|
958
|
+
crossFilterField: (spec) => spec.xKey,
|
|
959
|
+
schema: chartTypeSchema("bar", {
|
|
960
|
+
...CARTESIAN_PROPERTIES,
|
|
961
|
+
orientation: { enum: ["vertical", "horizontal"], description: 'Bar direction: "horizontal" lays categories on the y axis.' },
|
|
962
|
+
stacked: STACKED_SCHEMA
|
|
963
|
+
}, ["xKey", "series"])
|
|
964
|
+
}),
|
|
965
|
+
defineChartType({
|
|
966
|
+
type: "composed",
|
|
967
|
+
family: "cartesian",
|
|
968
|
+
render: ComposedChartRenderer,
|
|
969
|
+
crossFilterField: (spec) => spec.xKey,
|
|
970
|
+
schema: chartTypeSchema("composed", {
|
|
971
|
+
...CARTESIAN_PROPERTIES,
|
|
972
|
+
stacked: { type: "boolean", description: "Stacks the bar series." }
|
|
973
|
+
}, ["xKey", "series"])
|
|
974
|
+
}),
|
|
975
|
+
defineChartType({
|
|
976
|
+
type: "scatter",
|
|
977
|
+
family: "scatter",
|
|
978
|
+
render: ScatterChartRenderer,
|
|
979
|
+
crossFilterField: (spec) => spec.groupKey ?? spec.xKey,
|
|
980
|
+
categoryField: (spec) => spec.groupKey,
|
|
981
|
+
configFrom: (spec) => ({
|
|
982
|
+
[spec.yKey]: { label: spec.yAxis?.label ?? spec.yKey, format: spec.yAxis?.format }
|
|
983
|
+
}),
|
|
984
|
+
schema: chartTypeSchema("scatter", {
|
|
985
|
+
...FRAME_PROPERTIES,
|
|
986
|
+
xKey: { type: "string", description: "Row field of the x value (numeric)." },
|
|
987
|
+
yKey: { type: "string", description: "Row field of the y value." },
|
|
988
|
+
groupKey: {
|
|
989
|
+
type: "string",
|
|
990
|
+
description: "Row field that splits points into colored groups (at most 3); also the cross-filter field."
|
|
991
|
+
},
|
|
992
|
+
sizeKey: { type: "string", description: "Row field sized into bubble area." },
|
|
993
|
+
sizeRange: {
|
|
994
|
+
type: "array",
|
|
995
|
+
prefixItems: [{ type: "number" }, { type: "number" }],
|
|
996
|
+
items: false,
|
|
997
|
+
minItems: 2,
|
|
998
|
+
description: "Bubble size range in px\xB2. Defaults to [64, 400]."
|
|
999
|
+
}
|
|
1000
|
+
}, ["xKey", "yKey"])
|
|
1001
|
+
}),
|
|
1002
|
+
pieDef("pie"),
|
|
1003
|
+
pieDef("donut"),
|
|
1004
|
+
defineChartType({
|
|
1005
|
+
type: "radar",
|
|
1006
|
+
family: "radar",
|
|
1007
|
+
render: RadarChartRenderer,
|
|
1008
|
+
crossFilterField: (spec) => spec.xKey,
|
|
1009
|
+
schema: chartTypeSchema("radar", {
|
|
1010
|
+
xKey: { type: "string", description: "Row field of the spoke name." },
|
|
1011
|
+
series: { type: "array", items: SERIES_ITEM_SCHEMA, minItems: 1 },
|
|
1012
|
+
filled: { type: "boolean", description: "Translucent fill under each outline." }
|
|
1013
|
+
}, ["xKey", "series"])
|
|
1014
|
+
}),
|
|
1015
|
+
defineChartType({
|
|
1016
|
+
type: "radial-bar",
|
|
1017
|
+
family: "named",
|
|
1018
|
+
render: RadialBarChartRenderer,
|
|
1019
|
+
crossFilterField: (spec) => spec.nameKey,
|
|
1020
|
+
categoryField: (spec) => spec.nameKey,
|
|
1021
|
+
configFrom: namedConfig,
|
|
1022
|
+
schema: chartTypeSchema("radial-bar", {
|
|
1023
|
+
nameKey: { type: "string", description: "Row field of the ring name; also the cross-filter field." },
|
|
1024
|
+
valueKey: { type: "string", description: "Row field of the ring value." },
|
|
1025
|
+
min: { type: "number", description: "Domain start. Defaults to 0." },
|
|
1026
|
+
max: { type: "number", description: "Domain end. Defaults to the data max." },
|
|
1027
|
+
format: FORMAT_TOKEN
|
|
1028
|
+
}, ["nameKey", "valueKey"])
|
|
1029
|
+
}),
|
|
1030
|
+
defineChartType({
|
|
1031
|
+
type: "funnel",
|
|
1032
|
+
family: "named",
|
|
1033
|
+
render: FunnelChartRenderer,
|
|
1034
|
+
crossFilterField: (spec) => spec.nameKey,
|
|
1035
|
+
categoryField: (spec) => spec.nameKey,
|
|
1036
|
+
configFrom: namedConfig,
|
|
1037
|
+
schema: chartTypeSchema("funnel", {
|
|
1038
|
+
nameKey: { type: "string", description: "Row field of the stage name; also the cross-filter field." },
|
|
1039
|
+
valueKey: { type: "string", description: "Row field of the stage value." },
|
|
1040
|
+
format: FORMAT_TOKEN
|
|
1041
|
+
}, ["nameKey", "valueKey"])
|
|
1042
|
+
}),
|
|
1043
|
+
defineChartType({
|
|
1044
|
+
type: "treemap",
|
|
1045
|
+
family: "treemap",
|
|
1046
|
+
render: TreemapChartRenderer,
|
|
1047
|
+
crossFilterField: (spec) => spec.nameKey,
|
|
1048
|
+
categoryField: (spec) => spec.nameKey,
|
|
1049
|
+
configFrom: (spec) => ({
|
|
1050
|
+
[spec.sizeKey]: { label: spec.title ?? spec.sizeKey, format: spec.format }
|
|
1051
|
+
}),
|
|
1052
|
+
schema: chartTypeSchema("treemap", {
|
|
1053
|
+
nameKey: { type: "string", description: "Row field of the tile name; also the cross-filter field." },
|
|
1054
|
+
sizeKey: { type: "string", description: "Row field sized into tile area." },
|
|
1055
|
+
groupKey: { type: "string", description: "Row field that groups tiles into colored clusters." },
|
|
1056
|
+
format: FORMAT_TOKEN
|
|
1057
|
+
}, ["nameKey", "sizeKey"])
|
|
1058
|
+
})
|
|
1059
|
+
];
|
|
1060
|
+
var defaultChartRegistry = createChartRegistry(defaultChartTypes);
|
|
1061
|
+
function chartSpecJsonSchema(registry = defaultChartRegistry) {
|
|
1062
|
+
const defs = [...registry.types.values()];
|
|
1063
|
+
return {
|
|
1064
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1065
|
+
title: "ChartSpec",
|
|
1066
|
+
description: 'The serializable chart description <Chart> renders. Discriminated on "type".',
|
|
1067
|
+
type: "object",
|
|
1068
|
+
properties: { type: { enum: defs.map((def) => def.type) } },
|
|
1069
|
+
required: ["type"],
|
|
1070
|
+
allOf: defs.map((def) => ({
|
|
1071
|
+
if: { properties: { type: { const: def.type } }, required: ["type"] },
|
|
1072
|
+
then: def.schema
|
|
1073
|
+
}))
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
export {
|
|
1078
|
+
defineChartType,
|
|
1079
|
+
createChartRegistry,
|
|
1080
|
+
chartTypeSchema,
|
|
1081
|
+
defaultChartTypes,
|
|
1082
|
+
defaultChartRegistry,
|
|
1083
|
+
chartSpecJsonSchema
|
|
1084
|
+
};
|