@micha.bigler/ui-core-micha 2.42.3 → 3.0.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/WORK_ORDERS.md +1 -0
- package/dev/entries.jsx +6 -0
- package/dist/components/charts/BarChart.js +23 -12
- package/dist/components/charts/ChartFrame.js +23 -5
- package/dist/components/charts/LineChart.js +23 -12
- package/dist/components/charts/ScatterChart.js +29 -22
- package/dist/components/charts/TimeSeriesChart.js +6 -1
- package/dist/components/charts/chartDefaults.js +207 -124
- package/docs/CHART-LAYOUT.md +129 -0
- package/package.json +1 -1
- package/src/components/charts/BarChart.jsx +30 -28
- package/src/components/charts/ChartFrame.jsx +24 -5
- package/src/components/charts/LineChart.jsx +31 -29
- package/src/components/charts/ScatterChart.jsx +37 -29
- package/src/components/charts/TimeSeriesChart.jsx +6 -1
- package/src/components/charts/chartDefaults.js +237 -130
- package/tests/BarChart.test.jsx +27 -39
- package/tests/LineChart.test.jsx +21 -24
- package/tests/ScatterChart.test.jsx +21 -24
- package/tests/chartDefaults.test.js +313 -265
- package/work-orders/UCM-CHART-12.md +348 -0
|
@@ -11,19 +11,38 @@ import {
|
|
|
11
11
|
import { useTranslation } from 'react-i18next';
|
|
12
12
|
import { useTheme } from '@mui/material/styles';
|
|
13
13
|
import { exportChartPng, exportChartSvg } from './exportChart';
|
|
14
|
-
import { warnOnHeightMismatch } from './chartDefaults';
|
|
15
14
|
|
|
16
15
|
export const CHART_FRAME_ROOT_SX = { p: 2 };
|
|
17
16
|
export const CHART_FRAME_ALERT_SX = { mt: 2 };
|
|
18
17
|
|
|
18
|
+
/**
|
|
19
|
+
* UCM-CHART-12: `warnOnHeightMismatch` (the shared helper this note used to call into
|
|
20
|
+
* `chartDefaults`) was deleted along with `resolveChartHeight` -- both belonged to the
|
|
21
|
+
* `{minHeight, height, aspect}` trio the four chart presets used to accept, which that WO
|
|
22
|
+
* replaced with `resolveChartLayout`'s `size`/`height` model. `ChartFrame` keeps its OWN
|
|
23
|
+
* `minHeight`/`height`/`aspect` props unchanged (CHART-9: it is a card floor, not a chart size),
|
|
24
|
+
* so it keeps this note too -- same text, same condition, just no longer routed through the
|
|
25
|
+
* now-deleted shared function.
|
|
26
|
+
*/
|
|
27
|
+
function warnOnChartFrameHeightMismatch(minHeight, height) {
|
|
28
|
+
if (process.env.NODE_ENV === 'production') return;
|
|
29
|
+
if (minHeight == null || height == null || minHeight === height) return;
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.warn(
|
|
32
|
+
`[ui-core-micha] <ChartFrame> received both minHeight={${minHeight}} and height={${height}}; `
|
|
33
|
+
+ 'they disagree; height is ignored here and minHeight keeps sizing the wrapper as a floor. '
|
|
34
|
+
+ 'Pass only minHeight, or drop height if it was meant to size the whole card.',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
19
38
|
/**
|
|
20
39
|
* Chart-type-agnostic chart scaffolding. Children may be an X-Charts primitive or a bespoke SVG.
|
|
21
40
|
*
|
|
22
41
|
* `minHeight` (CHART-9): always just a floor on the content box, which holds a whole card --
|
|
23
42
|
* title, toolbar, chart, legend, footnotes, export links -- not a single chart. `height` is never
|
|
24
|
-
* applied to the box (unlike the four chart presets, whose box holds exactly one chart and
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
43
|
+
* applied to the box (unlike the four chart presets, whose box holds exactly one chart and use
|
|
44
|
+
* `resolveChartLayout`'s `size`/`height` resolution); it is only compared against `minHeight` for
|
|
45
|
+
* this component's own dev-mode mismatch note.
|
|
27
46
|
*/
|
|
28
47
|
export function ChartFrame({
|
|
29
48
|
title,
|
|
@@ -60,7 +79,7 @@ export function ChartFrame({
|
|
|
60
79
|
const callerSx = Array.isArray(sx) ? sx : [sx];
|
|
61
80
|
|
|
62
81
|
useEffect(
|
|
63
|
-
() =>
|
|
82
|
+
() => warnOnChartFrameHeightMismatch(minHeight, height),
|
|
64
83
|
[minHeight, height],
|
|
65
84
|
);
|
|
66
85
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { Box } from '@mui/material';
|
|
3
3
|
import { useTheme } from '@mui/material/styles';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
@@ -9,15 +9,12 @@ import {
|
|
|
9
9
|
import { useNeutralChartPalette } from './palette';
|
|
10
10
|
import {
|
|
11
11
|
DEFAULT_LEGEND_POSITION,
|
|
12
|
+
assertRemovedChartProp,
|
|
12
13
|
defaultNumericTickFormatter,
|
|
13
|
-
|
|
14
|
-
sizeYAxisForContent,
|
|
15
|
-
spaceForRotatedTicks,
|
|
16
|
-
warnOnHeightMismatch,
|
|
14
|
+
resolveChartLayout,
|
|
17
15
|
withAxisDefaults,
|
|
18
16
|
withChartSlotDefaults,
|
|
19
17
|
withGridDefaults,
|
|
20
|
-
withMarginDefaults,
|
|
21
18
|
} from './chartDefaults';
|
|
22
19
|
|
|
23
20
|
// Fixes MUI's hollow `background.paper`-filled marker (MarkElement.js's own
|
|
@@ -44,10 +41,9 @@ function FilledMarkElement({ color, style, ...props }) {
|
|
|
44
41
|
* (`withAxisDefaults` only ever falls back onto an axis without its own
|
|
45
42
|
* `label`).
|
|
46
43
|
*
|
|
47
|
-
* `
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* if `minHeight` is also passed and larger.
|
|
44
|
+
* `size` (UCM-CHART-12): `"compact" | "standard" | "tall"`, resolved through the theme's
|
|
45
|
+
* spacing scale by `resolveChartLayout`. `height` (px) is the documented escape for a justified
|
|
46
|
+
* special case -- prefer `size`. `minHeight`/`aspect`/`margin` are gone (see docs/CHART-LAYOUT.md).
|
|
51
47
|
*/
|
|
52
48
|
export function LineChart({
|
|
53
49
|
series = [],
|
|
@@ -56,13 +52,15 @@ export function LineChart({
|
|
|
56
52
|
xAxis,
|
|
57
53
|
yAxis,
|
|
58
54
|
palette,
|
|
59
|
-
|
|
55
|
+
size = 'standard',
|
|
60
56
|
height,
|
|
57
|
+
minHeight,
|
|
61
58
|
aspect,
|
|
59
|
+
margin,
|
|
60
|
+
xLabels = 'auto',
|
|
62
61
|
grid,
|
|
63
62
|
hideLegend = series.length <= 1,
|
|
64
63
|
legendPosition = DEFAULT_LEGEND_POSITION,
|
|
65
|
-
margin,
|
|
66
64
|
slots,
|
|
67
65
|
slotProps,
|
|
68
66
|
...chartProps
|
|
@@ -71,8 +69,9 @@ export function LineChart({
|
|
|
71
69
|
const { i18n } = useTranslation();
|
|
72
70
|
const neutralPalette = useNeutralChartPalette();
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
assertRemovedChartProp('LineChart', 'minHeight', minHeight, 'Use size="compact" | "standard" | "tall", or height for the documented escape.');
|
|
73
|
+
assertRemovedChartProp('LineChart', 'aspect', aspect, 'Removed with no replacement -- pick a size token; the chart no longer tracks width.');
|
|
74
|
+
assertRemovedChartProp('LineChart', 'margin', margin, 'Removed -- the layout model owns margins completely.');
|
|
76
75
|
|
|
77
76
|
const labelledXAxis = withAxisDefaults(
|
|
78
77
|
xAxis,
|
|
@@ -91,29 +90,32 @@ export function LineChart({
|
|
|
91
90
|
labelMarkType: 'square',
|
|
92
91
|
...item,
|
|
93
92
|
}));
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
93
|
+
const layout = resolveChartLayout({
|
|
94
|
+
size,
|
|
95
|
+
height,
|
|
96
|
+
xAxis: labelledXAxis,
|
|
97
|
+
yAxis: labelledYAxis,
|
|
98
|
+
series: defaultedSeries,
|
|
99
|
+
xLabels,
|
|
100
|
+
hideLegend,
|
|
101
|
+
legendPosition,
|
|
102
|
+
tickFontSize: theme.typography.caption.fontSize,
|
|
103
|
+
spacing: theme.spacing,
|
|
104
|
+
defaultLineHeight: theme.typography.caption.lineHeight,
|
|
105
|
+
});
|
|
104
106
|
|
|
105
107
|
return (
|
|
106
|
-
<Box data-testid="line-chart-container" sx={
|
|
108
|
+
<Box data-testid="line-chart-container" sx={layout.sx}>
|
|
107
109
|
<MuiLineChart
|
|
108
110
|
{...chartProps}
|
|
109
|
-
height={chartHeight}
|
|
111
|
+
height={layout.chartHeight}
|
|
110
112
|
series={defaultedSeries}
|
|
111
|
-
xAxis={
|
|
112
|
-
yAxis={
|
|
113
|
+
xAxis={layout.xAxis}
|
|
114
|
+
yAxis={layout.yAxis}
|
|
113
115
|
colors={palette || neutralPalette.categorical}
|
|
114
116
|
grid={withGridDefaults(grid)}
|
|
115
117
|
hideLegend={hideLegend}
|
|
116
|
-
margin={
|
|
118
|
+
margin={layout.margin}
|
|
117
119
|
slots={{ mark: FilledMarkElement, ...slots }}
|
|
118
120
|
slotProps={withChartSlotDefaults(slotProps, legendPosition)}
|
|
119
121
|
/>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { Box } from '@mui/material';
|
|
3
3
|
import { useTheme } from '@mui/material/styles';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
@@ -7,10 +7,9 @@ import { useXScale, useYScale } from '@mui/x-charts/hooks';
|
|
|
7
7
|
import { useNeutralChartPalette } from './palette';
|
|
8
8
|
import {
|
|
9
9
|
DEFAULT_LEGEND_POSITION,
|
|
10
|
+
assertRemovedChartProp,
|
|
10
11
|
defaultNumericTickFormatter,
|
|
11
|
-
|
|
12
|
-
sizeYAxisForContent,
|
|
13
|
-
warnOnHeightMismatch,
|
|
12
|
+
resolveChartLayout,
|
|
14
13
|
withAxisDefaults,
|
|
15
14
|
withChartSlotDefaults,
|
|
16
15
|
withGridDefaults,
|
|
@@ -153,10 +152,9 @@ export function ScatterReferenceLine({
|
|
|
153
152
|
* Reference geometry (a computed curve, a straight line, either with an optional label) is
|
|
154
153
|
* composed as `children` using `ScatterReferenceCurve`/`ScatterReferenceLine` above.
|
|
155
154
|
*
|
|
156
|
-
* `
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* if `minHeight` is also passed and larger.
|
|
155
|
+
* `size` (UCM-CHART-12): `"compact" | "standard" | "tall"`, resolved through the theme's
|
|
156
|
+
* spacing scale by `resolveChartLayout`. `height` (px) is the documented escape for a justified
|
|
157
|
+
* special case -- prefer `size`. `minHeight`/`aspect`/`margin` are gone (see docs/CHART-LAYOUT.md).
|
|
160
158
|
*
|
|
161
159
|
* Tested magnitude: a few hundred marks (hram's largest cloud is ~300 points). Not verified,
|
|
162
160
|
* and not implied to hold, at thousands+.
|
|
@@ -177,13 +175,15 @@ export function ScatterChart({
|
|
|
177
175
|
xAxis,
|
|
178
176
|
yAxis,
|
|
179
177
|
palette,
|
|
180
|
-
|
|
178
|
+
size = 'standard',
|
|
181
179
|
height,
|
|
180
|
+
minHeight,
|
|
182
181
|
aspect,
|
|
182
|
+
margin,
|
|
183
|
+
xLabels = 'auto',
|
|
183
184
|
grid,
|
|
184
185
|
hideLegend = series.length <= 1,
|
|
185
186
|
legendPosition = DEFAULT_LEGEND_POSITION,
|
|
186
|
-
margin,
|
|
187
187
|
sizeAccessor,
|
|
188
188
|
getPointStyle,
|
|
189
189
|
markerSize = DEFAULT_MARKER_RADIUS,
|
|
@@ -195,8 +195,9 @@ export function ScatterChart({
|
|
|
195
195
|
const { i18n } = useTranslation();
|
|
196
196
|
const neutralPalette = useNeutralChartPalette();
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
assertRemovedChartProp('ScatterChart', 'minHeight', minHeight, 'Use size="compact" | "standard" | "tall", or height for the documented escape.');
|
|
199
|
+
assertRemovedChartProp('ScatterChart', 'aspect', aspect, 'Removed with no replacement -- pick a size token; the chart no longer tracks width.');
|
|
200
|
+
assertRemovedChartProp('ScatterChart', 'margin', margin, 'Removed -- the layout model owns margins completely.');
|
|
200
201
|
|
|
201
202
|
const labelledXAxis = withAxisDefaults(
|
|
202
203
|
xAxis,
|
|
@@ -210,22 +211,29 @@ export function ScatterChart({
|
|
|
210
211
|
{ scaleType: 'linear', valueFormatter: defaultNumericTickFormatter(i18n.language) },
|
|
211
212
|
theme.typography.caption.fontSize,
|
|
212
213
|
);
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
// a scatter's data is `{x,y,z,id}` points, so `typeof value === 'number'` never matched and
|
|
220
|
-
// the fallback silently found zero candidates (reviewer finding, live-caught: it "looked fine"
|
|
221
|
-
// only because the untested case happened to fall back to MUI's flat default at a magnitude
|
|
222
|
-
// that didn't visibly overlap). Adapt AT THIS CALL SITE, not inside the shared helper --
|
|
223
|
-
// BarChart/LineChart's own contract must not change for this.
|
|
214
|
+
// `resolveChartLayout`'s own y-axis sizing (via `sizeYAxisForContent`) expects `series[i].data`
|
|
215
|
+
// to be a plain array of numbers -- true for BarChart/LineChart, but a scatter's data is
|
|
216
|
+
// `{x,y,z,id}` points. Adapt AT THIS CALL SITE, not inside the shared resolver -- BarChart/
|
|
217
|
+
// LineChart's own contract must not change for this (reviewer finding, live-caught under
|
|
218
|
+
// THEME-10: the untested case silently found zero candidates and fell back to MUI's flat
|
|
219
|
+
// default at a magnitude that didn't visibly overlap).
|
|
224
220
|
const yValueSeries = useMemo(
|
|
225
221
|
() => series.map((item) => ({ ...item, data: (item.data || []).map((point) => point?.y) })),
|
|
226
222
|
[series],
|
|
227
223
|
);
|
|
228
|
-
const
|
|
224
|
+
const layout = resolveChartLayout({
|
|
225
|
+
size,
|
|
226
|
+
height,
|
|
227
|
+
xAxis: labelledXAxis,
|
|
228
|
+
yAxis: labelledYAxis,
|
|
229
|
+
series: yValueSeries,
|
|
230
|
+
xLabels,
|
|
231
|
+
hideLegend,
|
|
232
|
+
legendPosition,
|
|
233
|
+
tickFontSize: theme.typography.caption.fontSize,
|
|
234
|
+
spacing: theme.spacing,
|
|
235
|
+
defaultLineHeight: theme.typography.caption.lineHeight,
|
|
236
|
+
});
|
|
229
237
|
|
|
230
238
|
const isNeutral = series.length <= 1 && !series[0]?.color;
|
|
231
239
|
const resolvedPalette = palette || (isNeutral ? [neutralPalette.neutral] : neutralPalette.categorical);
|
|
@@ -268,17 +276,17 @@ export function ScatterChart({
|
|
|
268
276
|
);
|
|
269
277
|
|
|
270
278
|
return (
|
|
271
|
-
<Box data-testid="scatter-chart-container" sx={
|
|
279
|
+
<Box data-testid="scatter-chart-container" sx={layout.sx}>
|
|
272
280
|
<MuiScatterChart
|
|
273
281
|
{...chartProps}
|
|
274
|
-
height={chartHeight}
|
|
282
|
+
height={layout.chartHeight}
|
|
275
283
|
series={orderedSeries.map((item) => ({ markerSize, ...item }))}
|
|
276
|
-
xAxis={
|
|
277
|
-
yAxis={
|
|
284
|
+
xAxis={layout.xAxis}
|
|
285
|
+
yAxis={layout.yAxis}
|
|
278
286
|
colors={resolvedPalette}
|
|
279
287
|
grid={withGridDefaults(grid)}
|
|
280
288
|
hideLegend={hideLegend}
|
|
281
|
-
margin={margin}
|
|
289
|
+
margin={layout.margin}
|
|
282
290
|
slots={{ marker: markerSlot }}
|
|
283
291
|
slotProps={withChartSlotDefaults(slotProps, legendPosition)}
|
|
284
292
|
>
|
|
@@ -30,6 +30,11 @@ import { useNeutralChartPalette } from './palette';
|
|
|
30
30
|
// zero-height chart in the first place (CHART-4). Do not "fix" this back
|
|
31
31
|
// to a responsive/aspect-ratio height without re-verifying the underlying
|
|
32
32
|
// MUI measurement bug is actually resolved.
|
|
33
|
+
//
|
|
34
|
+
// UCM-CHART-12: `CHART_HEIGHT` (320) is the number `"standard"` (BarChart's new `size` token) was
|
|
35
|
+
// deliberately pinned to, specifically so this preset's own default did not silently move under
|
|
36
|
+
// this migration -- still used here as ChartFrame's own `minHeight` floor (a card concern,
|
|
37
|
+
// unaffected by CHART-12), and the inner BarChart now takes `size="standard"` instead of `height`.
|
|
33
38
|
const CHART_HEIGHT = 320;
|
|
34
39
|
|
|
35
40
|
// Dynamic sx factories are outside the registry's documented top-level-object
|
|
@@ -283,7 +288,7 @@ export function TimeSeriesChart({
|
|
|
283
288
|
...(hasSecondaryAxis && series.axis === 'secondary' ? { yAxisId: 'secondary' } : {}),
|
|
284
289
|
}))}
|
|
285
290
|
palette={visiblePalette}
|
|
286
|
-
|
|
291
|
+
size="standard"
|
|
287
292
|
hideLegend={seriesConfig.length > 0}
|
|
288
293
|
skipAnimation={skipAnimation}
|
|
289
294
|
/>
|