@k2b/ui 0.51.0 → 0.52.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/dist/base.css +3 -0
- package/dist/browser/actions/Toolbar.js +8 -2
- package/dist/browser/actions/Toolbar.js.map +1 -1
- package/dist/browser/charts/core/bar-labels.js +5 -0
- package/dist/browser/charts/core/bar-labels.js.map +1 -0
- package/dist/browser/charts/core/bubble.js +166 -0
- package/dist/browser/charts/core/bubble.js.map +1 -0
- package/dist/browser/charts/core/default-styles.js +83 -0
- package/dist/browser/charts/core/default-styles.js.map +1 -0
- package/dist/browser/charts/core/funnel-layout.js +79 -0
- package/dist/browser/charts/core/funnel-layout.js.map +1 -0
- package/dist/browser/charts/core/funnel.js +27 -0
- package/dist/browser/charts/core/funnel.js.map +1 -0
- package/dist/browser/charts/core/index.js +6 -1
- package/dist/browser/charts/core/index.js.map +1 -1
- package/dist/browser/charts/core/pareto.js +120 -0
- package/dist/browser/charts/core/pareto.js.map +1 -0
- package/dist/browser/charts/core/radar.js +101 -0
- package/dist/browser/charts/core/radar.js.map +1 -0
- package/dist/browser/charts/core/registry.js +11 -1
- package/dist/browser/charts/core/registry.js.map +1 -1
- package/dist/browser/charts/core/render-size.js +3 -1
- package/dist/browser/charts/core/render-size.js.map +1 -1
- package/dist/browser/charts/core/render.js +19 -0
- package/dist/browser/charts/core/render.js.map +1 -1
- package/dist/browser/charts/core/snapshot.js +1 -1
- package/dist/browser/charts/core/snapshot.js.map +1 -1
- package/dist/browser/charts/core/svg-barGauge.js +67 -0
- package/dist/browser/charts/core/svg-barGauge.js.map +1 -0
- package/dist/browser/charts/core/svg-boxplot.js +127 -0
- package/dist/browser/charts/core/svg-boxplot.js.map +1 -0
- package/dist/browser/charts/core/svg-common.js +1065 -0
- package/dist/browser/charts/core/svg-common.js.map +1 -0
- package/dist/browser/charts/core/svg-gauge.js +185 -0
- package/dist/browser/charts/core/svg-gauge.js.map +1 -0
- package/dist/browser/charts/core/svg-heatmap.js +80 -0
- package/dist/browser/charts/core/svg-heatmap.js.map +1 -0
- package/dist/browser/charts/core/svg-histogram.js +73 -0
- package/dist/browser/charts/core/svg-histogram.js.map +1 -0
- package/dist/browser/charts/core/svg-renderers.js +9 -1777
- package/dist/browser/charts/core/svg-renderers.js.map +1 -1
- package/dist/browser/charts/core/svg-scatter.js +114 -0
- package/dist/browser/charts/core/svg-scatter.js.map +1 -0
- package/dist/browser/charts/core/treemap.js +138 -0
- package/dist/browser/charts/core/treemap.js.map +1 -0
- package/dist/browser/charts/description.js.map +1 -1
- package/dist/browser/content/ChartRoot.js +21 -7
- package/dist/browser/content/ChartRoot.js.map +1 -1
- package/dist/browser/content/ChartSnapshotView.js +4 -1
- package/dist/browser/content/ChartSnapshotView.js.map +1 -1
- package/dist/browser/content/DataTable.js +186 -193
- package/dist/browser/content/DataTable.js.map +1 -1
- package/dist/browser/content/Lightbox.js +55 -23
- package/dist/browser/content/Lightbox.js.map +1 -1
- package/dist/browser/content/ThumbnailStrip.js +174 -0
- package/dist/browser/content/ThumbnailStrip.js.map +1 -0
- package/dist/browser/content/chart-bar.js +98 -0
- package/dist/browser/content/chart-bar.js.map +1 -1
- package/dist/browser/content/chart-inspection-core.js +28 -8
- package/dist/browser/content/chart-inspection-core.js.map +1 -1
- package/dist/browser/content/chart-message-catalog.js +18 -0
- package/dist/browser/content/chart-message-catalog.js.map +1 -1
- package/dist/browser/content/index.js +1 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/thumbnail-strip-messages.js +24 -0
- package/dist/browser/content/thumbnail-strip-messages.js.map +1 -0
- package/dist/browser/inputs/ChoiceInputs.js +132 -101
- package/dist/browser/inputs/ChoiceInputs.js.map +1 -1
- package/dist/browser/inputs/index.js +1 -0
- package/dist/browser/inputs/index.js.map +1 -1
- package/dist/browser/inputs/markdown/MarkdownSelectionToolbar.js +330 -0
- package/dist/browser/inputs/markdown/MarkdownSelectionToolbar.js.map +1 -0
- package/dist/browser/internal/field.js +96 -77
- package/dist/browser/internal/field.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +7 -5
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/PanelDialog.js +92 -68
- package/dist/browser/layout/PanelDialog.js.map +1 -1
- package/dist/browser/surfaces/StatCell.js +82 -70
- package/dist/browser/surfaces/StatCell.js.map +1 -1
- package/dist/browser/surfaces/stat-cell-messages.js +4 -2
- package/dist/browser/surfaces/stat-cell-messages.js.map +1 -1
- package/dist/browser/widgets/DashboardFrame.js +93 -88
- package/dist/browser/widgets/DashboardFrame.js.map +1 -1
- package/dist/ssr/actions/Toolbar.js +8 -2
- package/dist/ssr/actions/Toolbar.js.map +1 -1
- package/dist/ssr/charts/core/bar-labels.js +5 -0
- package/dist/ssr/charts/core/bar-labels.js.map +1 -0
- package/dist/ssr/charts/core/bubble.js +166 -0
- package/dist/ssr/charts/core/bubble.js.map +1 -0
- package/dist/ssr/charts/core/default-styles.js +83 -0
- package/dist/ssr/charts/core/default-styles.js.map +1 -0
- package/dist/ssr/charts/core/funnel-layout.js +79 -0
- package/dist/ssr/charts/core/funnel-layout.js.map +1 -0
- package/dist/ssr/charts/core/funnel.js +27 -0
- package/dist/ssr/charts/core/funnel.js.map +1 -0
- package/dist/ssr/charts/core/index.js +6 -1
- package/dist/ssr/charts/core/index.js.map +1 -1
- package/dist/ssr/charts/core/pareto.js +120 -0
- package/dist/ssr/charts/core/pareto.js.map +1 -0
- package/dist/ssr/charts/core/radar.js +101 -0
- package/dist/ssr/charts/core/radar.js.map +1 -0
- package/dist/ssr/charts/core/registry.js +11 -1
- package/dist/ssr/charts/core/registry.js.map +1 -1
- package/dist/ssr/charts/core/render-size.js +3 -1
- package/dist/ssr/charts/core/render-size.js.map +1 -1
- package/dist/ssr/charts/core/render.js +19 -0
- package/dist/ssr/charts/core/render.js.map +1 -1
- package/dist/ssr/charts/core/snapshot.js +1 -1
- package/dist/ssr/charts/core/snapshot.js.map +1 -1
- package/dist/ssr/charts/core/svg-barGauge.js +67 -0
- package/dist/ssr/charts/core/svg-barGauge.js.map +1 -0
- package/dist/ssr/charts/core/svg-boxplot.js +127 -0
- package/dist/ssr/charts/core/svg-boxplot.js.map +1 -0
- package/dist/ssr/charts/core/svg-common.js +1065 -0
- package/dist/ssr/charts/core/svg-common.js.map +1 -0
- package/dist/ssr/charts/core/svg-gauge.js +185 -0
- package/dist/ssr/charts/core/svg-gauge.js.map +1 -0
- package/dist/ssr/charts/core/svg-heatmap.js +80 -0
- package/dist/ssr/charts/core/svg-heatmap.js.map +1 -0
- package/dist/ssr/charts/core/svg-histogram.js +73 -0
- package/dist/ssr/charts/core/svg-histogram.js.map +1 -0
- package/dist/ssr/charts/core/svg-renderers.js +9 -1777
- package/dist/ssr/charts/core/svg-renderers.js.map +1 -1
- package/dist/ssr/charts/core/svg-scatter.js +114 -0
- package/dist/ssr/charts/core/svg-scatter.js.map +1 -0
- package/dist/ssr/charts/core/treemap.js +138 -0
- package/dist/ssr/charts/core/treemap.js.map +1 -0
- package/dist/ssr/charts/description.js.map +1 -1
- package/dist/ssr/content/ChartRoot.js +21 -7
- package/dist/ssr/content/ChartRoot.js.map +1 -1
- package/dist/ssr/content/ChartSnapshotView.js +1 -0
- package/dist/ssr/content/ChartSnapshotView.js.map +1 -1
- package/dist/ssr/content/DataTable.js +12 -16
- package/dist/ssr/content/DataTable.js.map +1 -1
- package/dist/ssr/content/Lightbox.js +32 -3
- package/dist/ssr/content/Lightbox.js.map +1 -1
- package/dist/ssr/content/ThumbnailStrip.js +103 -0
- package/dist/ssr/content/ThumbnailStrip.js.map +1 -0
- package/dist/ssr/content/chart-bar.js +98 -0
- package/dist/ssr/content/chart-bar.js.map +1 -1
- package/dist/ssr/content/chart-inspection-core.js +28 -8
- package/dist/ssr/content/chart-inspection-core.js.map +1 -1
- package/dist/ssr/content/chart-message-catalog.js +18 -0
- package/dist/ssr/content/chart-message-catalog.js.map +1 -1
- package/dist/ssr/content/index.js +1 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/thumbnail-strip-messages.js +24 -0
- package/dist/ssr/content/thumbnail-strip-messages.js.map +1 -0
- package/dist/ssr/inputs/ChoiceInputs.js +55 -40
- package/dist/ssr/inputs/ChoiceInputs.js.map +1 -1
- package/dist/ssr/inputs/index.js +1 -0
- package/dist/ssr/inputs/index.js.map +1 -1
- package/dist/ssr/inputs/markdown/MarkdownSelectionToolbar.js +293 -0
- package/dist/ssr/inputs/markdown/MarkdownSelectionToolbar.js.map +1 -0
- package/dist/ssr/internal/field.js +36 -24
- package/dist/ssr/internal/field.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +5 -1
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/PanelDialog.js +40 -26
- package/dist/ssr/layout/PanelDialog.js.map +1 -1
- package/dist/ssr/surfaces/StatCell.js +29 -18
- package/dist/ssr/surfaces/StatCell.js.map +1 -1
- package/dist/ssr/surfaces/stat-cell-messages.js +4 -2
- package/dist/ssr/surfaces/stat-cell-messages.js.map +1 -1
- package/dist/ssr/widgets/DashboardFrame.js +6 -11
- package/dist/ssr/widgets/DashboardFrame.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/actions/Toolbar.d.ts +2 -0
- package/dist/types/charts/core/bar-labels.d.ts +1 -0
- package/dist/types/charts/core/bubble.d.ts +66 -0
- package/dist/types/charts/core/default-styles.d.ts +1 -0
- package/dist/types/charts/core/funnel-layout.d.ts +43 -0
- package/dist/types/charts/core/funnel.d.ts +13 -0
- package/dist/types/charts/core/index.d.ts +6 -1
- package/dist/types/charts/core/pareto.d.ts +40 -0
- package/dist/types/charts/core/radar.d.ts +47 -0
- package/dist/types/charts/core/registry.d.ts +10 -0
- package/dist/types/charts/core/render-size.d.ts +1 -1
- package/dist/types/charts/core/svg-barGauge.d.ts +3 -0
- package/dist/types/charts/core/svg-boxplot.d.ts +18 -0
- package/dist/types/charts/core/svg-common.d.ts +612 -0
- package/dist/types/charts/core/svg-gauge.d.ts +6 -0
- package/dist/types/charts/core/svg-heatmap.d.ts +6 -0
- package/dist/types/charts/core/svg-histogram.d.ts +16 -0
- package/dist/types/charts/core/svg-renderers.d.ts +8 -612
- package/dist/types/charts/core/svg-scatter.d.ts +23 -0
- package/dist/types/charts/core/treemap.d.ts +45 -0
- package/dist/types/charts/description.d.ts +63 -0
- package/dist/types/content/ChartRoot.d.ts +1 -1
- package/dist/types/content/Lightbox.d.ts +2 -0
- package/dist/types/content/ThumbnailStrip.d.ts +29 -0
- package/dist/types/content/chart-bar.d.ts +38 -2
- package/dist/types/content/chart-inspection-core.d.ts +7 -0
- package/dist/types/content/chart-inspection.d.ts +1 -0
- package/dist/types/content/chart-message-catalog.d.ts +8 -0
- package/dist/types/content/index.d.ts +1 -0
- package/dist/types/content/thumbnail-strip-messages.d.ts +10 -0
- package/dist/types/inputs/ChoiceInputs.d.ts +2 -0
- package/dist/types/inputs/index.d.ts +2 -0
- package/dist/types/inputs/markdown/MarkdownSelectionToolbar.d.ts +19 -0
- package/dist/types/internal/field.d.ts +2 -0
- package/dist/types/layout/PanelDialog.d.ts +5 -0
- package/dist/types/surfaces/stat-cell-messages.d.ts +1 -0
- package/dist/types/widgets/DashboardFrame.d.ts +4 -1
- package/package.json +2 -2
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Ported SVG algorithm, kept verbatim; separate module for per-kind frame loading.
|
|
2
|
+
|
|
3
|
+
import { chartDatumSvg, collectAxisValues, collectSeriesLegend, computeAxisScale, computeDomain, computePlotArea, DEFAULT_HEIGHT, DEFAULT_MARKERS, DEFAULT_WIDTH, emptyChart, fmt, isOnAxis, linearRegression, mapRange, markerPath, normalizePadding, pickMapper, pointValues, renderErrorBars, renderHeader, renderLegend, renderReferenceLines, renderXAxisNumeric, renderYAxis, svgRoot } from "./svg-common";
|
|
4
|
+
/**
|
|
5
|
+
* Render a scatter plot. Each series produces a group of circles, one per
|
|
6
|
+
* data point, classed `stdlib-chart-series-N` so series colors stay
|
|
7
|
+
* consistent across charts.
|
|
8
|
+
*
|
|
9
|
+
* Non-finite x or y values are filtered out. An empty `series` (or one with
|
|
10
|
+
* no finite points) renders an empty-state SVG with a "no data" label.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* charts.scatter({ series: [{ data: [{x:1,y:2},{x:2,y:5},{x:3,y:3}] }] });
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* charts.scatter({
|
|
17
|
+
* series: [
|
|
18
|
+
* { label: "Group A", data: [...] },
|
|
19
|
+
* { label: "Group B", data: [...] },
|
|
20
|
+
* ],
|
|
21
|
+
* xAxis: { label: "Time (s)" },
|
|
22
|
+
* yAxis: { label: "Value", format: v => `${v}k` },
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
export const scatter = opts => {
|
|
26
|
+
const width = opts.width ?? DEFAULT_WIDTH;
|
|
27
|
+
const height = opts.height ?? DEFAULT_HEIGHT;
|
|
28
|
+
const padding = normalizePadding(opts.padding);
|
|
29
|
+
const header = renderHeader(width, opts.title, opts.subtitle);
|
|
30
|
+
const legendItems = collectSeriesLegend(opts);
|
|
31
|
+
const legendHeight = legendItems.length > 0 ? 20 : 0;
|
|
32
|
+
const finitePoints = opts.series.flatMap(s => s.data).filter(p => Number.isFinite(p.x) && Number.isFinite(p.y));
|
|
33
|
+
if (finitePoints.length === 0) return emptyChart(width, height, opts.className);
|
|
34
|
+
const area = computePlotArea(width, height, padding, !!opts.xAxis?.label, !!opts.yAxis?.label, header.height, legendHeight);
|
|
35
|
+
const xAxis = computeAxisScale(collectAxisValues(opts.series, "x"), opts.xAxis);
|
|
36
|
+
const yAxis = computeAxisScale(collectAxisValues(opts.series, "y"), opts.yAxis);
|
|
37
|
+
const xMap = pickMapper(xAxis.scale);
|
|
38
|
+
const yMap = pickMapper(yAxis.scale);
|
|
39
|
+
|
|
40
|
+
// Size dimension: if any point has a finite `size`, normalize across all
|
|
41
|
+
// points to the configured pixel range; points without size fall back to
|
|
42
|
+
// the midpoint so they remain visible.
|
|
43
|
+
const sizes = finitePoints.map(p => p.size).filter(s => typeof s === "number" && Number.isFinite(s));
|
|
44
|
+
const sizeRange = opts.sizeRange ?? [3, 12];
|
|
45
|
+
const useSizes = sizes.length > 0;
|
|
46
|
+
const sizeDomain = useSizes ? computeDomain(sizes) : [0, 1];
|
|
47
|
+
const fallbackR = (sizeRange[0] + sizeRange[1]) / 2;
|
|
48
|
+
const body = [];
|
|
49
|
+
body.push(renderYAxis(yAxis, area, opts.yAxis));
|
|
50
|
+
body.push(renderXAxisNumeric(xAxis, area, height - legendHeight, opts.xAxis));
|
|
51
|
+
|
|
52
|
+
// Error bars render under the points so the points sit on top of the bar
|
|
53
|
+
// intersections — easier to read.
|
|
54
|
+
body.push(renderErrorBars(opts.series, xAxis, yAxis, area));
|
|
55
|
+
opts.series.forEach((s, i) => {
|
|
56
|
+
const cls = `stdlib-chart-series-${i % 8}`;
|
|
57
|
+
const shape = s.marker ?? (opts.autoVariant ? DEFAULT_MARKERS[i % DEFAULT_MARKERS.length] : "circle");
|
|
58
|
+
const elements = s.data.map((p, index) => ({
|
|
59
|
+
p,
|
|
60
|
+
index
|
|
61
|
+
})).filter(({
|
|
62
|
+
p
|
|
63
|
+
}) => isOnAxis(xAxis, p.x) && isOnAxis(yAxis, p.y)).map(({
|
|
64
|
+
p,
|
|
65
|
+
index
|
|
66
|
+
}) => {
|
|
67
|
+
const cx = xMap(p.x, xAxis.domain, [area.x0, area.x1]);
|
|
68
|
+
const cy = yMap(p.y, yAxis.domain, [area.y1, area.y0]);
|
|
69
|
+
const r = useSizes ? typeof p.size === "number" && Number.isFinite(p.size) ? mapRange(p.size, sizeDomain, sizeRange) : fallbackR : 3;
|
|
70
|
+
const datum = {
|
|
71
|
+
index,
|
|
72
|
+
seriesIndex: i,
|
|
73
|
+
role: "point",
|
|
74
|
+
label: s.label,
|
|
75
|
+
anchor: [cx, cy],
|
|
76
|
+
values: pointValues(p, opts.xAxis, opts.yAxis)
|
|
77
|
+
};
|
|
78
|
+
if (shape === "circle") {
|
|
79
|
+
return chartDatumSvg(`<circle class="stdlib-chart-point" cx="${fmt(cx)}" cy="${fmt(cy)}" r="${fmt(r)}"/>`, datum, opts.inspect);
|
|
80
|
+
}
|
|
81
|
+
return chartDatumSvg(`<path class="stdlib-chart-point" d="${markerPath(shape, cx, cy, r)}"/>`, datum, opts.inspect);
|
|
82
|
+
}).join("");
|
|
83
|
+
if (elements) body.push(`<g class="${cls}">${elements}</g>`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Optional trend line (across all series). Renders after data so it's visible.
|
|
87
|
+
if (opts.trendline) {
|
|
88
|
+
const reg = linearRegression(finitePoints);
|
|
89
|
+
if (reg !== null) {
|
|
90
|
+
const x0 = xAxis.domain[0];
|
|
91
|
+
const x1 = xAxis.domain[1];
|
|
92
|
+
const y0 = reg.slope * x0 + reg.intercept;
|
|
93
|
+
const y1 = reg.slope * x1 + reg.intercept;
|
|
94
|
+
// Clip to plot area in pixel space (visible portion of the regression).
|
|
95
|
+
const px0 = xMap(x0, xAxis.domain, [area.x0, area.x1]);
|
|
96
|
+
const px1 = xMap(x1, xAxis.domain, [area.x0, area.x1]);
|
|
97
|
+
const py0 = yMap(y0, yAxis.domain, [area.y1, area.y0]);
|
|
98
|
+
const py1 = yMap(y1, yAxis.domain, [area.y1, area.y0]);
|
|
99
|
+
body.push(`<line class="stdlib-chart-trendline" x1="${fmt(px0)}" y1="${fmt(py0)}" x2="${fmt(px1)}" y2="${fmt(py1)}"/>`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// References render AFTER data so labels stay on top (z-order matters in SVG).
|
|
104
|
+
if (opts.references && opts.references.length > 0) {
|
|
105
|
+
body.push(renderReferenceLines(opts.references, xAxis, yAxis, area));
|
|
106
|
+
}
|
|
107
|
+
const legend = renderLegend(legendItems, width, height - legendHeight);
|
|
108
|
+
return svgRoot({
|
|
109
|
+
width,
|
|
110
|
+
height,
|
|
111
|
+
className: opts.className
|
|
112
|
+
}, header.svg + body.join("") + legend.svg);
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=svg-scatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["charts/core/svg-scatter.ts"],"sourcesContent":["// Ported SVG algorithm, kept verbatim; separate module for per-kind frame loading.\n\nimport {\n chartDatumSvg,\n collectAxisValues,\n collectSeriesLegend,\n computeAxisScale,\n computeDomain,\n computePlotArea,\n DEFAULT_HEIGHT,\n DEFAULT_MARKERS,\n DEFAULT_WIDTH,\n emptyChart,\n fmt,\n isOnAxis,\n linearRegression,\n mapRange,\n markerPath,\n normalizePadding,\n pickMapper,\n pointValues,\n renderErrorBars,\n renderHeader,\n renderLegend,\n renderReferenceLines,\n renderXAxisNumeric,\n renderYAxis,\n type ScatterOptions,\n svgRoot,\n} from \"./svg-common\";\nimport type { ChartDatum } from \"./types\";\n/**\n * Render a scatter plot. Each series produces a group of circles, one per\n * data point, classed `stdlib-chart-series-N` so series colors stay\n * consistent across charts.\n *\n * Non-finite x or y values are filtered out. An empty `series` (or one with\n * no finite points) renders an empty-state SVG with a \"no data\" label.\n *\n * @example\n * charts.scatter({ series: [{ data: [{x:1,y:2},{x:2,y:5},{x:3,y:3}] }] });\n *\n * @example\n * charts.scatter({\n * series: [\n * { label: \"Group A\", data: [...] },\n * { label: \"Group B\", data: [...] },\n * ],\n * xAxis: { label: \"Time (s)\" },\n * yAxis: { label: \"Value\", format: v => `${v}k` },\n * });\n */\nexport const scatter = (opts: ScatterOptions): string => {\n const width = opts.width ?? DEFAULT_WIDTH;\n const height = opts.height ?? DEFAULT_HEIGHT;\n const padding = normalizePadding(opts.padding);\n const header = renderHeader(width, opts.title, opts.subtitle);\n const legendItems = collectSeriesLegend(opts);\n const legendHeight = legendItems.length > 0 ? 20 : 0;\n\n const finitePoints = opts.series.flatMap((s) => s.data).filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));\n if (finitePoints.length === 0) return emptyChart(width, height, opts.className);\n\n const area = computePlotArea(width, height, padding, !!opts.xAxis?.label, !!opts.yAxis?.label, header.height, legendHeight);\n\n const xAxis = computeAxisScale(collectAxisValues(opts.series, \"x\"), opts.xAxis);\n const yAxis = computeAxisScale(collectAxisValues(opts.series, \"y\"), opts.yAxis);\n const xMap = pickMapper(xAxis.scale);\n const yMap = pickMapper(yAxis.scale);\n\n // Size dimension: if any point has a finite `size`, normalize across all\n // points to the configured pixel range; points without size fall back to\n // the midpoint so they remain visible.\n const sizes = finitePoints.map((p) => p.size).filter((s): s is number => typeof s === \"number\" && Number.isFinite(s));\n const sizeRange = opts.sizeRange ?? [3, 12];\n const useSizes = sizes.length > 0;\n const sizeDomain = useSizes ? computeDomain(sizes) : ([0, 1] as [number, number]);\n const fallbackR = (sizeRange[0] + sizeRange[1]) / 2;\n\n const body: string[] = [];\n body.push(renderYAxis(yAxis, area, opts.yAxis));\n body.push(renderXAxisNumeric(xAxis, area, height - legendHeight, opts.xAxis));\n\n // Error bars render under the points so the points sit on top of the bar\n // intersections — easier to read.\n body.push(renderErrorBars(opts.series, xAxis, yAxis, area));\n\n opts.series.forEach((s, i) => {\n const cls = `stdlib-chart-series-${i % 8}`;\n const shape = s.marker ?? (opts.autoVariant ? DEFAULT_MARKERS[i % DEFAULT_MARKERS.length]! : \"circle\");\n const elements = s.data\n .map((p, index) => ({ p, index }))\n .filter(({ p }) => isOnAxis(xAxis, p.x) && isOnAxis(yAxis, p.y))\n .map(({ p, index }) => {\n const cx = xMap(p.x, xAxis.domain, [area.x0, area.x1]);\n const cy = yMap(p.y, yAxis.domain, [area.y1, area.y0]);\n const r = useSizes\n ? typeof p.size === \"number\" && Number.isFinite(p.size)\n ? mapRange(p.size, sizeDomain, sizeRange)\n : fallbackR\n : 3;\n const datum: ChartDatum = {\n index,\n seriesIndex: i,\n role: \"point\",\n label: s.label,\n anchor: [cx, cy],\n values: pointValues(p, opts.xAxis, opts.yAxis),\n };\n if (shape === \"circle\") {\n return chartDatumSvg(`<circle class=\"stdlib-chart-point\" cx=\"${fmt(cx)}\" cy=\"${fmt(cy)}\" r=\"${fmt(r)}\"/>`, datum, opts.inspect);\n }\n return chartDatumSvg(`<path class=\"stdlib-chart-point\" d=\"${markerPath(shape, cx, cy, r)}\"/>`, datum, opts.inspect);\n })\n .join(\"\");\n if (elements) body.push(`<g class=\"${cls}\">${elements}</g>`);\n });\n\n // Optional trend line (across all series). Renders after data so it's visible.\n if (opts.trendline) {\n const reg = linearRegression(finitePoints);\n if (reg !== null) {\n const x0 = xAxis.domain[0];\n const x1 = xAxis.domain[1];\n const y0 = reg.slope * x0 + reg.intercept;\n const y1 = reg.slope * x1 + reg.intercept;\n // Clip to plot area in pixel space (visible portion of the regression).\n const px0 = xMap(x0, xAxis.domain, [area.x0, area.x1]);\n const px1 = xMap(x1, xAxis.domain, [area.x0, area.x1]);\n const py0 = yMap(y0, yAxis.domain, [area.y1, area.y0]);\n const py1 = yMap(y1, yAxis.domain, [area.y1, area.y0]);\n body.push(`<line class=\"stdlib-chart-trendline\" x1=\"${fmt(px0)}\" y1=\"${fmt(py0)}\" x2=\"${fmt(px1)}\" y2=\"${fmt(py1)}\"/>`);\n }\n }\n\n // References render AFTER data so labels stay on top (z-order matters in SVG).\n if (opts.references && opts.references.length > 0) {\n body.push(renderReferenceLines(opts.references, xAxis, yAxis, area));\n }\n\n const legend = renderLegend(legendItems, width, height - legendHeight);\n return svgRoot({ width, height, className: opts.className }, header.svg + body.join(\"\") + legend.svg);\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { chartDatumSvg, escapeXml, renderHeader, svgRoot } from "./svg";
|
|
2
|
+
export function treemapLayout(spec, size = {
|
|
3
|
+
width: spec.width ?? 480,
|
|
4
|
+
height: spec.height ?? 280
|
|
5
|
+
}) {
|
|
6
|
+
const width = spec.displayWidth ?? size.width,
|
|
7
|
+
height = size.height;
|
|
8
|
+
const data = spec.data.map((d, index) => {
|
|
9
|
+
if (!Number.isFinite(d.value) || d.value < 0) throw new RangeError("Treemap values must be finite and nonnegative.");
|
|
10
|
+
if (!spec.groups.includes(d.group)) throw new RangeError("Treemap groups must contain every item group.");
|
|
11
|
+
return {
|
|
12
|
+
...d,
|
|
13
|
+
index
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
const total = data.reduce((s, d) => s + d.value, 0);
|
|
17
|
+
if (!Number.isFinite(total)) throw new RangeError("Treemap total must be finite.");
|
|
18
|
+
const header = renderHeader(width, spec.title, spec.subtitle);
|
|
19
|
+
const legend = [];
|
|
20
|
+
let lx = 0,
|
|
21
|
+
lr = 0;
|
|
22
|
+
if (spec.legend !== false) spec.groups.forEach((label, index) => {
|
|
23
|
+
const itemWidth = Math.min(width, label.length * 12 + 28);
|
|
24
|
+
if (lx && lx + itemWidth > width) {
|
|
25
|
+
lx = 0;
|
|
26
|
+
lr++;
|
|
27
|
+
}
|
|
28
|
+
legend.push({
|
|
29
|
+
label,
|
|
30
|
+
index,
|
|
31
|
+
x: lx,
|
|
32
|
+
row: lr
|
|
33
|
+
});
|
|
34
|
+
lx += itemWidth;
|
|
35
|
+
});
|
|
36
|
+
const legendHeight = legend.length ? (lr + 1) * 20 + 4 : 0,
|
|
37
|
+
plotHeight = Math.max(0, height - header.height - legendHeight);
|
|
38
|
+
const entries = data.filter(d => d.value > 0).sort((a, b) => b.value - a.value).map(d => ({
|
|
39
|
+
...d,
|
|
40
|
+
area: d.value / (total || 1) * width * plotHeight
|
|
41
|
+
})).filter(d => d.area > 0);
|
|
42
|
+
const cells = [];
|
|
43
|
+
let x = 0,
|
|
44
|
+
y = header.height,
|
|
45
|
+
w = width,
|
|
46
|
+
h = plotHeight;
|
|
47
|
+
const worst = (areas, side) => {
|
|
48
|
+
if (!areas.length || side <= 0) return Infinity;
|
|
49
|
+
const sum = areas.reduce((s, a) => s + a, 0),
|
|
50
|
+
lo = Math.min(...areas),
|
|
51
|
+
hi = Math.max(...areas);
|
|
52
|
+
return Math.max(side * side * hi / (sum * sum), sum * sum / (side * side * lo));
|
|
53
|
+
};
|
|
54
|
+
let offset = 0;
|
|
55
|
+
while (offset < entries.length) {
|
|
56
|
+
if (w <= 0 || h <= 0) break;
|
|
57
|
+
const row = [entries[offset++]];
|
|
58
|
+
const side = Math.min(w, h);
|
|
59
|
+
while (offset < entries.length && worst(row.map(c => c.area), side) >= worst([...row.map(c => c.area), entries[offset].area], side)) row.push(entries[offset++]);
|
|
60
|
+
const sum = row.reduce((s, c) => s + c.area, 0);
|
|
61
|
+
const vertical = w >= h;
|
|
62
|
+
const thickness = Math.min(vertical ? w : h, sum / (vertical ? h : w));
|
|
63
|
+
let pos = vertical ? y : x;
|
|
64
|
+
for (const d of row) {
|
|
65
|
+
const length = d.area / thickness;
|
|
66
|
+
cells.push({
|
|
67
|
+
...d,
|
|
68
|
+
x: vertical ? x : pos,
|
|
69
|
+
y: vertical ? pos : y,
|
|
70
|
+
width: vertical ? thickness : length,
|
|
71
|
+
height: vertical ? length : thickness
|
|
72
|
+
});
|
|
73
|
+
pos += length;
|
|
74
|
+
}
|
|
75
|
+
if (vertical) {
|
|
76
|
+
x += thickness;
|
|
77
|
+
w = Math.max(0, w - thickness);
|
|
78
|
+
} else {
|
|
79
|
+
y += thickness;
|
|
80
|
+
h = Math.max(0, h - thickness);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const d of data.filter(d => !cells.some(c => c.index === d.index))) cells.push({
|
|
84
|
+
...d,
|
|
85
|
+
x: 0,
|
|
86
|
+
y: 0,
|
|
87
|
+
width: 0,
|
|
88
|
+
height: 0
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
width,
|
|
92
|
+
height,
|
|
93
|
+
total,
|
|
94
|
+
cells,
|
|
95
|
+
header,
|
|
96
|
+
legend,
|
|
97
|
+
legendTop: height - legendHeight,
|
|
98
|
+
plotHeight
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function renderTreemapSvg(spec) {
|
|
102
|
+
const scene = treemapLayout(spec);
|
|
103
|
+
const number = spec.format ?? new Intl.NumberFormat(spec.locale ?? "en", {
|
|
104
|
+
maximumSignificantDigits: 15
|
|
105
|
+
}).format;
|
|
106
|
+
const percent = new Intl.NumberFormat(spec.locale ?? "en", {
|
|
107
|
+
style: "percent",
|
|
108
|
+
maximumFractionDigits: 1
|
|
109
|
+
});
|
|
110
|
+
const body = scene.header.svg.replace(/<text class="stdlib-chart-title"/g, '<text fill="var(--k2b-text,#27272a)" font-size="14" class="stdlib-chart-title"').replace(/<text class="stdlib-chart-subtitle"/g, '<text fill="var(--k2b-text-muted,#45454e)" font-size="11" class="stdlib-chart-subtitle"') + scene.cells.map(c => {
|
|
111
|
+
const group = spec.groups.indexOf(c.group) % 8 + 1;
|
|
112
|
+
const color = `var(--stdlib-chart-c${group},${["#3b82f6", "#10b981", "#f59e0b", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16", "#f97316"][group - 1]})`;
|
|
113
|
+
const text = c.width * (spec.displayWidth ?? 296) / scene.width > Math.max(56, Math.max(c.label.length * 12, number(c.value).length * 11) + 16) && c.height * Math.min(1, 180 / scene.height) > 48 ? `<text data-treemap-name="true" style="translate:0 calc((var(--k2b-chart-inverse-y,1) - 1) * 18px)" x="${c.x + 8}" y="${c.y + 18}" font-size="12" font-weight="600" fill="var(--k2b-text,#27272a)">${escapeXml(c.label)}</text><text data-treemap-value="true" style="translate:0 calc((var(--k2b-chart-inverse-y,1) - 1) * 38px)" x="${c.x + 8}" y="${c.y + 38}" font-size="11" font-weight="400" fill="var(--k2b-text-muted,#45454e)">${escapeXml(number(c.value))}</text>` : "";
|
|
114
|
+
return chartDatumSvg(`<rect class="k2b-chart-treemap-cell" x="${c.x + 1}" y="${c.y + 1}" width="${Math.max(0, c.width - 2)}" height="${Math.max(0, c.height - 2)}" rx="4" fill="color-mix(in srgb,${color} 55%,var(--k2b-surface,#fff))" stroke="none"/>${text}`, {
|
|
115
|
+
role: "item",
|
|
116
|
+
index: c.index,
|
|
117
|
+
label: c.label,
|
|
118
|
+
anchor: [c.x + c.width / 2, c.y + c.height / 2],
|
|
119
|
+
values: [{
|
|
120
|
+
key: "group",
|
|
121
|
+
value: c.group
|
|
122
|
+
}, {
|
|
123
|
+
key: "value",
|
|
124
|
+
value: c.value,
|
|
125
|
+
formatted: number(c.value)
|
|
126
|
+
}, {
|
|
127
|
+
key: "percent",
|
|
128
|
+
value: scene.total ? c.value / scene.total * 100 : 0,
|
|
129
|
+
formatted: scene.total ? percent.format(c.value / scene.total) : "–"
|
|
130
|
+
}]
|
|
131
|
+
}, spec.inspect);
|
|
132
|
+
}).join("") + scene.legend.map(item => `<rect x="${item.x}" y="${scene.legendTop + item.row * 20 + 6}" width="8" height="8" rx="2" fill="var(--stdlib-chart-c${item.index % 8 + 1},${["#3b82f6", "#10b981", "#f59e0b", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16", "#f97316"][item.index % 8]})"/><text x="${item.x + 13}" y="${scene.legendTop + item.row * 20 + 14}" font-size="11" fill="var(--k2b-text-muted,#45454e)">${escapeXml(item.label.slice(0, Math.floor((scene.width - 28) / 12)))}</text>`).join("");
|
|
133
|
+
return svgRoot({
|
|
134
|
+
...scene,
|
|
135
|
+
className: spec.className
|
|
136
|
+
}, body);
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=treemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["charts/core/treemap.ts"],"sourcesContent":["import { type ChartOptions, chartDatumSvg, escapeXml, renderHeader, svgRoot } from \"./svg\";\nexport type TreemapOptions = ChartOptions & {\n groups: string[];\n legend?: boolean;\n data: { label: string; group: string; value: number }[];\n locale?: string;\n format?: (value: number) => string;\n displayWidth?: number;\n};\nexport function treemapLayout(spec: TreemapOptions, size = { width: spec.width ?? 480, height: spec.height ?? 280 }) {\n const width = spec.displayWidth ?? size.width,\n height = size.height;\n const data = spec.data.map((d, index) => {\n if (!Number.isFinite(d.value) || d.value < 0) throw new RangeError(\"Treemap values must be finite and nonnegative.\");\n if (!spec.groups.includes(d.group)) throw new RangeError(\"Treemap groups must contain every item group.\");\n return { ...d, index };\n });\n const total = data.reduce((s, d) => s + d.value, 0);\n if (!Number.isFinite(total)) throw new RangeError(\"Treemap total must be finite.\");\n const header = renderHeader(width, spec.title, spec.subtitle);\n const legend: { label: string; x: number; row: number; index: number }[] = [];\n let lx = 0,\n lr = 0;\n if (spec.legend !== false)\n spec.groups.forEach((label, index) => {\n const itemWidth = Math.min(width, label.length * 12 + 28);\n if (lx && lx + itemWidth > width) {\n lx = 0;\n lr++;\n }\n legend.push({ label, index, x: lx, row: lr });\n lx += itemWidth;\n });\n const legendHeight = legend.length ? (lr + 1) * 20 + 4 : 0,\n plotHeight = Math.max(0, height - header.height - legendHeight);\n const entries = data\n .filter((d) => d.value > 0)\n .sort((a, b) => b.value - a.value)\n .map((d) => ({ ...d, area: (d.value / (total || 1)) * width * plotHeight }))\n .filter((d) => d.area > 0);\n const cells: ((typeof data)[number] & { x: number; y: number; width: number; height: number })[] = [];\n let x = 0,\n y = header.height,\n w = width,\n h = plotHeight;\n const worst = (areas: number[], side: number) => {\n if (!areas.length || side <= 0) return Infinity;\n const sum = areas.reduce((s, a) => s + a, 0),\n lo = Math.min(...areas),\n hi = Math.max(...areas);\n return Math.max((side * side * hi) / (sum * sum), (sum * sum) / (side * side * lo));\n };\n let offset = 0;\n while (offset < entries.length) {\n if (w <= 0 || h <= 0) break;\n const row = [entries[offset++]!];\n const side = Math.min(w, h);\n while (\n offset < entries.length &&\n worst(\n row.map((c) => c.area),\n side,\n ) >= worst([...row.map((c) => c.area), entries[offset]!.area], side)\n )\n row.push(entries[offset++]!);\n const sum = row.reduce((s, c) => s + c.area, 0);\n const vertical = w >= h;\n const thickness = Math.min(vertical ? w : h, sum / (vertical ? h : w));\n let pos = vertical ? y : x;\n for (const d of row) {\n const length = d.area / thickness;\n cells.push({\n ...d,\n x: vertical ? x : pos,\n y: vertical ? pos : y,\n width: vertical ? thickness : length,\n height: vertical ? length : thickness,\n });\n pos += length;\n }\n if (vertical) {\n x += thickness;\n w = Math.max(0, w - thickness);\n } else {\n y += thickness;\n h = Math.max(0, h - thickness);\n }\n }\n for (const d of data.filter((d) => !cells.some((c) => c.index === d.index))) cells.push({ ...d, x: 0, y: 0, width: 0, height: 0 });\n return { width, height, total, cells, header, legend, legendTop: height - legendHeight, plotHeight };\n}\nexport function renderTreemapSvg(spec: TreemapOptions): string {\n const scene = treemapLayout(spec);\n const number = spec.format ?? new Intl.NumberFormat(spec.locale ?? \"en\", { maximumSignificantDigits: 15 }).format;\n const percent = new Intl.NumberFormat(spec.locale ?? \"en\", { style: \"percent\", maximumFractionDigits: 1 });\n const body =\n scene.header.svg\n .replace(/<text class=\"stdlib-chart-title\"/g, '<text fill=\"var(--k2b-text,#27272a)\" font-size=\"14\" class=\"stdlib-chart-title\"')\n .replace(\n /<text class=\"stdlib-chart-subtitle\"/g,\n '<text fill=\"var(--k2b-text-muted,#45454e)\" font-size=\"11\" class=\"stdlib-chart-subtitle\"',\n ) +\n scene.cells\n .map((c) => {\n const group = (spec.groups.indexOf(c.group) % 8) + 1;\n const color = `var(--stdlib-chart-c${group},${[\"#3b82f6\", \"#10b981\", \"#f59e0b\", \"#8b5cf6\", \"#ec4899\", \"#06b6d4\", \"#84cc16\", \"#f97316\"][group - 1]})`;\n const text =\n (c.width * (spec.displayWidth ?? 296)) / scene.width >\n Math.max(56, Math.max(c.label.length * 12, number(c.value).length * 11) + 16) && c.height * Math.min(1, 180 / scene.height) > 48\n ? `<text data-treemap-name=\"true\" style=\"translate:0 calc((var(--k2b-chart-inverse-y,1) - 1) * 18px)\" x=\"${c.x + 8}\" y=\"${c.y + 18}\" font-size=\"12\" font-weight=\"600\" fill=\"var(--k2b-text,#27272a)\">${escapeXml(c.label)}</text><text data-treemap-value=\"true\" style=\"translate:0 calc((var(--k2b-chart-inverse-y,1) - 1) * 38px)\" x=\"${c.x + 8}\" y=\"${c.y + 38}\" font-size=\"11\" font-weight=\"400\" fill=\"var(--k2b-text-muted,#45454e)\">${escapeXml(number(c.value))}</text>`\n : \"\";\n return chartDatumSvg(\n `<rect class=\"k2b-chart-treemap-cell\" x=\"${c.x + 1}\" y=\"${c.y + 1}\" width=\"${Math.max(0, c.width - 2)}\" height=\"${Math.max(0, c.height - 2)}\" rx=\"4\" fill=\"color-mix(in srgb,${color} 55%,var(--k2b-surface,#fff))\" stroke=\"none\"/>${text}`,\n {\n role: \"item\",\n index: c.index,\n label: c.label,\n anchor: [c.x + c.width / 2, c.y + c.height / 2],\n values: [\n { key: \"group\", value: c.group },\n { key: \"value\", value: c.value, formatted: number(c.value) },\n {\n key: \"percent\",\n value: scene.total ? (c.value / scene.total) * 100 : 0,\n formatted: scene.total ? percent.format(c.value / scene.total) : \"–\",\n },\n ],\n },\n spec.inspect,\n );\n })\n .join(\"\") +\n scene.legend\n .map(\n (item) =>\n `<rect x=\"${item.x}\" y=\"${scene.legendTop + item.row * 20 + 6}\" width=\"8\" height=\"8\" rx=\"2\" fill=\"var(--stdlib-chart-c${(item.index % 8) + 1},${[\"#3b82f6\", \"#10b981\", \"#f59e0b\", \"#8b5cf6\", \"#ec4899\", \"#06b6d4\", \"#84cc16\", \"#f97316\"][item.index % 8]})\"/><text x=\"${item.x + 13}\" y=\"${scene.legendTop + item.row * 20 + 14}\" font-size=\"11\" fill=\"var(--k2b-text-muted,#45454e)\">${escapeXml(item.label.slice(0, Math.floor((scene.width - 28) / 12)))}</text>`,\n )\n .join(\"\");\n return svgRoot({ ...scene, className: spec.className }, body);\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["charts/description.ts"],"sourcesContent":["/** JSON number formatting. Functions belong to the rendering host, never the saved description. */\nexport type ChartNumberFormat = {\n style?: \"number\" | \"percent\" | \"currency\";\n currency?: string;\n unit?: string;\n decimals?: number;\n compact?: boolean;\n};\nexport type ChartDateFormat = { date: \"day\" | \"minute\" | \"second\" };\nexport type ChartFormat = ChartNumberFormat | ChartDateFormat;\nexport type ChartLimits = { maxSeries: number; maxValues: number; maxMarks: number };\nexport type ChartDescriptionAxis = {\n label?: string;\n domain?: number[];\n ticks?: number;\n scale?: \"linear\" | \"log\";\n format?: ChartFormat;\n};\ntype Reference = {\n value: number | string;\n axis?: \"x\" | \"y\";\n label?: string;\n style?: \"solid\" | \"dashed\" | \"dotted\";\n tone?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\";\n};\ntype Threshold = { value: number; label?: string };\ntype Series<N> = { label?: string; data: { x: N | string; y: N }[]; role?: \"reference\" };\ntype Common = { title: string; subtitle?: string; tooltip?: { label?: string; format?: ChartNumberFormat } };\ntype Variants<N> =\n | {\n kind: \"waterfall\";\n start: { label: string; value: number };\n steps: { label: string; value: number }[];\n end: { label: string };\n format?: ChartNumberFormat;\n }\n | {\n kind: \"area\";\n layout: \"stacked\";\n normalize?: boolean;\n x: (number | string)[];\n series: { label: string; data: (number | null)[] }[];\n legend?: boolean;\n format?: ChartNumberFormat;\n }\n | {\n kind: \"bar\";\n data: { label: string; value: N }[];\n x?: string[];\n series?: { label: string; data: (number | null)[] }[];\n layout?: \"columns\" | \"rows\" | \"grouped\" | \"stacked\";\n normalize?: boolean;\n compare?: { label: string; data: (number | null)[] };\n xAxis?: Pick<ChartDescriptionAxis, \"label\"> & { ordered?: boolean };\n yAxis?: ChartDescriptionAxis;\n colorByBar?: boolean;\n showValues?: boolean;\n legend?: boolean;\n references?: Reference[];\n }\n | {\n kind: \"hbar\";\n series: string[];\n rows: { group?: string; label: string; values: (number | null)[] }[];\n legend?: boolean;\n format?: ChartNumberFormat;\n }\n | {\n kind: \"line\";\n series: (Series<N> & { style?: \"solid\" | \"dashed\" | \"dotted\" })[];\n xAxis?: ChartDescriptionAxis;\n yAxis?: ChartDescriptionAxis;\n legend?: boolean;\n area?: boolean;\n smooth?: boolean;\n references?: Reference[];\n }\n | {\n kind: \"scatter\";\n series: Series<N>[];\n xAxis?: ChartDescriptionAxis;\n yAxis?: ChartDescriptionAxis;\n legend?: boolean;\n references?: Reference[];\n }\n | { kind: \"pie\" | \"donut\"; data: { label: string; value: N }[]; legend?: boolean; showLabels?: boolean; format?: ChartNumberFormat }\n | { kind: \"histogram\"; data: N[]; bins?: number | number[]; references?: Reference[] }\n | {\n kind: \"gauge\";\n value: N;\n min?: number;\n max?: number;\n label?: string;\n unit?: string;\n format?: ChartNumberFormat;\n thresholds?: Threshold[];\n }\n | { kind: \"sparkline\"; data: N[]; area?: boolean }\n | { kind: \"boxplot\"; data: { label: string; values: N[] }[]; yAxis?: ChartDescriptionAxis; references?: Reference[] }\n | {\n kind: \"heatmap\";\n layout: \"calendar\";\n data: [string, N | null][];\n scale: \"sequential\" | \"diverging\";\n label?: string;\n format?: ChartNumberFormat;\n locale?: string;\n }\n | {\n kind: \"heatmap\";\n layout?: undefined;\n data: { x: string; y: string; value: N }[];\n format?: ChartNumberFormat;\n showValues?: boolean;\n min?: number;\n max?: number;\n }\n | {\n kind: \"barGauge\";\n data: { label: string; value: N; min?: number; max?: number }[];\n min?: number;\n max?: number;\n unit?: string;\n format?: ChartNumberFormat;\n thresholds?: Threshold[];\n }\n | { kind: \"stat\"; value: N; format?: ChartNumberFormat; unit?: string; delta?: N; better?: \"up\" | \"down\"; sparkline?: N[] };\n\n/** Portable chart node. Dates are ISO text and missing observations are null, never zero. */\nexport type ChartDescription = Common &\n (\n | Exclude<Variants<number | null>, { kind: \"bar\" }>\n // Aligned series omit flat data; the validator supplies an empty array.\n | (Omit<Extract<Variants<number | null>, { kind: \"bar\" }>, \"data\"> & { data?: { label: string; value: number | null }[] })\n );\n/** Validated renderer input. The portable validator normalizes null gaps to non-finite numbers for drawing. */\nexport type ChartInput = Common &\n (\n | Exclude<Variants<number>, { kind: \"stat\" }>\n | (Omit<Extract<Variants<number>, { kind: \"stat\" }>, \"delta\"> & { delta: number | undefined })\n );\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":[],"sources":["charts/description.ts"],"sourcesContent":["/** JSON number formatting. Functions belong to the rendering host, never the saved description. */\nexport type ChartNumberFormat = {\n style?: \"number\" | \"percent\" | \"currency\";\n currency?: string;\n unit?: string;\n decimals?: number;\n compact?: boolean;\n};\nexport type ChartDateFormat = { date: \"day\" | \"minute\" | \"second\" };\nexport type ChartFormat = ChartNumberFormat | ChartDateFormat;\nexport type ChartLimits = { maxSeries: number; maxValues: number; maxMarks: number };\nexport type ChartDescriptionAxis = {\n label?: string;\n domain?: number[];\n ticks?: number;\n scale?: \"linear\" | \"log\";\n format?: ChartFormat;\n};\ntype Reference = {\n value: number | string;\n axis?: \"x\" | \"y\";\n label?: string;\n style?: \"solid\" | \"dashed\" | \"dotted\";\n tone?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\";\n};\ntype Threshold = { value: number; label?: string };\ntype Series<N> = { label?: string; data: { x: N | string; y: N }[]; role?: \"reference\" };\ntype Common = { title: string; subtitle?: string; tooltip?: { label?: string; format?: ChartNumberFormat } };\ntype Variants<N> =\n | {\n kind: \"waterfall\";\n start: { label: string; value: number };\n steps: { label: string; value: number }[];\n end: { label: string };\n format?: ChartNumberFormat;\n }\n | {\n kind: \"area\";\n layout: \"stacked\";\n normalize?: boolean;\n x: (number | string)[];\n series: { label: string; data: (number | null)[] }[];\n legend?: boolean;\n format?: ChartNumberFormat;\n }\n | {\n kind: \"bar\";\n data: { label: string; value: N }[];\n x?: string[];\n series?: { label: string; data: (number | null)[] }[];\n layout?: \"columns\" | \"rows\" | \"grouped\" | \"stacked\";\n normalize?: boolean;\n share?: boolean;\n limit?: number;\n tone?: \"in\" | \"out\";\n format?: ChartNumberFormat;\n compare?: { label: string; data: (number | null)[] };\n xAxis?: Pick<ChartDescriptionAxis, \"label\"> & { ordered?: boolean };\n yAxis?: ChartDescriptionAxis;\n colorByBar?: boolean;\n showValues?: boolean;\n legend?: boolean;\n references?: Reference[];\n }\n | { kind: \"funnel\"; data: { label: string; value: number | null }[]; format?: ChartNumberFormat }\n | {\n kind: \"hbar\";\n series: string[];\n rows: { group?: string; label: string; values: (number | null)[] }[];\n legend?: boolean;\n format?: ChartNumberFormat;\n }\n | {\n kind: \"line\";\n series: (Series<N> & { style?: \"solid\" | \"dashed\" | \"dotted\" })[];\n xAxis?: ChartDescriptionAxis;\n yAxis?: ChartDescriptionAxis;\n legend?: boolean;\n area?: boolean;\n smooth?: boolean;\n references?: Reference[];\n }\n | {\n kind: \"scatter\";\n series: Series<N>[];\n xAxis?: ChartDescriptionAxis;\n yAxis?: ChartDescriptionAxis;\n legend?: boolean;\n references?: Reference[];\n }\n | { kind: \"pie\" | \"donut\"; data: { label: string; value: N }[]; legend?: boolean; showLabels?: boolean; format?: ChartNumberFormat }\n | { kind: \"histogram\"; data: N[]; bins?: number | number[]; references?: Reference[] }\n | {\n kind: \"gauge\";\n value: N;\n min?: number;\n max?: number;\n label?: string;\n unit?: string;\n format?: ChartNumberFormat;\n thresholds?: Threshold[];\n }\n | { kind: \"sparkline\"; data: N[]; area?: boolean }\n | { kind: \"boxplot\"; data: { label: string; values: N[] }[]; yAxis?: ChartDescriptionAxis; references?: Reference[] }\n | {\n kind: \"heatmap\";\n layout: \"calendar\";\n data: [string, N | null][];\n scale: \"sequential\" | \"diverging\";\n label?: string;\n format?: ChartNumberFormat;\n locale?: string;\n }\n | {\n kind: \"heatmap\";\n layout?: undefined;\n data: { x: string; y: string; value: N }[];\n format?: ChartNumberFormat;\n showValues?: boolean;\n min?: number;\n max?: number;\n }\n | {\n kind: \"barGauge\";\n data: { label: string; value: N; min?: number; max?: number }[];\n min?: number;\n max?: number;\n unit?: string;\n format?: ChartNumberFormat;\n thresholds?: Threshold[];\n }\n | { kind: \"stat\"; value: N; format?: ChartNumberFormat; unit?: string; delta?: N; better?: \"up\" | \"down\"; sparkline?: N[] }\n | {\n kind: \"treemap\";\n groups: string[];\n data: { label: string; group: string; value: number }[];\n legend?: boolean;\n format?: ChartNumberFormat;\n locale?: string;\n }\n | {\n kind: \"pareto\";\n labels: [string, string];\n data: { label: string; value: number; prev: number }[];\n format?: ChartNumberFormat;\n locale?: string;\n }\n | {\n kind: \"bubble\";\n groups: string[];\n data: { label: string; group: string; x: number; y: number; size?: number }[];\n xLabel: string;\n yLabel: string;\n sizeLabel?: string;\n averages?: boolean;\n legend?: boolean;\n xFormat?: ChartNumberFormat;\n yFormat?: ChartNumberFormat;\n format?: ChartNumberFormat;\n locale?: string;\n }\n | {\n kind: \"radar\";\n axes: string[];\n max: number;\n unit?: string;\n series: { label: string; data: number[] }[];\n format?: ChartNumberFormat;\n legend?: boolean;\n locale?: string;\n };\n\n/** Portable chart node. Dates are ISO text and missing observations are null, never zero. */\nexport type ChartDescription = Common &\n (\n | Exclude<Variants<number | null>, { kind: \"bar\" }>\n // Aligned series omit flat data; the validator supplies an empty array.\n | (Omit<Extract<Variants<number | null>, { kind: \"bar\" }>, \"data\"> & { data?: { label: string; value: number | null }[] })\n );\n/** Validated renderer input. The portable validator normalizes null gaps to non-finite numbers for drawing. */\nexport type ChartInput = Common &\n (\n | Exclude<Variants<number>, { kind: \"stat\" }>\n | (Omit<Extract<Variants<number>, { kind: \"stat\" }>, \"delta\"> & { delta: number | undefined })\n );\n"],"mappings":"","ignoreList":[]}
|
|
@@ -88,10 +88,14 @@ import { useViewportMessages } from "./viewport-messages";
|
|
|
88
88
|
* block on truly empty inputs; partially-filled series get rendered
|
|
89
89
|
* as-is and the core handles the gaps. */
|
|
90
90
|
const isEmpty = props => {
|
|
91
|
+
if (props.kind === "treemap" || props.kind === "pareto") return !props.data.length;
|
|
92
|
+
if (props.kind === "bubble") return !props.data.length;
|
|
93
|
+
if (props.kind === "radar") return !props.series.length;
|
|
91
94
|
if (props.kind === "area") return !props.x.length || !props.series.length || props.x.every((_, i) => props.series.some(s => s.data[i] === null) || props.normalize && props.series.every(s => s.data[i] === 0));
|
|
92
95
|
if (props.kind === "line" || props.kind === "scatter") {
|
|
93
96
|
return !props.series?.length || props.series.every(s => !s.data.length);
|
|
94
97
|
}
|
|
98
|
+
if (props.kind === "funnel") return !props.data.length;
|
|
95
99
|
if (props.kind === "hbar") return !props.rows.length;
|
|
96
100
|
if (props.kind === "bar") return props.series ? !props.x?.length || !props.series.some(s => s.data.some(v => v !== null && Number.isFinite(v))) : !props.data?.length;
|
|
97
101
|
if (props.kind === "donut" || props.kind === "pie") {
|
|
@@ -109,7 +113,12 @@ const isEmpty = props => {
|
|
|
109
113
|
return false;
|
|
110
114
|
};
|
|
111
115
|
export const renderChartRoot = (props, renderChartSvg) => {
|
|
112
|
-
const
|
|
116
|
+
const drawingProps = () => {
|
|
117
|
+
// Pareto's labels name its two measures; other kinds use labels only for host controls.
|
|
118
|
+
if (props.kind === "pareto") return props;
|
|
119
|
+
const [, drawing] = splitProps(props, ["class", "style", "labels", "interactive", "selected", "onSelect", "tooltip", "cursor"]);
|
|
120
|
+
return drawing;
|
|
121
|
+
};
|
|
113
122
|
const messages = useUiMessages();
|
|
114
123
|
const chartMessages = useChartMessages();
|
|
115
124
|
const viewportMessages = useViewportMessages();
|
|
@@ -159,7 +168,7 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
159
168
|
const interactiveMap = () => props.kind === "map" && props.interactive === true;
|
|
160
169
|
const interactiveTimeline = () => props.kind === "stateTimeline" && props.interactive === true;
|
|
161
170
|
const draggable = () => interactiveMap() || interactiveTimeline();
|
|
162
|
-
const labels = () => props.labels ?? {};
|
|
171
|
+
const labels = () => props.kind === "pareto" ? {} : props.labels ?? {};
|
|
163
172
|
const accessibleName = () => {
|
|
164
173
|
const override = props.kind === "map" ? labels().interactiveMap : props.kind === "stateTimeline" ? labels().interactiveTimeline : props.kind === "line" ? labels().interactiveLine : labels().interactiveChart;
|
|
165
174
|
const title = "title" in props && typeof props.title === "string" && props.title.trim() ? props.title : undefined;
|
|
@@ -360,7 +369,7 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
360
369
|
select: selection => props.onSelect?.(selection),
|
|
361
370
|
format: selection => {
|
|
362
371
|
if (props.tooltip) return props.tooltip(selection);
|
|
363
|
-
const fields = props.kind === "heatmap" && props.layout === "calendar" ? resolveChartMessages(props.locale ?? locale()).chartFields : chartMessages().chartFields;
|
|
372
|
+
const fields = props.kind === "bubble" || props.kind === "radar" || props.kind === "treemap" || props.kind === "pareto" || props.kind === "heatmap" && props.layout === "calendar" ? resolveChartMessages(props.locale ?? locale()).chartFields : chartMessages().chartFields;
|
|
364
373
|
if (selection.kind === "area" && props.kind === "area") {
|
|
365
374
|
const amount = selection.datum.values.find(f => f.key === "value");
|
|
366
375
|
const share = selection.datum.values.find(f => f.key === "percent");
|
|
@@ -378,7 +387,11 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
378
387
|
return {
|
|
379
388
|
title: selection.datum.label ?? (selection.datum.seriesIndex !== undefined ? `${labels().series ?? chartMessages().series} ${selection.datum.seriesIndex + 1}` : undefined),
|
|
380
389
|
rows: selection.datum.values.filter(field => selection.kind !== "waterfall" || field.key !== "before").map(field => ({
|
|
381
|
-
label: props.kind === "
|
|
390
|
+
label: props.kind === "bubble" && field.key === "x" ? props.xLabel : props.kind === "bubble" && field.key === "y" ? props.yLabel : props.kind === "bubble" && field.key === "size" ? props.sizeLabel ?? fields({
|
|
391
|
+
key: "size"
|
|
392
|
+
}) : props.kind === "radar" && field.key.startsWith("series:") ? props.series[Number(field.key.slice(7))]?.label ?? fields({
|
|
393
|
+
key: "series"
|
|
394
|
+
}) : props.kind === "pareto" && (field.key === "value" || field.key === "previous") ? props.labels[field.key === "value" ? 0 : 1] : props.kind === "heatmap" && props.layout === "calendar" && field.key === "value" && props.label ? props.label : selection.kind === "line" && field.key === "y" ? selection.datum.label ?? labels().series ?? chartMessages().series : (props.kind === "bar" || props.kind === "hbar") && field.key.startsWith("series:") ? (props.kind === "hbar" ? props.series[Number(field.key.slice(7))] : props.series?.[Number(field.key.slice(7))]?.label) ?? fields({
|
|
382
395
|
key: field.key
|
|
383
396
|
}) : props.kind === "bar" && field.key === "previous" ? props.compare?.label ?? fields({
|
|
384
397
|
key: field.key
|
|
@@ -391,12 +404,12 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
391
404
|
}
|
|
392
405
|
});
|
|
393
406
|
const closeChartTooltip = () => inspection.close();
|
|
394
|
-
const svgMarkup = createMemo(() => renderChartSvg(props.kind === "heatmap" && props.layout === "calendar" ? {
|
|
407
|
+
const svgMarkup = createMemo(() => renderChartSvg(props.kind === "bubble" || props.kind === "radar" || props.kind === "treemap" || props.kind === "pareto" || props.kind === "heatmap" && props.layout === "calendar" ? {
|
|
395
408
|
...props,
|
|
396
409
|
locale: props.locale ?? locale()
|
|
397
410
|
} : {
|
|
398
|
-
...drawingProps,
|
|
399
|
-
...(props.kind === "bar" || props.kind === "hbar" ? {
|
|
411
|
+
...drawingProps(),
|
|
412
|
+
...(props.kind === "bar" || props.kind === "hbar" || props.kind === "funnel" ? {
|
|
400
413
|
locale: locale()
|
|
401
414
|
} : {})
|
|
402
415
|
}, size().width, size().height, interactiveMap() ? mapViewport() : undefined, interactiveTimeline() ? timelineViewport() : undefined, locale()));
|
|
@@ -418,6 +431,7 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
418
431
|
onCleanup(closeChartInspectionOnOutside({
|
|
419
432
|
container: () => containerRef,
|
|
420
433
|
tooltip: () => chartTooltipRef,
|
|
434
|
+
scroll: inspection.scroll,
|
|
421
435
|
close: closeChartTooltip,
|
|
422
436
|
keepScroll: () => props.kind === "heatmap" && props.layout === "calendar"
|
|
423
437
|
}));
|