@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.
Files changed (48) hide show
  1. package/dist/components/charts/AreaPlot/AreaPlot.cjs +1 -1
  2. package/dist/components/charts/AreaPlot/AreaPlot.cjs.map +1 -1
  3. package/dist/components/charts/AreaPlot/AreaPlot.js +166 -148
  4. package/dist/components/charts/AreaPlot/AreaPlot.js.map +1 -1
  5. package/dist/components/charts/BarChart/BarChart.cjs +1 -1
  6. package/dist/components/charts/BarChart/BarChart.cjs.map +1 -1
  7. package/dist/components/charts/BarChart/BarChart.d.ts +7 -0
  8. package/dist/components/charts/BarChart/BarChart.js +151 -120
  9. package/dist/components/charts/BarChart/BarChart.js.map +1 -1
  10. package/dist/components/charts/BoxPlot/BoxPlot.cjs +1 -1
  11. package/dist/components/charts/BoxPlot/BoxPlot.cjs.map +1 -1
  12. package/dist/components/charts/BoxPlot/BoxPlot.d.ts +4 -0
  13. package/dist/components/charts/BoxPlot/BoxPlot.js +133 -102
  14. package/dist/components/charts/BoxPlot/BoxPlot.js.map +1 -1
  15. package/dist/components/charts/ChartTooltip/lines.cjs.map +1 -1
  16. package/dist/components/charts/ChartTooltip/lines.d.ts +2 -0
  17. package/dist/components/charts/ChartTooltip/lines.js.map +1 -1
  18. package/dist/components/charts/Chromatogram/Chromatogram.cjs +1 -1
  19. package/dist/components/charts/Chromatogram/Chromatogram.cjs.map +1 -1
  20. package/dist/components/charts/Chromatogram/Chromatogram.js +111 -114
  21. package/dist/components/charts/Chromatogram/Chromatogram.js.map +1 -1
  22. package/dist/components/charts/Chromatogram/plotBuilder.cjs +4 -1
  23. package/dist/components/charts/Chromatogram/plotBuilder.cjs.map +1 -1
  24. package/dist/components/charts/Chromatogram/plotBuilder.d.ts +32 -2
  25. package/dist/components/charts/Chromatogram/plotBuilder.js +170 -127
  26. package/dist/components/charts/Chromatogram/plotBuilder.js.map +1 -1
  27. package/dist/components/charts/Chromatogram/regionOverlays.cjs +1 -1
  28. package/dist/components/charts/Chromatogram/regionOverlays.cjs.map +1 -1
  29. package/dist/components/charts/Chromatogram/regionOverlays.js +11 -11
  30. package/dist/components/charts/Chromatogram/regionOverlays.js.map +1 -1
  31. package/dist/components/charts/Chromatogram/types.d.ts +4 -2
  32. package/dist/components/charts/Electropherogram/Electropherogram.cjs +1 -1
  33. package/dist/components/charts/Electropherogram/Electropherogram.cjs.map +1 -1
  34. package/dist/components/charts/Electropherogram/Electropherogram.js +89 -88
  35. package/dist/components/charts/Electropherogram/Electropherogram.js.map +1 -1
  36. package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.cjs +1 -1
  37. package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.cjs.map +1 -1
  38. package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.js +4 -5
  39. package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.js.map +1 -1
  40. package/dist/components/charts/chart-scale.cjs +2 -0
  41. package/dist/components/charts/chart-scale.cjs.map +1 -0
  42. package/dist/components/charts/chart-scale.d.ts +61 -0
  43. package/dist/components/charts/chart-scale.js +61 -0
  44. package/dist/components/charts/chart-scale.js.map +1 -0
  45. package/dist/index.css +1 -1
  46. package/dist/index.scoped.css +1 -1
  47. package/dist/index.tailwind.css +1 -1
  48. package/package.json +1 -1
