@eifi1/ui-kit 0.15.0 → 0.15.1
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/chart.d.ts +2 -1
- package/dist/components/facing-pair.d.ts +1 -0
- package/dist/components/series-chart-budget.d.ts +60 -0
- package/dist/components/series-chart-budget.js +56 -0
- package/dist/components/series-chart-budget.js.map +1 -0
- package/dist/components/series-chart.d.ts +54 -2
- package/dist/components/series-chart.js +68 -13
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/slider.js +6 -2
- package/dist/components/slider.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/package.json +1 -1
- package/src/components/series-chart-budget.ts +121 -0
- package/src/components/series-chart.tsx +146 -8
- package/src/components/slider.tsx +6 -2
package/dist/chart.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export { ChartConfig, ChartContainer, ChartLegend, ChartLegendContent, ChartSeriesConfig, ChartTooltip, ChartTooltipContent, useChart } from './components/chart.js';
|
|
2
2
|
export { CHART_COLORS, HEATMAP_HEX, HeatStops, PALETTE_HEX, lerpHex, paletteFor, textOn } from './theme/chart-palette.js';
|
|
3
3
|
export { Treemap, TreemapCell, TreemapCellProps, TreemapNode, TreemapProps, fitLabel } from './components/treemap.js';
|
|
4
|
-
export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, SeriesChart, SeriesChartAxis, SeriesChartHit, SeriesChartMarker, SeriesChartPoint, SeriesChartProps, SeriesChartReference, SeriesChartRow, SeriesChartSeries, SeriesChartSpan, SeriesChartTickValues, SeriesChartTone, SeriesChartTooltip, SeriesChartType, SeriesChartX, SeriesChartXTick, SeriesChartXValue, SeriesSource, StaticSeriesChart, anchoredBand, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, seriesKey, seriesLegendEntries, soleSeriesColor, visibleSeries } from './components/series-chart.js';
|
|
4
|
+
export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, SeriesChart, SeriesChartAxis, SeriesChartHit, SeriesChartMarker, SeriesChartPoint, SeriesChartProps, SeriesChartReference, SeriesChartRow, SeriesChartSeries, SeriesChartSpan, SeriesChartTickValues, SeriesChartTone, SeriesChartTooltip, SeriesChartType, SeriesChartX, SeriesChartXTick, SeriesChartXValue, SeriesLegendAxes, SeriesSource, StaticSeriesChart, anchoredBand, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, seriesKey, seriesLegendEntries, soleSeriesColor, visibleSeries } from './components/series-chart.js';
|
|
5
5
|
export { DEFAULT_Y_AXIS, NO_ZOOM, SharedXZoom, ZOOM_MIN_DRAG, ZOOM_SQUARE_ENOUGH, ZoomAxes, ZoomAxesSetting, ZoomBinding, ZoomDrag, ZoomFitSeries, ZoomFitSource, ZoomRow, ZoomSelection, ZoomState, ZoomTarget, axisExtent, defaultZoomAxes, fitXToY, fitYToX, selectionFromDrag, withChartZoom, zoomAfter, zoomAxesFor, zoomDomains } from './components/chart-zoom.js';
|
|
6
6
|
export { LegendColumn, LegendEntry, LegendGroup, STEP_DASH, STROKE_PATTERNS, StaticLegend, StaticLegendProps, ToggleLegend, ToggleLegendProps, strokeDash, toggleHidden } from './components/toggle-legend.js';
|
|
7
7
|
export { FACING_SIDES, FacingSide, facingAxes, facingBand, facingHeadingPad } from './components/facing-pair.js';
|
|
8
8
|
export { DEFAULT_SERIES_CHART_LABELS, SeriesChartLabels } from './components/series-chart-labels.js';
|
|
9
9
|
export { PieChart, PieChartProps, PieChartSlice } from './components/pie-chart.js';
|
|
10
10
|
export { DEFAULT_PIE_CHART_LABELS, PieChartLabels } from './components/pie-chart-labels.js';
|
|
11
|
+
export { SeriesChartAxisBudget } from './components/series-chart-budget.js';
|
|
11
12
|
export { TimeTickUnit } from './components/series-chart-ticks.js';
|
|
12
13
|
import 'recharts';
|
|
13
14
|
import 'react';
|
|
@@ -5,6 +5,7 @@ import 'recharts';
|
|
|
5
5
|
import './series-chart-labels.js';
|
|
6
6
|
import './toggle-legend.js';
|
|
7
7
|
import './series-chart-ticks.js';
|
|
8
|
+
import './series-chart-budget.js';
|
|
8
9
|
|
|
9
10
|
/** Which chart of a facing pair. Physical, like the axes: charts do not mirror in RTL. */
|
|
10
11
|
type FacingSide = "left" | "right";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many y axes a chart draws. A number caps the TOTAL; `{ left, right }` caps each
|
|
3
|
+
* side (a side left out is not capped). Axes over the cap are hidden in declaration
|
|
4
|
+
* order — the first of each side is the last to go, see {@link budgetedAxes}.
|
|
5
|
+
*/
|
|
6
|
+
type SeriesChartAxisBudget = number | {
|
|
7
|
+
left?: number;
|
|
8
|
+
right?: number;
|
|
9
|
+
};
|
|
10
|
+
/** The part of an axis the budget reads. */
|
|
11
|
+
interface BudgetAxis {
|
|
12
|
+
id: string;
|
|
13
|
+
orientation?: "left" | "right";
|
|
14
|
+
hide?: boolean;
|
|
15
|
+
title?: string;
|
|
16
|
+
width?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The narrowest the plot may get before the automatic budget steps in, in px.
|
|
20
|
+
*
|
|
21
|
+
* About a phone's plot with one axis a side (390 px viewport, card padding, two bands
|
|
22
|
+
* of 48 + 16), where a curve's shape still reads. It is the point below which the chart
|
|
23
|
+
* is already broken today — a plot the width of a thumb between four columns of numbers
|
|
24
|
+
* — so the rule changes nothing that was working.
|
|
25
|
+
*/
|
|
26
|
+
declare const MIN_PLOT_WIDTH = 160;
|
|
27
|
+
/** The budget the automatic rule applies: one axis a side. */
|
|
28
|
+
declare const NARROW_AXIS_BUDGET: SeriesChartAxisBudget;
|
|
29
|
+
/**
|
|
30
|
+
* The ids of the VISIBLE axes a budget hides — axes already `hide: true` are neither
|
|
31
|
+
* counted nor returned. Empty for no budget.
|
|
32
|
+
*
|
|
33
|
+
* Kept, in order of priority: the first visible axis of each side (in declaration order,
|
|
34
|
+
* so the axis a caller lists first — the one the grid hangs its rules off — always
|
|
35
|
+
* stays), then the rest in declaration order, until the cap. Per side, that is simply the
|
|
36
|
+
* first `left` left-hand axes and the first `right` right-hand ones.
|
|
37
|
+
*/
|
|
38
|
+
declare function budgetedAxes(axes: readonly BudgetAxis[], budget: SeriesChartAxisBudget | undefined): string[];
|
|
39
|
+
/**
|
|
40
|
+
* The automatic budget for a chart `width` px wide: {@link NARROW_AXIS_BUDGET} when the
|
|
41
|
+
* visible axes' bands would leave the plot under {@link MIN_PLOT_WIDTH} AND some side
|
|
42
|
+
* draws more than one axis — the only case one-a-side gives anything back. `undefined`
|
|
43
|
+
* otherwise, and for a width that is not known (no layout, no `ResizeObserver`).
|
|
44
|
+
*
|
|
45
|
+
* Decided on the CHART's width and the axes as declared, never on the plot the budget
|
|
46
|
+
* produced, so hiding an axis cannot widen the plot back over the line and bring it
|
|
47
|
+
* back: the rule has no feedback to oscillate on.
|
|
48
|
+
*/
|
|
49
|
+
declare function autoAxisBudget(axes: readonly BudgetAxis[], width: number | undefined, bandWidth: (axis: BudgetAxis) => number): SeriesChartAxisBudget | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* The unit an axis' series say when the axis is not drawn: its `unit`, or else the
|
|
52
|
+
* parenthesised tail of its title — "Load (N)" says "N", which is how the apps write
|
|
53
|
+
* every axis title. `undefined` when neither says one.
|
|
54
|
+
*/
|
|
55
|
+
declare function axisUnit(axis: {
|
|
56
|
+
unit?: string;
|
|
57
|
+
title?: string;
|
|
58
|
+
}): string | undefined;
|
|
59
|
+
|
|
60
|
+
export { MIN_PLOT_WIDTH, NARROW_AXIS_BUDGET, type SeriesChartAxisBudget, autoAxisBudget, axisUnit, budgetedAxes };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
const MIN_PLOT_WIDTH = 160;
|
|
3
|
+
const NARROW_AXIS_BUDGET = { left: 1, right: 1 };
|
|
4
|
+
const BARE_SIDE_MARGIN = 10;
|
|
5
|
+
const sideOf = (axis) => axis.orientation ?? "left";
|
|
6
|
+
function budgetedAxes(axes, budget) {
|
|
7
|
+
if (budget === void 0) return [];
|
|
8
|
+
const visible = axes.filter((axis) => !axis.hide);
|
|
9
|
+
if (typeof budget === "number") {
|
|
10
|
+
const cap = Math.max(0, Math.floor(budget));
|
|
11
|
+
const firsts = /* @__PURE__ */ new Set();
|
|
12
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13
|
+
for (const axis of visible) {
|
|
14
|
+
if (!seen.has(sideOf(axis))) firsts.add(axis.id);
|
|
15
|
+
seen.add(sideOf(axis));
|
|
16
|
+
}
|
|
17
|
+
const priority = [
|
|
18
|
+
...visible.filter((axis) => firsts.has(axis.id)),
|
|
19
|
+
...visible.filter((axis) => !firsts.has(axis.id))
|
|
20
|
+
];
|
|
21
|
+
const kept = new Set(priority.slice(0, cap).map((axis) => axis.id));
|
|
22
|
+
return visible.filter((axis) => !kept.has(axis.id)).map((axis) => axis.id);
|
|
23
|
+
}
|
|
24
|
+
const used = { left: 0, right: 0 };
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const axis of visible) {
|
|
27
|
+
const side = sideOf(axis);
|
|
28
|
+
const cap = budget[side];
|
|
29
|
+
if (cap !== void 0 && used[side] >= Math.max(0, Math.floor(cap))) out.push(axis.id);
|
|
30
|
+
else used[side] += 1;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
function autoAxisBudget(axes, width, bandWidth) {
|
|
35
|
+
if (width === void 0 || !(width > 0)) return void 0;
|
|
36
|
+
const visible = axes.filter((axis) => !axis.hide);
|
|
37
|
+
const left = visible.filter((axis) => sideOf(axis) === "left");
|
|
38
|
+
const right = visible.filter((axis) => sideOf(axis) === "right");
|
|
39
|
+
if (left.length <= 1 && right.length <= 1) return void 0;
|
|
40
|
+
const bands = visible.reduce((sum, axis) => sum + bandWidth(axis), 0);
|
|
41
|
+
const margins = (left.length ? 0 : BARE_SIDE_MARGIN) + (right.length ? 0 : BARE_SIDE_MARGIN);
|
|
42
|
+
return width - bands - margins < MIN_PLOT_WIDTH ? NARROW_AXIS_BUDGET : void 0;
|
|
43
|
+
}
|
|
44
|
+
function axisUnit(axis) {
|
|
45
|
+
if (axis.unit !== void 0) return axis.unit || void 0;
|
|
46
|
+
const tail = /\(([^()]+)\)\s*$/.exec(axis.title ?? "");
|
|
47
|
+
return tail?.[1].trim() || void 0;
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
MIN_PLOT_WIDTH,
|
|
51
|
+
NARROW_AXIS_BUDGET,
|
|
52
|
+
autoAxisBudget,
|
|
53
|
+
axisUnit,
|
|
54
|
+
budgetedAxes
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=series-chart-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/series-chart-budget.ts"],"sourcesContent":["// The axis budget of a series chart: which of its y axes are drawn, when there are more\n// than the width can carry. Pure, and apart from the chart, so the rule is tested on\n// numbers rather than on a jsdom that has no layout to measure.\n//\n// lenkbank's curve plot declares four visible axes — velocity, position and acceleration\n// on the left, loads on the right — and every one reserves its band of ticks and title.\n// At 1280 px that is a quarter of the card; at 390 px it is all of it, and the plot the\n// axes are for is ten pixels wide. An axis over budget is drawn the way a `hide: true`\n// one always was: it still scales its lines (and the zoom still refits it), but it draws\n// no ticks, no title, and reserves no width.\n\n/**\n * How many y axes a chart draws. A number caps the TOTAL; `{ left, right }` caps each\n * side (a side left out is not capped). Axes over the cap are hidden in declaration\n * order — the first of each side is the last to go, see {@link budgetedAxes}.\n */\nexport type SeriesChartAxisBudget = number | { left?: number; right?: number };\n\n/** The part of an axis the budget reads. */\ninterface BudgetAxis {\n id: string;\n orientation?: \"left\" | \"right\";\n hide?: boolean;\n title?: string;\n width?: number;\n}\n\n/**\n * The narrowest the plot may get before the automatic budget steps in, in px.\n *\n * About a phone's plot with one axis a side (390 px viewport, card padding, two bands\n * of 48 + 16), where a curve's shape still reads. It is the point below which the chart\n * is already broken today — a plot the width of a thumb between four columns of numbers\n * — so the rule changes nothing that was working.\n */\nexport const MIN_PLOT_WIDTH = 160;\n\n/** The budget the automatic rule applies: one axis a side. */\nexport const NARROW_AXIS_BUDGET: SeriesChartAxisBudget = { left: 1, right: 1 };\n\n/** The plot's margin on a side no axis band covers — `SeriesPlot`'s own. */\nconst BARE_SIDE_MARGIN = 10;\n\nconst sideOf = (axis: BudgetAxis) => axis.orientation ?? \"left\";\n\n/**\n * The ids of the VISIBLE axes a budget hides — axes already `hide: true` are neither\n * counted nor returned. Empty for no budget.\n *\n * Kept, in order of priority: the first visible axis of each side (in declaration order,\n * so the axis a caller lists first — the one the grid hangs its rules off — always\n * stays), then the rest in declaration order, until the cap. Per side, that is simply the\n * first `left` left-hand axes and the first `right` right-hand ones.\n */\nexport function budgetedAxes(\n axes: readonly BudgetAxis[],\n budget: SeriesChartAxisBudget | undefined,\n): string[] {\n if (budget === undefined) return [];\n const visible = axes.filter((axis) => !axis.hide);\n if (typeof budget === \"number\") {\n const cap = Math.max(0, Math.floor(budget));\n const firsts = new Set<string>();\n const seen = new Set<string>();\n for (const axis of visible) {\n if (!seen.has(sideOf(axis))) firsts.add(axis.id);\n seen.add(sideOf(axis));\n }\n const priority = [\n ...visible.filter((axis) => firsts.has(axis.id)),\n ...visible.filter((axis) => !firsts.has(axis.id)),\n ];\n const kept = new Set(priority.slice(0, cap).map((axis) => axis.id));\n return visible.filter((axis) => !kept.has(axis.id)).map((axis) => axis.id);\n }\n const used = { left: 0, right: 0 };\n const out: string[] = [];\n for (const axis of visible) {\n const side = sideOf(axis);\n const cap = budget[side];\n if (cap !== undefined && used[side] >= Math.max(0, Math.floor(cap))) out.push(axis.id);\n else used[side] += 1;\n }\n return out;\n}\n\n/**\n * The automatic budget for a chart `width` px wide: {@link NARROW_AXIS_BUDGET} when the\n * visible axes' bands would leave the plot under {@link MIN_PLOT_WIDTH} AND some side\n * draws more than one axis — the only case one-a-side gives anything back. `undefined`\n * otherwise, and for a width that is not known (no layout, no `ResizeObserver`).\n *\n * Decided on the CHART's width and the axes as declared, never on the plot the budget\n * produced, so hiding an axis cannot widen the plot back over the line and bring it\n * back: the rule has no feedback to oscillate on.\n */\nexport function autoAxisBudget(\n axes: readonly BudgetAxis[],\n width: number | undefined,\n bandWidth: (axis: BudgetAxis) => number,\n): SeriesChartAxisBudget | undefined {\n if (width === undefined || !(width > 0)) return undefined;\n const visible = axes.filter((axis) => !axis.hide);\n const left = visible.filter((axis) => sideOf(axis) === \"left\");\n const right = visible.filter((axis) => sideOf(axis) === \"right\");\n if (left.length <= 1 && right.length <= 1) return undefined;\n const bands = visible.reduce((sum, axis) => sum + bandWidth(axis), 0);\n const margins = (left.length ? 0 : BARE_SIDE_MARGIN) + (right.length ? 0 : BARE_SIDE_MARGIN);\n return width - bands - margins < MIN_PLOT_WIDTH ? NARROW_AXIS_BUDGET : undefined;\n}\n\n/**\n * The unit an axis' series say when the axis is not drawn: its `unit`, or else the\n * parenthesised tail of its title — \"Load (N)\" says \"N\", which is how the apps write\n * every axis title. `undefined` when neither says one.\n */\nexport function axisUnit(axis: { unit?: string; title?: string }): string | undefined {\n if (axis.unit !== undefined) return axis.unit || undefined;\n const tail = /\\(([^()]+)\\)\\s*$/.exec(axis.title ?? \"\");\n return tail?.[1].trim() || undefined;\n}\n"],"mappings":";AAmCO,MAAM,iBAAiB;AAGvB,MAAM,qBAA4C,EAAE,MAAM,GAAG,OAAO,EAAE;AAG7E,MAAM,mBAAmB;AAEzB,MAAM,SAAS,CAAC,SAAqB,KAAK,eAAe;AAWlD,SAAS,aACd,MACA,QACU;AACV,MAAI,WAAW,OAAW,QAAO,CAAC;AAClC,QAAM,UAAU,KAAK,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI;AAChD,MAAI,OAAO,WAAW,UAAU;AAC9B,UAAM,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,CAAC;AAC1C,UAAM,SAAS,oBAAI,IAAY;AAC/B,UAAM,OAAO,oBAAI,IAAY;AAC7B,eAAW,QAAQ,SAAS;AAC1B,UAAI,CAAC,KAAK,IAAI,OAAO,IAAI,CAAC,EAAG,QAAO,IAAI,KAAK,EAAE;AAC/C,WAAK,IAAI,OAAO,IAAI,CAAC;AAAA,IACvB;AACA,UAAM,WAAW;AAAA,MACf,GAAG,QAAQ,OAAO,CAAC,SAAS,OAAO,IAAI,KAAK,EAAE,CAAC;AAAA,MAC/C,GAAG,QAAQ,OAAO,CAAC,SAAS,CAAC,OAAO,IAAI,KAAK,EAAE,CAAC;AAAA,IAClD;AACA,UAAM,OAAO,IAAI,IAAI,SAAS,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,SAAS,KAAK,EAAE,CAAC;AAClE,WAAO,QAAQ,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,EAAE;AAAA,EAC3E;AACA,QAAM,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE;AACjC,QAAM,MAAgB,CAAC;AACvB,aAAW,QAAQ,SAAS;AAC1B,UAAM,OAAO,OAAO,IAAI;AACxB,UAAM,MAAM,OAAO,IAAI;AACvB,QAAI,QAAQ,UAAa,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,GAAG,CAAC,EAAG,KAAI,KAAK,KAAK,EAAE;AAAA,QAChF,MAAK,IAAI,KAAK;AAAA,EACrB;AACA,SAAO;AACT;AAYO,SAAS,eACd,MACA,OACA,WACmC;AACnC,MAAI,UAAU,UAAa,EAAE,QAAQ,GAAI,QAAO;AAChD,QAAM,UAAU,KAAK,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI;AAChD,QAAM,OAAO,QAAQ,OAAO,CAAC,SAAS,OAAO,IAAI,MAAM,MAAM;AAC7D,QAAM,QAAQ,QAAQ,OAAO,CAAC,SAAS,OAAO,IAAI,MAAM,OAAO;AAC/D,MAAI,KAAK,UAAU,KAAK,MAAM,UAAU,EAAG,QAAO;AAClD,QAAM,QAAQ,QAAQ,OAAO,CAAC,KAAK,SAAS,MAAM,UAAU,IAAI,GAAG,CAAC;AACpE,QAAM,WAAW,KAAK,SAAS,IAAI,qBAAqB,MAAM,SAAS,IAAI;AAC3E,SAAO,QAAQ,QAAQ,UAAU,iBAAiB,qBAAqB;AACzE;AAOO,SAAS,SAAS,MAA6D;AACpF,MAAI,KAAK,SAAS,OAAW,QAAO,KAAK,QAAQ;AACjD,QAAM,OAAO,mBAAmB,KAAK,KAAK,SAAS,EAAE;AACrD,SAAO,OAAO,CAAC,EAAE,KAAK,KAAK;AAC7B;","names":[]}
|
|
@@ -4,6 +4,7 @@ import { ZoomAxesSetting, ZoomBinding } from './chart-zoom.js';
|
|
|
4
4
|
import { LegendEntry } from './toggle-legend.js';
|
|
5
5
|
import { SeriesChartLabels } from './series-chart-labels.js';
|
|
6
6
|
import { TimeTickUnit } from './series-chart-ticks.js';
|
|
7
|
+
import { SeriesChartAxisBudget } from './series-chart-budget.js';
|
|
7
8
|
import 'recharts';
|
|
8
9
|
|
|
9
10
|
interface SeriesChartSeries {
|
|
@@ -131,6 +132,15 @@ interface SeriesChartAxisShape {
|
|
|
131
132
|
color?: string;
|
|
132
133
|
/** What the TICKS need, in px. The title's strip is added on top. Default 48. */
|
|
133
134
|
width?: number;
|
|
135
|
+
/**
|
|
136
|
+
* The unit, bare — `"mm/s"`. The title says it while the axis is drawn; when it is not
|
|
137
|
+
* (`hide`, or over the chart's axis budget — see {@link SeriesChartProps.maxVisibleAxes})
|
|
138
|
+
* the series on it say it instead, as "Velocity (mm/s)": in the tooltip of a chart
|
|
139
|
+
* whose budget hid the axis, and in the legend {@link seriesLegendEntries} builds when
|
|
140
|
+
* handed the axes. Default: the parenthesised tail of `title` ("Load (N)" → "N").
|
|
141
|
+
* `""` for a quantity with no unit whose title merely ends in brackets.
|
|
142
|
+
*/
|
|
143
|
+
unit?: string;
|
|
134
144
|
/**
|
|
135
145
|
* Pin the scale instead of fitting it to this chart's data. For a ROW of charts
|
|
136
146
|
* read against each other: auto-fitted, a 200 N loop and a 2000 N one draw the
|
|
@@ -426,6 +436,37 @@ interface SeriesChartProps {
|
|
|
426
436
|
/** Where the tooltip may go: for a chart inside a scroll wrapper. See
|
|
427
437
|
* {@link SeriesChartTooltip}. */
|
|
428
438
|
tooltip?: SeriesChartTooltip;
|
|
439
|
+
/**
|
|
440
|
+
* The most y axes the chart DRAWS: a total, or `{ left, right }` per side. Axes over it
|
|
441
|
+
* are drawn as `hide: true` ones are — they still scale their lines and the zoom still
|
|
442
|
+
* refits them, but they draw no ticks or title and reserve no width — in declaration
|
|
443
|
+
* order, so the first axis of each side is the last to go (`series-chart-budget.ts`).
|
|
444
|
+
* The series on a hidden axis say its unit instead (see {@link SeriesChartAxis.unit}).
|
|
445
|
+
* Default: no cap — only the automatic one of `axisBudget`.
|
|
446
|
+
*/
|
|
447
|
+
maxVisibleAxes?: SeriesChartAxisBudget;
|
|
448
|
+
/**
|
|
449
|
+
* `"auto"` (the default): a chart so narrow that its axes' bands would leave the plot
|
|
450
|
+
* under 160 px (`MIN_PLOT_WIDTH`) draws ONE axis a side — lenkbank's four-axis curve
|
|
451
|
+
* plot on a phone, whose plot was 10 px wide. Measured on the chart's own box
|
|
452
|
+
* (`ResizeObserver`), and applied on top of `maxVisibleAxes`.
|
|
453
|
+
*
|
|
454
|
+
* On by default because it cannot touch a layout that works: it needs a side with two
|
|
455
|
+
* or more axes AND a plot that would otherwise be under 160 px — a four-axis chart
|
|
456
|
+
* below about 440 px, a two-left-axis one below about 300. A chart with at most one
|
|
457
|
+
* axis a side (every facing pair, every single-axis chart) is never budgeted, at any
|
|
458
|
+
* width. `"off"` keeps every declared axis whatever the width: for a stack of
|
|
459
|
+
* multi-axis charts that must keep identical bands, or a caller budgeting itself.
|
|
460
|
+
* Without layout (jsdom, SSR) nothing is measured and nothing is hidden.
|
|
461
|
+
*/
|
|
462
|
+
axisBudget?: "auto" | "off";
|
|
463
|
+
/**
|
|
464
|
+
* The ids of the axes the budget hid (not those declared `hide`), every time that set
|
|
465
|
+
* changes — for a legend that says their units: pass it to {@link seriesLegendEntries}
|
|
466
|
+
* as `budgeted`. Called after the render that hid them; empty once the chart is wide
|
|
467
|
+
* again.
|
|
468
|
+
*/
|
|
469
|
+
onAxisBudget?: (hidden: readonly string[]) => void;
|
|
429
470
|
/** Supplied by `withChartZoom` and by nothing else. */
|
|
430
471
|
zoom?: ZoomBinding;
|
|
431
472
|
/** Per-chart strings over `<UiKitProvider labels={{ seriesChart }}>`. */
|
|
@@ -510,13 +551,24 @@ declare function anchoredBand(extent: readonly [number, number] | undefined): [n
|
|
|
510
551
|
* and filtering after keeps each series in the colour its legend entry shows.
|
|
511
552
|
*/
|
|
512
553
|
declare function visibleSeries(series: readonly SeriesChartSeries[], hidden: ReadonlySet<string>): SeriesChartSeries[];
|
|
554
|
+
/** Where a legend learns which axes are not drawn — see {@link seriesLegendEntries}. */
|
|
555
|
+
interface SeriesLegendAxes {
|
|
556
|
+
/** The chart's axes. A series on one that is `hide`, or `budgeted`, says its unit. */
|
|
557
|
+
axes?: readonly SeriesChartAxis[];
|
|
558
|
+
/** What the chart's `onAxisBudget` last said. */
|
|
559
|
+
budgeted?: readonly string[];
|
|
560
|
+
}
|
|
513
561
|
/**
|
|
514
562
|
* The `ToggleLegend` entries for a chart's FULL series list — colours resolved the way
|
|
515
563
|
* the chart resolves them, and a stroke mark for a line drawn in a pattern (dashed,
|
|
516
564
|
* step) so the key promises the stroke the plot draws. A bar or an area is a swatch.
|
|
517
565
|
* Pair with {@link visibleSeries} for the chart itself.
|
|
566
|
+
*
|
|
567
|
+
* Handed the chart's `axes` (and the `budgeted` ids its `onAxisBudget` reports), a
|
|
568
|
+
* series whose axis is not drawn says the unit that axis would have: "Velocity (mm/s)"
|
|
569
|
+
* — see {@link SeriesChartAxis.unit}. Without them, the labels are as given.
|
|
518
570
|
*/
|
|
519
|
-
declare function seriesLegendEntries(series: readonly SeriesChartSeries[]): LegendEntry[];
|
|
571
|
+
declare function seriesLegendEntries(series: readonly SeriesChartSeries[], { axes, budgeted }?: SeriesLegendAxes): LegendEntry[];
|
|
520
572
|
/**
|
|
521
573
|
* {@link SeriesChart} without the zoom: the same picture, no drag layer, no reset
|
|
522
574
|
* button. For a thumbnail, a print view, or a chart the consumer wraps in its own
|
|
@@ -531,4 +583,4 @@ declare function StaticSeriesChart(props: SeriesChartProps): react.JSX.Element;
|
|
|
531
583
|
*/
|
|
532
584
|
declare function SeriesChart(props: SeriesChartProps): react.JSX.Element;
|
|
533
585
|
|
|
534
|
-
export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, SeriesChart, type SeriesChartAxis, type SeriesChartHit, type SeriesChartMarker, type SeriesChartPoint, type SeriesChartProps, type SeriesChartReference, type SeriesChartRow, type SeriesChartSeries, type SeriesChartSpan, type SeriesChartTickValues, type SeriesChartTone, type SeriesChartTooltip, type SeriesChartType, type SeriesChartX, type SeriesChartXTick, type SeriesChartXValue, type SeriesSource, StaticSeriesChart, TimeTickUnit, anchoredBand, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, seriesKey, seriesLegendEntries, soleSeriesColor, visibleSeries };
|
|
586
|
+
export { AXIS_TICK_WIDTH, AXIS_TITLE_STRIP, SeriesChart, type SeriesChartAxis, SeriesChartAxisBudget, type SeriesChartHit, type SeriesChartMarker, type SeriesChartPoint, type SeriesChartProps, type SeriesChartReference, type SeriesChartRow, type SeriesChartSeries, type SeriesChartSpan, type SeriesChartTickValues, type SeriesChartTone, type SeriesChartTooltip, type SeriesChartType, type SeriesChartX, type SeriesChartXTick, type SeriesChartXValue, type SeriesLegendAxes, type SeriesSource, StaticSeriesChart, TimeTickUnit, anchoredBand, axisBandWidth, mergeSeries, oneAxis, padBand, paddedDomain, seriesKey, seriesLegendEntries, soleSeriesColor, visibleSeries };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import {
|
|
5
5
|
Area,
|
|
6
6
|
Bar,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { STEP_DASH, strokeDash } from "./toggle-legend.js";
|
|
26
26
|
import { DEFAULT_SERIES_CHART_LABELS } from "./series-chart-labels.js";
|
|
27
27
|
import { categoryTicks, integerTicks, niceTicks, timeTicksWithUnit } from "./series-chart-ticks.js";
|
|
28
|
+
import { autoAxisBudget, axisUnit, budgetedAxes } from "./series-chart-budget.js";
|
|
28
29
|
import { paletteFor } from "../theme/chart-palette.js";
|
|
29
30
|
import { useKitLabels, useKitLocale } from "../i18n/kit-labels.js";
|
|
30
31
|
import { cn } from "../lib/cn.js";
|
|
@@ -104,14 +105,28 @@ function anchoredBand(extent) {
|
|
|
104
105
|
function visibleSeries(series, hidden) {
|
|
105
106
|
return series.map((entry, index) => ({ ...entry, color: entry.color ?? paletteFor(index) })).filter((entry) => !hidden.has(entry.key));
|
|
106
107
|
}
|
|
107
|
-
function
|
|
108
|
+
function withUnit(label, unit) {
|
|
109
|
+
if (!unit) return label;
|
|
110
|
+
if (typeof label === "string" || typeof label === "number") return `${label} (${unit})`;
|
|
111
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
112
|
+
label,
|
|
113
|
+
" (",
|
|
114
|
+
unit,
|
|
115
|
+
")"
|
|
116
|
+
] });
|
|
117
|
+
}
|
|
118
|
+
function seriesLegendEntries(series, { axes, budgeted } = {}) {
|
|
119
|
+
const undrawn = /* @__PURE__ */ new Map();
|
|
120
|
+
for (const axis of axes ?? []) {
|
|
121
|
+
if (axis.hide || budgeted?.includes(axis.id)) undrawn.set(axis.id, axisUnit(axis));
|
|
122
|
+
}
|
|
108
123
|
return series.map((entry, index) => {
|
|
109
124
|
const own = entry.dash ?? (entry.dashed ? 1 : 0);
|
|
110
125
|
const dash = entry.step ? STEP_DASH : own;
|
|
111
126
|
const line = (entry.type ?? "line") === "line";
|
|
112
127
|
return {
|
|
113
128
|
key: entry.key,
|
|
114
|
-
label: entry.label,
|
|
129
|
+
label: withUnit(entry.label, undrawn.get(entry.axis ?? DEFAULT_Y_AXIS)),
|
|
115
130
|
color: entry.color ?? paletteFor(index),
|
|
116
131
|
...line && dash !== 0 ? { marker: "stroke", dash } : {}
|
|
117
132
|
};
|
|
@@ -323,13 +338,44 @@ function SeriesPlot({
|
|
|
323
338
|
labels: labelsProp,
|
|
324
339
|
locale: localeProp,
|
|
325
340
|
className,
|
|
326
|
-
minBarLength
|
|
341
|
+
minBarLength,
|
|
342
|
+
maxVisibleAxes,
|
|
343
|
+
axisBudget = "auto",
|
|
344
|
+
onAxisBudget
|
|
327
345
|
}) {
|
|
328
346
|
const labels = useKitLabels("seriesChart", DEFAULT_SERIES_CHART_LABELS, labelsProp);
|
|
329
347
|
const locale = useKitLocale(localeProp);
|
|
330
348
|
const number = useDefaultFormat(localeProp);
|
|
331
349
|
const [keyStop, setKeyStop] = useState(0);
|
|
332
350
|
const [keyFocus, setKeyFocus] = useState(void 0);
|
|
351
|
+
const [box, setBox] = useState(null);
|
|
352
|
+
const [width, setWidth] = useState(void 0);
|
|
353
|
+
useLayoutEffect(() => {
|
|
354
|
+
if (!box || axisBudget === "off" || typeof ResizeObserver === "undefined") return;
|
|
355
|
+
const measure = () => setWidth(box.getBoundingClientRect().width || void 0);
|
|
356
|
+
measure();
|
|
357
|
+
const observer = new ResizeObserver(measure);
|
|
358
|
+
observer.observe(box);
|
|
359
|
+
return () => observer.disconnect();
|
|
360
|
+
}, [box, axisBudget]);
|
|
361
|
+
const capped = budgetedAxes(axes, maxVisibleAxes);
|
|
362
|
+
const afterCap = capped.length ? axes.map((axis) => capped.includes(axis.id) ? { ...axis, hide: true } : axis) : axes;
|
|
363
|
+
const auto = axisBudget === "off" ? [] : budgetedAxes(
|
|
364
|
+
afterCap,
|
|
365
|
+
autoAxisBudget(
|
|
366
|
+
afterCap,
|
|
367
|
+
width,
|
|
368
|
+
(axis) => axisBandWidth(axis.width, Boolean(axis.title))
|
|
369
|
+
)
|
|
370
|
+
);
|
|
371
|
+
const budgeted = [...capped, ...auto];
|
|
372
|
+
const budgetKey = budgeted.join("\n");
|
|
373
|
+
const reported = useRef("");
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
if (budgetKey === reported.current) return;
|
|
376
|
+
reported.current = budgetKey;
|
|
377
|
+
onAxisBudget?.(budgetKey ? budgetKey.split("\n") : []);
|
|
378
|
+
}, [budgetKey, onAxisBudget]);
|
|
333
379
|
const heightClass = typeof height === "string" ? height : void 0;
|
|
334
380
|
const heightStyle = typeof height === "number" ? { height } : void 0;
|
|
335
381
|
if (!model.source.length || !series.length) {
|
|
@@ -346,14 +392,21 @@ function SeriesPlot({
|
|
|
346
392
|
const xKey = model.plotKey;
|
|
347
393
|
const plotted = model.rows;
|
|
348
394
|
const rows = model.source;
|
|
395
|
+
const drawnAxes = budgeted.length ? axes.map((axis) => budgeted.includes(axis.id) ? { ...axis, hide: true } : axis) : axes;
|
|
396
|
+
const lostUnit = new Map(
|
|
397
|
+
axes.filter((axis) => budgeted.includes(axis.id)).map((axis) => [axis.id, axisUnit(axis)])
|
|
398
|
+
);
|
|
349
399
|
const config = Object.fromEntries(
|
|
350
400
|
series.map((entry, index) => [
|
|
351
401
|
entry.key,
|
|
352
|
-
{
|
|
402
|
+
{
|
|
403
|
+
label: withUnit(entry.label, lostUnit.get(entry.axis ?? DEFAULT_Y_AXIS)),
|
|
404
|
+
color: entry.color ?? paletteFor(index)
|
|
405
|
+
}
|
|
353
406
|
])
|
|
354
407
|
);
|
|
355
|
-
const visible =
|
|
356
|
-
const gridAxis = (visible[0] ??
|
|
408
|
+
const visible = drawnAxes.filter((axis) => !axis.hide);
|
|
409
|
+
const gridAxis = (visible[0] ?? drawnAxes[0])?.id ?? DEFAULT_Y_AXIS;
|
|
357
410
|
const onLeft = visible.some((axis) => (axis.orientation ?? "left") === "left");
|
|
358
411
|
const onRight = visible.some((axis) => axis.orientation === "right");
|
|
359
412
|
const margin = {
|
|
@@ -511,6 +564,8 @@ function SeriesPlot({
|
|
|
511
564
|
return /* @__PURE__ */ jsx(
|
|
512
565
|
ChartContainer,
|
|
513
566
|
{
|
|
567
|
+
ref: setBox,
|
|
568
|
+
"data-axis-budget": budgeted.length ? budgetKey.replace(/\n/g, " ") : void 0,
|
|
514
569
|
config,
|
|
515
570
|
className: cn("w-full", heightClass, className, onPointClick && "cursor-pointer"),
|
|
516
571
|
style: heightStyle,
|
|
@@ -555,7 +610,7 @@ function SeriesPlot({
|
|
|
555
610
|
)
|
|
556
611
|
}
|
|
557
612
|
),
|
|
558
|
-
|
|
613
|
+
drawnAxes.map((axis) => {
|
|
559
614
|
const domain = fittedY(axis);
|
|
560
615
|
const zoomed = anchored(axis) ? zoom?.xDomain !== void 0 : zoom?.yDomains[axis.id] !== void 0;
|
|
561
616
|
return /* @__PURE__ */ jsx(
|
|
@@ -567,7 +622,7 @@ function SeriesPlot({
|
|
|
567
622
|
ticks: resolveTicks(axis.tickValues, domain, finite) ?? (wholeOnly(axis) ? integerTicks(domain) ?? niceTicks(domain) : niceTicks(domain)),
|
|
568
623
|
allowDataOverflow: zoomed,
|
|
569
624
|
orientation: axis.orientation ?? "left",
|
|
570
|
-
width: axisBandWidth(axis.width, Boolean(axis.title)
|
|
625
|
+
width: axis.hide ? 0 : axisBandWidth(axis.width, Boolean(axis.title)),
|
|
571
626
|
tickLine: false,
|
|
572
627
|
axisLine: false,
|
|
573
628
|
tickFormatter: axis.format ?? number,
|
|
@@ -610,7 +665,7 @@ function SeriesPlot({
|
|
|
610
665
|
const own = entry.dash ?? (entry.dashed ? 1 : 0);
|
|
611
666
|
const dash = entry.step ? strokeDash(STEP_DASH) : typeof own === "string" ? own : strokeDash(own);
|
|
612
667
|
const curve = entry.step ? "stepAfter" : entry.curve ?? "monotone";
|
|
613
|
-
const
|
|
668
|
+
const width2 = entry.strokeWidth ?? (entry.step ? STEP_WIDTH : LINE_WIDTH);
|
|
614
669
|
if (type === "bar") {
|
|
615
670
|
const stub = entry.minBarLength ?? minBarLength;
|
|
616
671
|
return /* @__PURE__ */ jsx(
|
|
@@ -643,7 +698,7 @@ function SeriesPlot({
|
|
|
643
698
|
dataKey: entry.key,
|
|
644
699
|
stackId: entry.stack,
|
|
645
700
|
stroke: color,
|
|
646
|
-
strokeWidth:
|
|
701
|
+
strokeWidth: width2,
|
|
647
702
|
strokeDasharray: dash,
|
|
648
703
|
fill: color,
|
|
649
704
|
fillOpacity: entry.fillOpacity ?? (entry.stack !== void 0 ? 0.55 : 0.2),
|
|
@@ -662,7 +717,7 @@ function SeriesPlot({
|
|
|
662
717
|
type: curve,
|
|
663
718
|
dataKey: entry.key,
|
|
664
719
|
stroke: color,
|
|
665
|
-
strokeWidth:
|
|
720
|
+
strokeWidth: width2,
|
|
666
721
|
strokeDasharray: dash,
|
|
667
722
|
dot: dotFor(entry),
|
|
668
723
|
...entry.activeDot !== void 0 ? { activeDot: entry.activeDot } : {},
|