@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.148.1 → 1.0.0-beta.150.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/components/charts/AreaPlot/AreaPlot.cjs +1 -1
- package/dist/components/charts/AreaPlot/AreaPlot.cjs.map +1 -1
- package/dist/components/charts/AreaPlot/AreaPlot.js +166 -148
- package/dist/components/charts/AreaPlot/AreaPlot.js.map +1 -1
- package/dist/components/charts/BarChart/BarChart.cjs +1 -1
- package/dist/components/charts/BarChart/BarChart.cjs.map +1 -1
- package/dist/components/charts/BarChart/BarChart.d.ts +7 -0
- package/dist/components/charts/BarChart/BarChart.js +151 -120
- package/dist/components/charts/BarChart/BarChart.js.map +1 -1
- package/dist/components/charts/BoxPlot/BoxPlot.cjs +1 -1
- package/dist/components/charts/BoxPlot/BoxPlot.cjs.map +1 -1
- package/dist/components/charts/BoxPlot/BoxPlot.d.ts +4 -0
- package/dist/components/charts/BoxPlot/BoxPlot.js +133 -102
- package/dist/components/charts/BoxPlot/BoxPlot.js.map +1 -1
- package/dist/components/charts/ChartTooltip/lines.cjs.map +1 -1
- package/dist/components/charts/ChartTooltip/lines.d.ts +2 -0
- package/dist/components/charts/ChartTooltip/lines.js.map +1 -1
- package/dist/components/charts/Chromatogram/Chromatogram.cjs +1 -1
- package/dist/components/charts/Chromatogram/Chromatogram.cjs.map +1 -1
- package/dist/components/charts/Chromatogram/Chromatogram.js +111 -114
- package/dist/components/charts/Chromatogram/Chromatogram.js.map +1 -1
- package/dist/components/charts/Chromatogram/plotBuilder.cjs +4 -1
- package/dist/components/charts/Chromatogram/plotBuilder.cjs.map +1 -1
- package/dist/components/charts/Chromatogram/plotBuilder.d.ts +32 -2
- package/dist/components/charts/Chromatogram/plotBuilder.js +170 -127
- package/dist/components/charts/Chromatogram/plotBuilder.js.map +1 -1
- package/dist/components/charts/Chromatogram/regionOverlays.cjs +1 -1
- package/dist/components/charts/Chromatogram/regionOverlays.cjs.map +1 -1
- package/dist/components/charts/Chromatogram/regionOverlays.js +11 -11
- package/dist/components/charts/Chromatogram/regionOverlays.js.map +1 -1
- package/dist/components/charts/Chromatogram/types.d.ts +4 -2
- package/dist/components/charts/Electropherogram/Electropherogram.cjs +1 -1
- package/dist/components/charts/Electropherogram/Electropherogram.cjs.map +1 -1
- package/dist/components/charts/Electropherogram/Electropherogram.js +89 -88
- package/dist/components/charts/Electropherogram/Electropherogram.js.map +1 -1
- package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.cjs +1 -1
- package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.cjs.map +1 -1
- package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.js +4 -5
- package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.js.map +1 -1
- package/dist/components/charts/chart-scale.cjs +2 -0
- package/dist/components/charts/chart-scale.cjs.map +1 -0
- package/dist/components/charts/chart-scale.d.ts +61 -0
- package/dist/components/charts/chart-scale.js +61 -0
- package/dist/components/charts/chart-scale.js.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.scoped.css +1 -1
- package/dist/index.tailwind.css +1 -1
- package/package.json +1 -1
|
@@ -1,59 +1,72 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useRef as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
1
|
+
import { jsxs as $, jsx as tt } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as k, useMemo as u, useEffect as j } from "react";
|
|
3
|
+
import { resolveChartScale as et, thinTicks as ot, maxTickCount as rt, Y_TICK_LABEL_SPACING as nt, REGULAR_SCALE as D } from "../chart-scale.js";
|
|
4
|
+
import { useChartTooltip as it } from "../ChartTooltip/ChartTooltip.js";
|
|
5
|
+
import { loadPlotly as lt, getLoadedPlotly as W } from "../plotly-loader.js";
|
|
6
|
+
import { useElementSize as ct } from "../../../hooks/use-element-size.js";
|
|
7
|
+
import { usePlotlyTheme as at, CHART_FONT_FAMILY as p } from "../../../hooks/use-plotly-theme.js";
|
|
8
|
+
import { cn as st } from "../../../lib/utils.js";
|
|
9
|
+
import { seriesColor as ht } from "../../../utils/colors.js";
|
|
10
|
+
const ft = {
|
|
11
|
+
...D,
|
|
12
|
+
axisTitleStandoff: 30,
|
|
13
|
+
legendFontSize: 16,
|
|
14
|
+
legendLineHeight: 21,
|
|
15
|
+
margin: { ...D.margin, r: 30, tTitle: 60, tNoTitle: 30 }
|
|
16
|
+
}, ut = 0.24, gt = 0.8, At = ({
|
|
10
17
|
dataSeries: a,
|
|
11
|
-
width:
|
|
12
|
-
height:
|
|
13
|
-
xRange:
|
|
14
|
-
yRange:
|
|
15
|
-
variant:
|
|
16
|
-
xTitle:
|
|
17
|
-
yTitle:
|
|
18
|
-
title:
|
|
19
|
-
barWidth:
|
|
20
|
-
xTickText:
|
|
18
|
+
width: B,
|
|
19
|
+
height: P,
|
|
20
|
+
xRange: T,
|
|
21
|
+
yRange: y,
|
|
22
|
+
variant: m = "group",
|
|
23
|
+
xTitle: z,
|
|
24
|
+
yTitle: L,
|
|
25
|
+
title: w,
|
|
26
|
+
barWidth: b,
|
|
27
|
+
xTickText: _
|
|
21
28
|
}) => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
let
|
|
26
|
-
a.forEach((
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}),
|
|
30
|
-
|
|
29
|
+
const g = k(null), n = at(), { bindTooltip: U, tooltipElement: K } = it({ xLabel: z, yLabel: L }), [q, H] = ct(), s = B ?? H.width, l = P ?? H.height, V = s > 0 && l > 0, J = B === void 0, Q = P === void 0, o = et(s, l, ft), A = w ? o.margin.tTitle : o.margin.tNoTitle, C = k({ width: s, height: l });
|
|
30
|
+
C.current = { width: s, height: l };
|
|
31
|
+
const v = k(!1), E = k({ width: 0, height: 0 }), { yMin: X, yMax: G } = u(() => {
|
|
32
|
+
let t = Number.MAX_VALUE, e = Number.MIN_VALUE, c = Number.MAX_VALUE, i = Number.MIN_VALUE;
|
|
33
|
+
a.forEach((f) => {
|
|
34
|
+
f.x.forEach((M) => {
|
|
35
|
+
t = Math.min(t, M), e = Math.max(e, M);
|
|
36
|
+
}), f.y.forEach((M) => {
|
|
37
|
+
c = Math.min(c, M), i = Math.max(i, M);
|
|
31
38
|
});
|
|
32
39
|
});
|
|
33
|
-
const h = (
|
|
40
|
+
const h = (e - t) * 0.1, r = (i - c) * 0.1;
|
|
34
41
|
return {
|
|
35
|
-
xMin:
|
|
36
|
-
xMax:
|
|
37
|
-
yMin:
|
|
38
|
-
yMax:
|
|
42
|
+
xMin: t - h,
|
|
43
|
+
xMax: e + h,
|
|
44
|
+
yMin: m === "stack" ? 0 : c - r,
|
|
45
|
+
yMax: i + r
|
|
39
46
|
};
|
|
40
|
-
}, [a,
|
|
41
|
-
() =>
|
|
42
|
-
[
|
|
43
|
-
),
|
|
44
|
-
() => [...new Set(a.flatMap((
|
|
47
|
+
}, [a, m]), x = u(
|
|
48
|
+
() => y || [X, G],
|
|
49
|
+
[y, X, G]
|
|
50
|
+
), d = u(
|
|
51
|
+
() => [...new Set(a.flatMap((t) => t.x))].sort((t, e) => t - e),
|
|
45
52
|
[a]
|
|
46
|
-
),
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
), O = !!_ && _.length === d.length, N = u(() => {
|
|
54
|
+
if (b !== void 0) return b;
|
|
55
|
+
const t = d.slice(1).map((r, f) => r - d[f]).sort((r, f) => r - f);
|
|
56
|
+
if (t.length === 0) return;
|
|
57
|
+
const e = Math.floor(t.length / 2), c = t.length % 2 === 1 ? t[e] : (t[e - 1] + t[e]) / 2, i = m === "group" ? Math.max(1, a.length) : 1, h = Math.min(ut, gt / i);
|
|
58
|
+
return c * h;
|
|
59
|
+
}, [b, d, m, a.length]), S = u(() => {
|
|
60
|
+
const t = x[1] - x[0];
|
|
61
|
+
let e = Math.pow(10, Math.floor(Math.log10(t)));
|
|
62
|
+
t / e > 10 && (e = e * 2), t / e < 4 && (e = e / 2);
|
|
63
|
+
const c = [];
|
|
64
|
+
let i = Math.ceil(x[0] / e) * e;
|
|
65
|
+
for (; i <= x[1]; )
|
|
66
|
+
c.push(i), i += e;
|
|
67
|
+
return c;
|
|
68
|
+
}, [x]), Y = u(() => {
|
|
69
|
+
switch (m) {
|
|
57
70
|
case "stack":
|
|
58
71
|
return "stack";
|
|
59
72
|
case "overlay":
|
|
@@ -61,110 +74,121 @@ const tt = 60, ot = 30, ht = ({
|
|
|
61
74
|
default:
|
|
62
75
|
return "group";
|
|
63
76
|
}
|
|
64
|
-
}, [
|
|
77
|
+
}, [m]), F = u(
|
|
78
|
+
() => ot(
|
|
79
|
+
S,
|
|
80
|
+
rt(
|
|
81
|
+
l - A - o.margin.b,
|
|
82
|
+
o.tickFontSize * nt
|
|
83
|
+
)
|
|
84
|
+
),
|
|
85
|
+
[S, l, A, o]
|
|
86
|
+
), Z = F.join(","), R = k(F);
|
|
87
|
+
R.current = F;
|
|
88
|
+
const I = u(
|
|
65
89
|
() => ({
|
|
66
|
-
tickcolor:
|
|
67
|
-
ticklen:
|
|
90
|
+
tickcolor: n.tickColor,
|
|
91
|
+
ticklen: o.ticklen,
|
|
68
92
|
tickwidth: 1,
|
|
69
93
|
ticks: "outside",
|
|
70
94
|
tickfont: {
|
|
71
|
-
size:
|
|
72
|
-
color:
|
|
73
|
-
family:
|
|
95
|
+
size: o.tickFontSize,
|
|
96
|
+
color: n.textColor,
|
|
97
|
+
family: p,
|
|
74
98
|
weight: 400
|
|
75
99
|
},
|
|
76
|
-
linecolor:
|
|
100
|
+
linecolor: n.lineColor,
|
|
77
101
|
linewidth: 1,
|
|
78
102
|
position: 0,
|
|
79
103
|
zeroline: !1
|
|
80
104
|
}),
|
|
81
|
-
[
|
|
105
|
+
[n, o]
|
|
82
106
|
);
|
|
83
|
-
return
|
|
84
|
-
if (!
|
|
85
|
-
const
|
|
86
|
-
x:
|
|
87
|
-
y:
|
|
107
|
+
return j(() => {
|
|
108
|
+
if (!g.current || !V) return;
|
|
109
|
+
const t = a.map((r, f) => ({
|
|
110
|
+
x: r.x,
|
|
111
|
+
y: r.y,
|
|
88
112
|
type: "bar",
|
|
89
|
-
name:
|
|
113
|
+
name: r.name,
|
|
90
114
|
hoverinfo: "none",
|
|
91
115
|
marker: {
|
|
92
|
-
color:
|
|
116
|
+
color: ht(f, r.color)
|
|
93
117
|
},
|
|
94
|
-
width:
|
|
95
|
-
error_y:
|
|
96
|
-
})),
|
|
97
|
-
...
|
|
118
|
+
...N === void 0 ? {} : { width: N },
|
|
119
|
+
error_y: r.error_y
|
|
120
|
+
})), e = {
|
|
121
|
+
...w ? {
|
|
98
122
|
title: {
|
|
99
|
-
text:
|
|
123
|
+
text: w,
|
|
100
124
|
font: {
|
|
101
|
-
size:
|
|
102
|
-
family:
|
|
103
|
-
color:
|
|
125
|
+
size: o.titleFontSize,
|
|
126
|
+
family: p,
|
|
127
|
+
color: n.textColor
|
|
104
128
|
}
|
|
105
129
|
}
|
|
106
130
|
} : {},
|
|
107
|
-
width:
|
|
108
|
-
height:
|
|
131
|
+
width: C.current.width,
|
|
132
|
+
height: C.current.height,
|
|
109
133
|
margin: {
|
|
110
|
-
l:
|
|
111
|
-
r:
|
|
134
|
+
l: o.margin.l,
|
|
135
|
+
r: o.margin.r,
|
|
112
136
|
// Reserve room for tick labels, the x-axis title, and the
|
|
113
137
|
// container-anchored bottom legend stacked beneath them.
|
|
114
|
-
b:
|
|
115
|
-
t:
|
|
138
|
+
b: o.margin.b,
|
|
139
|
+
t: A,
|
|
116
140
|
pad: 0
|
|
117
141
|
},
|
|
118
|
-
paper_bgcolor:
|
|
119
|
-
plot_bgcolor:
|
|
142
|
+
paper_bgcolor: n.paperBg,
|
|
143
|
+
plot_bgcolor: n.plotBg,
|
|
120
144
|
font: {
|
|
121
|
-
family:
|
|
145
|
+
family: p
|
|
122
146
|
},
|
|
123
147
|
barmode: Y,
|
|
124
148
|
bargap: 0.15,
|
|
125
149
|
dragmode: !1,
|
|
126
150
|
xaxis: {
|
|
127
151
|
title: {
|
|
128
|
-
text:
|
|
152
|
+
text: z,
|
|
129
153
|
font: {
|
|
130
|
-
size:
|
|
131
|
-
color:
|
|
132
|
-
family:
|
|
154
|
+
size: o.axisTitleFontSize,
|
|
155
|
+
color: n.textSecondary,
|
|
156
|
+
family: p,
|
|
133
157
|
weight: 400
|
|
134
158
|
},
|
|
135
|
-
standoff:
|
|
159
|
+
standoff: o.axisTitleStandoff
|
|
136
160
|
},
|
|
137
|
-
gridcolor:
|
|
138
|
-
range:
|
|
139
|
-
autorange: !
|
|
161
|
+
gridcolor: n.gridColor,
|
|
162
|
+
range: T,
|
|
163
|
+
autorange: !T,
|
|
140
164
|
tickmode: "array",
|
|
141
|
-
tickvals:
|
|
142
|
-
...
|
|
165
|
+
tickvals: d,
|
|
166
|
+
...O ? { ticktext: _ } : {},
|
|
143
167
|
showgrid: !0,
|
|
144
168
|
// Reserve space for tick labels + the axis title so the bottom legend
|
|
145
169
|
// can't overlap them at small sizes (SW-2157).
|
|
146
170
|
automargin: !0,
|
|
147
|
-
...
|
|
171
|
+
...I
|
|
148
172
|
},
|
|
149
173
|
yaxis: {
|
|
150
174
|
title: {
|
|
151
|
-
text:
|
|
175
|
+
text: L,
|
|
152
176
|
font: {
|
|
153
|
-
size:
|
|
154
|
-
color:
|
|
155
|
-
family:
|
|
177
|
+
size: o.axisTitleFontSize,
|
|
178
|
+
color: n.textSecondary,
|
|
179
|
+
family: p,
|
|
156
180
|
weight: 400
|
|
157
181
|
},
|
|
158
|
-
standoff:
|
|
182
|
+
standoff: o.axisTitleStandoff
|
|
159
183
|
},
|
|
160
|
-
gridcolor:
|
|
161
|
-
range:
|
|
162
|
-
autorange: !
|
|
184
|
+
gridcolor: n.gridColor,
|
|
185
|
+
range: y,
|
|
186
|
+
autorange: !y,
|
|
163
187
|
tickmode: "array",
|
|
164
|
-
tickvals:
|
|
188
|
+
tickvals: R.current,
|
|
165
189
|
showgrid: !0,
|
|
166
190
|
automargin: !0,
|
|
167
|
-
...
|
|
191
|
+
...I
|
|
168
192
|
},
|
|
169
193
|
legend: {
|
|
170
194
|
// Anchor to the bottom of the container (not the plot area) so the
|
|
@@ -177,43 +201,50 @@ const tt = 60, ot = 30, ht = ({
|
|
|
177
201
|
yref: "container",
|
|
178
202
|
orientation: "h",
|
|
179
203
|
font: {
|
|
180
|
-
size:
|
|
181
|
-
color:
|
|
182
|
-
family:
|
|
183
|
-
weight: 500
|
|
204
|
+
size: o.legendFontSize,
|
|
205
|
+
color: n.legendColor,
|
|
206
|
+
family: p,
|
|
207
|
+
weight: 500,
|
|
208
|
+
lineheight: o.legendLineHeight
|
|
184
209
|
}
|
|
185
210
|
},
|
|
186
211
|
showlegend: a.length > 1
|
|
187
|
-
},
|
|
212
|
+
}, c = {
|
|
188
213
|
// Sizing is driven from the measured container; disable Plotly's own
|
|
189
214
|
// window-resize responsiveness (it can't see container resizes).
|
|
190
215
|
responsive: !1,
|
|
191
216
|
displayModeBar: !1,
|
|
192
217
|
displaylogo: !1
|
|
193
218
|
};
|
|
194
|
-
let
|
|
195
|
-
return
|
|
196
|
-
|
|
219
|
+
let i = !1, h = null;
|
|
220
|
+
return lt().then((r) => {
|
|
221
|
+
i || !g.current || (r.newPlot(g.current, t, e, c), U(g.current), h = g.current, v.current = !0, E.current = { ...C.current });
|
|
197
222
|
}), () => {
|
|
198
|
-
|
|
223
|
+
i = !0, h && (W().purge(h), v.current = !1);
|
|
199
224
|
};
|
|
200
|
-
}, [a,
|
|
201
|
-
const
|
|
202
|
-
!
|
|
225
|
+
}, [a, V, T, y, z, L, w, N, Y, I, d, S, O, _, n, o, A, U]), j(() => {
|
|
226
|
+
const t = g.current;
|
|
227
|
+
!t || !v.current || s <= 0 || l <= 0 || E.current.width === s && E.current.height === l || (E.current = { width: s, height: l }, W().relayout(t, {
|
|
228
|
+
width: s,
|
|
229
|
+
height: l,
|
|
230
|
+
// Re-thin the y ticks for the new plot height (SW-2298). The flattened
|
|
231
|
+
// key is valid relayout input but not part of the Layout type.
|
|
232
|
+
"yaxis.tickvals": R.current
|
|
233
|
+
}).catch(() => {
|
|
203
234
|
}));
|
|
204
|
-
}, [l,
|
|
235
|
+
}, [s, l, Z]), /* @__PURE__ */ $(
|
|
205
236
|
"div",
|
|
206
237
|
{
|
|
207
|
-
ref:
|
|
208
|
-
className:
|
|
238
|
+
ref: q,
|
|
239
|
+
className: st("bar-graph-container relative", J && "w-full", Q && "h-full"),
|
|
209
240
|
children: [
|
|
210
|
-
/* @__PURE__ */
|
|
211
|
-
|
|
241
|
+
/* @__PURE__ */ tt("div", { ref: g, style: { width: "100%", height: "100%" } }),
|
|
242
|
+
K
|
|
212
243
|
]
|
|
213
244
|
}
|
|
214
245
|
);
|
|
215
246
|
};
|
|
216
247
|
export {
|
|
217
|
-
|
|
248
|
+
At as BarChart
|
|
218
249
|
};
|
|
219
250
|
//# sourceMappingURL=BarChart.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BarChart.js","sources":["../../../../src/components/charts/BarChart/BarChart.tsx"],"sourcesContent":["import React, { useEffect, useRef, useMemo } from \"react\";\n\nimport { useChartTooltip } from \"../ChartTooltip\";\nimport { getLoadedPlotly, loadPlotly } from \"../plotly-loader\";\n\nimport { useElementSize } from \"@/hooks/use-element-size\";\nimport { CHART_FONT_FAMILY, usePlotlyTheme } from \"@/hooks/use-plotly-theme\";\nimport { cn } from \"@/lib/utils\";\nimport { seriesColor } from \"@/utils/colors\";\n\ninterface BarDataSeries {\n x: number[];\n y: number[];\n name: string;\n /** Optional color override (auto-assigned from CHART_COLORS if not provided) */\n color?: string;\n error_y?: {\n type: \"data\";\n array: number[];\n visible: boolean;\n };\n}\n\ntype BarChartVariant = \"group\" | \"stack\" | \"overlay\";\n\n/** Top margin reserving room for the 32px title; reduced when no title is set */\nconst TITLE_MARGIN_TOP = 60;\nconst NO_TITLE_MARGIN_TOP = 30;\n\ninterface BarChartProps {\n dataSeries: BarDataSeries[];\n /**\n * Fixed width in pixels. When omitted, the chart fills its container and\n * tracks the container's width via a `ResizeObserver`.\n */\n width?: number;\n /**\n * Fixed height in pixels. When omitted, the chart fills its container and\n * tracks the container's height via a `ResizeObserver`.\n */\n height?: number;\n xRange?: [number, number];\n yRange?: [number, number];\n variant?: BarChartVariant;\n xTitle?: string;\n yTitle?: string;\n title?: string;\n barWidth?: number;\n /**\n * Categorical labels for the x-axis ticks. When provided, the x data values\n * still drive bar positioning but the displayed tick labels match these\n * strings in order (e.g. [\"Mon\", \"Tue\", …]). Should align 1:1 with the\n * unique, ordered x values across all series.\n */\n xTickText?: string[];\n}\n\nconst BarChart: React.FC<BarChartProps> = ({\n dataSeries,\n width,\n height,\n xRange,\n yRange,\n variant = \"group\",\n xTitle,\n yTitle,\n title,\n barWidth = 24,\n xTickText,\n}) => {\n const plotRef = useRef<HTMLDivElement>(null);\n const theme = usePlotlyTheme();\n const { bindTooltip, tooltipElement } = useChartTooltip({ xLabel: xTitle, yLabel: yTitle });\n\n // Omitted width/height → fill the container and track its measured size;\n // explicit pixel values override. See AreaPlot for the reference pattern.\n const [containerRef, measured] = useElementSize<HTMLDivElement>();\n const resolvedWidth = width ?? measured.width;\n const resolvedHeight = height ?? measured.height;\n const hasSize = resolvedWidth > 0 && resolvedHeight > 0;\n // Fill is per-dimension: omit width to fill the container width, omit height\n // to fill its height (so e.g. a fixed width with a container-driven height works).\n const fillWidth = width === undefined;\n const fillHeight = height === undefined;\n const sizeRef = useRef({ width: resolvedWidth, height: resolvedHeight });\n sizeRef.current = { width: resolvedWidth, height: resolvedHeight };\n const plotInitedRef = useRef(false);\n // Size last applied to the plot, so the resize effect can skip a redundant\n // relayout right after newPlot already drew at that size.\n const appliedSizeRef = useRef({ width: 0, height: 0 });\n\n const { yMin, yMax } = useMemo(() => {\n let minX = Number.MAX_VALUE;\n let maxX = Number.MIN_VALUE;\n let minY = Number.MAX_VALUE;\n let maxY = Number.MIN_VALUE;\n\n dataSeries.forEach((series) => {\n series.x.forEach((x) => {\n minX = Math.min(minX, x);\n maxX = Math.max(maxX, x);\n });\n series.y.forEach((y) => {\n minY = Math.min(minY, y);\n maxY = Math.max(maxY, y);\n });\n });\n\n const xPadding = (maxX - minX) * 0.1;\n const yPadding = (maxY - minY) * 0.1;\n\n return {\n xMin: minX - xPadding,\n xMax: maxX + xPadding,\n yMin: variant === \"stack\" ? 0 : minY - yPadding,\n yMax: maxY + yPadding,\n };\n }, [dataSeries, variant]);\n\n const effectiveYRange = useMemo(\n () => yRange || [yMin, yMax],\n [yRange, yMin, yMax],\n );\n\n const xTicks = useMemo(\n () => [...new Set(dataSeries.flatMap((s) => s.x))].sort((a, b) => a - b),\n [dataSeries],\n );\n\n // Only apply categorical labels when they align 1:1 with the tick positions;\n // a mismatch would silently mis-label ticks, so fall back to numeric ticks.\n const useCategoricalX = !!xTickText && xTickText.length === xTicks.length;\n\n const yTicks = useMemo(() => {\n const range = effectiveYRange[1] - effectiveYRange[0];\n let step = Math.pow(10, Math.floor(Math.log10(range)));\n\n if (range / step > 10) step = step * 2;\n if (range / step < 4) step = step / 2;\n\n const ticks = [];\n let current = Math.ceil(effectiveYRange[0] / step) * step;\n while (current <= effectiveYRange[1]) {\n ticks.push(current);\n current += step;\n }\n return ticks;\n }, [effectiveYRange]);\n\n const barMode = useMemo((): \"group\" | \"stack\" | \"overlay\" => {\n switch (variant) {\n case \"stack\":\n return \"stack\";\n case \"overlay\":\n return \"overlay\";\n case \"group\":\n default:\n return \"group\";\n }\n }, [variant]);\n\n const tickOptions = useMemo(\n () => ({\n tickcolor: theme.tickColor,\n ticklen: 12,\n tickwidth: 1,\n ticks: \"outside\" as const,\n tickfont: {\n size: 16,\n color: theme.textColor,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n linecolor: theme.lineColor,\n linewidth: 1,\n position: 0,\n zeroline: false,\n }),\n [theme],\n );\n\n useEffect(() => {\n if (!plotRef.current || !hasSize) return;\n\n const data = dataSeries.map((series, index) => ({\n x: series.x,\n y: series.y,\n type: \"bar\" as const,\n name: series.name,\n hoverinfo: \"none\" as const,\n marker: {\n color: seriesColor(index, series.color),\n },\n width: barWidth,\n error_y: series.error_y,\n }));\n\n const layout = {\n ...(title\n ? {\n title: {\n text: title,\n font: {\n size: 32,\n family: CHART_FONT_FAMILY,\n color: theme.textColor,\n },\n },\n }\n : {}),\n width: sizeRef.current.width,\n height: sizeRef.current.height,\n margin: {\n l: 80,\n r: 30,\n // Reserve room for tick labels, the x-axis title, and the\n // container-anchored bottom legend stacked beneath them.\n b: 96,\n t: title ? TITLE_MARGIN_TOP : NO_TITLE_MARGIN_TOP,\n pad: 0,\n },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: {\n family: CHART_FONT_FAMILY,\n },\n barmode: barMode,\n bargap: 0.15,\n dragmode: false as const,\n xaxis: {\n title: {\n text: xTitle,\n font: {\n size: 16,\n color: theme.textSecondary,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n standoff: 32,\n },\n gridcolor: theme.gridColor,\n range: xRange,\n autorange: !xRange,\n tickmode: \"array\" as const,\n tickvals: xTicks,\n ...(useCategoricalX ? { ticktext: xTickText } : {}),\n showgrid: true,\n // Reserve space for tick labels + the axis title so the bottom legend\n // can't overlap them at small sizes (SW-2157).\n automargin: true,\n ...tickOptions,\n },\n yaxis: {\n title: {\n text: yTitle,\n font: {\n size: 16,\n color: theme.textSecondary,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n standoff: 30,\n },\n gridcolor: theme.gridColor,\n range: yRange,\n autorange: !yRange,\n tickmode: \"array\" as const,\n tickvals: yTicks,\n showgrid: true,\n automargin: true,\n ...tickOptions,\n },\n legend: {\n // Anchor to the bottom of the container (not the plot area) so the\n // legend always clears the x-axis tick labels + title — a paper-relative\n // fractional offset collapses into the ticks at small heights (SW-2157).\n x: 0.5,\n y: 0,\n xanchor: \"center\" as const,\n yanchor: \"bottom\" as const,\n yref: \"container\" as const,\n orientation: \"h\" as const,\n font: {\n size: 16,\n color: theme.legendColor,\n family: CHART_FONT_FAMILY,\n weight: 500,\n },\n },\n showlegend: dataSeries.length > 1,\n };\n\n const config = {\n // Sizing is driven from the measured container; disable Plotly's own\n // window-resize responsiveness (it can't see container resizes).\n responsive: false,\n displayModeBar: false,\n displaylogo: false,\n };\n\n // Plotly is loaded lazily so it stays out of the consumer's main chunk\n // (SW-2007); the draw is skipped if the effect re-runs or unmounts first.\n let cancelled = false;\n let plotElement: HTMLDivElement | null = null;\n void loadPlotly().then((plotly) => {\n if (cancelled || !plotRef.current) return;\n plotly.newPlot(plotRef.current, data, layout, config);\n bindTooltip(plotRef.current);\n\n // Capture ref value for cleanup\n plotElement = plotRef.current;\n plotInitedRef.current = true;\n appliedSizeRef.current = { ...sizeRef.current };\n });\n\n return () => {\n cancelled = true;\n if (plotElement) {\n getLoadedPlotly().purge(plotElement);\n plotInitedRef.current = false;\n }\n };\n }, [dataSeries, hasSize, xRange, yRange, xTitle, yTitle, title, barWidth, barMode, tickOptions, xTicks, yTicks, useCategoricalX, xTickText, theme, bindTooltip]);\n\n // Resize in place when the measured/overridden size changes — cheaper than\n // recreating the plot, and it preserves tooltip/event bindings.\n useEffect(() => {\n const plotElement = plotRef.current;\n if (!plotElement || !plotInitedRef.current || resolvedWidth <= 0 || resolvedHeight <= 0) {\n return;\n }\n // newPlot already drew at the current size; skip the redundant relayout\n // (it would queue an automargin redraw that can reject if we unmount first).\n if (\n appliedSizeRef.current.width === resolvedWidth &&\n appliedSizeRef.current.height === resolvedHeight\n ) {\n return;\n }\n appliedSizeRef.current = { width: resolvedWidth, height: resolvedHeight };\n // Swallow rejections from a relayout that races an unmount/purge.\n // plotInitedRef guarantees Plotly finished loading, so sync access is safe.\n void getLoadedPlotly()\n .relayout(plotElement, { width: resolvedWidth, height: resolvedHeight })\n .catch(() => {});\n }, [resolvedWidth, resolvedHeight]);\n\n return (\n <div\n ref={containerRef}\n className={cn(\"bar-graph-container relative\", fillWidth && \"w-full\", fillHeight && \"h-full\")}\n >\n <div ref={plotRef} style={{ width: \"100%\", height: \"100%\" }} />\n {tooltipElement}\n </div>\n );\n};\n\nexport { BarChart };\nexport type { BarDataSeries, BarChartVariant, BarChartProps };\n"],"names":["TITLE_MARGIN_TOP","NO_TITLE_MARGIN_TOP","BarChart","dataSeries","width","height","xRange","yRange","variant","xTitle","yTitle","title","barWidth","xTickText","plotRef","useRef","theme","usePlotlyTheme","bindTooltip","tooltipElement","useChartTooltip","containerRef","measured","useElementSize","resolvedWidth","resolvedHeight","hasSize","fillWidth","fillHeight","sizeRef","plotInitedRef","appliedSizeRef","yMin","yMax","useMemo","minX","maxX","minY","maxY","series","x","y","xPadding","yPadding","effectiveYRange","xTicks","s","a","b","useCategoricalX","yTicks","range","step","ticks","current","barMode","tickOptions","CHART_FONT_FAMILY","useEffect","data","index","seriesColor","layout","config","cancelled","plotElement","loadPlotly","plotly","getLoadedPlotly","jsxs","cn","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BarChart.js","sources":["../../../../src/components/charts/BarChart/BarChart.tsx"],"sourcesContent":["import React, { useEffect, useRef, useMemo } from \"react\";\n\nimport {\n REGULAR_SCALE,\n Y_TICK_LABEL_SPACING,\n maxTickCount,\n resolveChartScale,\n thinTicks,\n} from \"../chart-scale\";\nimport { useChartTooltip } from \"../ChartTooltip\";\nimport { getLoadedPlotly, loadPlotly } from \"../plotly-loader\";\n\nimport type Plotly from \"plotly.js-dist\";\n\nimport { useElementSize } from \"@/hooks/use-element-size\";\nimport { CHART_FONT_FAMILY, usePlotlyTheme } from \"@/hooks/use-plotly-theme\";\nimport { cn } from \"@/lib/utils\";\nimport { seriesColor } from \"@/utils/colors\";\n\ninterface BarDataSeries {\n x: number[];\n y: number[];\n name: string;\n /** Optional color override (auto-assigned from CHART_COLORS if not provided) */\n color?: string;\n error_y?: {\n type: \"data\";\n array: number[];\n visible: boolean;\n };\n}\n\ntype BarChartVariant = \"group\" | \"stack\" | \"overlay\";\n\n/** Full-size chrome: the bar chart runs a larger legend and tighter top margin */\nconst BAR_REGULAR_SCALE = {\n ...REGULAR_SCALE,\n axisTitleStandoff: 30,\n legendFontSize: 16,\n legendLineHeight: 21,\n margin: { ...REGULAR_SCALE.margin, r: 30, tTitle: 60, tNoTitle: 30 },\n};\n\n/**\n * Default bar width as a fraction of the median gap between x positions. A\n * fixed data-unit width (formerly 24) only suited x spaced by ~100 and swamped\n * the axis for index-style x such as 0..6 (SW-2298). The median (not the\n * minimum) keeps one tight pair in otherwise wide-spaced data from shrinking\n * every bar to a hairline.\n */\nconst DEFAULT_BAR_WIDTH_FRACTION = 0.24;\n/**\n * Grouped bars share one slot per x position; cap the group at this fraction\n * of the gap so four or more series no longer overflow into the neighbour.\n */\nconst MAX_GROUP_WIDTH_FRACTION = 0.8;\n\ninterface BarChartProps {\n dataSeries: BarDataSeries[];\n /**\n * Fixed width in pixels. When omitted, the chart fills its container and\n * tracks the container's width via a `ResizeObserver`.\n */\n width?: number;\n /**\n * Fixed height in pixels. When omitted, the chart fills its container and\n * tracks the container's height via a `ResizeObserver`.\n */\n height?: number;\n xRange?: [number, number];\n yRange?: [number, number];\n variant?: BarChartVariant;\n xTitle?: string;\n yTitle?: string;\n title?: string;\n /**\n * Bar width in x-axis data units. Defaults to about a quarter of the median\n * gap between x positions (narrower when four or more grouped series must\n * share a slot), so bars keep the same visual weight whether x runs 0..6 or\n * 200..1000. With very uneven spacing the closest bars can still overlap or\n * the widest gaps look sparse — pass an explicit width in that case.\n */\n barWidth?: number;\n /**\n * Categorical labels for the x-axis ticks. When provided, the x data values\n * still drive bar positioning but the displayed tick labels match these\n * strings in order (e.g. [\"Mon\", \"Tue\", …]). Should align 1:1 with the\n * unique, ordered x values across all series.\n */\n xTickText?: string[];\n}\n\nconst BarChart: React.FC<BarChartProps> = ({\n dataSeries,\n width,\n height,\n xRange,\n yRange,\n variant = \"group\",\n xTitle,\n yTitle,\n title,\n barWidth,\n xTickText,\n}) => {\n const plotRef = useRef<HTMLDivElement>(null);\n const theme = usePlotlyTheme();\n const { bindTooltip, tooltipElement } = useChartTooltip({ xLabel: xTitle, yLabel: yTitle });\n\n // Omitted width/height → fill the container and track its measured size;\n // explicit pixel values override. See AreaPlot for the reference pattern.\n const [containerRef, measured] = useElementSize<HTMLDivElement>();\n const resolvedWidth = width ?? measured.width;\n const resolvedHeight = height ?? measured.height;\n const hasSize = resolvedWidth > 0 && resolvedHeight > 0;\n // Fill is per-dimension: omit width to fill the container width, omit height\n // to fill its height (so e.g. a fixed width with a container-driven height works).\n const fillWidth = width === undefined;\n const fillHeight = height === undefined;\n // Fonts, tick length and margins step down on small canvases so the plot\n // area (not the chrome) gets the pixels (SW-2298).\n const scale = resolveChartScale(resolvedWidth, resolvedHeight, BAR_REGULAR_SCALE);\n const marginTop = title ? scale.margin.tTitle : scale.margin.tNoTitle;\n const sizeRef = useRef({ width: resolvedWidth, height: resolvedHeight });\n sizeRef.current = { width: resolvedWidth, height: resolvedHeight };\n const plotInitedRef = useRef(false);\n // Size last applied to the plot, so the resize effect can skip a redundant\n // relayout right after newPlot already drew at that size.\n const appliedSizeRef = useRef({ width: 0, height: 0 });\n\n const { yMin, yMax } = useMemo(() => {\n let minX = Number.MAX_VALUE;\n let maxX = Number.MIN_VALUE;\n let minY = Number.MAX_VALUE;\n let maxY = Number.MIN_VALUE;\n\n dataSeries.forEach((series) => {\n series.x.forEach((x) => {\n minX = Math.min(minX, x);\n maxX = Math.max(maxX, x);\n });\n series.y.forEach((y) => {\n minY = Math.min(minY, y);\n maxY = Math.max(maxY, y);\n });\n });\n\n const xPadding = (maxX - minX) * 0.1;\n const yPadding = (maxY - minY) * 0.1;\n\n return {\n xMin: minX - xPadding,\n xMax: maxX + xPadding,\n yMin: variant === \"stack\" ? 0 : minY - yPadding,\n yMax: maxY + yPadding,\n };\n }, [dataSeries, variant]);\n\n const effectiveYRange = useMemo(\n () => yRange || [yMin, yMax],\n [yRange, yMin, yMax],\n );\n\n const xTicks = useMemo(\n () => [...new Set(dataSeries.flatMap((s) => s.x))].sort((a, b) => a - b),\n [dataSeries],\n );\n\n // Only apply categorical labels when they align 1:1 with the tick positions;\n // a mismatch would silently mis-label ticks, so fall back to numeric ticks.\n const useCategoricalX = !!xTickText && xTickText.length === xTicks.length;\n\n const resolvedBarWidth = useMemo(() => {\n if (barWidth !== undefined) return barWidth;\n // xTicks is the sorted, de-duplicated data x, so every gap is > 0\n const gaps = xTicks.slice(1).map((x, i) => x - xTicks[i]).sort((a, b) => a - b);\n // A single x position has no gap to scale from; let Plotly size the bar.\n if (gaps.length === 0) return;\n const mid = Math.floor(gaps.length / 2);\n const medianGap = gaps.length % 2 === 1 ? gaps[mid] : (gaps[mid - 1] + gaps[mid]) / 2;\n const groupedSeries = variant === \"group\" ? Math.max(1, dataSeries.length) : 1;\n const fraction = Math.min(DEFAULT_BAR_WIDTH_FRACTION, MAX_GROUP_WIDTH_FRACTION / groupedSeries);\n return medianGap * fraction;\n }, [barWidth, xTicks, variant, dataSeries.length]);\n\n const yTicks = useMemo(() => {\n const range = effectiveYRange[1] - effectiveYRange[0];\n let step = Math.pow(10, Math.floor(Math.log10(range)));\n\n if (range / step > 10) step = step * 2;\n if (range / step < 4) step = step / 2;\n\n const ticks = [];\n let current = Math.ceil(effectiveYRange[0] / step) * step;\n while (current <= effectiveYRange[1]) {\n ticks.push(current);\n current += step;\n }\n return ticks;\n }, [effectiveYRange]);\n\n const barMode = useMemo((): \"group\" | \"stack\" | \"overlay\" => {\n switch (variant) {\n case \"stack\":\n return \"stack\";\n case \"overlay\":\n return \"overlay\";\n case \"group\":\n default:\n return \"group\";\n }\n }, [variant]);\n\n // Y ticks thinned to what fits the plot height. Derived from the resolved\n // (not last-plotted) height so an in-place resize also updates them via the\n // relayout effect below, instead of only when the scale bucket flips.\n const yTickVals = useMemo(\n () =>\n thinTicks(\n yTicks,\n maxTickCount(\n resolvedHeight - marginTop - scale.margin.b,\n scale.tickFontSize * Y_TICK_LABEL_SPACING,\n ),\n ),\n [yTicks, resolvedHeight, marginTop, scale],\n );\n const yTickKey = yTickVals.join(\",\");\n const yTickValsRef = useRef(yTickVals);\n yTickValsRef.current = yTickVals;\n\n const tickOptions = useMemo(\n () => ({\n tickcolor: theme.tickColor,\n ticklen: scale.ticklen,\n tickwidth: 1,\n ticks: \"outside\" as const,\n tickfont: {\n size: scale.tickFontSize,\n color: theme.textColor,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n linecolor: theme.lineColor,\n linewidth: 1,\n position: 0,\n zeroline: false,\n }),\n [theme, scale],\n );\n\n useEffect(() => {\n if (!plotRef.current || !hasSize) return;\n\n const data = dataSeries.map((series, index) => ({\n x: series.x,\n y: series.y,\n type: \"bar\" as const,\n name: series.name,\n hoverinfo: \"none\" as const,\n marker: {\n color: seriesColor(index, series.color),\n },\n ...(resolvedBarWidth === undefined ? {} : { width: resolvedBarWidth }),\n error_y: series.error_y,\n }));\n\n const layout = {\n ...(title\n ? {\n title: {\n text: title,\n font: {\n size: scale.titleFontSize,\n family: CHART_FONT_FAMILY,\n color: theme.textColor,\n },\n },\n }\n : {}),\n width: sizeRef.current.width,\n height: sizeRef.current.height,\n margin: {\n l: scale.margin.l,\n r: scale.margin.r,\n // Reserve room for tick labels, the x-axis title, and the\n // container-anchored bottom legend stacked beneath them.\n b: scale.margin.b,\n t: marginTop,\n pad: 0,\n },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: {\n family: CHART_FONT_FAMILY,\n },\n barmode: barMode,\n bargap: 0.15,\n dragmode: false as const,\n xaxis: {\n title: {\n text: xTitle,\n font: {\n size: scale.axisTitleFontSize,\n color: theme.textSecondary,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n standoff: scale.axisTitleStandoff,\n },\n gridcolor: theme.gridColor,\n range: xRange,\n autorange: !xRange,\n tickmode: \"array\" as const,\n tickvals: xTicks,\n ...(useCategoricalX ? { ticktext: xTickText } : {}),\n showgrid: true,\n // Reserve space for tick labels + the axis title so the bottom legend\n // can't overlap them at small sizes (SW-2157).\n automargin: true,\n ...tickOptions,\n },\n yaxis: {\n title: {\n text: yTitle,\n font: {\n size: scale.axisTitleFontSize,\n color: theme.textSecondary,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n standoff: scale.axisTitleStandoff,\n },\n gridcolor: theme.gridColor,\n range: yRange,\n autorange: !yRange,\n tickmode: \"array\" as const,\n tickvals: yTickValsRef.current,\n showgrid: true,\n automargin: true,\n ...tickOptions,\n },\n legend: {\n // Anchor to the bottom of the container (not the plot area) so the\n // legend always clears the x-axis tick labels + title — a paper-relative\n // fractional offset collapses into the ticks at small heights (SW-2157).\n x: 0.5,\n y: 0,\n xanchor: \"center\" as const,\n yanchor: \"bottom\" as const,\n yref: \"container\" as const,\n orientation: \"h\" as const,\n font: {\n size: scale.legendFontSize,\n color: theme.legendColor,\n family: CHART_FONT_FAMILY,\n weight: 500,\n lineheight: scale.legendLineHeight,\n },\n },\n showlegend: dataSeries.length > 1,\n };\n\n const config = {\n // Sizing is driven from the measured container; disable Plotly's own\n // window-resize responsiveness (it can't see container resizes).\n responsive: false,\n displayModeBar: false,\n displaylogo: false,\n };\n\n // Plotly is loaded lazily so it stays out of the consumer's main chunk\n // (SW-2007); the draw is skipped if the effect re-runs or unmounts first.\n let cancelled = false;\n let plotElement: HTMLDivElement | null = null;\n void loadPlotly().then((plotly) => {\n if (cancelled || !plotRef.current) return;\n plotly.newPlot(plotRef.current, data, layout, config);\n bindTooltip(plotRef.current);\n\n // Capture ref value for cleanup\n plotElement = plotRef.current;\n plotInitedRef.current = true;\n appliedSizeRef.current = { ...sizeRef.current };\n });\n\n return () => {\n cancelled = true;\n if (plotElement) {\n getLoadedPlotly().purge(plotElement);\n plotInitedRef.current = false;\n }\n };\n }, [dataSeries, hasSize, xRange, yRange, xTitle, yTitle, title, resolvedBarWidth, barMode, tickOptions, xTicks, yTicks, useCategoricalX, xTickText, theme, scale, marginTop, bindTooltip]);\n\n // Resize in place when the measured/overridden size changes — cheaper than\n // recreating the plot, and it preserves tooltip/event bindings.\n useEffect(() => {\n const plotElement = plotRef.current;\n if (!plotElement || !plotInitedRef.current || resolvedWidth <= 0 || resolvedHeight <= 0) {\n return;\n }\n // newPlot already drew at the current size; skip the redundant relayout\n // (it would queue an automargin redraw that can reject if we unmount first).\n if (\n appliedSizeRef.current.width === resolvedWidth &&\n appliedSizeRef.current.height === resolvedHeight\n ) {\n return;\n }\n appliedSizeRef.current = { width: resolvedWidth, height: resolvedHeight };\n // Swallow rejections from a relayout that races an unmount/purge.\n // plotInitedRef guarantees Plotly finished loading, so sync access is safe.\n void getLoadedPlotly()\n .relayout(plotElement, {\n width: resolvedWidth,\n height: resolvedHeight,\n // Re-thin the y ticks for the new plot height (SW-2298). The flattened\n // key is valid relayout input but not part of the Layout type.\n \"yaxis.tickvals\": yTickValsRef.current,\n } as Partial<Plotly.Layout>)\n .catch(() => {});\n }, [resolvedWidth, resolvedHeight, yTickKey]);\n\n return (\n <div\n ref={containerRef}\n className={cn(\"bar-graph-container relative\", fillWidth && \"w-full\", fillHeight && \"h-full\")}\n >\n <div ref={plotRef} style={{ width: \"100%\", height: \"100%\" }} />\n {tooltipElement}\n </div>\n );\n};\n\nexport { BarChart };\nexport type { BarDataSeries, BarChartVariant, BarChartProps };\n"],"names":["BAR_REGULAR_SCALE","REGULAR_SCALE","DEFAULT_BAR_WIDTH_FRACTION","MAX_GROUP_WIDTH_FRACTION","BarChart","dataSeries","width","height","xRange","yRange","variant","xTitle","yTitle","title","barWidth","xTickText","plotRef","useRef","theme","usePlotlyTheme","bindTooltip","tooltipElement","useChartTooltip","containerRef","measured","useElementSize","resolvedWidth","resolvedHeight","hasSize","fillWidth","fillHeight","scale","resolveChartScale","marginTop","sizeRef","plotInitedRef","appliedSizeRef","yMin","yMax","useMemo","minX","maxX","minY","maxY","series","x","y","xPadding","yPadding","effectiveYRange","xTicks","s","a","b","useCategoricalX","resolvedBarWidth","gaps","i","mid","medianGap","groupedSeries","fraction","yTicks","range","step","ticks","current","barMode","yTickVals","thinTicks","maxTickCount","Y_TICK_LABEL_SPACING","yTickKey","yTickValsRef","tickOptions","CHART_FONT_FAMILY","useEffect","data","index","seriesColor","layout","config","cancelled","plotElement","loadPlotly","plotly","getLoadedPlotly","jsxs","cn","jsx"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const j=require("react/jsx-runtime"),r=require("react"),k=require("../chart-scale.cjs"),K=require("../ChartTooltip/ChartTooltip.cjs"),S=require("../plotly-loader.cjs"),D=require("../../../hooks/use-element-size.cjs"),m=require("../../../hooks/use-plotly-theme.cjs"),G=require("../../../lib/utils.cjs"),J=require("../../../utils/colors.cjs"),Q=-1.8;function Z(c){const{x:i,y:h,name:x}=c;return!i||i.length===h.length?i:i.length===1?Array.from({length:h.length},()=>i[0]):(console.warn(`[BoxPlot] Series "${x}" has ${i.length} x values for ${h.length} y values; Plotly will only plot the first ${Math.min(i.length,h.length)} samples. Pass one x per sample, or a single x to label the whole box.`),i)}const ee=({dataSeries:c,width:i,height:h,xRange:x,yRange:T,xTitle:v,yTitle:C,title:b="Box Plot",showPoints:N=!1})=>{const f=r.useRef(null),o=m.usePlotlyTheme(),{bindTooltip:R,tooltipElement:$}=K.useChartTooltip({xLabel:v,yLabel:C}),[V,E]=D.useElementSize(),s=i??E.width,l=h??E.height,I=s>0&&l>0,U=i===void 0,W=h===void 0,e=k.resolveChartScale(s,l),M=b?e.margin.tTitle:e.margin.tNoTitle,w=r.useRef({width:s,height:l});w.current={width:s,height:l};const A=r.useRef(!1),_=r.useRef({width:0,height:0}),{yMin:O,yMax:Y}=r.useMemo(()=>{let n=Number.MAX_VALUE,t=Number.MIN_VALUE;c.forEach(a=>{a.y.forEach(y=>{n=Math.min(n,y),t=Math.max(t,y)})});const g=(t-n)*.1;return{yMin:n-g,yMax:t+g}},[c]),p=r.useMemo(()=>T||[O,Y],[T,O,Y]),F=r.useMemo(()=>{const n=p[1]-p[0];let t=Math.pow(10,Math.floor(Math.log10(n)));n/t>10&&(t=t*2),n/t<4&&(t=t/2);const g=[];let a=Math.ceil(p[0]/t)*t;for(;a<=p[1];)g.push(a),a+=t;return g},[p]),z=r.useMemo(()=>k.thinTicks(F,k.maxTickCount(l-M-e.margin.b,e.tickFontSize*k.Y_TICK_LABEL_SPACING)),[F,l,M,e]),X=z.join(","),L=r.useRef(z);L.current=z;const P=r.useMemo(()=>({tickcolor:o.tickColor,ticklen:e.ticklen,tickwidth:1,ticks:"outside",tickfont:{size:e.tickFontSize,color:o.textColor,family:m.CHART_FONT_FAMILY,weight:400},linecolor:o.lineColor,linewidth:1,position:0,zeroline:!1}),[o,e]),q=r.useMemo(()=>({text:b,x:.5,y:.95,xanchor:"center",yanchor:"top",font:{size:e.titleFontSize,weight:600,family:m.CHART_FONT_FAMILY,color:o.textColor,lineheight:1.2,standoff:30}}),[b,o,e]),B=r.useMemo(()=>c.map(Z),[c]);return r.useEffect(()=>{if(!f.current||!I)return;const n=c.map((u,H)=>{const d=J.seriesColor(H,u.color);return{y:u.y,x:B[H],type:"box",name:u.name,hoverinfo:"none",marker:{color:d},line:{color:d},fillcolor:typeof d=="string"&&d.startsWith("#")&&d.length===7?`${d}40`:d,boxpoints:N?u.boxpoints||"outliers":!1,jitter:u.jitter||.3,pointpos:u.pointpos||Q}}),t={width:w.current.width,height:w.current.height,title:q,margin:{l:e.margin.l,r:e.margin.r,b:e.margin.b,t:M,pad:0},paper_bgcolor:o.paperBg,plot_bgcolor:o.plotBg,font:{family:m.CHART_FONT_FAMILY},dragmode:!1,xaxis:{title:{text:v,font:{size:e.axisTitleFontSize,color:o.textSecondary,family:m.CHART_FONT_FAMILY,weight:400},standoff:e.axisTitleStandoff},gridcolor:o.gridColor,range:x,autorange:!x,showgrid:!0,automargin:!0,...P},yaxis:{title:{text:C,font:{size:e.axisTitleFontSize,color:o.textSecondary,family:m.CHART_FONT_FAMILY,weight:400},standoff:e.axisTitleStandoff},gridcolor:o.gridColor,range:T,autorange:!T,tickmode:"array",tickvals:L.current,showgrid:!0,automargin:!0,...P},legend:{x:.5,y:0,xanchor:"center",yanchor:"bottom",yref:"container",orientation:"h",font:{size:e.legendFontSize,color:o.legendColor,family:m.CHART_FONT_FAMILY,weight:500,lineheight:e.legendLineHeight}},showlegend:!0},g={responsive:!1,displayModeBar:!1,displaylogo:!1};let a=!1,y=null;return S.loadPlotly().then(u=>{a||!f.current||(u.newPlot(f.current,n,t,g),R(f.current),y=f.current,A.current=!0,_.current={...w.current})}),()=>{a=!0,y&&(S.getLoadedPlotly().purge(y),A.current=!1)}},[c,B,I,x,T,p,v,C,N,q,P,F,o,e,M,R]),r.useEffect(()=>{const n=f.current;!n||!A.current||s<=0||l<=0||_.current.width===s&&_.current.height===l||(_.current={width:s,height:l},S.getLoadedPlotly().relayout(n,{width:s,height:l,"yaxis.tickvals":L.current}).catch(()=>{}))},[s,l,X]),j.jsxs("div",{ref:V,className:G.cn("boxplot-container relative",U&&"w-full",W&&"h-full"),children:[j.jsx("div",{ref:f,style:{width:"100%",height:"100%"}}),$]})};exports.BoxPlot=ee;
|
|
2
2
|
//# sourceMappingURL=BoxPlot.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BoxPlot.cjs","sources":["../../../../src/components/charts/BoxPlot/BoxPlot.tsx"],"sourcesContent":["import React, { useEffect, useRef, useMemo } from \"react\";\n\nimport { useChartTooltip } from \"../ChartTooltip\";\nimport { getLoadedPlotly, loadPlotly } from \"../plotly-loader\";\n\nimport { useElementSize } from \"@/hooks/use-element-size\";\nimport { CHART_FONT_FAMILY, usePlotlyTheme } from \"@/hooks/use-plotly-theme\";\nimport { cn } from \"@/lib/utils\";\nimport { seriesColor } from \"@/utils/colors\";\n\n/** Default point position offset from the box edge */\nconst DEFAULT_POINT_POSITION = -1.8;\n\ninterface BoxDataSeries {\n y: number[];\n name: string;\n /** Optional color override (auto-assigned from CHART_COLORS if not provided) */\n color?: string;\n x?: string[] | number[];\n boxpoints?: \"all\" | \"outliers\" | \"suspectedoutliers\" | false;\n jitter?: number;\n pointpos?: number;\n}\n\ninterface BoxPlotProps {\n dataSeries: BoxDataSeries[];\n /**\n * Fixed width in pixels. When omitted, the chart fills its container and\n * tracks the container's width via a `ResizeObserver`.\n */\n width?: number;\n /**\n * Fixed height in pixels. When omitted, the chart fills its container and\n * tracks the container's height via a `ResizeObserver`.\n */\n height?: number;\n xRange?: [number, number];\n yRange?: [number, number];\n xTitle?: string;\n yTitle?: string;\n title?: string;\n showPoints?: boolean;\n}\n\nconst BoxPlot: React.FC<BoxPlotProps> = ({\n dataSeries,\n width,\n height,\n xRange,\n yRange,\n xTitle,\n yTitle,\n title = \"Box Plot\",\n showPoints = false,\n}) => {\n const plotRef = useRef<HTMLDivElement>(null);\n const theme = usePlotlyTheme();\n const { bindTooltip, tooltipElement } = useChartTooltip({ xLabel: xTitle, yLabel: yTitle });\n\n // Omitted width/height → fill the container and track its measured size;\n // explicit pixel values override. See AreaPlot for the reference pattern.\n const [containerRef, measured] = useElementSize<HTMLDivElement>();\n const resolvedWidth = width ?? measured.width;\n const resolvedHeight = height ?? measured.height;\n const hasSize = resolvedWidth > 0 && resolvedHeight > 0;\n // Fill is per-dimension: omit width to fill the container width, omit height\n // to fill its height (so e.g. a fixed width with a container-driven height works).\n const fillWidth = width === undefined;\n const fillHeight = height === undefined;\n const sizeRef = useRef({ width: resolvedWidth, height: resolvedHeight });\n sizeRef.current = { width: resolvedWidth, height: resolvedHeight };\n const plotInitedRef = useRef(false);\n // Size last applied to the plot, so the resize effect can skip a redundant\n // relayout right after newPlot already drew at that size.\n const appliedSizeRef = useRef({ width: 0, height: 0 });\n\n const { yMin, yMax } = useMemo(() => {\n let minY = Number.MAX_VALUE;\n let maxY = Number.MIN_VALUE;\n\n dataSeries.forEach((series) => {\n series.y.forEach((y) => {\n minY = Math.min(minY, y);\n maxY = Math.max(maxY, y);\n });\n });\n\n const yPadding = (maxY - minY) * 0.1;\n\n return {\n yMin: minY - yPadding,\n yMax: maxY + yPadding,\n };\n }, [dataSeries]);\n\n const effectiveYRange = useMemo(\n () => yRange || [yMin, yMax],\n [yRange, yMin, yMax],\n );\n\n const yTicks = useMemo(() => {\n const range = effectiveYRange[1] - effectiveYRange[0];\n let step = Math.pow(10, Math.floor(Math.log10(range)));\n\n if (range / step > 10) step = step * 2;\n if (range / step < 4) step = step / 2;\n\n const ticks = [];\n let current = Math.ceil(effectiveYRange[0] / step) * step;\n while (current <= effectiveYRange[1]) {\n ticks.push(current);\n current += step;\n }\n return ticks;\n }, [effectiveYRange]);\n\n const tickOptions = useMemo(\n () => ({\n tickcolor: theme.tickColor,\n ticklen: 12,\n tickwidth: 1,\n ticks: \"outside\" as const,\n tickfont: {\n size: 16,\n color: theme.textColor,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n linecolor: theme.lineColor,\n linewidth: 1,\n position: 0,\n zeroline: false,\n }),\n [theme],\n );\n\n const titleOptions = useMemo(\n () => ({\n text: title,\n x: 0.5,\n y: 0.95,\n xanchor: \"center\" as const,\n yanchor: \"top\" as const,\n font: {\n size: 32,\n weight: 600,\n family: CHART_FONT_FAMILY,\n color: theme.textColor,\n lineheight: 1.2,\n standoff: 30,\n },\n }),\n [title, theme],\n );\n\n useEffect(() => {\n if (!plotRef.current || !hasSize) return;\n\n const data = dataSeries.map((series, index) => {\n const color = seriesColor(index, series.color);\n return {\n y: series.y,\n x: series.x,\n type: \"box\" as const,\n name: series.name,\n hoverinfo: \"none\" as const,\n marker: {\n color,\n },\n line: {\n color,\n },\n fillcolor:\n typeof color === \"string\" && color.startsWith(\"#\") && color.length === 7\n ? `${color}40`\n : color, // Add transparency for hex colors only\n boxpoints: showPoints\n ? series.boxpoints || \"outliers\"\n : (false as const),\n jitter: series.jitter || 0.3,\n pointpos: series.pointpos || DEFAULT_POINT_POSITION,\n };\n });\n\n const layout = {\n width: sizeRef.current.width,\n height: sizeRef.current.height,\n title: titleOptions,\n // Bottom margin reserves room for tick labels, the x-axis title, and the\n // container-anchored bottom legend stacked beneath them.\n margin: { l: 80, r: 40, b: 96, t: 80, pad: 0 },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: {\n family: CHART_FONT_FAMILY,\n },\n dragmode: false as const,\n xaxis: {\n title: {\n text: xTitle,\n font: {\n size: 16,\n color: theme.textSecondary,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n standoff: 15,\n },\n gridcolor: theme.gridColor,\n range: xRange,\n autorange: !xRange,\n showgrid: true,\n // Reserve space for tick labels + the axis title so the bottom legend\n // can't overlap them at small sizes (SW-2157).\n automargin: true,\n ...tickOptions,\n },\n yaxis: {\n title: {\n text: yTitle,\n font: {\n size: 16,\n color: theme.textSecondary,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n standoff: 15,\n },\n gridcolor: theme.gridColor,\n range: yRange,\n autorange: !yRange,\n tickmode: \"array\" as const,\n tickvals: yTicks,\n showgrid: true,\n automargin: true,\n ...tickOptions,\n },\n legend: {\n // Anchor to the bottom of the container (not the plot area) so the\n // legend always clears the x-axis tick labels + title — a paper-relative\n // fractional offset collapses into the ticks at small heights (SW-2157).\n x: 0.5,\n y: 0,\n xanchor: \"center\" as const,\n yanchor: \"bottom\" as const,\n yref: \"container\" as const,\n orientation: \"h\" as const,\n font: {\n size: 13,\n color: theme.legendColor,\n family: CHART_FONT_FAMILY,\n weight: 500,\n lineheight: 18,\n },\n },\n showlegend: true,\n };\n\n const config = {\n // Sizing is driven from the measured container; disable Plotly's own\n // window-resize responsiveness (it can't see container resizes).\n responsive: false,\n displayModeBar: false,\n displaylogo: false,\n };\n\n // Plotly is loaded lazily so it stays out of the consumer's main chunk\n // (SW-2007); the draw is skipped if the effect re-runs or unmounts first.\n let cancelled = false;\n let plotElement: HTMLDivElement | null = null;\n void loadPlotly().then((plotly) => {\n if (cancelled || !plotRef.current) return;\n plotly.newPlot(plotRef.current, data, layout, config);\n bindTooltip(plotRef.current);\n\n // Capture ref value for cleanup\n plotElement = plotRef.current;\n plotInitedRef.current = true;\n appliedSizeRef.current = { ...sizeRef.current };\n });\n\n return () => {\n cancelled = true;\n if (plotElement) {\n getLoadedPlotly().purge(plotElement);\n plotInitedRef.current = false;\n }\n };\n }, [dataSeries, hasSize, xRange, yRange, effectiveYRange, xTitle, yTitle, showPoints, titleOptions, tickOptions, yTicks, theme, bindTooltip]);\n\n // Resize in place when the measured/overridden size changes — cheaper than\n // recreating the plot, and it preserves tooltip/event bindings.\n useEffect(() => {\n const plotElement = plotRef.current;\n if (!plotElement || !plotInitedRef.current || resolvedWidth <= 0 || resolvedHeight <= 0) {\n return;\n }\n // newPlot already drew at the current size; skip the redundant relayout\n // (it would queue an automargin redraw that can reject if we unmount first).\n if (\n appliedSizeRef.current.width === resolvedWidth &&\n appliedSizeRef.current.height === resolvedHeight\n ) {\n return;\n }\n appliedSizeRef.current = { width: resolvedWidth, height: resolvedHeight };\n // Swallow rejections from a relayout that races an unmount/purge.\n // plotInitedRef guarantees Plotly finished loading, so sync access is safe.\n void getLoadedPlotly()\n .relayout(plotElement, { width: resolvedWidth, height: resolvedHeight })\n .catch(() => {});\n }, [resolvedWidth, resolvedHeight]);\n\n return (\n <div\n ref={containerRef}\n className={cn(\"boxplot-container relative\", fillWidth && \"w-full\", fillHeight && \"h-full\")}\n >\n <div ref={plotRef} style={{ width: \"100%\", height: \"100%\" }} />\n {tooltipElement}\n </div>\n );\n};\n\nexport { BoxPlot };\nexport type { BoxDataSeries, BoxPlotProps };\n"],"names":["DEFAULT_POINT_POSITION","BoxPlot","dataSeries","width","height","xRange","yRange","xTitle","yTitle","title","showPoints","plotRef","useRef","theme","usePlotlyTheme","bindTooltip","tooltipElement","useChartTooltip","containerRef","measured","useElementSize","resolvedWidth","resolvedHeight","hasSize","fillWidth","fillHeight","sizeRef","plotInitedRef","appliedSizeRef","yMin","yMax","useMemo","minY","maxY","series","y","yPadding","effectiveYRange","yTicks","range","step","ticks","current","tickOptions","CHART_FONT_FAMILY","titleOptions","useEffect","data","index","color","seriesColor","layout","config","cancelled","plotElement","loadPlotly","plotly","getLoadedPlotly","jsxs","cn","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BoxPlot.cjs","sources":["../../../../src/components/charts/BoxPlot/BoxPlot.tsx"],"sourcesContent":["import React, { useEffect, useRef, useMemo } from \"react\";\n\nimport { Y_TICK_LABEL_SPACING, maxTickCount, resolveChartScale, thinTicks } from \"../chart-scale\";\nimport { useChartTooltip } from \"../ChartTooltip\";\nimport { getLoadedPlotly, loadPlotly } from \"../plotly-loader\";\n\nimport type Plotly from \"plotly.js-dist\";\n\nimport { useElementSize } from \"@/hooks/use-element-size\";\nimport { CHART_FONT_FAMILY, usePlotlyTheme } from \"@/hooks/use-plotly-theme\";\nimport { cn } from \"@/lib/utils\";\nimport { seriesColor } from \"@/utils/colors\";\n\n/** Default point position offset from the box edge */\nconst DEFAULT_POINT_POSITION = -1.8;\n\n/**\n * Plotly pairs `x[i]` with `y[i]` and silently drops the samples past the\n * shorter array — a one-entry `x` therefore drew a flat line instead of a box\n * (SW-2298). A single x names the whole box and is broadcast to every sample;\n * any other length mismatch is a data bug, so warn rather than guess.\n */\nfunction resolveBoxX(series: BoxDataSeries): BoxDataSeries[\"x\"] {\n const { x, y, name } = series;\n if (!x || x.length === y.length) return x;\n if (x.length === 1) {\n return Array.from({ length: y.length }, () => x[0]) as string[] | number[];\n }\n console.warn(\n `[BoxPlot] Series \"${name}\" has ${x.length} x values for ${y.length} y values; ` +\n `Plotly will only plot the first ${Math.min(x.length, y.length)} samples. ` +\n \"Pass one x per sample, or a single x to label the whole box.\",\n );\n return x;\n}\n\ninterface BoxDataSeries {\n y: number[];\n name: string;\n /** Optional color override (auto-assigned from CHART_COLORS if not provided) */\n color?: string;\n /**\n * Category per sample, aligned with `y`. A single entry labels the whole\n * box (one category per series) and is applied to every sample.\n */\n x?: string[] | number[];\n boxpoints?: \"all\" | \"outliers\" | \"suspectedoutliers\" | false;\n jitter?: number;\n pointpos?: number;\n}\n\ninterface BoxPlotProps {\n dataSeries: BoxDataSeries[];\n /**\n * Fixed width in pixels. When omitted, the chart fills its container and\n * tracks the container's width via a `ResizeObserver`.\n */\n width?: number;\n /**\n * Fixed height in pixels. When omitted, the chart fills its container and\n * tracks the container's height via a `ResizeObserver`.\n */\n height?: number;\n xRange?: [number, number];\n yRange?: [number, number];\n xTitle?: string;\n yTitle?: string;\n title?: string;\n showPoints?: boolean;\n}\n\nconst BoxPlot: React.FC<BoxPlotProps> = ({\n dataSeries,\n width,\n height,\n xRange,\n yRange,\n xTitle,\n yTitle,\n title = \"Box Plot\",\n showPoints = false,\n}) => {\n const plotRef = useRef<HTMLDivElement>(null);\n const theme = usePlotlyTheme();\n const { bindTooltip, tooltipElement } = useChartTooltip({ xLabel: xTitle, yLabel: yTitle });\n\n // Omitted width/height → fill the container and track its measured size;\n // explicit pixel values override. See AreaPlot for the reference pattern.\n const [containerRef, measured] = useElementSize<HTMLDivElement>();\n const resolvedWidth = width ?? measured.width;\n const resolvedHeight = height ?? measured.height;\n const hasSize = resolvedWidth > 0 && resolvedHeight > 0;\n // Fill is per-dimension: omit width to fill the container width, omit height\n // to fill its height (so e.g. a fixed width with a container-driven height works).\n const fillWidth = width === undefined;\n const fillHeight = height === undefined;\n // Fonts, tick length and margins step down on small canvases so the plot\n // area (not the chrome) gets the pixels (SW-2298).\n const scale = resolveChartScale(resolvedWidth, resolvedHeight);\n // `title` defaults to \"Box Plot\"; an explicit \"\" drops it and its margin.\n const marginTop = title ? scale.margin.tTitle : scale.margin.tNoTitle;\n const sizeRef = useRef({ width: resolvedWidth, height: resolvedHeight });\n sizeRef.current = { width: resolvedWidth, height: resolvedHeight };\n const plotInitedRef = useRef(false);\n // Size last applied to the plot, so the resize effect can skip a redundant\n // relayout right after newPlot already drew at that size.\n const appliedSizeRef = useRef({ width: 0, height: 0 });\n\n const { yMin, yMax } = useMemo(() => {\n let minY = Number.MAX_VALUE;\n let maxY = Number.MIN_VALUE;\n\n dataSeries.forEach((series) => {\n series.y.forEach((y) => {\n minY = Math.min(minY, y);\n maxY = Math.max(maxY, y);\n });\n });\n\n const yPadding = (maxY - minY) * 0.1;\n\n return {\n yMin: minY - yPadding,\n yMax: maxY + yPadding,\n };\n }, [dataSeries]);\n\n const effectiveYRange = useMemo(\n () => yRange || [yMin, yMax],\n [yRange, yMin, yMax],\n );\n\n const yTicks = useMemo(() => {\n const range = effectiveYRange[1] - effectiveYRange[0];\n let step = Math.pow(10, Math.floor(Math.log10(range)));\n\n if (range / step > 10) step = step * 2;\n if (range / step < 4) step = step / 2;\n\n const ticks = [];\n let current = Math.ceil(effectiveYRange[0] / step) * step;\n while (current <= effectiveYRange[1]) {\n ticks.push(current);\n current += step;\n }\n return ticks;\n }, [effectiveYRange]);\n\n // Y ticks thinned to what fits the plot height. Derived from the resolved\n // (not last-plotted) height so an in-place resize also updates them via the\n // relayout effect below, instead of only when the scale bucket flips.\n const yTickVals = useMemo(\n () =>\n thinTicks(\n yTicks,\n maxTickCount(\n resolvedHeight - marginTop - scale.margin.b,\n scale.tickFontSize * Y_TICK_LABEL_SPACING,\n ),\n ),\n [yTicks, resolvedHeight, marginTop, scale],\n );\n const yTickKey = yTickVals.join(\",\");\n const yTickValsRef = useRef(yTickVals);\n yTickValsRef.current = yTickVals;\n\n const tickOptions = useMemo(\n () => ({\n tickcolor: theme.tickColor,\n ticklen: scale.ticklen,\n tickwidth: 1,\n ticks: \"outside\" as const,\n tickfont: {\n size: scale.tickFontSize,\n color: theme.textColor,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n linecolor: theme.lineColor,\n linewidth: 1,\n position: 0,\n zeroline: false,\n }),\n [theme, scale],\n );\n\n const titleOptions = useMemo(\n () => ({\n text: title,\n x: 0.5,\n y: 0.95,\n xanchor: \"center\" as const,\n yanchor: \"top\" as const,\n font: {\n size: scale.titleFontSize,\n weight: 600,\n family: CHART_FONT_FAMILY,\n color: theme.textColor,\n lineheight: 1.2,\n standoff: 30,\n },\n }),\n [title, theme, scale],\n );\n\n // Resolved once per data change so a length-mismatch warning fires once,\n // not on every theme- or size-driven re-plot.\n const resolvedX = useMemo(() => dataSeries.map(resolveBoxX), [dataSeries]);\n\n useEffect(() => {\n if (!plotRef.current || !hasSize) return;\n\n const data = dataSeries.map((series, index) => {\n const color = seriesColor(index, series.color);\n return {\n y: series.y,\n x: resolvedX[index],\n type: \"box\" as const,\n name: series.name,\n hoverinfo: \"none\" as const,\n marker: {\n color,\n },\n line: {\n color,\n },\n fillcolor:\n typeof color === \"string\" && color.startsWith(\"#\") && color.length === 7\n ? `${color}40`\n : color, // Add transparency for hex colors only\n boxpoints: showPoints\n ? series.boxpoints || \"outliers\"\n : (false as const),\n jitter: series.jitter || 0.3,\n pointpos: series.pointpos || DEFAULT_POINT_POSITION,\n };\n });\n\n const layout = {\n width: sizeRef.current.width,\n height: sizeRef.current.height,\n title: titleOptions,\n // Bottom margin reserves room for tick labels, the x-axis title, and the\n // container-anchored bottom legend stacked beneath them.\n margin: {\n l: scale.margin.l,\n r: scale.margin.r,\n b: scale.margin.b,\n t: marginTop,\n pad: 0,\n },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: {\n family: CHART_FONT_FAMILY,\n },\n dragmode: false as const,\n xaxis: {\n title: {\n text: xTitle,\n font: {\n size: scale.axisTitleFontSize,\n color: theme.textSecondary,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n standoff: scale.axisTitleStandoff,\n },\n gridcolor: theme.gridColor,\n range: xRange,\n autorange: !xRange,\n showgrid: true,\n // Reserve space for tick labels + the axis title so the bottom legend\n // can't overlap them at small sizes (SW-2157).\n automargin: true,\n ...tickOptions,\n },\n yaxis: {\n title: {\n text: yTitle,\n font: {\n size: scale.axisTitleFontSize,\n color: theme.textSecondary,\n family: CHART_FONT_FAMILY,\n weight: 400,\n },\n standoff: scale.axisTitleStandoff,\n },\n gridcolor: theme.gridColor,\n range: yRange,\n autorange: !yRange,\n tickmode: \"array\" as const,\n tickvals: yTickValsRef.current,\n showgrid: true,\n automargin: true,\n ...tickOptions,\n },\n legend: {\n // Anchor to the bottom of the container (not the plot area) so the\n // legend always clears the x-axis tick labels + title — a paper-relative\n // fractional offset collapses into the ticks at small heights (SW-2157).\n x: 0.5,\n y: 0,\n xanchor: \"center\" as const,\n yanchor: \"bottom\" as const,\n yref: \"container\" as const,\n orientation: \"h\" as const,\n font: {\n size: scale.legendFontSize,\n color: theme.legendColor,\n family: CHART_FONT_FAMILY,\n weight: 500,\n lineheight: scale.legendLineHeight,\n },\n },\n showlegend: true,\n };\n\n const config = {\n // Sizing is driven from the measured container; disable Plotly's own\n // window-resize responsiveness (it can't see container resizes).\n responsive: false,\n displayModeBar: false,\n displaylogo: false,\n };\n\n // Plotly is loaded lazily so it stays out of the consumer's main chunk\n // (SW-2007); the draw is skipped if the effect re-runs or unmounts first.\n let cancelled = false;\n let plotElement: HTMLDivElement | null = null;\n void loadPlotly().then((plotly) => {\n if (cancelled || !plotRef.current) return;\n plotly.newPlot(plotRef.current, data, layout, config);\n bindTooltip(plotRef.current);\n\n // Capture ref value for cleanup\n plotElement = plotRef.current;\n plotInitedRef.current = true;\n appliedSizeRef.current = { ...sizeRef.current };\n });\n\n return () => {\n cancelled = true;\n if (plotElement) {\n getLoadedPlotly().purge(plotElement);\n plotInitedRef.current = false;\n }\n };\n }, [dataSeries, resolvedX, hasSize, xRange, yRange, effectiveYRange, xTitle, yTitle, showPoints, titleOptions, tickOptions, yTicks, theme, scale, marginTop, bindTooltip]);\n\n // Resize in place when the measured/overridden size changes — cheaper than\n // recreating the plot, and it preserves tooltip/event bindings.\n useEffect(() => {\n const plotElement = plotRef.current;\n if (!plotElement || !plotInitedRef.current || resolvedWidth <= 0 || resolvedHeight <= 0) {\n return;\n }\n // newPlot already drew at the current size; skip the redundant relayout\n // (it would queue an automargin redraw that can reject if we unmount first).\n if (\n appliedSizeRef.current.width === resolvedWidth &&\n appliedSizeRef.current.height === resolvedHeight\n ) {\n return;\n }\n appliedSizeRef.current = { width: resolvedWidth, height: resolvedHeight };\n // Swallow rejections from a relayout that races an unmount/purge.\n // plotInitedRef guarantees Plotly finished loading, so sync access is safe.\n void getLoadedPlotly()\n .relayout(plotElement, {\n width: resolvedWidth,\n height: resolvedHeight,\n // Re-thin the y ticks for the new plot height (SW-2298). The flattened\n // key is valid relayout input but not part of the Layout type.\n \"yaxis.tickvals\": yTickValsRef.current,\n } as Partial<Plotly.Layout>)\n .catch(() => {});\n }, [resolvedWidth, resolvedHeight, yTickKey]);\n\n return (\n <div\n ref={containerRef}\n className={cn(\"boxplot-container relative\", fillWidth && \"w-full\", fillHeight && \"h-full\")}\n >\n <div ref={plotRef} style={{ width: \"100%\", height: \"100%\" }} />\n {tooltipElement}\n </div>\n );\n};\n\nexport { BoxPlot };\nexport type { BoxDataSeries, BoxPlotProps };\n"],"names":["DEFAULT_POINT_POSITION","resolveBoxX","series","x","y","name","BoxPlot","dataSeries","width","height","xRange","yRange","xTitle","yTitle","title","showPoints","plotRef","useRef","theme","usePlotlyTheme","bindTooltip","tooltipElement","useChartTooltip","containerRef","measured","useElementSize","resolvedWidth","resolvedHeight","hasSize","fillWidth","fillHeight","scale","resolveChartScale","marginTop","sizeRef","plotInitedRef","appliedSizeRef","yMin","yMax","useMemo","minY","maxY","yPadding","effectiveYRange","yTicks","range","step","ticks","current","yTickVals","thinTicks","maxTickCount","Y_TICK_LABEL_SPACING","yTickKey","yTickValsRef","tickOptions","CHART_FONT_FAMILY","titleOptions","resolvedX","useEffect","data","index","color","seriesColor","layout","config","cancelled","plotElement","loadPlotly","plotly","getLoadedPlotly","jsxs","cn","jsx"],"mappings":"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"}
|
|
@@ -4,6 +4,10 @@ interface BoxDataSeries {
|
|
|
4
4
|
name: string;
|
|
5
5
|
/** Optional color override (auto-assigned from CHART_COLORS if not provided) */
|
|
6
6
|
color?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Category per sample, aligned with `y`. A single entry labels the whole
|
|
9
|
+
* box (one category per series) and is applied to every sample.
|
|
10
|
+
*/
|
|
7
11
|
x?: string[] | number[];
|
|
8
12
|
boxpoints?: "all" | "outliers" | "suspectedoutliers" | false;
|
|
9
13
|
jitter?: number;
|