@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.147.1 → 1.0.0-beta.149.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 (57) hide show
  1. package/README.md +65 -7
  2. package/dist/components/charts/AreaPlot/AreaPlot.cjs +1 -1
  3. package/dist/components/charts/AreaPlot/AreaPlot.cjs.map +1 -1
  4. package/dist/components/charts/AreaPlot/AreaPlot.js +166 -148
  5. package/dist/components/charts/AreaPlot/AreaPlot.js.map +1 -1
  6. package/dist/components/charts/BarChart/BarChart.cjs +1 -1
  7. package/dist/components/charts/BarChart/BarChart.cjs.map +1 -1
  8. package/dist/components/charts/BarChart/BarChart.d.ts +7 -0
  9. package/dist/components/charts/BarChart/BarChart.js +151 -120
  10. package/dist/components/charts/BarChart/BarChart.js.map +1 -1
  11. package/dist/components/charts/BoxPlot/BoxPlot.cjs +1 -1
  12. package/dist/components/charts/BoxPlot/BoxPlot.cjs.map +1 -1
  13. package/dist/components/charts/BoxPlot/BoxPlot.d.ts +4 -0
  14. package/dist/components/charts/BoxPlot/BoxPlot.js +133 -102
  15. package/dist/components/charts/BoxPlot/BoxPlot.js.map +1 -1
  16. package/dist/components/charts/ChartTooltip/lines.cjs.map +1 -1
  17. package/dist/components/charts/ChartTooltip/lines.d.ts +2 -0
  18. package/dist/components/charts/ChartTooltip/lines.js.map +1 -1
  19. package/dist/components/charts/Chromatogram/Chromatogram.cjs +1 -1
  20. package/dist/components/charts/Chromatogram/Chromatogram.cjs.map +1 -1
  21. package/dist/components/charts/Chromatogram/Chromatogram.js +111 -114
  22. package/dist/components/charts/Chromatogram/Chromatogram.js.map +1 -1
  23. package/dist/components/charts/Chromatogram/plotBuilder.cjs +4 -1
  24. package/dist/components/charts/Chromatogram/plotBuilder.cjs.map +1 -1
  25. package/dist/components/charts/Chromatogram/plotBuilder.d.ts +32 -2
  26. package/dist/components/charts/Chromatogram/plotBuilder.js +170 -127
  27. package/dist/components/charts/Chromatogram/plotBuilder.js.map +1 -1
  28. package/dist/components/charts/Chromatogram/regionOverlays.cjs +1 -1
  29. package/dist/components/charts/Chromatogram/regionOverlays.cjs.map +1 -1
  30. package/dist/components/charts/Chromatogram/regionOverlays.js +11 -11
  31. package/dist/components/charts/Chromatogram/regionOverlays.js.map +1 -1
  32. package/dist/components/charts/Chromatogram/types.d.ts +4 -2
  33. package/dist/components/charts/Electropherogram/Electropherogram.cjs +1 -1
  34. package/dist/components/charts/Electropherogram/Electropherogram.cjs.map +1 -1
  35. package/dist/components/charts/Electropherogram/Electropherogram.js +84 -83
  36. package/dist/components/charts/Electropherogram/Electropherogram.js.map +1 -1
  37. package/dist/components/charts/Histogram/Histogram.cjs +1 -1
  38. package/dist/components/charts/Histogram/Histogram.cjs.map +1 -1
  39. package/dist/components/charts/Histogram/Histogram.js +42 -42
  40. package/dist/components/charts/Histogram/Histogram.js.map +1 -1
  41. package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.cjs +1 -1
  42. package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.cjs.map +1 -1
  43. package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.js +4 -5
  44. package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.js.map +1 -1
  45. package/dist/components/charts/chart-scale.cjs +2 -0
  46. package/dist/components/charts/chart-scale.cjs.map +1 -0
  47. package/dist/components/charts/chart-scale.d.ts +61 -0
  48. package/dist/components/charts/chart-scale.js +61 -0
  49. package/dist/components/charts/chart-scale.js.map +1 -0
  50. package/dist/index.css +1 -1
  51. package/dist/index.scoped.css +6 -0
  52. package/dist/index.tailwind.css +1 -1
  53. package/dist/ui/skeleton.cjs +1 -1
  54. package/dist/ui/skeleton.cjs.map +1 -1
  55. package/dist/ui/skeleton.js +1 -1
  56. package/dist/ui/skeleton.js.map +1 -1
  57. package/package.json +8 -2
