@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":"AAAA;;AAEA,SACE,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,cAAc,EACd,eAAe,EACf,aAAa,EACb,UAAU,EACV,GAAG,EACH,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,UAAU,EACV,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,EAEX,OAAO,QACF,cAAc;AAErB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAM,OAAO,GAAI,IAAoB,IAAa;EACvD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,aAAa;EACzC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,cAAc;EAC5C,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC;EAC9C,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC;EAC7D,MAAM,WAAW,GAAG,mBAAmB,CAAC,IAAI,CAAC;EAC7C,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC;EAEpD,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAE,CAAC,IAAK,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAE,CAAC,IAAK,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;EACnH,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;EAE/E,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;EAE3H,MAAM,KAAK,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC;EAC/E,MAAM,KAAK,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC;EAC/E,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;EACpC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC;;EAEpC;EACA;EACA;EACA,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAE,CAAC,IAAK,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAE,CAAC,IAAkB,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;EACrH,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC;EAC3C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;EACjC,MAAM,UAAU,GAAG,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,GAAI,CAAC,CAAC,EAAE,CAAC,CAAsB;EACjF,MAAM,SAAS,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;EAEnD,MAAM,IAAc,GAAG,EAAE;EACzB,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;EAC/C,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;;EAE7E;EACA;EACA,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;EAE3D,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK;IAC5B,MAAM,GAAG,GAAG,uBAAuB,CAAC,GAAG,CAAC,EAAE;IAC1C,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,WAAW,GAAG,eAAe,CAAC,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,GAAI,QAAQ,CAAC;IACtG,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CACpB,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM;MAAE,CAAC;MAAE;IAAM,CAAC,CAAC,CAAC,CACjC,MAAM,CAAC,CAAC;MAAE;IAAE,CAAC,KAAK,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAC/D,GAAG,CAAC,CAAC;MAAE,CAAC;MAAE;IAAM,CAAC,KAAK;MACrB,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;MACtD,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;MACtD,MAAM,CAAC,GAAG,QAAQ,GACd,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GACnD,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,GACvC,SAAS,GACX,CAAC;MACL,MAAM,KAAiB,GAAG;QACxB,KAAK;QACL,WAAW,EAAE,CAAC;QACd,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;QAChB,MAAM,EAAE,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK;MAC/C,CAAC;MACD,IAAI,KAAK,KAAK,QAAQ,EAAE;QACtB,OAAO,aAAa,CAAC,0CAA0C,GAAG,CAAC,EAAE,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC;MACjI;MACA,OAAO,aAAa,CAAC,uCAAuC,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC;IACrH,CAAC,CAAC,CACD,IAAI,CAAC,EAAE,CAAC;IACX,IAAI,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,aAAa,GAAG,KAAK,QAAQ,MAAM,CAAC;EAC9D,CAAC,CAAC;;EAEF;EACA,IAAI,IAAI,CAAC,SAAS,EAAE;IAClB,MAAM,GAAG,GAAG,gBAAgB,CAAC,YAAY,CAAC;IAC1C,IAAI,GAAG,KAAK,IAAI,EAAE;MAChB,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;MAC1B,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;MAC1B,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,GAAG,EAAE,GAAG,GAAG,CAAC,SAAS;MACzC,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,GAAG,EAAE,GAAG,GAAG,CAAC,SAAS;MACzC;MACA,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;MACtD,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;MACtD,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;MACtD,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;MACtD,IAAI,CAAC,IAAI,CAAC,4CAA4C,GAAG,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;IACzH;EACF;;EAEA;EACA,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE;IACjD,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;EACtE;EAEA,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC;EACtE,OAAO,OAAO,CAAC;IAAE,KAAK;IAAE,MAAM;IAAE,SAAS,EAAE,IAAI,CAAC;EAAU,CAAC,EAAE,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC;AACvG,CAAC","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":"AAAA,SAA4B,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,QAAQ,OAAO;AAS1F,OAAO,SAAS,aAAa,CAAC,IAAoB,EAAE,IAAI,GAAG;EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,GAAG;EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI;AAAI,CAAC,EAAE;EACnH,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,KAAK;IAC3C,MAAM,GAAG,IAAI,CAAC,MAAM;EACtB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK;IACvC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,MAAM,IAAI,UAAU,CAAC,gDAAgD,CAAC;IACpH,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,UAAU,CAAC,+CAA+C,CAAC;IACzG,OAAO;MAAE,GAAG,CAAC;MAAE;IAAM,CAAC;EACxB,CAAC,CAAC;EACF,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;EACnD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,UAAU,CAAC,+BAA+B,CAAC;EAClF,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC;EAC7D,MAAM,MAAkE,GAAG,EAAE;EAC7E,IAAI,EAAE,GAAG,CAAC;IACR,EAAE,GAAG,CAAC;EACR,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,EACvB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK;IACpC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,EAAE,GAAG,EAAE,CAAC;IACzD,IAAI,EAAE,IAAI,EAAE,GAAG,SAAS,GAAG,KAAK,EAAE;MAChC,EAAE,GAAG,CAAC;MACN,EAAE,EAAE;IACN;IACA,MAAM,CAAC,IAAI,CAAC;MAAE,KAAK;MAAE,KAAK;MAAE,CAAC,EAAE,EAAE;MAAE,GAAG,EAAE;IAAG,CAAC,CAAC;IAC7C,EAAE,IAAI,SAAS;EACjB,CAAC,CAAC;EACJ,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC;IACxD,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,GAAG,YAAY,CAAC;EACjE,MAAM,OAAO,GAAG,IAAI,CACjB,MAAM,CAAE,CAAC,IAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAC1B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CACjC,GAAG,CAAE,CAAC,KAAM;IAAE,GAAG,CAAC;IAAE,IAAI,EAAG,CAAC,CAAC,KAAK,IAAI,KAAK,IAAI,CAAC,CAAC,GAAI,KAAK,GAAG;EAAW,CAAC,CAAC,CAAC,CAC3E,MAAM,CAAE,CAAC,IAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;EAC5B,MAAM,KAA0F,GAAG,EAAE;EACrG,IAAI,CAAC,GAAG,CAAC;IACP,CAAC,GAAG,MAAM,CAAC,MAAM;IACjB,CAAC,GAAG,KAAK;IACT,CAAC,GAAG,UAAU;EAChB,MAAM,KAAK,GAAG,CAAC,KAAe,EAAE,IAAY,KAAK;IAC/C,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,EAAE,OAAO,QAAQ;IAC/C,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;MAC1C,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;MACvB,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACzB,OAAO,IAAI,CAAC,GAAG,CAAE,IAAI,GAAG,IAAI,GAAG,EAAE,IAAK,GAAG,GAAG,GAAG,CAAC,EAAG,GAAG,GAAG,GAAG,IAAK,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC;EACrF,CAAC;EACD,IAAI,MAAM,GAAG,CAAC;EACd,OAAO,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE;IAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;IACtB,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAE;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3B,OACE,MAAM,GAAG,OAAO,CAAC,MAAM,IACvB,KAAK,CACH,GAAG,CAAC,GAAG,CAAE,CAAC,IAAK,CAAC,CAAC,IAAI,CAAC,EACtB,IACF,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAE,CAAC,IAAK,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAEpE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC;IACvB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,QAAQ,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,IAAI,GAAG,GAAG,QAAQ,GAAG,CAAC,GAAG,CAAC;IAC1B,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE;MACnB,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,GAAG,SAAS;MACjC,KAAK,CAAC,IAAI,CAAC;QACT,GAAG,CAAC;QACJ,CAAC,EAAE,QAAQ,GAAG,CAAC,GAAG,GAAG;QACrB,CAAC,EAAE,QAAQ,GAAG,GAAG,GAAG,CAAC;QACrB,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM;QACpC,MAAM,EAAE,QAAQ,GAAG,MAAM,GAAG;MAC9B,CAAC,CAAC;MACF,GAAG,IAAI,MAAM;IACf;IACA,IAAI,QAAQ,EAAE;MACZ,CAAC,IAAI,SAAS;MACd,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAChC,CAAC,MAAM;MACL,CAAC,IAAI,SAAS;MACd,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAChC;EACF;EACA,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAE,CAAC,IAAK,CAAC,KAAK,CAAC,IAAI,CAAE,CAAC,IAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC;IAAE,GAAG,CAAC;IAAE,CAAC,EAAE,CAAC;IAAE,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,CAAC;IAAE,MAAM,EAAE;EAAE,CAAC,CAAC;EAClI,OAAO;IAAE,KAAK;IAAE,MAAM;IAAE,KAAK;IAAE,KAAK;IAAE,MAAM;IAAE,MAAM;IAAE,SAAS,EAAE,MAAM,GAAG,YAAY;IAAE;EAAW,CAAC;AACtG;AACA,OAAO,SAAS,gBAAgB,CAAC,IAAoB,EAAU;EAC7D,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC;EACjC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE;IAAE,wBAAwB,EAAE;EAAG,CAAC,CAAC,CAAC,MAAM;EACjH,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE;IAAE,KAAK,EAAE,SAAS;IAAE,qBAAqB,EAAE;EAAE,CAAC,CAAC;EAC1G,MAAM,IAAI,GACR,KAAK,CAAC,MAAM,CAAC,GAAG,CACb,OAAO,CAAC,mCAAmC,EAAE,gFAAgF,CAAC,CAC9H,OAAO,CACN,sCAAsC,EACtC,yFACF,CAAC,GACH,KAAK,CAAC,KAAK,CACR,GAAG,CAAE,CAAC,IAAK;IACV,MAAM,KAAK,GAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAI,CAAC;IACpD,MAAM,KAAK,GAAG,uBAAuB,KAAK,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG;IACpJ,MAAM,IAAI,GACP,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,IAAI,GAAG,CAAC,GAAI,KAAK,CAAC,KAAK,GAClD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,GAC9H,yGAAyG,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,qEAAqE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,iHAAiH,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,2EAA2E,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,GAC7c,EAAE;IACR,OAAO,aAAa,CAClB,2CAA2C,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,aAAa,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,oCAAoC,KAAK,iDAAiD,IAAI,EAAE,EAC3O;MACE,IAAI,EAAE,MAAM;MACZ,KAAK,EAAE,CAAC,CAAC,KAAK;MACd,KAAK,EAAE,CAAC,CAAC,KAAK;MACd,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;MAC/C,MAAM,EAAE,CACN;QAAE,GAAG,EAAE,OAAO;QAAE,KAAK,EAAE,CAAC,CAAC;MAAM,CAAC,EAChC;QAAE,GAAG,EAAE,OAAO;QAAE,KAAK,EAAE,CAAC,CAAC,KAAK;QAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK;MAAE,CAAC,EAC5D;QACE,GAAG,EAAE,SAAS;QACd,KAAK,EAAE,KAAK,CAAC,KAAK,GAAI,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,GAAI,GAAG,GAAG,CAAC;QACtD,SAAS,EAAE,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG;MACnE,CAAC;IAEL,CAAC,EACD,IAAI,CAAC,OACP,CAAC;EACH,CAAC,CAAC,CACD,IAAI,CAAC,EAAE,CAAC,GACX,KAAK,CAAC,MAAM,CACT,GAAG,CACD,IAAI,IACH,YAAY,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,2DAA4D,IAAI,CAAC,KAAK,GAAG,CAAC,GAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,yDAAyD,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAC/b,CAAC,CACA,IAAI,CAAC,EAAE,CAAC;EACb,OAAO,OAAO,CAAC;IAAE,GAAG,KAAK;IAAE,SAAS,EAAE,IAAI,CAAC;EAAU,CAAC,EAAE,IAAI,CAAC;AAC/D","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":[]}
|
|
@@ -98,10 +98,14 @@ import { useViewportMessages } from "./viewport-messages";
|
|
|
98
98
|
* block on truly empty inputs; partially-filled series get rendered
|
|
99
99
|
* as-is and the core handles the gaps. */
|
|
100
100
|
const isEmpty = props => {
|
|
101
|
+
if (props.kind === "treemap" || props.kind === "pareto") return !props.data.length;
|
|
102
|
+
if (props.kind === "bubble") return !props.data.length;
|
|
103
|
+
if (props.kind === "radar") return !props.series.length;
|
|
101
104
|
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));
|
|
102
105
|
if (props.kind === "line" || props.kind === "scatter") {
|
|
103
106
|
return !props.series?.length || props.series.every(s => !s.data.length);
|
|
104
107
|
}
|
|
108
|
+
if (props.kind === "funnel") return !props.data.length;
|
|
105
109
|
if (props.kind === "hbar") return !props.rows.length;
|
|
106
110
|
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;
|
|
107
111
|
if (props.kind === "donut" || props.kind === "pie") {
|
|
@@ -119,7 +123,12 @@ const isEmpty = props => {
|
|
|
119
123
|
return false;
|
|
120
124
|
};
|
|
121
125
|
export const renderChartRoot = (props, renderChartSvg) => {
|
|
122
|
-
const
|
|
126
|
+
const drawingProps = () => {
|
|
127
|
+
// Pareto's labels name its two measures; other kinds use labels only for host controls.
|
|
128
|
+
if (props.kind === "pareto") return props;
|
|
129
|
+
const [, drawing] = splitProps(props, ["class", "style", "labels", "interactive", "selected", "onSelect", "tooltip", "cursor"]);
|
|
130
|
+
return drawing;
|
|
131
|
+
};
|
|
123
132
|
const messages = useUiMessages();
|
|
124
133
|
const chartMessages = useChartMessages();
|
|
125
134
|
const viewportMessages = useViewportMessages();
|
|
@@ -169,7 +178,7 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
169
178
|
const interactiveMap = () => props.kind === "map" && props.interactive === true;
|
|
170
179
|
const interactiveTimeline = () => props.kind === "stateTimeline" && props.interactive === true;
|
|
171
180
|
const draggable = () => interactiveMap() || interactiveTimeline();
|
|
172
|
-
const labels = () => props.labels ?? {};
|
|
181
|
+
const labels = () => props.kind === "pareto" ? {} : props.labels ?? {};
|
|
173
182
|
const accessibleName = () => {
|
|
174
183
|
const override = props.kind === "map" ? labels().interactiveMap : props.kind === "stateTimeline" ? labels().interactiveTimeline : props.kind === "line" ? labels().interactiveLine : labels().interactiveChart;
|
|
175
184
|
const title = "title" in props && typeof props.title === "string" && props.title.trim() ? props.title : undefined;
|
|
@@ -370,7 +379,7 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
370
379
|
select: selection => props.onSelect?.(selection),
|
|
371
380
|
format: selection => {
|
|
372
381
|
if (props.tooltip) return props.tooltip(selection);
|
|
373
|
-
const fields = props.kind === "heatmap" && props.layout === "calendar" ? resolveChartMessages(props.locale ?? locale()).chartFields : chartMessages().chartFields;
|
|
382
|
+
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;
|
|
374
383
|
if (selection.kind === "area" && props.kind === "area") {
|
|
375
384
|
const amount = selection.datum.values.find(f => f.key === "value");
|
|
376
385
|
const share = selection.datum.values.find(f => f.key === "percent");
|
|
@@ -388,7 +397,11 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
388
397
|
return {
|
|
389
398
|
title: selection.datum.label ?? (selection.datum.seriesIndex !== undefined ? `${labels().series ?? chartMessages().series} ${selection.datum.seriesIndex + 1}` : undefined),
|
|
390
399
|
rows: selection.datum.values.filter(field => selection.kind !== "waterfall" || field.key !== "before").map(field => ({
|
|
391
|
-
label: props.kind === "
|
|
400
|
+
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({
|
|
401
|
+
key: "size"
|
|
402
|
+
}) : props.kind === "radar" && field.key.startsWith("series:") ? props.series[Number(field.key.slice(7))]?.label ?? fields({
|
|
403
|
+
key: "series"
|
|
404
|
+
}) : 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({
|
|
392
405
|
key: field.key
|
|
393
406
|
}) : props.kind === "bar" && field.key === "previous" ? props.compare?.label ?? fields({
|
|
394
407
|
key: field.key
|
|
@@ -401,12 +414,12 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
401
414
|
}
|
|
402
415
|
});
|
|
403
416
|
const closeChartTooltip = () => inspection.close();
|
|
404
|
-
const svgMarkup = createMemo(() => renderChartSvg(props.kind === "heatmap" && props.layout === "calendar" ? {
|
|
417
|
+
const svgMarkup = createMemo(() => renderChartSvg(props.kind === "bubble" || props.kind === "radar" || props.kind === "treemap" || props.kind === "pareto" || props.kind === "heatmap" && props.layout === "calendar" ? {
|
|
405
418
|
...props,
|
|
406
419
|
locale: props.locale ?? locale()
|
|
407
420
|
} : {
|
|
408
|
-
...drawingProps,
|
|
409
|
-
...(props.kind === "bar" || props.kind === "hbar" ? {
|
|
421
|
+
...drawingProps(),
|
|
422
|
+
...(props.kind === "bar" || props.kind === "hbar" || props.kind === "funnel" ? {
|
|
410
423
|
locale: locale()
|
|
411
424
|
} : {})
|
|
412
425
|
}, size().width, size().height, interactiveMap() ? mapViewport() : undefined, interactiveTimeline() ? timelineViewport() : undefined, locale()));
|
|
@@ -428,6 +441,7 @@ export const renderChartRoot = (props, renderChartSvg) => {
|
|
|
428
441
|
onCleanup(closeChartInspectionOnOutside({
|
|
429
442
|
container: () => containerRef,
|
|
430
443
|
tooltip: () => chartTooltipRef,
|
|
444
|
+
scroll: inspection.scroll,
|
|
431
445
|
close: closeChartTooltip,
|
|
432
446
|
keepScroll: () => props.kind === "heatmap" && props.layout === "calendar"
|
|
433
447
|
}));
|