@@ -1,58 +1,77 @@
1
- import { jsxs as W, jsx as R } from "react/jsx-runtime";
2
- import { useRef as x, useMemo as m, useEffect as T } from "react";
3
- import { useChartTooltip as V } from "../ChartTooltip/ChartTooltip.js";
4
- import { loadPlotly as D, getLoadedPlotly as j } from "../plotly-loader.js";
5
- import { useElementSize as X } from "../../../hooks/use-element-size.js";
6
- import { usePlotlyTheme as $, CHART_FONT_FAMILY as u } from "../../../hooks/use-plotly-theme.js";
7
- import { cn as q } from "../../../lib/utils.js";
8
- import { seriesColor as G } from "../../../utils/colors.js";
9
- const J = -1.8, nt = ({
10
- dataSeries: p,
11
- width: z,
12
- height: C,
13
- xRange: w,
14
- yRange: d,
15
- xTitle: M,
16
- yTitle: b,
17
- title: E = "Box Plot",
18
- showPoints: P = !1
1
+ import { jsxs as D, jsx as G } from "react/jsx-runtime";
2
+ import { useRef as k, useMemo as m, useEffect as H } from "react";
3
+ import { resolveChartScale as q, thinTicks as J, maxTickCount as Q, Y_TICK_LABEL_SPACING as Z } from "../chart-scale.js";
4
+ import { useChartTooltip as tt } from "../ChartTooltip/ChartTooltip.js";
5
+ import { loadPlotly as et, getLoadedPlotly as V } from "../plotly-loader.js";
6
+ import { useElementSize as ot } from "../../../hooks/use-element-size.js";
7
+ import { usePlotlyTheme as rt, CHART_FONT_FAMILY as y } from "../../../hooks/use-plotly-theme.js";
8
+ import { cn as nt } from "../../../lib/utils.js";
9
+ import { seriesColor as it } from "../../../utils/colors.js";
10
+ const lt = -1.8;
11
+ function ct(c) {
12
+ const { x: n, y: h, name: x } = c;
13
+ return !n || n.length === h.length ? n : n.length === 1 ? Array.from({ length: h.length }, () => n[0]) : (console.warn(
14
+ `[BoxPlot] Series "${x}" has ${n.length} x values for ${h.length} y values; Plotly will only plot the first ${Math.min(n.length, h.length)} samples. Pass one x per sample, or a single x to label the whole box.`
15
+ ), n);
16
+ }
17
+ const yt = ({
18
+ dataSeries: c,
19
+ width: n,
20
+ height: h,
21
+ xRange: x,
22
+ yRange: w,
23
+ xTitle: z,
24
+ yTitle: M,
25
+ title: C = "Box Plot",
26
+ showPoints: A = !1
19
27
  }) => {
20
- const c = x(null), o = $(), { bindTooltip: _, tooltipElement: Y } = V({ xLabel: M, yLabel: b }), [F, L] = X(), i = z ?? L.width, n = C ?? L.height, N = i > 0 && n > 0, H = z === void 0, S = C === void 0, g = x({ width: i, height: n });
21
- g.current = { width: i, height: n };
22
- const v = x(!1), y = x({ width: 0, height: 0 }), { yMin: A, yMax: I } = m(() => {
23
- let e = Number.MAX_VALUE, t = Number.MIN_VALUE;
24
- p.forEach((l) => {
25
- l.y.forEach((f) => {
26
- e = Math.min(e, f), t = Math.max(t, f);
28
+ const f = k(null), o = rt(), { bindTooltip: F, tooltipElement: R } = tt({ xLabel: z, yLabel: M }), [U, N] = ot(), l = n ?? N.width, i = h ?? N.height, B = l > 0 && i > 0, W = n === void 0, X = h === void 0, t = q(l, i), v = C ? t.margin.tTitle : t.margin.tNoTitle, T = k({ width: l, height: i });
29
+ T.current = { width: l, height: i };
30
+ const P = k(!1), b = k({ width: 0, height: 0 }), { yMin: I, yMax: O } = m(() => {
31
+ let r = Number.MAX_VALUE, e = Number.MIN_VALUE;
32
+ c.forEach((a) => {
33
+ a.y.forEach((p) => {
34
+ r = Math.min(r, p), e = Math.max(e, p);
27
35
  });
28
36
  });
29
- const a = (t - e) * 0.1;
37
+ const u = (e - r) * 0.1;
30
38
  return {
31
- yMin: e - a,
32
- yMax: t + a
39
+ yMin: r - u,
40
+ yMax: e + u
33
41
  };
34
- }, [p]), h = m(
35
- () => d || [A, I],
36
- [d, A, I]
37
- ), O = m(() => {
38
- const e = h[1] - h[0];
39
- let t = Math.pow(10, Math.floor(Math.log10(e)));
40
- e / t > 10 && (t = t * 2), e / t < 4 && (t = t / 2);
41
- const a = [];
42
- let l = Math.ceil(h[0] / t) * t;
43
- for (; l <= h[1]; )
44
- a.push(l), l += t;
45
- return a;
46
- }, [h]), k = m(
42
+ }, [c]), d = m(
43
+ () => w || [I, O],
44
+ [w, I, O]
45
+ ), S = m(() => {
46
+ const r = d[1] - d[0];
47
+ let e = Math.pow(10, Math.floor(Math.log10(r)));
48
+ r / e > 10 && (e = e * 2), r / e < 4 && (e = e / 2);
49
+ const u = [];
50
+ let a = Math.ceil(d[0] / e) * e;
51
+ for (; a <= d[1]; )
52
+ u.push(a), a += e;
53
+ return u;
54
+ }, [d]), E = m(
55
+ () => J(
56
+ S,
57
+ Q(
58
+ i - v - t.margin.b,
59
+ t.tickFontSize * Z
60
+ )
61
+ ),
62
+ [S, i, v, t]
63
+ ), K = E.join(","), _ = k(E);
64
+ _.current = E;
65
+ const L = m(
47
66
  () => ({
48
67
  tickcolor: o.tickColor,
49
- ticklen: 12,
68
+ ticklen: t.ticklen,
50
69
  tickwidth: 1,
51
70
  ticks: "outside",
52
71
  tickfont: {
53
- size: 16,
72
+ size: t.tickFontSize,
54
73
  color: o.textColor,
55
- family: u,
74
+ family: y,
56
75
  weight: 400
57
76
  },
58
77
  linecolor: o.lineColor,
@@ -60,99 +79,105 @@ const J = -1.8, nt = ({
60
79
  position: 0,
61
80
  zeroline: !1
62
81
  }),
63
- [o]
64
- ), B = m(
82
+ [o, t]
83
+ ), j = m(
65
84
  () => ({
66
- text: E,
85
+ text: C,
67
86
  x: 0.5,
68
87
  y: 0.95,
69
88
  xanchor: "center",
70
89
  yanchor: "top",
71
90
  font: {
72
- size: 32,
91
+ size: t.titleFontSize,
73
92
  weight: 600,
74
- family: u,
93
+ family: y,
75
94
  color: o.textColor,
76
95
  lineheight: 1.2,
77
96
  standoff: 30
78
97
  }
79
98
  }),
80
- [E, o]
81
- );
82
- return T(() => {
83
- if (!c.current || !N) return;
84
- const e = p.map((r, U) => {
85
- const s = G(U, r.color);
99
+ [C, o, t]
100
+ ), Y = m(() => c.map(ct), [c]);
101
+ return H(() => {
102
+ if (!f.current || !B) return;
103
+ const r = c.map((s, $) => {
104
+ const g = it($, s.color);
86
105
  return {
87
- y: r.y,
88
- x: r.x,
106
+ y: s.y,
107
+ x: Y[$],
89
108
  type: "box",
90
- name: r.name,
109
+ name: s.name,
91
110
  hoverinfo: "none",
92
111
  marker: {
93
- color: s
112
+ color: g
94
113
  },
95
114
  line: {
96
- color: s
115
+ color: g
97
116
  },
98
- fillcolor: typeof s == "string" && s.startsWith("#") && s.length === 7 ? `${s}40` : s,
117
+ fillcolor: typeof g == "string" && g.startsWith("#") && g.length === 7 ? `${g}40` : g,
99
118
  // Add transparency for hex colors only
100
- boxpoints: P ? r.boxpoints || "outliers" : !1,
101
- jitter: r.jitter || 0.3,
102
- pointpos: r.pointpos || J
119
+ boxpoints: A ? s.boxpoints || "outliers" : !1,
120
+ jitter: s.jitter || 0.3,
121
+ pointpos: s.pointpos || lt
103
122
  };
104
- }), t = {
105
- width: g.current.width,
106
- height: g.current.height,
107
- title: B,
123
+ }), e = {
124
+ width: T.current.width,
125
+ height: T.current.height,
126
+ title: j,
108
127
  // Bottom margin reserves room for tick labels, the x-axis title, and the
109
128
  // container-anchored bottom legend stacked beneath them.
110
- margin: { l: 80, r: 40, b: 96, t: 80, pad: 0 },
129
+ margin: {
130
+ l: t.margin.l,
131
+ r: t.margin.r,
132
+ b: t.margin.b,
133
+ t: v,
134
+ pad: 0
135
+ },
111
136
  paper_bgcolor: o.paperBg,
112
137
  plot_bgcolor: o.plotBg,
113
138
  font: {
114
- family: u
139
+ family: y
115
140
  },
116
141
  dragmode: !1,
117
142
  xaxis: {
118
143
  title: {
119
- text: M,
144
+ text: z,
120
145
  font: {
121
- size: 16,
146
+ size: t.axisTitleFontSize,
122
147
  color: o.textSecondary,
123
- family: u,
148
+ family: y,
124
149
  weight: 400
125
150
  },
126
- standoff: 15
151
+ standoff: t.axisTitleStandoff
127
152
  },
128
153
  gridcolor: o.gridColor,
129
- range: w,
130
- autorange: !w,
154
+ range: x,
155
+ autorange: !x,
131
156
  showgrid: !0,
132
157
  // Reserve space for tick labels + the axis title so the bottom legend
133
158
  // can't overlap them at small sizes (SW-2157).
134
159
  automargin: !0,
135
- ...k
160
+ ...L
136
161
  },
137
162
  yaxis: {
138
163
  title: {
139
- text: b,
164
+ text: M,
140
165
  font: {
141
- size: 16,
166
+ size: t.axisTitleFontSize,
142
167
  color: o.textSecondary,
143
- family: u,
168
+ family: y,
144
169
  weight: 400
145
170
  },
146
- standoff: 15
171
+ standoff: t.axisTitleStandoff
147
172
  },
148
173
  gridcolor: o.gridColor,
149
- range: d,
150
- autorange: !d,
174
+ range: w,
175
+ autorange: !w,
151
176
  tickmode: "array",
152
- tickvals: O,
177
+ tickvals: _.current,
153
178
  showgrid: !0,
154
179
  automargin: !0,
155
- ...k
180
+ ...L
156
181
  },
157
182
  legend: {
158
183
  // Anchor to the bottom of the container (not the plot area) so the
@@ -165,44 +190,50 @@ const J = -1.8, nt = ({
165
190
  yref: "container",
166
191
  orientation: "h",
167
192
  font: {
168
- size: 13,
193
+ size: t.legendFontSize,
169
194
  color: o.legendColor,
170
- family: u,
195
+ family: y,
171
196
  weight: 500,
172
- lineheight: 18
197
+ lineheight: t.legendLineHeight
173
198
  }
174
199
  },
175
200
  showlegend: !0
176
- }, a = {
201
+ }, u = {
177
202
  // Sizing is driven from the measured container; disable Plotly's own
178
203
  // window-resize responsiveness (it can't see container resizes).
179
204
  responsive: !1,
180
205
  displayModeBar: !1,
181
206
  displaylogo: !1
182
207
  };
183
- let l = !1, f = null;
184
- return D().then((r) => {
185
- l || !c.current || (r.newPlot(c.current, e, t, a), _(c.current), f = c.current, v.current = !0, y.current = { ...g.current });
208
+ let a = !1, p = null;
209
+ return et().then((s) => {
210
+ a || !f.current || (s.newPlot(f.current, r, e, u), F(f.current), p = f.current, P.current = !0, b.current = { ...T.current });
186
211
  }), () => {
187
- l = !0, f && (j().purge(f), v.current = !1);
212
+ a = !0, p && (V().purge(p), P.current = !1);
188
213
  };
189
- }, [p, N, w, d, h, M, b, P, B, k, O, o, _]), T(() => {
190
- const e = c.current;
191
- !e || !v.current || i <= 0 || n <= 0 || y.current.width === i && y.current.height === n || (y.current = { width: i, height: n }, j().relayout(e, { width: i, height: n }).catch(() => {
214
+ }, [c, Y, B, x, w, d, z, M, A, j, L, S, o, t, v, F]), H(() => {
215
+ const r = f.current;
216
+ !r || !P.current || l <= 0 || i <= 0 || b.current.width === l && b.current.height === i || (b.current = { width: l, height: i }, V().relayout(r, {
217
+ width: l,
218
+ height: i,
219
+ // Re-thin the y ticks for the new plot height (SW-2298). The flattened
220
+ // key is valid relayout input but not part of the Layout type.
221
+ "yaxis.tickvals": _.current
222
+ }).catch(() => {
192
223
  }));
193
- }, [i, n]), /* @__PURE__ */ W(
224
+ }, [l, i, K]), /* @__PURE__ */ D(
194
225
  "div",
195
226
  {
196
- ref: F,
197
- className: q("boxplot-container relative", H && "w-full", S && "h-full"),
227
+ ref: U,
228
+ className: nt("boxplot-container relative", W && "w-full", X && "h-full"),
198
229
  children: [
199
- /* @__PURE__ */ R("div", { ref: c, style: { width: "100%", height: "100%" } }),
200
- Y
230
+ /* @__PURE__ */ G("div", { ref: f, style: { width: "100%", height: "100%" } }),
231
+ R
201
232
  ]
202
233
  }
203
234
  );
204
235
  };
205
236
  export {
206
- nt as BoxPlot
237
+ yt as BoxPlot
207
238
  };
208
239
  //# sourceMappingURL=BoxPlot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BoxPlot.js","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.js","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":";;;;;;;;;AAcA,MAAMA,KAAyB;AAQ/B,SAASC,GAAYC,GAA2C;AAC9D,QAAM,EAAE,GAAAC,GAAG,GAAAC,GAAG,MAAAC,EAAA,IAASH;AACvB,SAAI,CAACC,KAAKA,EAAE,WAAWC,EAAE,SAAeD,IACpCA,EAAE,WAAW,IACR,MAAM,KAAK,EAAE,QAAQC,EAAE,UAAU,MAAMD,EAAE,CAAC,CAAC,KAEpD,QAAQ;AAAA,IACN,qBAAqBE,CAAI,SAASF,EAAE,MAAM,iBAAiBC,EAAE,MAAM,8CAC9B,KAAK,IAAID,EAAE,QAAQC,EAAE,MAAM,CAAC;AAAA,EAAA,GAG5DD;AACT;AAqCA,MAAMG,KAAkC,CAAC;AAAA,EACvC,YAAAC;AAAA,EACA,OAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,OAAAC,IAAQ;AAAA,EACR,YAAAC,IAAa;AACf,MAAM;AACJ,QAAMC,IAAUC,EAAuB,IAAI,GACrCC,IAAQC,GAAA,GACR,EAAE,aAAAC,GAAa,gBAAAC,EAAA,IAAmBC,GAAgB,EAAE,QAAQV,GAAQ,QAAQC,GAAQ,GAIpF,CAACU,GAAcC,CAAQ,IAAIC,GAAA,GAC3BC,IAAgBlB,KAASgB,EAAS,OAClCG,IAAiBlB,KAAUe,EAAS,QACpCI,IAAUF,IAAgB,KAAKC,IAAiB,GAGhDE,IAAYrB,MAAU,QACtBsB,IAAarB,MAAW,QAGxBsB,IAAQC,EAAkBN,GAAeC,CAAc,GAEvDM,IAAYnB,IAAQiB,EAAM,OAAO,SAASA,EAAM,OAAO,UACvDG,IAAUjB,EAAO,EAAE,OAAOS,GAAe,QAAQC,GAAgB;AACvE,EAAAO,EAAQ,UAAU,EAAE,OAAOR,GAAe,QAAQC,EAAA;AAClD,QAAMQ,IAAgBlB,EAAO,EAAK,GAG5BmB,IAAiBnB,EAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,GAE/C,EAAE,MAAAoB,GAAM,MAAAC,EAAA,IAASC,EAAQ,MAAM;AACnC,QAAIC,IAAO,OAAO,WACdC,IAAO,OAAO;AAElB,IAAAlC,EAAW,QAAQ,CAACL,MAAW;AAC7B,MAAAA,EAAO,EAAE,QAAQ,CAACE,MAAM;AACtB,QAAAoC,IAAO,KAAK,IAAIA,GAAMpC,CAAC,GACvBqC,IAAO,KAAK,IAAIA,GAAMrC,CAAC;AAAA,MACzB,CAAC;AAAA,IACH,CAAC;AAED,UAAMsC,KAAYD,IAAOD,KAAQ;AAEjC,WAAO;AAAA,MACL,MAAMA,IAAOE;AAAA,MACb,MAAMD,IAAOC;AAAA,IAAA;AAAA,EAEjB,GAAG,CAACnC,CAAU,CAAC,GAEToC,IAAkBJ;AAAA,IACtB,MAAM5B,KAAU,CAAC0B,GAAMC,CAAI;AAAA,IAC3B,CAAC3B,GAAQ0B,GAAMC,CAAI;AAAA,EAAA,GAGfM,IAASL,EAAQ,MAAM;AAC3B,UAAMM,IAAQF,EAAgB,CAAC,IAAIA,EAAgB,CAAC;AACpD,QAAIG,IAAO,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,MAAMD,CAAK,CAAC,CAAC;AAErD,IAAIA,IAAQC,IAAO,OAAIA,IAAOA,IAAO,IACjCD,IAAQC,IAAO,MAAGA,IAAOA,IAAO;AAEpC,UAAMC,IAAQ,CAAA;AACd,QAAIC,IAAU,KAAK,KAAKL,EAAgB,CAAC,IAAIG,CAAI,IAAIA;AACrD,WAAOE,KAAWL,EAAgB,CAAC;AACjC,MAAAI,EAAM,KAAKC,CAAO,GAClBA,KAAWF;AAEb,WAAOC;AAAA,EACT,GAAG,CAACJ,CAAe,CAAC,GAKdM,IAAYV;AAAA,IAChB,MACEW;AAAA,MACEN;AAAA,MACAO;AAAA,QACExB,IAAiBM,IAAYF,EAAM,OAAO;AAAA,QAC1CA,EAAM,eAAeqB;AAAA,MAAA;AAAA,IACvB;AAAA,IAEJ,CAACR,GAAQjB,GAAgBM,GAAWF,CAAK;AAAA,EAAA,GAErCsB,IAAWJ,EAAU,KAAK,GAAG,GAC7BK,IAAerC,EAAOgC,CAAS;AACrC,EAAAK,EAAa,UAAUL;AAEvB,QAAMM,IAAchB;AAAA,IAClB,OAAO;AAAA,MACL,WAAWrB,EAAM;AAAA,MACjB,SAASa,EAAM;AAAA,MACf,WAAW;AAAA,MACX,OAAO;AAAA,MACP,UAAU;AAAA,QACR,MAAMA,EAAM;AAAA,QACZ,OAAOb,EAAM;AAAA,QACb,QAAQsC;AAAA,QACR,QAAQ;AAAA,MAAA;AAAA,MAEV,WAAWtC,EAAM;AAAA,MACjB,WAAW;AAAA,MACX,UAAU;AAAA,MACV,UAAU;AAAA,IAAA;AAAA,IAEZ,CAACA,GAAOa,CAAK;AAAA,EAAA,GAGT0B,IAAelB;AAAA,IACnB,OAAO;AAAA,MACL,MAAMzB;AAAA,MACN,GAAG;AAAA,MACH,GAAG;AAAA,MACH,SAAS;AAAA,MACT,SAAS;AAAA,MACT,MAAM;AAAA,QACJ,MAAMiB,EAAM;AAAA,QACZ,QAAQ;AAAA,QACR,QAAQyB;AAAA,QACR,OAAOtC,EAAM;AAAA,QACb,YAAY;AAAA,QACZ,UAAU;AAAA,MAAA;AAAA,IACZ;AAAA,IAEF,CAACJ,GAAOI,GAAOa,CAAK;AAAA,EAAA,GAKhB2B,IAAYnB,EAAQ,MAAMhC,EAAW,IAAIN,EAAW,GAAG,CAACM,CAAU,CAAC;AAEzE,SAAAoD,EAAU,MAAM;AACd,QAAI,CAAC3C,EAAQ,WAAW,CAACY,EAAS;AAElC,UAAMgC,IAAOrD,EAAW,IAAI,CAACL,GAAQ2D,MAAU;AAC7C,YAAMC,IAAQC,GAAYF,GAAO3D,EAAO,KAAK;AAC7C,aAAO;AAAA,QACL,GAAGA,EAAO;AAAA,QACV,GAAGwD,EAAUG,CAAK;AAAA,QAClB,MAAM;AAAA,QACN,MAAM3D,EAAO;AAAA,QACb,WAAW;AAAA,QACX,QAAQ;AAAA,UACN,OAAA4D;AAAA,QAAA;AAAA,QAEF,MAAM;AAAA,UACJ,OAAAA;AAAA,QAAA;AAAA,QAEF,WACE,OAAOA,KAAU,YAAYA,EAAM,WAAW,GAAG,KAAKA,EAAM,WAAW,IACnE,GAAGA,CAAK,OACRA;AAAA;AAAA,QACN,WAAW/C,IACPb,EAAO,aAAa,aACnB;AAAA,QACL,QAAQA,EAAO,UAAU;AAAA,QACzB,UAAUA,EAAO,YAAYF;AAAA,MAAA;AAAA,IAEjC,CAAC,GAEKgE,IAAS;AAAA,MACb,OAAO9B,EAAQ,QAAQ;AAAA,MACvB,QAAQA,EAAQ,QAAQ;AAAA,MACxB,OAAOuB;AAAA;AAAA;AAAA,MAGP,QAAQ;AAAA,QACN,GAAG1B,EAAM,OAAO;AAAA,QAChB,GAAGA,EAAM,OAAO;AAAA,QAChB,GAAGA,EAAM,OAAO;AAAA,QAChB,GAAGE;AAAA,QACH,KAAK;AAAA,MAAA;AAAA,MAEP,eAAef,EAAM;AAAA,MACrB,cAAcA,EAAM;AAAA,MACpB,MAAM;AAAA,QACJ,QAAQsC;AAAA,MAAA;AAAA,MAEV,UAAU;AAAA,MACV,OAAO;AAAA,QACL,OAAO;AAAA,UACL,MAAM5C;AAAA,UACN,MAAM;AAAA,YACJ,MAAMmB,EAAM;AAAA,YACZ,OAAOb,EAAM;AAAA,YACb,QAAQsC;AAAA,YACR,QAAQ;AAAA,UAAA;AAAA,UAEV,UAAUzB,EAAM;AAAA,QAAA;AAAA,QAElB,WAAWb,EAAM;AAAA,QACjB,OAAOR;AAAA,QACP,WAAW,CAACA;AAAA,QACZ,UAAU;AAAA;AAAA;AAAA,QAGV,YAAY;AAAA,QACZ,GAAG6C;AAAA,MAAA;AAAA,MAEL,OAAO;AAAA,QACL,OAAO;AAAA,UACL,MAAM1C;AAAA,UACN,MAAM;AAAA,YACJ,MAAMkB,EAAM;AAAA,YACZ,OAAOb,EAAM;AAAA,YACb,QAAQsC;AAAA,YACR,QAAQ;AAAA,UAAA;AAAA,UAEV,UAAUzB,EAAM;AAAA,QAAA;AAAA,QAElB,WAAWb,EAAM;AAAA,QACjB,OAAOP;AAAA,QACP,WAAW,CAACA;AAAA,QACZ,UAAU;AAAA,QACV,UAAU2C,EAAa;AAAA,QACvB,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,GAAGC;AAAA,MAAA;AAAA,MAEL,QAAQ;AAAA;AAAA;AAAA;AAAA,QAIN,GAAG;AAAA,QACH,GAAG;AAAA,QACH,SAAS;AAAA,QACT,SAAS;AAAA,QACT,MAAM;AAAA,QACN,aAAa;AAAA,QACb,MAAM;AAAA,UACJ,MAAMxB,EAAM;AAAA,UACZ,OAAOb,EAAM;AAAA,UACb,QAAQsC;AAAA,UACR,QAAQ;AAAA,UACR,YAAYzB,EAAM;AAAA,QAAA;AAAA,MACpB;AAAA,MAEF,YAAY;AAAA,IAAA,GAGRkC,IAAS;AAAA;AAAA;AAAA,MAGb,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,aAAa;AAAA,IAAA;AAKf,QAAIC,IAAY,IACZC,IAAqC;AACzC,WAAKC,GAAA,EAAa,KAAK,CAACC,MAAW;AACjC,MAAIH,KAAa,CAAClD,EAAQ,YAC1BqD,EAAO,QAAQrD,EAAQ,SAAS4C,GAAMI,GAAQC,CAAM,GACpD7C,EAAYJ,EAAQ,OAAO,GAG3BmD,IAAcnD,EAAQ,SACtBmB,EAAc,UAAU,IACxBC,EAAe,UAAU,EAAE,GAAGF,EAAQ,QAAA;AAAA,IACxC,CAAC,GAEM,MAAM;AACX,MAAAgC,IAAY,IACRC,MACFG,EAAA,EAAkB,MAAMH,CAAW,GACnChC,EAAc,UAAU;AAAA,IAE5B;AAAA,EACF,GAAG,CAAC5B,GAAYmD,GAAW9B,GAASlB,GAAQC,GAAQgC,GAAiB/B,GAAQC,GAAQE,GAAY0C,GAAcF,GAAaX,GAAQ1B,GAAOa,GAAOE,GAAWb,CAAW,CAAC,GAIzKuC,EAAU,MAAM;AACd,UAAMQ,IAAcnD,EAAQ;AAC5B,IAAI,CAACmD,KAAe,CAAChC,EAAc,WAAWT,KAAiB,KAAKC,KAAkB,KAMpFS,EAAe,QAAQ,UAAUV,KACjCU,EAAe,QAAQ,WAAWT,MAIpCS,EAAe,UAAU,EAAE,OAAOV,GAAe,QAAQC,EAAA,GAGpD2C,EAAA,EACF,SAASH,GAAa;AAAA,MACrB,OAAOzC;AAAA,MACP,QAAQC;AAAA;AAAA;AAAA,MAGR,kBAAkB2B,EAAa;AAAA,IAAA,CACN,EAC1B,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnB,GAAG,CAAC5B,GAAeC,GAAgB0B,CAAQ,CAAC,GAG1C,gBAAAkB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAKhD;AAAA,MACL,WAAWiD,GAAG,8BAA8B3C,KAAa,UAAUC,KAAc,QAAQ;AAAA,MAEzF,UAAA;AAAA,QAAA,gBAAA2C,EAAC,OAAA,EAAI,KAAKzD,GAAS,OAAO,EAAE,OAAO,QAAQ,QAAQ,OAAA,EAAO,CAAG;AAAA,QAC5DK;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -1 +1 @@
1
- {"version":3,"file":"lines.cjs","sources":["../../../../src/components/charts/ChartTooltip/lines.ts"],"sourcesContent":["/**\n * Minimal structural view of a Plotly hover point — covers cartesian, pie,\n * and heatmap traces without depending on Plotly's loose event typings.\n */\nexport interface ChartTooltipHoverPoint {\n x?: number | string;\n y?: number | string;\n z?: number;\n /** Pie slice label */\n label?: string;\n /** Pie slice value */\n value?: number;\n /** Pie slice raw value (Plotly's `v` alias) */\n v?: number;\n /** Pie slice percentage (0–100) */\n percent?: number;\n /** Per-point hover text (e.g. PlateMap wells), lines separated by `<br>` */\n text?: string;\n pointIndex?: number;\n data?: { name?: string };\n /** Per-point custom data attached to the trace */\n customdata?: unknown;\n /** Hovered point bounds in plot-div pixels (provided by Plotly) */\n bbox?: { x0: number; x1: number; y0: number; y1: number };\n /**\n * Plotly axis objects (internal) used to project a data value to a plot-div\n * pixel. Bars report a `bbox` that sits below their visual top, so we anchor\n * to `_offset + l2p(value)` instead.\n */\n yaxis?: { _offset?: number; l2p?: (value: number) => number };\n xaxis?: { _offset?: number; l2p?: (value: number) => number };\n}\n\nconst formatValue = (value: number | string): string => {\n if (typeof value !== \"number\") return String(value);\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n};\n\nconst pieLines = (point: ChartTooltipHoverPoint): string[] => {\n const lines = [\n String(point.label),\n `Value: ${formatValue((point.value ?? point.v)!)}`,\n ];\n if (typeof point.percent === \"number\") {\n lines.push(`${point.percent.toFixed(1)}%`);\n } else if (typeof point.text === \"string\" && point.text.endsWith(\"%\")) {\n lines.push(point.text);\n }\n return lines;\n};\n\n/** Unified hover across multiple traces: shared x, one line per trace */\nconst unifiedLines = (\n points: ChartTooltipHoverPoint[],\n xLabel: string,\n): string[] => {\n const [first] = points;\n const lines =\n first.x === undefined ? [] : [`${xLabel}: ${formatValue(first.x)}`];\n for (const point of points) {\n if (point.y === undefined) continue;\n lines.push(`${point.data?.name ?? \"Series\"}: ${formatValue(point.y)}`);\n }\n return lines;\n};\n\nconst cartesianLines = (\n point: ChartTooltipHoverPoint,\n xLabel: string,\n yLabel: string,\n): string[] => {\n const lines: string[] = [];\n if (point.data?.name) lines.push(point.data.name);\n if (point.x !== undefined) lines.push(`${xLabel}: ${formatValue(point.x)}`);\n if (point.y !== undefined) lines.push(`${yLabel}: ${formatValue(point.y)}`);\n if (point.z !== undefined) lines.push(`Value: ${formatValue(point.z)}`);\n return lines;\n};\n\n/**\n * Build default tooltip lines from Plotly hover points. Handles pie slices,\n * text-carrying traces (e.g. PlateMap wells), single cartesian points, and\n * unified multi-trace hovers.\n */\nexport function chartTooltipLines(\n points: ChartTooltipHoverPoint[],\n labels?: { xLabel?: string; yLabel?: string },\n): string[] {\n if (points.length === 0) return [];\n const [first] = points;\n\n // Pie hover points carry label/value without axis coordinates; bar points\n // also alias label/value but always have x/y\n if (\n first.label !== undefined &&\n (first.value ?? first.v) !== undefined &&\n first.x === undefined &&\n first.y === undefined\n ) {\n return pieLines(first);\n }\n\n // Traces that carry their own hover text (e.g. PlateMap wells)\n if (typeof first.text === \"string\" && first.text.length > 0) {\n return first.text.split(\"<br>\").filter(Boolean);\n }\n\n const xLabel = labels?.xLabel || \"X\";\n const yLabel = labels?.yLabel || \"Y\";\n return points.length > 1\n ? unifiedLines(points, xLabel)\n : cartesianLines(first, xLabel, yLabel);\n}\n"],"names":["formatValue","value","pieLines","point","lines","unifiedLines","points","xLabel","first","cartesianLines","yLabel","chartTooltipLines","labels"],"mappings":"gFAiCA,MAAMA,EAAeC,GACf,OAAOA,GAAU,UACd,OAAO,UAAUA,CAAK,EADS,OAAOA,CAAK,EACDA,EAAM,QAAQ,CAAC,EAG5DC,EAAYC,GAA4C,CAC5D,MAAMC,EAAQ,CACZ,OAAOD,EAAM,KAAK,EAClB,UAAUH,EAAaG,EAAM,OAASA,EAAM,CAAG,CAAC,EAAA,EAElD,OAAI,OAAOA,EAAM,SAAY,SAC3BC,EAAM,KAAK,GAAGD,EAAM,QAAQ,QAAQ,CAAC,CAAC,GAAG,EAChC,OAAOA,EAAM,MAAS,UAAYA,EAAM,KAAK,SAAS,GAAG,GAClEC,EAAM,KAAKD,EAAM,IAAI,EAEhBC,CACT,EAGMC,EAAe,CACnBC,EACAC,IACa,CACb,KAAM,CAACC,CAAK,EAAIF,EACVF,EACJI,EAAM,IAAM,OAAY,CAAA,EAAK,CAAC,GAAGD,CAAM,KAAKP,EAAYQ,EAAM,CAAC,CAAC,EAAE,EACpE,UAAWL,KAASG,EACdH,EAAM,IAAM,QAChBC,EAAM,KAAK,GAAGD,EAAM,MAAM,MAAQ,QAAQ,KAAKH,EAAYG,EAAM,CAAC,CAAC,EAAE,EAEvE,OAAOC,CACT,EAEMK,EAAiB,CACrBN,EACAI,EACAG,IACa,CACb,MAAMN,EAAkB,CAAA,EACxB,OAAID,EAAM,MAAM,QAAY,KAAKA,EAAM,KAAK,IAAI,EAC5CA,EAAM,IAAM,QAAWC,EAAM,KAAK,GAAGG,CAAM,KAAKP,EAAYG,EAAM,CAAC,CAAC,EAAE,EACtEA,EAAM,IAAM,QAAWC,EAAM,KAAK,GAAGM,CAAM,KAAKV,EAAYG,EAAM,CAAC,CAAC,EAAE,EACtEA,EAAM,IAAM,QAAWC,EAAM,KAAK,UAAUJ,EAAYG,EAAM,CAAC,CAAC,EAAE,EAC/DC,CACT,EAOO,SAASO,EACdL,EACAM,EACU,CACV,GAAIN,EAAO,SAAW,EAAG,MAAO,CAAA,EAChC,KAAM,CAACE,CAAK,EAAIF,EAIhB,GACEE,EAAM,QAAU,SACfA,EAAM,OAASA,EAAM,KAAO,QAC7BA,EAAM,IAAM,QACZA,EAAM,IAAM,OAEZ,OAAON,EAASM,CAAK,EAIvB,GAAI,OAAOA,EAAM,MAAS,UAAYA,EAAM,KAAK,OAAS,EACxD,OAAOA,EAAM,KAAK,MAAM,MAAM,EAAE,OAAO,OAAO,EAGhD,MAAMD,EAASK,GAAQ,QAAU,IAC3BF,EAASE,GAAQ,QAAU,IACjC,OAAON,EAAO,OAAS,EACnBD,EAAaC,EAAQC,CAAM,EAC3BE,EAAeD,EAAOD,EAAQG,CAAM,CAC1C"}
1
+ {"version":3,"file":"lines.cjs","sources":["../../../../src/components/charts/ChartTooltip/lines.ts"],"sourcesContent":["/**\n * Minimal structural view of a Plotly hover point — covers cartesian, pie,\n * and heatmap traces without depending on Plotly's loose event typings.\n */\nexport interface ChartTooltipHoverPoint {\n x?: number | string;\n y?: number | string;\n z?: number;\n /** Pie slice label */\n label?: string;\n /** Pie slice value */\n value?: number;\n /** Pie slice raw value (Plotly's `v` alias) */\n v?: number;\n /** Pie slice percentage (0–100) */\n percent?: number;\n /** Per-point hover text (e.g. PlateMap wells), lines separated by `<br>` */\n text?: string;\n pointIndex?: number;\n data?: { name?: string };\n /** Per-point custom data attached to the trace */\n customdata?: unknown;\n /** Index of the trace the point belongs to (Plotly always supplies it) */\n curveNumber?: number;\n /** Hovered point bounds in plot-div pixels (provided by Plotly) */\n bbox?: { x0: number; x1: number; y0: number; y1: number };\n /**\n * Plotly axis objects (internal) used to project a data value to a plot-div\n * pixel. Bars report a `bbox` that sits below their visual top, so we anchor\n * to `_offset + l2p(value)` instead.\n */\n yaxis?: { _offset?: number; l2p?: (value: number) => number };\n xaxis?: { _offset?: number; l2p?: (value: number) => number };\n}\n\nconst formatValue = (value: number | string): string => {\n if (typeof value !== \"number\") return String(value);\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n};\n\nconst pieLines = (point: ChartTooltipHoverPoint): string[] => {\n const lines = [\n String(point.label),\n `Value: ${formatValue((point.value ?? point.v)!)}`,\n ];\n if (typeof point.percent === \"number\") {\n lines.push(`${point.percent.toFixed(1)}%`);\n } else if (typeof point.text === \"string\" && point.text.endsWith(\"%\")) {\n lines.push(point.text);\n }\n return lines;\n};\n\n/** Unified hover across multiple traces: shared x, one line per trace */\nconst unifiedLines = (\n points: ChartTooltipHoverPoint[],\n xLabel: string,\n): string[] => {\n const [first] = points;\n const lines =\n first.x === undefined ? [] : [`${xLabel}: ${formatValue(first.x)}`];\n for (const point of points) {\n if (point.y === undefined) continue;\n lines.push(`${point.data?.name ?? \"Series\"}: ${formatValue(point.y)}`);\n }\n return lines;\n};\n\nconst cartesianLines = (\n point: ChartTooltipHoverPoint,\n xLabel: string,\n yLabel: string,\n): string[] => {\n const lines: string[] = [];\n if (point.data?.name) lines.push(point.data.name);\n if (point.x !== undefined) lines.push(`${xLabel}: ${formatValue(point.x)}`);\n if (point.y !== undefined) lines.push(`${yLabel}: ${formatValue(point.y)}`);\n if (point.z !== undefined) lines.push(`Value: ${formatValue(point.z)}`);\n return lines;\n};\n\n/**\n * Build default tooltip lines from Plotly hover points. Handles pie slices,\n * text-carrying traces (e.g. PlateMap wells), single cartesian points, and\n * unified multi-trace hovers.\n */\nexport function chartTooltipLines(\n points: ChartTooltipHoverPoint[],\n labels?: { xLabel?: string; yLabel?: string },\n): string[] {\n if (points.length === 0) return [];\n const [first] = points;\n\n // Pie hover points carry label/value without axis coordinates; bar points\n // also alias label/value but always have x/y\n if (\n first.label !== undefined &&\n (first.value ?? first.v) !== undefined &&\n first.x === undefined &&\n first.y === undefined\n ) {\n return pieLines(first);\n }\n\n // Traces that carry their own hover text (e.g. PlateMap wells)\n if (typeof first.text === \"string\" && first.text.length > 0) {\n return first.text.split(\"<br>\").filter(Boolean);\n }\n\n const xLabel = labels?.xLabel || \"X\";\n const yLabel = labels?.yLabel || \"Y\";\n return points.length > 1\n ? unifiedLines(points, xLabel)\n : cartesianLines(first, xLabel, yLabel);\n}\n"],"names":["formatValue","value","pieLines","point","lines","unifiedLines","points","xLabel","first","cartesianLines","yLabel","chartTooltipLines","labels"],"mappings":"gFAmCA,MAAMA,EAAeC,GACf,OAAOA,GAAU,UACd,OAAO,UAAUA,CAAK,EADS,OAAOA,CAAK,EACDA,EAAM,QAAQ,CAAC,EAG5DC,EAAYC,GAA4C,CAC5D,MAAMC,EAAQ,CACZ,OAAOD,EAAM,KAAK,EAClB,UAAUH,EAAaG,EAAM,OAASA,EAAM,CAAG,CAAC,EAAA,EAElD,OAAI,OAAOA,EAAM,SAAY,SAC3BC,EAAM,KAAK,GAAGD,EAAM,QAAQ,QAAQ,CAAC,CAAC,GAAG,EAChC,OAAOA,EAAM,MAAS,UAAYA,EAAM,KAAK,SAAS,GAAG,GAClEC,EAAM,KAAKD,EAAM,IAAI,EAEhBC,CACT,EAGMC,EAAe,CACnBC,EACAC,IACa,CACb,KAAM,CAACC,CAAK,EAAIF,EACVF,EACJI,EAAM,IAAM,OAAY,CAAA,EAAK,CAAC,GAAGD,CAAM,KAAKP,EAAYQ,EAAM,CAAC,CAAC,EAAE,EACpE,UAAWL,KAASG,EACdH,EAAM,IAAM,QAChBC,EAAM,KAAK,GAAGD,EAAM,MAAM,MAAQ,QAAQ,KAAKH,EAAYG,EAAM,CAAC,CAAC,EAAE,EAEvE,OAAOC,CACT,EAEMK,EAAiB,CACrBN,EACAI,EACAG,IACa,CACb,MAAMN,EAAkB,CAAA,EACxB,OAAID,EAAM,MAAM,QAAY,KAAKA,EAAM,KAAK,IAAI,EAC5CA,EAAM,IAAM,QAAWC,EAAM,KAAK,GAAGG,CAAM,KAAKP,EAAYG,EAAM,CAAC,CAAC,EAAE,EACtEA,EAAM,IAAM,QAAWC,EAAM,KAAK,GAAGM,CAAM,KAAKV,EAAYG,EAAM,CAAC,CAAC,EAAE,EACtEA,EAAM,IAAM,QAAWC,EAAM,KAAK,UAAUJ,EAAYG,EAAM,CAAC,CAAC,EAAE,EAC/DC,CACT,EAOO,SAASO,EACdL,EACAM,EACU,CACV,GAAIN,EAAO,SAAW,EAAG,MAAO,CAAA,EAChC,KAAM,CAACE,CAAK,EAAIF,EAIhB,GACEE,EAAM,QAAU,SACfA,EAAM,OAASA,EAAM,KAAO,QAC7BA,EAAM,IAAM,QACZA,EAAM,IAAM,OAEZ,OAAON,EAASM,CAAK,EAIvB,GAAI,OAAOA,EAAM,MAAS,UAAYA,EAAM,KAAK,OAAS,EACxD,OAAOA,EAAM,KAAK,MAAM,MAAM,EAAE,OAAO,OAAO,EAGhD,MAAMD,EAASK,GAAQ,QAAU,IAC3BF,EAASE,GAAQ,QAAU,IACjC,OAAON,EAAO,OAAS,EACnBD,EAAaC,EAAQC,CAAM,EAC3BE,EAAeD,EAAOD,EAAQG,CAAM,CAC1C"}
@@ -22,6 +22,8 @@ export interface ChartTooltipHoverPoint {
22
22
  };
23
23
  /** Per-point custom data attached to the trace */
24
24
  customdata?: unknown;
25
+ /** Index of the trace the point belongs to (Plotly always supplies it) */
26
+ curveNumber?: number;
25
27
  /** Hovered point bounds in plot-div pixels (provided by Plotly) */
26
28
  bbox?: {
27
29
  x0: number;
@@ -1 +1 @@
1
- {"version":3,"file":"lines.js","sources":["../../../../src/components/charts/ChartTooltip/lines.ts"],"sourcesContent":["/**\n * Minimal structural view of a Plotly hover point — covers cartesian, pie,\n * and heatmap traces without depending on Plotly's loose event typings.\n */\nexport interface ChartTooltipHoverPoint {\n x?: number | string;\n y?: number | string;\n z?: number;\n /** Pie slice label */\n label?: string;\n /** Pie slice value */\n value?: number;\n /** Pie slice raw value (Plotly's `v` alias) */\n v?: number;\n /** Pie slice percentage (0–100) */\n percent?: number;\n /** Per-point hover text (e.g. PlateMap wells), lines separated by `<br>` */\n text?: string;\n pointIndex?: number;\n data?: { name?: string };\n /** Per-point custom data attached to the trace */\n customdata?: unknown;\n /** Hovered point bounds in plot-div pixels (provided by Plotly) */\n bbox?: { x0: number; x1: number; y0: number; y1: number };\n /**\n * Plotly axis objects (internal) used to project a data value to a plot-div\n * pixel. Bars report a `bbox` that sits below their visual top, so we anchor\n * to `_offset + l2p(value)` instead.\n */\n yaxis?: { _offset?: number; l2p?: (value: number) => number };\n xaxis?: { _offset?: number; l2p?: (value: number) => number };\n}\n\nconst formatValue = (value: number | string): string => {\n if (typeof value !== \"number\") return String(value);\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n};\n\nconst pieLines = (point: ChartTooltipHoverPoint): string[] => {\n const lines = [\n String(point.label),\n `Value: ${formatValue((point.value ?? point.v)!)}`,\n ];\n if (typeof point.percent === \"number\") {\n lines.push(`${point.percent.toFixed(1)}%`);\n } else if (typeof point.text === \"string\" && point.text.endsWith(\"%\")) {\n lines.push(point.text);\n }\n return lines;\n};\n\n/** Unified hover across multiple traces: shared x, one line per trace */\nconst unifiedLines = (\n points: ChartTooltipHoverPoint[],\n xLabel: string,\n): string[] => {\n const [first] = points;\n const lines =\n first.x === undefined ? [] : [`${xLabel}: ${formatValue(first.x)}`];\n for (const point of points) {\n if (point.y === undefined) continue;\n lines.push(`${point.data?.name ?? \"Series\"}: ${formatValue(point.y)}`);\n }\n return lines;\n};\n\nconst cartesianLines = (\n point: ChartTooltipHoverPoint,\n xLabel: string,\n yLabel: string,\n): string[] => {\n const lines: string[] = [];\n if (point.data?.name) lines.push(point.data.name);\n if (point.x !== undefined) lines.push(`${xLabel}: ${formatValue(point.x)}`);\n if (point.y !== undefined) lines.push(`${yLabel}: ${formatValue(point.y)}`);\n if (point.z !== undefined) lines.push(`Value: ${formatValue(point.z)}`);\n return lines;\n};\n\n/**\n * Build default tooltip lines from Plotly hover points. Handles pie slices,\n * text-carrying traces (e.g. PlateMap wells), single cartesian points, and\n * unified multi-trace hovers.\n */\nexport function chartTooltipLines(\n points: ChartTooltipHoverPoint[],\n labels?: { xLabel?: string; yLabel?: string },\n): string[] {\n if (points.length === 0) return [];\n const [first] = points;\n\n // Pie hover points carry label/value without axis coordinates; bar points\n // also alias label/value but always have x/y\n if (\n first.label !== undefined &&\n (first.value ?? first.v) !== undefined &&\n first.x === undefined &&\n first.y === undefined\n ) {\n return pieLines(first);\n }\n\n // Traces that carry their own hover text (e.g. PlateMap wells)\n if (typeof first.text === \"string\" && first.text.length > 0) {\n return first.text.split(\"<br>\").filter(Boolean);\n }\n\n const xLabel = labels?.xLabel || \"X\";\n const yLabel = labels?.yLabel || \"Y\";\n return points.length > 1\n ? unifiedLines(points, xLabel)\n : cartesianLines(first, xLabel, yLabel);\n}\n"],"names":["formatValue","value","pieLines","point","lines","unifiedLines","points","xLabel","first","cartesianLines","yLabel","chartTooltipLines","labels"],"mappings":"AAiCA,MAAMA,IAAc,CAACC,MACf,OAAOA,KAAU,YACd,OAAO,UAAUA,CAAK,IADS,OAAOA,CAAK,IACDA,EAAM,QAAQ,CAAC,GAG5DC,IAAW,CAACC,MAA4C;AAC5D,QAAMC,IAAQ;AAAA,IACZ,OAAOD,EAAM,KAAK;AAAA,IAClB,UAAUH,EAAaG,EAAM,SAASA,EAAM,CAAG,CAAC;AAAA,EAAA;AAElD,SAAI,OAAOA,EAAM,WAAY,WAC3BC,EAAM,KAAK,GAAGD,EAAM,QAAQ,QAAQ,CAAC,CAAC,GAAG,IAChC,OAAOA,EAAM,QAAS,YAAYA,EAAM,KAAK,SAAS,GAAG,KAClEC,EAAM,KAAKD,EAAM,IAAI,GAEhBC;AACT,GAGMC,IAAe,CACnBC,GACAC,MACa;AACb,QAAM,CAACC,CAAK,IAAIF,GACVF,IACJI,EAAM,MAAM,SAAY,CAAA,IAAK,CAAC,GAAGD,CAAM,KAAKP,EAAYQ,EAAM,CAAC,CAAC,EAAE;AACpE,aAAWL,KAASG;AAClB,IAAIH,EAAM,MAAM,UAChBC,EAAM,KAAK,GAAGD,EAAM,MAAM,QAAQ,QAAQ,KAAKH,EAAYG,EAAM,CAAC,CAAC,EAAE;AAEvE,SAAOC;AACT,GAEMK,IAAiB,CACrBN,GACAI,GACAG,MACa;AACb,QAAMN,IAAkB,CAAA;AACxB,SAAID,EAAM,MAAM,UAAY,KAAKA,EAAM,KAAK,IAAI,GAC5CA,EAAM,MAAM,UAAWC,EAAM,KAAK,GAAGG,CAAM,KAAKP,EAAYG,EAAM,CAAC,CAAC,EAAE,GACtEA,EAAM,MAAM,UAAWC,EAAM,KAAK,GAAGM,CAAM,KAAKV,EAAYG,EAAM,CAAC,CAAC,EAAE,GACtEA,EAAM,MAAM,UAAWC,EAAM,KAAK,UAAUJ,EAAYG,EAAM,CAAC,CAAC,EAAE,GAC/DC;AACT;AAOO,SAASO,EACdL,GACAM,GACU;AACV,MAAIN,EAAO,WAAW,EAAG,QAAO,CAAA;AAChC,QAAM,CAACE,CAAK,IAAIF;AAIhB,MACEE,EAAM,UAAU,WACfA,EAAM,SAASA,EAAM,OAAO,UAC7BA,EAAM,MAAM,UACZA,EAAM,MAAM;AAEZ,WAAON,EAASM,CAAK;AAIvB,MAAI,OAAOA,EAAM,QAAS,YAAYA,EAAM,KAAK,SAAS;AACxD,WAAOA,EAAM,KAAK,MAAM,MAAM,EAAE,OAAO,OAAO;AAGhD,QAAMD,IAASK,GAAQ,UAAU,KAC3BF,IAASE,GAAQ,UAAU;AACjC,SAAON,EAAO,SAAS,IACnBD,EAAaC,GAAQC,CAAM,IAC3BE,EAAeD,GAAOD,GAAQG,CAAM;AAC1C;"}
1
+ {"version":3,"file":"lines.js","sources":["../../../../src/components/charts/ChartTooltip/lines.ts"],"sourcesContent":["/**\n * Minimal structural view of a Plotly hover point — covers cartesian, pie,\n * and heatmap traces without depending on Plotly's loose event typings.\n */\nexport interface ChartTooltipHoverPoint {\n x?: number | string;\n y?: number | string;\n z?: number;\n /** Pie slice label */\n label?: string;\n /** Pie slice value */\n value?: number;\n /** Pie slice raw value (Plotly's `v` alias) */\n v?: number;\n /** Pie slice percentage (0–100) */\n percent?: number;\n /** Per-point hover text (e.g. PlateMap wells), lines separated by `<br>` */\n text?: string;\n pointIndex?: number;\n data?: { name?: string };\n /** Per-point custom data attached to the trace */\n customdata?: unknown;\n /** Index of the trace the point belongs to (Plotly always supplies it) */\n curveNumber?: number;\n /** Hovered point bounds in plot-div pixels (provided by Plotly) */\n bbox?: { x0: number; x1: number; y0: number; y1: number };\n /**\n * Plotly axis objects (internal) used to project a data value to a plot-div\n * pixel. Bars report a `bbox` that sits below their visual top, so we anchor\n * to `_offset + l2p(value)` instead.\n */\n yaxis?: { _offset?: number; l2p?: (value: number) => number };\n xaxis?: { _offset?: number; l2p?: (value: number) => number };\n}\n\nconst formatValue = (value: number | string): string => {\n if (typeof value !== \"number\") return String(value);\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n};\n\nconst pieLines = (point: ChartTooltipHoverPoint): string[] => {\n const lines = [\n String(point.label),\n `Value: ${formatValue((point.value ?? point.v)!)}`,\n ];\n if (typeof point.percent === \"number\") {\n lines.push(`${point.percent.toFixed(1)}%`);\n } else if (typeof point.text === \"string\" && point.text.endsWith(\"%\")) {\n lines.push(point.text);\n }\n return lines;\n};\n\n/** Unified hover across multiple traces: shared x, one line per trace */\nconst unifiedLines = (\n points: ChartTooltipHoverPoint[],\n xLabel: string,\n): string[] => {\n const [first] = points;\n const lines =\n first.x === undefined ? [] : [`${xLabel}: ${formatValue(first.x)}`];\n for (const point of points) {\n if (point.y === undefined) continue;\n lines.push(`${point.data?.name ?? \"Series\"}: ${formatValue(point.y)}`);\n }\n return lines;\n};\n\nconst cartesianLines = (\n point: ChartTooltipHoverPoint,\n xLabel: string,\n yLabel: string,\n): string[] => {\n const lines: string[] = [];\n if (point.data?.name) lines.push(point.data.name);\n if (point.x !== undefined) lines.push(`${xLabel}: ${formatValue(point.x)}`);\n if (point.y !== undefined) lines.push(`${yLabel}: ${formatValue(point.y)}`);\n if (point.z !== undefined) lines.push(`Value: ${formatValue(point.z)}`);\n return lines;\n};\n\n/**\n * Build default tooltip lines from Plotly hover points. Handles pie slices,\n * text-carrying traces (e.g. PlateMap wells), single cartesian points, and\n * unified multi-trace hovers.\n */\nexport function chartTooltipLines(\n points: ChartTooltipHoverPoint[],\n labels?: { xLabel?: string; yLabel?: string },\n): string[] {\n if (points.length === 0) return [];\n const [first] = points;\n\n // Pie hover points carry label/value without axis coordinates; bar points\n // also alias label/value but always have x/y\n if (\n first.label !== undefined &&\n (first.value ?? first.v) !== undefined &&\n first.x === undefined &&\n first.y === undefined\n ) {\n return pieLines(first);\n }\n\n // Traces that carry their own hover text (e.g. PlateMap wells)\n if (typeof first.text === \"string\" && first.text.length > 0) {\n return first.text.split(\"<br>\").filter(Boolean);\n }\n\n const xLabel = labels?.xLabel || \"X\";\n const yLabel = labels?.yLabel || \"Y\";\n return points.length > 1\n ? unifiedLines(points, xLabel)\n : cartesianLines(first, xLabel, yLabel);\n}\n"],"names":["formatValue","value","pieLines","point","lines","unifiedLines","points","xLabel","first","cartesianLines","yLabel","chartTooltipLines","labels"],"mappings":"AAmCA,MAAMA,IAAc,CAACC,MACf,OAAOA,KAAU,YACd,OAAO,UAAUA,CAAK,IADS,OAAOA,CAAK,IACDA,EAAM,QAAQ,CAAC,GAG5DC,IAAW,CAACC,MAA4C;AAC5D,QAAMC,IAAQ;AAAA,IACZ,OAAOD,EAAM,KAAK;AAAA,IAClB,UAAUH,EAAaG,EAAM,SAASA,EAAM,CAAG,CAAC;AAAA,EAAA;AAElD,SAAI,OAAOA,EAAM,WAAY,WAC3BC,EAAM,KAAK,GAAGD,EAAM,QAAQ,QAAQ,CAAC,CAAC,GAAG,IAChC,OAAOA,EAAM,QAAS,YAAYA,EAAM,KAAK,SAAS,GAAG,KAClEC,EAAM,KAAKD,EAAM,IAAI,GAEhBC;AACT,GAGMC,IAAe,CACnBC,GACAC,MACa;AACb,QAAM,CAACC,CAAK,IAAIF,GACVF,IACJI,EAAM,MAAM,SAAY,CAAA,IAAK,CAAC,GAAGD,CAAM,KAAKP,EAAYQ,EAAM,CAAC,CAAC,EAAE;AACpE,aAAWL,KAASG;AAClB,IAAIH,EAAM,MAAM,UAChBC,EAAM,KAAK,GAAGD,EAAM,MAAM,QAAQ,QAAQ,KAAKH,EAAYG,EAAM,CAAC,CAAC,EAAE;AAEvE,SAAOC;AACT,GAEMK,IAAiB,CACrBN,GACAI,GACAG,MACa;AACb,QAAMN,IAAkB,CAAA;AACxB,SAAID,EAAM,MAAM,UAAY,KAAKA,EAAM,KAAK,IAAI,GAC5CA,EAAM,MAAM,UAAWC,EAAM,KAAK,GAAGG,CAAM,KAAKP,EAAYG,EAAM,CAAC,CAAC,EAAE,GACtEA,EAAM,MAAM,UAAWC,EAAM,KAAK,GAAGM,CAAM,KAAKV,EAAYG,EAAM,CAAC,CAAC,EAAE,GACtEA,EAAM,MAAM,UAAWC,EAAM,KAAK,UAAUJ,EAAYG,EAAM,CAAC,CAAC,EAAE,GAC/DC;AACT;AAOO,SAASO,EACdL,GACAM,GACU;AACV,MAAIN,EAAO,WAAW,EAAG,QAAO,CAAA;AAChC,QAAM,CAACE,CAAK,IAAIF;AAIhB,MACEE,EAAM,UAAU,WACfA,EAAM,SAASA,EAAM,OAAO,UAC7BA,EAAM,MAAM,UACZA,EAAM,MAAM;AAEZ,WAAON,EAASM,CAAK;AAIvB,MAAI,OAAOA,EAAM,QAAS,YAAYA,EAAM,KAAK,SAAS;AACxD,WAAOA,EAAM,KAAK,MAAM,MAAM,EAAE,OAAO,OAAO;AAGhD,QAAMD,IAASK,GAAQ,UAAU,KAC3BF,IAASE,GAAQ,UAAU;AACjC,SAAON,EAAO,SAAS,IACnBD,EAAaC,GAAQC,CAAM,IAC3BE,EAAeD,GAAOD,GAAQG,CAAM;AAC1C;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("react/jsx-runtime"),r=require("react"),de=require("../ChartTooltip/ChartTooltip.cjs"),j=require("../plotly-loader.cjs"),_=require("./annotations.cjs"),B=require("./dataProcessing.cjs"),ie=require("./peakDetection.cjs"),f=require("./plotBuilder.cjs"),fe=require("../../composed/EmptyState/EmptyState.cjs"),pe=require("../../../hooks/use-plotly-theme.cjs"),me=[],he=({series:c,width:y=900,height:g=500,title:D,xAxisTitle:v="Retention Time (min)",yAxisTitle:k="Signal (mAU)",annotations:E=me,xRange:H,yRange:$,showLegend:T=!0,showGridX:U=!0,showGridY:W=!0,showMarkers:A=!1,markerSize:x=4,showCrosshairs:F=!1,baselineCorrection:I="none",baselineWindowSize:J=50,peakDetectionOptions:p,showPeakAreas:R=!1,boundaryMarkers:K="none",annotationOverlapThreshold:q=.4,showExportButton:Q=!0,selectedPeakIds:L,onPeakClick:V,onPeakHover:Y,selectionAppearance:m,annotationStyle:Z="arrow",titleFontSize:G=20,titleTopMargin:O})=>{const h=p!==void 0,b=r.useRef(null),X=pe.usePlotlyTheme(),{bindTooltip:z,tooltipElement:ae}=de.useChartTooltip({xLabel:v,yLabel:k}),w=r.useRef(V),M=r.useRef(Y);w.current=V,M.current=Y;const N=r.useRef(null),ee=r.useRef([]),l=r.useMemo(()=>c.map(e=>{const t=B.validateSeriesData(e.x,e.y);return{...e,x:t.x,y:B.applyBaselineCorrection(t.y,I,J)}}),[c,I,J]),s=r.useMemo(()=>{if(E.length===0||l.length===0)return E;const{x:e,y:t}=l[0];return B.processUserAnnotations(E,e,t)},[E,l]),d=r.useMemo(()=>{const e=[];return h&&p&&l.forEach((t,o)=>{const a=ie.detectPeaks(t.x,t.y,p);a.length>0&&e.push({peaks:a,seriesIndex:o})}),e},[l,h,p]),te=m?.selected?.borderColor,oe=m?.selected?.backgroundColor,re=m?.selected?.bold,ne=m?.unselected?.opacity,ce=m?.hoverLineWidthMultiplier,C=r.useMemo(()=>_.resolveSelectionAppearance({selected:{borderColor:te,backgroundColor:oe,bold:re},unselected:{opacity:ne},hoverLineWidthMultiplier:ce}),[te,oe,re,ne,ce]),le=r.useMemo(()=>{const e=[];return s.forEach((t,o)=>{e.push({peak:{...t,id:t.id??`user-ann-${o}`},seriesIndex:0,seriesName:c[0]?.name??"",isAutoDetected:!1})}),d.forEach(({peaks:t,seriesIndex:o})=>{t.forEach((a,u)=>{e.push({peak:{...a,id:`peak-${o}-${u}`},seriesIndex:o,seriesName:c[o]?.name??`Series ${o+1}`,isAutoDetected:!0})})}),e},[s,d,c]),S=r.useMemo(()=>{const e=(L?.length??0)>0,t={selectedPeakIds:L??[],anySelected:e,appearance:C,annotationStyle:Z},o=[];s.forEach((n,i)=>{o.push({peak:{...n,id:n.id??`user-ann-${i}`},seriesIndex:-1})}),R&&h&&d.forEach(({peaks:n,seriesIndex:i})=>{n.forEach((ue,se)=>{o.push({peak:{...ue,id:`peak-${i}-${se}`},seriesIndex:i})})});const a=_.groupOverlappingPeaks(o,q),u=[];for(const n of a)u.push(..._.createGroupAnnotations(n,t));return u},[s,d,R,h,q,L,C,Z]);return ee.current=S,r.useEffect(()=>{const e=b.current;if(!e||c.length===0)return;const t=f.buildTraceData({processedSeries:l,processedAnnotations:s,allDetectedPeaks:d,allPeaksForInteraction:le,showMarkers:A,markerSize:x,xAxisTitle:v,yAxisTitle:k,boundaryMarkers:K}),o=f.buildLayout({title:D,titleFontSize:G,titleTopMargin:O,width:y,height:g,xAxisTitle:v,yAxisTitle:k,xRange:H,yRange:$,showLegend:T,seriesCount:c.length,showGridX:U,showGridY:W,showCrosshairs:F,theme:X,peakAnnotations:ee.current}),a=f.buildConfig({showExportButton:Q,width:y,height:g});let u=!1,n=null;return j.loadPlotly().then(i=>{u||!b.current||(i.newPlot(e,t,o,a),z(e),e.on("plotly_click",f.createClickHandler(w)),e.on("plotly_hover",f.createHoverHandler(e,l.length,N,M,C.hoverLineWidthMultiplier)),e.on("plotly_unhover",f.createUnhoverHandler(e,N,M)),n=e)}),()=>{u=!0,N.current=null,n&&j.getLoadedPlotly().purge(n)}},[l,d,le,c.length,y,g,D,G,O,v,k,s,H,$,T,U,W,A,x,F,h,p,R,K,q,Q,X,z,C]),r.useEffect(()=>{const e=b.current;e&&e._fullLayout&&j.getLoadedPlotly().relayout(e,{annotations:S})},[S]),c.length===0?P.jsx("div",{className:"chromatogram-chart-container relative flex items-center justify-center",style:{width:y,height:g},children:P.jsx(fe.EmptyState,{variant:"no-data",title:"No chromatogram data",description:"There is no signal data to display yet."})}):P.jsxs("div",{className:"chromatogram-chart-container relative",children:[P.jsx("div",{ref:b,style:{width:"100%",height:"100%"}}),ae]})};exports.Chromatogram=he;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("react/jsx-runtime"),r=require("react"),ie=require("../ChartTooltip/ChartTooltip.cjs"),j=require("../plotly-loader.cjs"),T=require("./annotations.cjs"),_=require("./dataProcessing.cjs"),de=require("./peakDetection.cjs"),s=require("./plotBuilder.cjs"),fe=require("../../composed/EmptyState/EmptyState.cjs"),pe=require("../../../hooks/use-plotly-theme.cjs"),me=[],he=({series:n,width:g=900,height:y=500,title:B,xAxisTitle:P="Retention Time (min)",yAxisTitle:R="Signal (mAU)",annotations:v=me,xRange:D,yRange:H,showLegend:$=!0,showGridX:A=!0,showGridY:U=!0,showMarkers:W=!1,markerSize:x=4,showCrosshairs:F=!1,baselineCorrection:I="none",baselineWindowSize:J=50,peakDetectionOptions:p,showPeakAreas:b=!1,boundaryMarkers:K="none",annotationOverlapThreshold:q=.4,showExportButton:Q=!0,selectedPeakIds:L,onPeakClick:V,onPeakHover:Y,selectionAppearance:m,annotationStyle:Z="arrow",titleFontSize:G=20,titleTopMargin:O})=>{const h=p!==void 0,k=r.useRef(null),X=pe.usePlotlyTheme(),{bindTooltip:z,tooltipElement:ue}=ie.useChartTooltip({getLines:e=>s.buildChromatogramTooltipLines(e,{series:n,xAxisTitle:P,yAxisTitle:R})}),w=r.useRef(V),M=r.useRef(Y);w.current=V,M.current=Y;const N=r.useRef(null),ee=r.useRef([]),l=r.useMemo(()=>n.map(e=>{const t=_.validateSeriesData(e.x,e.y);return{...e,x:t.x,y:_.applyBaselineCorrection(t.y,I,J)}}),[n,I,J]),i=r.useMemo(()=>{if(v.length===0||l.length===0)return v;const{x:e,y:t}=l[0];return _.processUserAnnotations(v,e,t)},[v,l]),d=r.useMemo(()=>{const e=[];return h&&p&&l.forEach((t,o)=>{const u=de.detectPeaks(t.x,t.y,p);u.length>0&&e.push({peaks:u,seriesIndex:o})}),e},[l,h,p]),te=m?.selected?.borderColor,oe=m?.selected?.backgroundColor,re=m?.selected?.bold,ne=m?.unselected?.opacity,ce=m?.hoverLineWidthMultiplier,C=r.useMemo(()=>T.resolveSelectionAppearance({selected:{borderColor:te,backgroundColor:oe,bold:re},unselected:{opacity:ne},hoverLineWidthMultiplier:ce}),[te,oe,re,ne,ce]),le=r.useMemo(()=>{const e=[];return i.forEach((t,o)=>{e.push({peak:{...t,id:t.id??`user-ann-${o}`},seriesIndex:0,seriesName:n[0]?.name??"",isAutoDetected:!1})}),d.forEach(({peaks:t,seriesIndex:o})=>{t.forEach((u,a)=>{e.push({peak:{...u,id:`peak-${o}-${a}`},seriesIndex:o,seriesName:n[o]?.name??`Series ${o+1}`,isAutoDetected:!0})})}),e},[i,d,n]),S=r.useMemo(()=>{const e=(L?.length??0)>0,t={selectedPeakIds:L??[],anySelected:e,appearance:C,annotationStyle:Z},o=[];i.forEach((c,f)=>{o.push({peak:{...c,id:c.id??`user-ann-${f}`},seriesIndex:-1})}),b&&h&&d.forEach(({peaks:c,seriesIndex:f})=>{c.forEach((ae,se)=>{o.push({peak:{...ae,id:`peak-${f}-${se}`},seriesIndex:f})})});const u=T.groupOverlappingPeaks(o,q),a=[];for(const c of u)a.push(...T.createGroupAnnotations(c,t));return a},[i,d,b,h,q,L,C,Z]);return ee.current=S,r.useEffect(()=>{const e=k.current;if(!e||n.length===0)return;const t=s.buildTraceData({processedSeries:l,processedAnnotations:i,allDetectedPeaks:d,allPeaksForInteraction:le,showMarkers:W,markerSize:x,boundaryMarkers:K}),o=s.buildLayout({title:B,titleFontSize:G,titleTopMargin:O,width:g,height:y,xAxisTitle:P,yAxisTitle:R,xRange:D,yRange:H,showLegend:$,seriesCount:n.length,showGridX:A,showGridY:U,showCrosshairs:F,theme:X,peakAnnotations:ee.current}),u=s.buildConfig({showExportButton:Q,width:g,height:y});let a=!1,c=null;return j.loadPlotly().then(f=>{a||!k.current||(f.newPlot(e,t,o,u),z(e),e.on("plotly_click",s.createClickHandler(w)),e.on("plotly_hover",s.createHoverHandler(e,l.length,N,M,C.hoverLineWidthMultiplier)),e.on("plotly_unhover",s.createUnhoverHandler(e,N,M)),c=e)}),()=>{a=!0,N.current=null,c&&j.getLoadedPlotly().purge(c)}},[l,d,le,n.length,g,y,B,G,O,P,R,i,D,H,$,A,U,W,x,F,h,p,b,K,q,Q,X,z,C]),r.useEffect(()=>{const e=k.current;e&&e._fullLayout&&j.getLoadedPlotly().relayout(e,{annotations:S})},[S]),n.length===0?E.jsx("div",{className:"chromatogram-chart-container relative flex items-center justify-center",style:{width:g,height:y},children:E.jsx(fe.EmptyState,{variant:"no-data",title:"No chromatogram data",description:"There is no signal data to display yet."})}):E.jsxs("div",{className:"chromatogram-chart-container relative",children:[E.jsx("div",{ref:k,style:{width:"100%",height:"100%"}}),ue]})};exports.Chromatogram=he;
2
2
  //# sourceMappingURL=Chromatogram.cjs.map