@@ -1 +1 @@
1
- {"version":3,"file":"BarChart.cjs","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.cjs","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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"}
@@ -30,6 +30,13 @@ interface BarChartProps {
30
30
  xTitle?: string;
31
31
  yTitle?: string;
32
32
  title?: string;
33
+ /**
34
+ * Bar width in x-axis data units. Defaults to about a quarter of the median
35
+ * gap between x positions (narrower when four or more grouped series must
36
+ * share a slot), so bars keep the same visual weight whether x runs 0..6 or
37
+ * 200..1000. With very uneven spacing the closest bars can still overlap or
38
+ * the widest gaps look sparse — pass an explicit width in that case.
39
+ */
33
40
  barWidth?: number;
34
41
  /**
35
42
  * Categorical labels for the x-axis ticks. When provided, the x data values
@@ -1,59 +1,72 @@
1
- import { jsxs as q, jsx as D } from "react/jsx-runtime";
2
- import { useRef as b, useMemo as f, useEffect as H } from "react";
3
- import { useChartTooltip as J } from "../ChartTooltip/ChartTooltip.js";
4
- import { loadPlotly as K, getLoadedPlotly as T } from "../plotly-loader.js";
5
- import { useElementSize as Q } from "../../../hooks/use-element-size.js";
6
- import { usePlotlyTheme as S, CHART_FONT_FAMILY as u } from "../../../hooks/use-plotly-theme.js";
7
- import { cn as Z } from "../../../lib/utils.js";
8
- import { seriesColor as $ } from "../../../utils/colors.js";
9
- const tt = 60, ot = 30, ht = ({
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: A,
12
- height: L,
13
- xRange: _,
14
- yRange: m,
15
- variant: p = "group",
16
- xTitle: E,
17
- yTitle: v,
18
- title: y,
19
- barWidth: I = 24,
20
- xTickText: w
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 s = b(null), e = S(), { bindTooltip: P, tooltipElement: j } = J({ xLabel: E, yLabel: v }), [F, O] = Q(), l = A ?? O.width, c = L ?? O.height, X = l > 0 && c > 0, G = A === void 0, W = L === void 0, M = b({ width: l, height: c });
23
- M.current = { width: l, height: c };
24
- const C = b(!1), x = b({ width: 0, height: 0 }), { yMin: B, yMax: R } = f(() => {
25
- let o = Number.MAX_VALUE, t = Number.MIN_VALUE, i = Number.MAX_VALUE, r = Number.MIN_VALUE;
26
- a.forEach((k) => {
27
- k.x.forEach((g) => {
28
- o = Math.min(o, g), t = Math.max(t, g);
29
- }), k.y.forEach((g) => {
30
- i = Math.min(i, g), r = Math.max(r, g);
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 = (t - o) * 0.1, n = (r - i) * 0.1;
40
+ const h = (e - t) * 0.1, r = (i - c) * 0.1;
34
41
  return {
35
- xMin: o - h,
36
- xMax: t + h,
37
- yMin: p === "stack" ? 0 : i - n,
38
- yMax: r + n
42
+ xMin: t - h,
43
+ xMax: e + h,
44
+ yMin: m === "stack" ? 0 : c - r,
45
+ yMax: i + r
39
46
  };
40
- }, [a, p]), d = f(
41
- () => m || [B, R],
42
- [m, B, R]
43
- ), N = f(
44
- () => [...new Set(a.flatMap((o) => o.x))].sort((o, t) => o - t),
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
- ), U = !!w && w.length === N.length, V = f(() => {
47
- const o = d[1] - d[0];
48
- let t = Math.pow(10, Math.floor(Math.log10(o)));
49
- o / t > 10 && (t = t * 2), o / t < 4 && (t = t / 2);
50
- const i = [];
51
- let r = Math.ceil(d[0] / t) * t;
52
- for (; r <= d[1]; )
53
- i.push(r), r += t;
54
- return i;
55
- }, [d]), Y = f(() => {
56
- switch (p) {
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
- }, [p]), z = f(
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: e.tickColor,
67
- ticklen: 12,
90
+ tickcolor: n.tickColor,
91
+ ticklen: o.ticklen,
68
92
  tickwidth: 1,
69
93
  ticks: "outside",
70
94
  tickfont: {
71
- size: 16,
72
- color: e.textColor,
73
- family: u,
95
+ size: o.tickFontSize,
96
+ color: n.textColor,
97
+ family: p,
74
98
  weight: 400
75
99
  },
76
- linecolor: e.lineColor,
100
+ linecolor: n.lineColor,
77
101
  linewidth: 1,
78
102
  position: 0,
79
103
  zeroline: !1
80
104
  }),
81
- [e]
105
+ [n, o]
82
106
  );
83
- return H(() => {
84
- if (!s.current || !X) return;
85
- const o = a.map((n, k) => ({
86
- x: n.x,
87
- y: n.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: n.name,
113
+ name: r.name,
90
114
  hoverinfo: "none",
91
115
  marker: {
92
- color: $(k, n.color)
116
+ color: ht(f, r.color)
93
117
  },
94
- width: I,
95
- error_y: n.error_y
96
- })), t = {
97
- ...y ? {
118
+ ...N === void 0 ? {} : { width: N },
119
+ error_y: r.error_y
120
+ })), e = {
121
+ ...w ? {
98
122
  title: {
99
- text: y,
123
+ text: w,
100
124
  font: {
101
- size: 32,
102
- family: u,
103
- color: e.textColor
125
+ size: o.titleFontSize,
126
+ family: p,
127
+ color: n.textColor
104
128
  }
105
129
  }
106
130
  } : {},
107
- width: M.current.width,
108
- height: M.current.height,
131
+ width: C.current.width,
132
+ height: C.current.height,
109
133
  margin: {
110
- l: 80,
111
- r: 30,
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: 96,
115
- t: y ? tt : ot,
138
+ b: o.margin.b,
139
+ t: A,
116
140
  pad: 0
117
141
  },
118
- paper_bgcolor: e.paperBg,
119
- plot_bgcolor: e.plotBg,
142
+ paper_bgcolor: n.paperBg,
143
+ plot_bgcolor: n.plotBg,
120
144
  font: {
121
- family: u
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: E,
152
+ text: z,
129
153
  font: {
130
- size: 16,
131
- color: e.textSecondary,
132
- family: u,
154
+ size: o.axisTitleFontSize,
155
+ color: n.textSecondary,
156
+ family: p,
133
157
  weight: 400
134
158
  },
135
- standoff: 32
159
+ standoff: o.axisTitleStandoff
136
160
  },
137
- gridcolor: e.gridColor,
138
- range: _,
139
- autorange: !_,
161
+ gridcolor: n.gridColor,
162
+ range: T,
163
+ autorange: !T,
140
164
  tickmode: "array",
141
- tickvals: N,
142
- ...U ? { ticktext: w } : {},
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
- ...z
171
+ ...I
148
172
  },
149
173
  yaxis: {
150
174
  title: {
151
- text: v,
175
+ text: L,
152
176
  font: {
153
- size: 16,
154
- color: e.textSecondary,
155
- family: u,
177
+ size: o.axisTitleFontSize,
178
+ color: n.textSecondary,
179
+ family: p,
156
180
  weight: 400
157
181
  },
158
- standoff: 30
182
+ standoff: o.axisTitleStandoff
159
183
  },
160
- gridcolor: e.gridColor,
161
- range: m,
162
- autorange: !m,
184
+ gridcolor: n.gridColor,
185
+ range: y,
186
+ autorange: !y,
163
187
  tickmode: "array",
164
- tickvals: V,
188
+ tickvals: R.current,
165
189
  showgrid: !0,
166
190
  automargin: !0,
167
- ...z
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: 16,
181
- color: e.legendColor,
182
- family: u,
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
- }, i = {
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 r = !1, h = null;
195
- return K().then((n) => {
196
- r || !s.current || (n.newPlot(s.current, o, t, i), P(s.current), h = s.current, C.current = !0, x.current = { ...M.current });
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
- r = !0, h && (T().purge(h), C.current = !1);
223
+ i = !0, h && (W().purge(h), v.current = !1);
199
224
  };
200
- }, [a, X, _, m, E, v, y, I, Y, z, N, V, U, w, e, P]), H(() => {
201
- const o = s.current;
202
- !o || !C.current || l <= 0 || c <= 0 || x.current.width === l && x.current.height === c || (x.current = { width: l, height: c }, T().relayout(o, { width: l, height: c }).catch(() => {
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, c]), /* @__PURE__ */ q(
235
+ }, [s, l, Z]), /* @__PURE__ */ $(
205
236
  "div",
206
237
  {
207
- ref: F,
208
- className: Z("bar-graph-container relative", G && "w-full", W && "h-full"),
238
+ ref: q,
239
+ className: st("bar-graph-container relative", J && "w-full", Q && "h-full"),
209
240
  children: [
210
- /* @__PURE__ */ D("div", { ref: s, style: { width: "100%", height: "100%" } }),
211
- j
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
- ht as BarChart
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 z=require("react/jsx-runtime"),r=require("react"),B=require("../ChartTooltip/ChartTooltip.cjs"),A=require("../plotly-loader.cjs"),S=require("../../../hooks/use-element-size.cjs"),h=require("../../../hooks/use-plotly-theme.cjs"),U=require("../../../lib/utils.cjs"),W=require("../../../utils/colors.cjs"),V=-1.8,D=({dataSeries:p,width:C,height:b,xRange:x,yRange:g,xTitle:M,yTitle:w,title:v="Box Plot",showPoints:L=!1})=>{const s=r.useRef(null),e=h.usePlotlyTheme(),{bindTooltip:F,tooltipElement:I}=B.useChartTooltip({xLabel:M,yLabel:w}),[Y,k]=S.useElementSize(),i=C??k.width,l=b??k.height,E=i>0&&l>0,q=C===void 0,H=b===void 0,m=r.useRef({width:i,height:l});m.current={width:i,height:l};const _=r.useRef(!1),y=r.useRef({width:0,height:0}),{yMin:N,yMax:O}=r.useMemo(()=>{let o=Number.MAX_VALUE,t=Number.MIN_VALUE;p.forEach(c=>{c.y.forEach(d=>{o=Math.min(o,d),t=Math.max(t,d)})});const a=(t-o)*.1;return{yMin:o-a,yMax:t+a}},[p]),f=r.useMemo(()=>g||[N,O],[g,N,O]),P=r.useMemo(()=>{const o=f[1]-f[0];let t=Math.pow(10,Math.floor(Math.log10(o)));o/t>10&&(t=t*2),o/t<4&&(t=t/2);const a=[];let c=Math.ceil(f[0]/t)*t;for(;c<=f[1];)a.push(c),c+=t;return a},[f]),T=r.useMemo(()=>({tickcolor:e.tickColor,ticklen:12,tickwidth:1,ticks:"outside",tickfont:{size:16,color:e.textColor,family:h.CHART_FONT_FAMILY,weight:400},linecolor:e.lineColor,linewidth:1,position:0,zeroline:!1}),[e]),R=r.useMemo(()=>({text:v,x:.5,y:.95,xanchor:"center",yanchor:"top",font:{size:32,weight:600,family:h.CHART_FONT_FAMILY,color:e.textColor,lineheight:1.2,standoff:30}}),[v,e]);return r.useEffect(()=>{if(!s.current||!E)return;const o=p.map((n,j)=>{const u=W.seriesColor(j,n.color);return{y:n.y,x:n.x,type:"box",name:n.name,hoverinfo:"none",marker:{color:u},line:{color:u},fillcolor:typeof u=="string"&&u.startsWith("#")&&u.length===7?`${u}40`:u,boxpoints:L?n.boxpoints||"outliers":!1,jitter:n.jitter||.3,pointpos:n.pointpos||V}}),t={width:m.current.width,height:m.current.height,title:R,margin:{l:80,r:40,b:96,t:80,pad:0},paper_bgcolor:e.paperBg,plot_bgcolor:e.plotBg,font:{family:h.CHART_FONT_FAMILY},dragmode:!1,xaxis:{title:{text:M,font:{size:16,color:e.textSecondary,family:h.CHART_FONT_FAMILY,weight:400},standoff:15},gridcolor:e.gridColor,range:x,autorange:!x,showgrid:!0,automargin:!0,...T},yaxis:{title:{text:w,font:{size:16,color:e.textSecondary,family:h.CHART_FONT_FAMILY,weight:400},standoff:15},gridcolor:e.gridColor,range:g,autorange:!g,tickmode:"array",tickvals:P,showgrid:!0,automargin:!0,...T},legend:{x:.5,y:0,xanchor:"center",yanchor:"bottom",yref:"container",orientation:"h",font:{size:13,color:e.legendColor,family:h.CHART_FONT_FAMILY,weight:500,lineheight:18}},showlegend:!0},a={responsive:!1,displayModeBar:!1,displaylogo:!1};let c=!1,d=null;return A.loadPlotly().then(n=>{c||!s.current||(n.newPlot(s.current,o,t,a),F(s.current),d=s.current,_.current=!0,y.current={...m.current})}),()=>{c=!0,d&&(A.getLoadedPlotly().purge(d),_.current=!1)}},[p,E,x,g,f,M,w,L,R,T,P,e,F]),r.useEffect(()=>{const o=s.current;!o||!_.current||i<=0||l<=0||y.current.width===i&&y.current.height===l||(y.current={width:i,height:l},A.getLoadedPlotly().relayout(o,{width:i,height:l}).catch(()=>{}))},[i,l]),z.jsxs("div",{ref:Y,className:U.cn("boxplot-container relative",q&&"w-full",H&&"h-full"),children:[z.jsx("div",{ref:s,style:{width:"100%",height:"100%"}}),I]})};exports.BoxPlot=D;
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