basalt-ui 1.15.0 → 1.17.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/CHANGELOG.md +14 -0
- package/agent/rules/basalt-charts.md +74 -13
- package/agent/templates/CLAUDE-block.md.tpl +9 -6
- package/dist/charts/cursor/resolve.d.ts +29 -5
- package/dist/charts/cursor/resolve.d.ts.map +1 -1
- package/dist/charts/cursor/resolve.js +25 -4
- package/dist/charts/hooks/useChartCursor.d.ts +8 -1
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
- package/dist/charts/hooks/useChartCursor.js +6 -2
- package/dist/charts/index.d.ts +2 -1
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.d.ts +30 -8
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +10 -3
- package/dist/charts/kinds/Bars.test.d.ts +2 -0
- package/dist/charts/kinds/Bars.test.d.ts.map +1 -0
- package/dist/charts/kinds/DualPanel.d.ts +30 -0
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +82 -12
- package/dist/charts/kinds/DualPanel.test.d.ts +2 -0
- package/dist/charts/kinds/DualPanel.test.d.ts.map +1 -0
- package/dist/charts/kinds/MultiLine.d.ts +9 -0
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +45 -33
- package/dist/charts/kinds/MultiLine.test.d.ts +2 -0
- package/dist/charts/kinds/MultiLine.test.d.ts.map +1 -0
- package/dist/charts/kinds/StackedArea.d.ts +9 -0
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +16 -1
- package/dist/charts/kinds/StackedArea.test.d.ts +2 -0
- package/dist/charts/kinds/StackedArea.test.d.ts.map +1 -0
- package/dist/charts/kinds/ZonedLine.d.ts +9 -0
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +5 -0
- package/dist/charts/kinds/ZonedLine.test.d.ts +2 -0
- package/dist/charts/kinds/ZonedLine.test.d.ts.map +1 -0
- package/dist/charts/primitives/CartesianChart.d.ts +45 -6
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +29 -12
- package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.d.ts +8 -1
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +1 -1
- package/dist/charts/primitives/ChartLegend.d.ts +3 -0
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +4 -0
- package/dist/charts/primitives/ChartTooltip.d.ts +6 -1
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +3 -2
- package/dist/charts/primitives/ChartTooltip.test.d.ts +2 -0
- package/dist/charts/primitives/ChartTooltip.test.d.ts.map +1 -0
- package/dist/charts/primitives/XZoneRects.d.ts +22 -8
- package/dist/charts/primitives/XZoneRects.d.ts.map +1 -1
- package/dist/charts/primitives/XZoneRects.js +39 -2
- package/dist/charts/series.d.ts +16 -3
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/series.js +2 -1
- package/dist/charts/utils/domain.d.ts +15 -0
- package/dist/charts/utils/domain.d.ts.map +1 -1
- package/dist/charts/utils/domain.js +6 -1
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/charts/cursor/resolve.test.ts +62 -0
- package/src/charts/cursor/resolve.ts +71 -7
- package/src/charts/hooks/useChartCursor.ts +12 -1
- package/src/charts/index.ts +2 -0
- package/src/charts/kinds/Bars.test.tsx +183 -0
- package/src/charts/kinds/Bars.tsx +41 -12
- package/src/charts/kinds/DualPanel.test.tsx +441 -0
- package/src/charts/kinds/DualPanel.tsx +127 -11
- package/src/charts/kinds/MultiLine.test.tsx +218 -0
- package/src/charts/kinds/MultiLine.tsx +64 -36
- package/src/charts/kinds/StackedArea.test.tsx +114 -0
- package/src/charts/kinds/StackedArea.tsx +25 -1
- package/src/charts/kinds/ZonedLine.test.tsx +147 -0
- package/src/charts/kinds/ZonedLine.tsx +14 -0
- package/src/charts/primitives/CartesianChart.test.tsx +269 -0
- package/src/charts/primitives/CartesianChart.tsx +79 -19
- package/src/charts/primitives/ChartFrame.test.tsx +42 -0
- package/src/charts/primitives/ChartFrame.tsx +9 -2
- package/src/charts/primitives/ChartLegend.test.tsx +30 -0
- package/src/charts/primitives/ChartLegend.tsx +7 -0
- package/src/charts/primitives/ChartTooltip.test.tsx +23 -0
- package/src/charts/primitives/ChartTooltip.tsx +7 -1
- package/src/charts/primitives/XZoneRects.test.tsx +60 -0
- package/src/charts/primitives/XZoneRects.tsx +75 -10
- package/src/charts/series.test.ts +51 -0
- package/src/charts/series.ts +18 -6
- package/src/charts/utils/domain.test.ts +31 -1
- package/src/charts/utils/domain.ts +19 -0
- package/src/charts/primitives/CartesianChart.test.ts +0 -99
|
@@ -7,7 +7,7 @@ import { VX } from "../../tokens/index.js";
|
|
|
7
7
|
import { useChartCursor } from "../hooks/useChartCursor.js";
|
|
8
8
|
import { autoMargin, probeAxisLabels } from "../layout/auto-margin.js";
|
|
9
9
|
import { deriveTooltipRows } from "../series.js";
|
|
10
|
-
import { padAutoLower } from "../utils/domain.js";
|
|
10
|
+
import { padAutoLower, padAutoUpper } from "../utils/domain.js";
|
|
11
11
|
import { fmtAxisDate } from "../utils/format.js";
|
|
12
12
|
import { smartTicks, smartTicksEvery } from "../utils/ticks.js";
|
|
13
13
|
import { AxisBottomDate, AxisLeftNumeric, AxisRightNumeric } from "./Axes.js";
|
|
@@ -41,8 +41,7 @@ function resolveAxisDomain(cfg, data, series, extraBounds = []) {
|
|
|
41
41
|
if (min === Infinity) return [0, cfg?.autoMaxFloor ?? 1];
|
|
42
42
|
const pad = cfg?.autoPad ?? DEFAULT_AUTO_PAD;
|
|
43
43
|
const ceil = cfg?.autoMinCeil ?? 0;
|
|
44
|
-
const
|
|
45
|
-
const upper = Math.max(paddedMax, cfg?.autoMaxFloor ?? -Infinity);
|
|
44
|
+
const upper = padAutoUpper(Math.max(max, cfg?.autoMaxFloor ?? -Infinity), pad);
|
|
46
45
|
const lower = padAutoLower(Math.min(min, ceil), pad);
|
|
47
46
|
if (lower === upper) return [Math.min(lower, 0), upper === 0 ? 1 : upper * pad];
|
|
48
47
|
return [lower, upper];
|
|
@@ -66,6 +65,7 @@ function CartesianChart({
|
|
|
66
65
|
tooltip,
|
|
67
66
|
margin: marginOverride,
|
|
68
67
|
cursorValue,
|
|
68
|
+
cursorResolution,
|
|
69
69
|
ariaLabel,
|
|
70
70
|
isPending,
|
|
71
71
|
children
|
|
@@ -99,6 +99,7 @@ function CartesianChart({
|
|
|
99
99
|
...tooltip !== void 0 && { tooltip },
|
|
100
100
|
...marginOverride !== void 0 && { marginOverride },
|
|
101
101
|
...cursorValue !== void 0 && { cursorValue },
|
|
102
|
+
...cursorResolution !== void 0 && { cursorResolution },
|
|
102
103
|
...ariaLabel !== void 0 && { ariaLabel },
|
|
103
104
|
plot,
|
|
104
105
|
highlighted,
|
|
@@ -123,6 +124,7 @@ function CartesianPlot({
|
|
|
123
124
|
tooltip,
|
|
124
125
|
marginOverride,
|
|
125
126
|
cursorValue,
|
|
127
|
+
cursorResolution,
|
|
126
128
|
ariaLabel,
|
|
127
129
|
plot,
|
|
128
130
|
highlighted,
|
|
@@ -154,17 +156,19 @@ function CartesianPlot({
|
|
|
154
156
|
() => probeAxisLabels({
|
|
155
157
|
domain: leftDomain,
|
|
156
158
|
ticks: leftTicks,
|
|
159
|
+
nice: y?.nice ?? false,
|
|
157
160
|
...y?.format !== void 0 && { format: y.format }
|
|
158
161
|
}),
|
|
159
|
-
[leftDomain, leftTicks, y?.format]
|
|
162
|
+
[leftDomain, leftTicks, y?.format, y?.nice]
|
|
160
163
|
);
|
|
161
164
|
const { labels: rightLabels, format: rightFormat } = useMemo(
|
|
162
165
|
() => rightDomain === null ? { labels: [], format: (v) => String(v) } : probeAxisLabels({
|
|
163
166
|
domain: rightDomain,
|
|
164
167
|
ticks: rightTicks,
|
|
168
|
+
nice: y2?.nice ?? false,
|
|
165
169
|
...y2?.format !== void 0 && { format: y2.format }
|
|
166
170
|
}),
|
|
167
|
-
[rightDomain, rightTicks, y2?.format]
|
|
171
|
+
[rightDomain, rightTicks, y2?.format, y2?.nice]
|
|
168
172
|
);
|
|
169
173
|
const xLabels = useMemo(() => keys.map(formatX), [keys, formatX]);
|
|
170
174
|
const margin = useMemo(
|
|
@@ -183,12 +187,20 @@ function CartesianPlot({
|
|
|
183
187
|
[keys, xMax]
|
|
184
188
|
);
|
|
185
189
|
const yScale = useMemo(
|
|
186
|
-
() => scaleLinear({
|
|
187
|
-
|
|
190
|
+
() => scaleLinear({
|
|
191
|
+
domain: leftDomain,
|
|
192
|
+
range: [yMax, 0],
|
|
193
|
+
...y?.nice === true && { nice: true }
|
|
194
|
+
}),
|
|
195
|
+
[leftDomain, yMax, y?.nice]
|
|
188
196
|
);
|
|
189
197
|
const y2Scale = useMemo(
|
|
190
|
-
() => rightDomain === null ? null : scaleLinear({
|
|
191
|
-
|
|
198
|
+
() => rightDomain === null ? null : scaleLinear({
|
|
199
|
+
domain: rightDomain,
|
|
200
|
+
range: [yMax, 0],
|
|
201
|
+
...y2?.nice === true && { nice: true }
|
|
202
|
+
}),
|
|
203
|
+
[rightDomain, yMax, y2?.nice]
|
|
192
204
|
);
|
|
193
205
|
const tickValues = useMemo(
|
|
194
206
|
() => xTicks === void 0 ? smartTicks(keys, xMax) : smartTicksEvery(keys, xTicks),
|
|
@@ -199,7 +211,8 @@ function CartesianPlot({
|
|
|
199
211
|
chartId,
|
|
200
212
|
getKey: getX,
|
|
201
213
|
xScale,
|
|
202
|
-
marginLeft: margin.left
|
|
214
|
+
marginLeft: margin.left,
|
|
215
|
+
...cursorResolution !== void 0 && { resolution: cursorResolution }
|
|
203
216
|
});
|
|
204
217
|
const svgRef = useRef(null);
|
|
205
218
|
const tooltipEnabled = tooltip !== false;
|
|
@@ -207,6 +220,7 @@ function CartesianPlot({
|
|
|
207
220
|
const point = cursor.point;
|
|
208
221
|
const cursorX = point === null ? null : xScale(getX(point)) ?? null;
|
|
209
222
|
const badge = tooltipCfg?.label === void 0 || point === null ? null : tooltipCfg.label(point);
|
|
223
|
+
const formatHeader = tooltipCfg?.formatHeader;
|
|
210
224
|
const tooltipSeries = useMemo(
|
|
211
225
|
() => visible.map(
|
|
212
226
|
(s) => s.formatValue === void 0 ? { ...s, formatValue: s.axis === "right" ? rightFormat : leftFormat } : s
|
|
@@ -321,11 +335,14 @@ function CartesianPlot({
|
|
|
321
335
|
TooltipHeader,
|
|
322
336
|
{
|
|
323
337
|
date: getX(point),
|
|
338
|
+
...formatHeader !== void 0 && {
|
|
339
|
+
format: (key) => formatHeader(key, point)
|
|
340
|
+
},
|
|
324
341
|
...badge !== null && { label: badge.text, labelColor: badge.color }
|
|
325
342
|
}
|
|
326
343
|
),
|
|
327
344
|
/* @__PURE__ */ jsxs(TooltipBody, { children: [
|
|
328
|
-
tooltipCfg?.prependRows?.(point),
|
|
345
|
+
tooltipCfg?.prependRows?.(point, { visible, hidden }),
|
|
329
346
|
rows.map((row) => /* @__PURE__ */ jsx(
|
|
330
347
|
TooltipRow,
|
|
331
348
|
{
|
|
@@ -338,7 +355,7 @@ function CartesianPlot({
|
|
|
338
355
|
},
|
|
339
356
|
row.key
|
|
340
357
|
)),
|
|
341
|
-
tooltipCfg?.extraRows?.(point)
|
|
358
|
+
tooltipCfg?.extraRows?.(point, { visible, hidden })
|
|
342
359
|
] })
|
|
343
360
|
] })
|
|
344
361
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CartesianChart.test.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/CartesianChart.test.
|
|
1
|
+
{"version":3,"file":"CartesianChart.test.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/CartesianChart.test.tsx"],"names":[],"mappings":""}
|
|
@@ -43,9 +43,16 @@ export type ChartFrameProps = {
|
|
|
43
43
|
*/
|
|
44
44
|
isPending?: boolean;
|
|
45
45
|
/**
|
|
46
|
-
* Accessible text alternative for the chart, applied as `aria-label` (+ `role="
|
|
46
|
+
* Accessible text alternative for the chart, applied as `aria-label` (+ `role="group"`) on the
|
|
47
47
|
* outer container so screen readers announce something other than an unlabeled graphic. Every
|
|
48
48
|
* kind composing `ChartFrame` should accept and forward this from its own props.
|
|
49
|
+
*
|
|
50
|
+
* MUST stay `role="group"`, never `role="img"`. Per the ARIA spec, every descendant of an
|
|
51
|
+
* `role="img"` element is presentational, which erases the `HoverOverlay`'s `role="slider"` from
|
|
52
|
+
* the accessibility tree entirely — a screen reader announces the label and then the
|
|
53
|
+
* keyboard-scrubbable slider underneath it is simply unreachable, silently, with no error
|
|
54
|
+
* anywhere. `role="group"` announces the same label while keeping descendants exposed. Do not
|
|
55
|
+
* "simplify" this back to `img` — it looks like a no-op refactor and it is not.
|
|
49
56
|
*/
|
|
50
57
|
ariaLabel?: string;
|
|
51
58
|
/** Draw the SVG marks given the plot rect that already excludes the legend band, plus the set
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAIrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAOhF;0EAC0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wBAAwB;IACxB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,uGAAuG;IACvG,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;IAC9B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uFAAuF;IACvF,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAA;IACjC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB
|
|
1
|
+
{"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAIrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAOhF;0EAC0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wBAAwB;IACxB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,uGAAuG;IACvG,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;IAC9B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uFAAuF;IACvF,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAA;IACjC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;oDACgD;IAChD,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,SAAS,CAAA;CACxC,CAAA;AAED,0FAA0F;AAC1F,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC5B,CAAA;AAcD;oFACoF;AACpF,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAC1C,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,iBAAiB,GAAG,KAAK,GAAG,SAAS,EAC7C,KAAK,CAAC,EAAE,qBAAqB,GAC5B,gBAAgB,GAAG,KAAK,CAS1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,MAAM,EACN,WAAW,EACX,IAAY,EACZ,QAA4B,EAC5B,OAAO,EACP,MAAW,EACX,SAAS,EACT,SAAiB,EACjB,QAAQ,GACT,EAAE,eAAe,GAAG,SAAS,CAsE7B"}
|
|
@@ -80,7 +80,7 @@ function ChartFrame({
|
|
|
80
80
|
{
|
|
81
81
|
ref: containerRef,
|
|
82
82
|
style: outerStyle(fill, vertical),
|
|
83
|
-
...ariaLabel !== void 0 && { role: "
|
|
83
|
+
...ariaLabel !== void 0 && { role: "group", "aria-label": ariaLabel },
|
|
84
84
|
...isPending && { "aria-busy": "true" },
|
|
85
85
|
children: [
|
|
86
86
|
legendNode !== null && (placement === "top" || placement === "left") && legendNode,
|
|
@@ -10,6 +10,9 @@ export type LegendEntry = {
|
|
|
10
10
|
dashed?: boolean;
|
|
11
11
|
/** bar swatch opacity — honored instead of a hardcoded value, so it cannot lie about the fill. */
|
|
12
12
|
fillOpacity?: number;
|
|
13
|
+
/** line/splitLine swatch stroke opacity — parity with `fillOpacity` above, so a dimmed
|
|
14
|
+
* companion line's swatch cannot lie about its own opacity either. */
|
|
15
|
+
strokeOpacity?: number;
|
|
13
16
|
/** Drives `groups` rendering (series → hairline divider → overlay → reference). */
|
|
14
17
|
role?: SeriesRole;
|
|
15
18
|
/** Companions folded under this entry via `parent` (`deriveLegend`) — rendered as compact,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;
|
|
1
|
+
{"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;0EACsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAgOD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAkB,EAClB,WAAW,EACX,MAAkB,EAClB,QAAQ,EACR,OAAY,EACZ,SAAoB,EACpB,MAAc,EACd,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C,kCAAkC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,+BAmEA"}
|
|
@@ -70,6 +70,7 @@ function LegendSwatch({ item, idPrefix }) {
|
|
|
70
70
|
y2: 7,
|
|
71
71
|
stroke: item.color,
|
|
72
72
|
strokeWidth: item.strokeWidth ?? 2.5,
|
|
73
|
+
strokeOpacity: item.strokeOpacity ?? 1,
|
|
73
74
|
strokeDasharray: item.dashed ? VX.dashArray : void 0
|
|
74
75
|
}
|
|
75
76
|
),
|
|
@@ -82,6 +83,7 @@ function LegendSwatch({ item, idPrefix }) {
|
|
|
82
83
|
y2: 7,
|
|
83
84
|
stroke: item.secondColor,
|
|
84
85
|
strokeWidth: item.strokeWidth ?? 2.5,
|
|
86
|
+
strokeOpacity: item.strokeOpacity ?? 1,
|
|
85
87
|
strokeDasharray: item.dashed ? VX.dashArray : void 0
|
|
86
88
|
}
|
|
87
89
|
)
|
|
@@ -124,6 +126,7 @@ function LegendSwatch({ item, idPrefix }) {
|
|
|
124
126
|
y2: 3,
|
|
125
127
|
stroke: item.color,
|
|
126
128
|
strokeWidth: item.strokeWidth ?? 3,
|
|
129
|
+
strokeOpacity: item.strokeOpacity ?? 1,
|
|
127
130
|
strokeLinecap: "round",
|
|
128
131
|
strokeDasharray: item.dashed ? VX.dashArray : void 0
|
|
129
132
|
}
|
|
@@ -166,6 +169,7 @@ function LegendChildSwatch({ item }) {
|
|
|
166
169
|
y2: 5,
|
|
167
170
|
stroke: item.color,
|
|
168
171
|
strokeWidth: item.strokeWidth ?? 2,
|
|
172
|
+
strokeOpacity: item.strokeOpacity ?? 1,
|
|
169
173
|
strokeDasharray: item.dashed ? VX.dashArray : void 0
|
|
170
174
|
}
|
|
171
175
|
) });
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
/** Tooltip header — shows formatted date + optional right-aligned label with color. */
|
|
3
|
-
export declare function TooltipHeader({ date, label, labelColor, }: {
|
|
3
|
+
export declare function TooltipHeader({ date, label, labelColor, format, }: {
|
|
4
4
|
date: string;
|
|
5
5
|
label?: string;
|
|
6
6
|
labelColor?: string;
|
|
7
|
+
/** Overrides `fmtTooltipDate` for the header text. Default: today's `fmtTooltipDate` behavior,
|
|
8
|
+
* unchanged. The seam exists because `fmtTooltipDate` regexes `YYYY-MM-DD` out of the key and
|
|
9
|
+
* builds a LOCAL `Date`, so a UTC ISO domain key names the wrong day next to `formatX`/the
|
|
10
|
+
* tooltip badge, which both resolve locally — a caller with a UTC key needs to supply its own. */
|
|
11
|
+
format?: (date: string) => string;
|
|
7
12
|
}): import("react").JSX.Element;
|
|
8
13
|
/** Tooltip row — swatch + label + value. */
|
|
9
14
|
export declare function TooltipRow({ color, label, value, valueColor, shape, strokeWidth, dashed, }: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartTooltip.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAuBrD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,UAAU,
|
|
1
|
+
{"version":3,"file":"ChartTooltip.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAuBrD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,UAAU,EACV,MAAuB,GACxB,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;sGAGkG;IAClG,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC,+BAoCA;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,EACL,KAAK,EACL,UAAU,EACV,KAAK,EACL,WAAW,EACX,MAAM,GACP,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,+BA0CA;AAED,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEhE;AAOD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACvC,QAAQ,EAAE,SAAS,CAAA;CACpB,sCA4CA"}
|
|
@@ -19,7 +19,8 @@ const TOOLTIP_STYLES = {
|
|
|
19
19
|
function TooltipHeader({
|
|
20
20
|
date,
|
|
21
21
|
label,
|
|
22
|
-
labelColor
|
|
22
|
+
labelColor,
|
|
23
|
+
format = fmtTooltipDate
|
|
23
24
|
}) {
|
|
24
25
|
return /* @__PURE__ */ jsxs(
|
|
25
26
|
"div",
|
|
@@ -42,7 +43,7 @@ function TooltipHeader({
|
|
|
42
43
|
fontFamily: "var(--basalt-font-mono)",
|
|
43
44
|
color: VX.ink
|
|
44
45
|
},
|
|
45
|
-
children:
|
|
46
|
+
children: format(date)
|
|
46
47
|
}
|
|
47
48
|
),
|
|
48
49
|
label !== void 0 && /* @__PURE__ */ jsx(
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChartTooltip.test.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartTooltip.test.tsx"],"names":[],"mappings":""}
|
|
@@ -11,24 +11,38 @@ export type XZoneSpec = {
|
|
|
11
11
|
/** Right edge — a domain key produced by the kind's `getX`. Omit for "end of the plot area". */
|
|
12
12
|
to?: string;
|
|
13
13
|
fill: string;
|
|
14
|
+
/**
|
|
15
|
+
* `'center'` (default, unchanged behaviour) resolves a present bound to `xScale(key)` — the
|
|
16
|
+
* point's own center, so a band reads "from this sample to that sample" rather than
|
|
17
|
+
* edge-to-edge. `'edge'` resolves a present `from` to `xScale(from) - step/2` and a present `to`
|
|
18
|
+
* to `xScale(to) + step/2` (`step` read from `xScale.step()`), so the band covers both terminal
|
|
19
|
+
* slots in full — a two-key band widens by exactly one step, and `from === to` renders one step
|
|
20
|
+
* wide instead of being skipped as degenerate. An omitted `from`/`to` still resolves to the plot
|
|
21
|
+
* edge (`xScale.range()[0]`/`[1]`) in both modes, unshifted — omission means "the plot's own
|
|
22
|
+
* edge", not "the first/last sample's edge". A resolved `'edge'` bound is clamped into the plot
|
|
23
|
+
* range so a band anchored at the first or last sample cannot paint outside the plot area.
|
|
24
|
+
*/
|
|
25
|
+
align?: 'center' | 'edge';
|
|
14
26
|
};
|
|
15
27
|
/**
|
|
16
28
|
* Vertical zone backgrounds (x-range overlays — twilight bands, a shaded time window) — one
|
|
17
29
|
* <rect> per zone, spanning y=[0, height] and x=[scale(from), scale(to)]. The x analog of
|
|
18
30
|
* `ZoneRects`, for a `scalePoint` x-axis instead of a `scaleLinear` y-axis.
|
|
19
31
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
32
|
+
* Bound resolution depends on `XZoneSpec.align` (see its JSDoc for the full `'center'`/`'edge'`
|
|
33
|
+
* contract) — default `'center'` resolves a present key to the point's own center; `'edge'`
|
|
34
|
+
* widens by half a step at each present bound so the band covers the terminal slots in full.
|
|
23
35
|
*
|
|
24
36
|
* A key that is not in the scale's domain skips that band entirely (renders nothing for it) —
|
|
25
|
-
* it is NEVER clamped to a plot edge
|
|
26
|
-
* chart for a typo'd or stale key, which is the exact class of
|
|
27
|
-
* draw is two different states" doctrine rejects: an unknown key
|
|
28
|
-
* as "the whole plot".
|
|
37
|
+
* it is NEVER clamped to a plot edge, in either mode. Clamping an unknown key would silently
|
|
38
|
+
* paint a band across the whole chart for a typo'd or stale key, which is the exact class of
|
|
39
|
+
* quiet lie the repo's "nothing to draw is two different states" doctrine rejects: an unknown key
|
|
40
|
+
* must read as "not drawn", not as "the whole plot".
|
|
29
41
|
*
|
|
30
42
|
* A degenerate resolved range (right edge <= left edge, e.g. an inverted `from`/`to` pair) is
|
|
31
|
-
* also skipped — no negative-width rect.
|
|
43
|
+
* also skipped — no negative-width rect. In `'center'` mode `from === to` is degenerate (skipped,
|
|
44
|
+
* today's behaviour); in `'edge'` mode it is NOT — the half-step widening on each side always
|
|
45
|
+
* produces a positive-width, one-step-wide rect.
|
|
32
46
|
*/
|
|
33
47
|
export declare function XZoneRects({ zones, height, xScale, }: {
|
|
34
48
|
zones: XZoneSpec[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"XZoneRects.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/XZoneRects.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C;;;;GAIG;AACH,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;AAEvD,MAAM,MAAM,SAAS,GAAG;IACtB,iGAAiG;IACjG,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gGAAgG;IAChG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"XZoneRects.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/XZoneRects.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C;;;;GAIG;AACH,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;AAEvD,MAAM,MAAM,SAAS,GAAG;IACtB,iGAAiG;IACjG,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gGAAgG;IAChG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;;;OAUG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;CAC1B,CAAA;AAkCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,MAAM,GACP,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,UAAU,CAAA;CACnB,+BAyCA"}
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
function clamp(value, min, max) {
|
|
3
|
+
return Math.min(Math.max(value, min), max);
|
|
4
|
+
}
|
|
5
|
+
function resolveBound({
|
|
6
|
+
key,
|
|
7
|
+
omittedEdge,
|
|
8
|
+
xScale,
|
|
9
|
+
align,
|
|
10
|
+
edgeSign,
|
|
11
|
+
rangeMin,
|
|
12
|
+
rangeMax
|
|
13
|
+
}) {
|
|
14
|
+
if (key === void 0) return omittedEdge;
|
|
15
|
+
const center = xScale(key);
|
|
16
|
+
if (center === void 0) return void 0;
|
|
17
|
+
if (align === "center") return center;
|
|
18
|
+
return clamp(center + edgeSign * (xScale.step() / 2), rangeMin, rangeMax);
|
|
19
|
+
}
|
|
2
20
|
function XZoneRects({
|
|
3
21
|
zones,
|
|
4
22
|
height,
|
|
@@ -6,8 +24,27 @@ function XZoneRects({
|
|
|
6
24
|
}) {
|
|
7
25
|
return /* @__PURE__ */ jsx(Fragment, { children: zones.map((z, i) => {
|
|
8
26
|
const [rangeStart, rangeEnd] = xScale.range();
|
|
9
|
-
const
|
|
10
|
-
const
|
|
27
|
+
const rangeMin = Math.min(rangeStart, rangeEnd);
|
|
28
|
+
const rangeMax = Math.max(rangeStart, rangeEnd);
|
|
29
|
+
const align = z.align ?? "center";
|
|
30
|
+
const xFrom = resolveBound({
|
|
31
|
+
key: z.from,
|
|
32
|
+
omittedEdge: rangeStart,
|
|
33
|
+
xScale,
|
|
34
|
+
align,
|
|
35
|
+
edgeSign: -1,
|
|
36
|
+
rangeMin,
|
|
37
|
+
rangeMax
|
|
38
|
+
});
|
|
39
|
+
const xTo = resolveBound({
|
|
40
|
+
key: z.to,
|
|
41
|
+
omittedEdge: rangeEnd,
|
|
42
|
+
xScale,
|
|
43
|
+
align,
|
|
44
|
+
edgeSign: 1,
|
|
45
|
+
rangeMin,
|
|
46
|
+
rangeMax
|
|
47
|
+
});
|
|
11
48
|
if (xFrom === void 0 || xTo === void 0) return null;
|
|
12
49
|
if (xTo <= xFrom) return null;
|
|
13
50
|
return /* @__PURE__ */ jsx(
|
package/dist/charts/series.d.ts
CHANGED
|
@@ -52,6 +52,14 @@ export type SeriesStyle = {
|
|
|
52
52
|
strokeWidth?: number;
|
|
53
53
|
/** bar/area: the swatch honors this so it cannot lie. */
|
|
54
54
|
fillOpacity?: number;
|
|
55
|
+
/**
|
|
56
|
+
* A MARK property — dims the plotted stroke, e.g. a faint moving-average companion line. The
|
|
57
|
+
* legend swatch honors it too (parity with `fillOpacity`, which it already honors — a swatch
|
|
58
|
+
* that lies is the thing the derived-legend rule exists to prevent). The TOOLTIP ROW swatch and
|
|
59
|
+
* the CROSSHAIR DOT deliberately do NOT honor it: those are 12px value-readout chips where a
|
|
60
|
+
* sub-1 opacity reads as a rendering bug, not as data.
|
|
61
|
+
*/
|
|
62
|
+
strokeOpacity?: number;
|
|
55
63
|
/** Which y-axis this series is measured against. Default 'left'. `'right'` is what makes a
|
|
56
64
|
* chart dual-axis — `CartesianChart` reads it for the crosshair dots and the tooltip. */
|
|
57
65
|
axis?: 'left' | 'right';
|
|
@@ -75,12 +83,17 @@ export type SeriesStyle = {
|
|
|
75
83
|
export type ChartSeries<T> = SeriesStyle & {
|
|
76
84
|
/** null = line gap + skipped tooltip row. */
|
|
77
85
|
getValue: (d: T) => number | null;
|
|
78
|
-
/** Per-series override of the shared tooltip/value formatter.
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
/** Per-series override of the shared tooltip/value formatter. Receives the hovered datum
|
|
87
|
+
* alongside the value so a row can read fields beyond the plotted number (e.g. `97.5 kg (92.5 ×
|
|
88
|
+
* 3)`). */
|
|
89
|
+
formatValue?: (v: number, d: T) => string;
|
|
90
|
+
/** PR star / status dot. Return null for no marker at that point. `ring` defaults true (today's
|
|
91
|
+
* punched-out stroke, unchanged); `false` omits the stroke entirely. `fillOpacity` defaults 1. */
|
|
81
92
|
getMarker?: (d: T) => {
|
|
82
93
|
color?: string;
|
|
83
94
|
r?: number;
|
|
95
|
+
fillOpacity?: number;
|
|
96
|
+
ring?: boolean;
|
|
84
97
|
} | null;
|
|
85
98
|
};
|
|
86
99
|
/** A single derived tooltip row, ready for `TooltipRow`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"series.d.ts","sourceRoot":"","sources":["../../src/charts/series.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE3D;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;AAEhD,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE3C,uCAAuC;AACvC,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAA;AAE3D,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAA;AAEjE;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,wBAAwB;IACxB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,uGAAuG;AACvG,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,UAAU,CAAA;IAChB,uBAAuB;IACvB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;6FACyF;IACzF,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,wBAAwB;IACxB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,uFAAuF;AACvF,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,WAAW,GAAG;IACzC,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAA;IACjC
|
|
1
|
+
{"version":3,"file":"series.d.ts","sourceRoot":"","sources":["../../src/charts/series.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE3D;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;AAEhD,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE3C,uCAAuC;AACvC,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAA;AAE3D,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAA;AAEjE;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,wBAAwB;IACxB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,uGAAuG;AACvG,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,UAAU,CAAA;IAChB,uBAAuB;IACvB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;6FACyF;IACzF,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,wBAAwB;IACxB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,uFAAuF;AACvF,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,WAAW,GAAG;IACzC,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAA;IACjC;;eAEW;IACX,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACzC;sGACkG;IAClG,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;CAClG,CAAA;AAED,4DAA4D;AAC5D,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,KAAK,CAAA;IACrB,MAAM,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAmBD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAoB1E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,MAAM,EAAE,SAAS,WAAW,CAAC,CAAC,CAAC,EAAE,EACjC,KAAK,EAAE,CAAC,EACR,cAAc,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GACpC,cAAc,EAAE,CAkBlB"}
|
package/dist/charts/series.js
CHANGED
|
@@ -9,6 +9,7 @@ function legendEntryFor(s) {
|
|
|
9
9
|
dashed: s.dash === "dashed",
|
|
10
10
|
...s.strokeWidth !== void 0 && { strokeWidth: s.strokeWidth },
|
|
11
11
|
...s.fillOpacity !== void 0 && { fillOpacity: s.fillOpacity },
|
|
12
|
+
...s.strokeOpacity !== void 0 && { strokeOpacity: s.strokeOpacity },
|
|
12
13
|
...s.role !== void 0 && { role: s.role },
|
|
13
14
|
...s.note !== void 0 && { note: s.note }
|
|
14
15
|
};
|
|
@@ -42,7 +43,7 @@ function deriveTooltipRows(series, datum, fallbackFormat) {
|
|
|
42
43
|
key: s.key,
|
|
43
44
|
label: s.label,
|
|
44
45
|
color: s.color,
|
|
45
|
-
value: format(value),
|
|
46
|
+
value: format(value, datum),
|
|
46
47
|
shape: shapeFor(s.mark),
|
|
47
48
|
dashed: s.dash === "dashed",
|
|
48
49
|
...s.strokeWidth !== void 0 && { strokeWidth: s.strokeWidth }
|
|
@@ -17,4 +17,19 @@
|
|
|
17
17
|
* pre-existing, still-correct behavior for genuinely negative-swinging metrics.
|
|
18
18
|
*/
|
|
19
19
|
export declare function padAutoLower(candidate: number, pad: number): number;
|
|
20
|
+
/**
|
|
21
|
+
* Auto y-domain upper-bound padding — the exact mirror of {@link padAutoLower}, sharing its
|
|
22
|
+
* sign-safety: pad AWAY from zero (`upper >= candidate`), never toward it. `candidate` is the
|
|
23
|
+
* caller's `Math.max(dataMax, floor)` — the `autoMaxFloor` clamp has already been applied, so
|
|
24
|
+
* padding runs AFTER the floor, not before it. Applying the floor post-pad (the pre-existing bug)
|
|
25
|
+
* lands a floored axis top exactly on the floor value with zero headroom — a target line pinned to
|
|
26
|
+
* that floor then sits glued to the plot edge instead of getting the same breathing room every
|
|
27
|
+
* other bound gets.
|
|
28
|
+
*
|
|
29
|
+
* - `candidate === 0` stays exactly 0 — never pushed positive.
|
|
30
|
+
* - `candidate > 0` multiplies by `pad`, moving the ceiling further UP (away from zero).
|
|
31
|
+
* - `candidate < 0` divides by `pad`, moving the ceiling UP toward (and past) zero — the mirror of
|
|
32
|
+
* `padAutoLower`'s `candidate > 0` branch.
|
|
33
|
+
*/
|
|
34
|
+
export declare function padAutoUpper(candidate: number, pad: number): number;
|
|
20
35
|
//# sourceMappingURL=domain.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../../src/charts/utils/domain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGnE"}
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../../../src/charts/utils/domain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGnE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGnE"}
|
|
@@ -2,6 +2,11 @@ function padAutoLower(candidate, pad) {
|
|
|
2
2
|
if (candidate === 0) return 0;
|
|
3
3
|
return candidate > 0 ? candidate / pad : candidate * pad;
|
|
4
4
|
}
|
|
5
|
+
function padAutoUpper(candidate, pad) {
|
|
6
|
+
if (candidate === 0) return 0;
|
|
7
|
+
return candidate > 0 ? candidate * pad : candidate / pad;
|
|
8
|
+
}
|
|
5
9
|
export {
|
|
6
|
-
padAutoLower
|
|
10
|
+
padAutoLower,
|
|
11
|
+
padAutoUpper
|
|
7
12
|
};
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v1.
|
|
1
|
+
# basalt-ui v1.17.0
|
|
2
2
|
# Opinionated framework for Mantine-based React apps — theme, app shell, visx charts, toolchain presets
|
|
3
3
|
#
|
|
4
4
|
# basalt-ui is an opinionated framework for Mantine-based React apps.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# ./tokens.css subpath. Guide: docs/FRAMEWORK-FREE.md in the repo.
|
|
20
20
|
|
|
21
21
|
Package: basalt-ui
|
|
22
|
-
Version: 1.
|
|
22
|
+
Version: 1.17.0
|
|
23
23
|
|
|
24
24
|
## basalt-ui
|
|
25
25
|
Import: import { ... } from 'basalt-ui'
|
package/package.json
CHANGED
|
@@ -59,3 +59,65 @@ describe('resolveCursorPoint', () => {
|
|
|
59
59
|
expect(resolveCursorPoint(i, '2026-08-02')).toBeNull()
|
|
60
60
|
})
|
|
61
61
|
})
|
|
62
|
+
|
|
63
|
+
describe("resolveCursorPoint — 'leading' resolution (bucket-keyed domains)", () => {
|
|
64
|
+
const weekly = (days: string[]) => buildDomainIndex(daily(days), (p) => p.date, 'leading')
|
|
65
|
+
|
|
66
|
+
test('resolves an exact key, same as nearest', () => {
|
|
67
|
+
const i = weekly(['01', '08', '15'])
|
|
68
|
+
expect(resolveCursorPoint(i, '2026-08-08')?.date).toBe('2026-08-08')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test("a target anywhere inside a bucket resolves to that bucket's LEADING key, not the nearest one", () => {
|
|
72
|
+
// The bug this replaces: a weekly chart keyed by the Monday of each week (step 7 days),
|
|
73
|
+
// hovered from a daily sibling landing in the back half of the bucket, used to resolve to the
|
|
74
|
+
// FOLLOWING week under nearest-match. Every back-half day must resolve to the bucket it is
|
|
75
|
+
// actually inside.
|
|
76
|
+
const i = weekly(['01', '08', '15'])
|
|
77
|
+
expect(resolveCursorPoint(i, '2026-08-02')?.date).toBe('2026-08-01')
|
|
78
|
+
expect(resolveCursorPoint(i, '2026-08-05')?.date).toBe('2026-08-01')
|
|
79
|
+
expect(resolveCursorPoint(i, '2026-08-07')?.date).toBe('2026-08-01')
|
|
80
|
+
expect(resolveCursorPoint(i, '2026-08-09')?.date).toBe('2026-08-08')
|
|
81
|
+
expect(resolveCursorPoint(i, '2026-08-14')?.date).toBe('2026-08-08')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('a key BEFORE the first bucket resolves to null — no bucket contains it', () => {
|
|
85
|
+
// Deliberately NOT 'nearest''s one-step tolerance. The first key is the leading edge of the
|
|
86
|
+
// first bucket, so anything earlier is outside every bucket; snapping it onto the first one
|
|
87
|
+
// would paint a crosshair on a bucket that provably does not contain the hovered key.
|
|
88
|
+
const i = weekly(['02', '09', '16'])
|
|
89
|
+
expect(resolveCursorPoint(i, '2026-08-01')).toBeNull()
|
|
90
|
+
expect(resolveCursorPoint(i, '2026-08-02')?.date).toBe('2026-08-02')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('the final bucket ends at last + step — that instant is already outside it', () => {
|
|
94
|
+
const i = weekly(['01', '08', '15']) // step = 7, so the last bucket covers [Aug 15, Aug 22)
|
|
95
|
+
expect(resolveCursorPoint(i, '2026-08-21')?.date).toBe('2026-08-15') // last day inside
|
|
96
|
+
expect(resolveCursorPoint(i, '2026-08-22')).toBeNull() // exactly last + step, exclusive
|
|
97
|
+
expect(resolveCursorPoint(i, '2026-08-23')).toBeNull()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('returns null well beyond the domain, so unrelated charts never sync', () => {
|
|
101
|
+
const i = weekly(['01', '08', '15'])
|
|
102
|
+
expect(resolveCursorPoint(i, '2026-09-30')).toBeNull()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('categorical domains fall back to exact match only, same as nearest', () => {
|
|
106
|
+
const points = [{ date: 'Direct' }, { date: 'Referral' }]
|
|
107
|
+
const i = buildDomainIndex(points, (p) => p.date, 'leading')
|
|
108
|
+
expect(resolveCursorPoint(i, 'Direct')?.date).toBe('Direct')
|
|
109
|
+
expect(resolveCursorPoint(i, 'Organic')).toBeNull()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('a single-point chart resolves only its own key', () => {
|
|
113
|
+
const i = weekly(['01'])
|
|
114
|
+
expect(resolveCursorPoint(i, '2026-08-01')?.date).toBe('2026-08-01')
|
|
115
|
+
expect(resolveCursorPoint(i, '2026-08-02')).toBeNull()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('an explicit resolution argument overrides the index-built default', () => {
|
|
119
|
+
// Built as 'nearest', resolved as 'leading' — the per-call argument wins.
|
|
120
|
+
const i = index(daily(['01', '08', '15']))
|
|
121
|
+
expect(resolveCursorPoint(i, '2026-08-05', 'leading')?.date).toBe('2026-08-01')
|
|
122
|
+
})
|
|
123
|
+
})
|