@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 +1 @@
1
- {"version":3,"file":"plotBuilder.cjs","sources":["../../../../src/components/charts/Chromatogram/plotBuilder.ts"],"sourcesContent":["import { CHART_COLORS } from \"../../../utils/colors\";\nimport { getLoadedPlotly } from \"../plotly-loader\";\n\nimport { createBoundaryMarkerTraces } from \"./boundaryMarkers\";\nimport { CHROMATOGRAM_LAYOUT, CHROMATOGRAM_TRACE } from \"./constants\";\nimport { buildHoverExtraContent, collectPeaksWithBoundaryData } from \"./dataProcessing\";\nimport { createRegionOverlayTraces } from \"./regionOverlays\";\n\nimport type { ChromatogramSeries, PeakAnnotation, BoundaryMarkerStyle, PeakSelectEvent } from \"./types\";\nimport type Plotly from \"plotly.js-dist\";\n\nimport { CHART_FONT_FAMILY, type PlotlyThemeColors } from \"@/hooks/use-plotly-theme\";\n\ntype PeakForInteraction = {\n peak: PeakAnnotation & { id: string };\n seriesIndex: number;\n seriesName: string;\n isAutoDetected: boolean;\n};\n\ntype BuildTraceDataParams = {\n processedSeries: ChromatogramSeries[];\n processedAnnotations: PeakAnnotation[];\n allDetectedPeaks: { peaks: PeakAnnotation[]; seriesIndex: number }[];\n allPeaksForInteraction: PeakForInteraction[];\n showMarkers: boolean;\n markerSize: number;\n xAxisTitle: string;\n yAxisTitle: string;\n boundaryMarkers: BoundaryMarkerStyle;\n};\n\ntype BuildLayoutParams = {\n title?: string;\n titleFontSize: number;\n titleTopMargin?: number;\n width: number;\n height: number;\n xAxisTitle: string;\n yAxisTitle: string;\n xRange?: [number, number];\n yRange?: [number, number];\n showLegend: boolean;\n seriesCount: number;\n showGridX: boolean;\n showGridY: boolean;\n showCrosshairs: boolean;\n theme: PlotlyThemeColors;\n peakAnnotations: Partial<Plotly.Annotations>[];\n};\n\ntype BuildConfigParams = {\n showExportButton: boolean;\n width: number;\n height: number;\n};\n\nexport type { PeakForInteraction, BuildTraceDataParams, BuildLayoutParams, BuildConfigParams };\n\nexport function buildTraceData(params: BuildTraceDataParams): Plotly.Data[] {\n const {\n processedSeries,\n processedAnnotations,\n allDetectedPeaks,\n allPeaksForInteraction,\n showMarkers,\n markerSize,\n xAxisTitle,\n yAxisTitle,\n boundaryMarkers,\n } = params;\n\n const plotData: Plotly.Data[] = processedSeries.map((s, index) => {\n const traceColor = s.color || CHART_COLORS[index % CHART_COLORS.length];\n const extraContent = buildHoverExtraContent(s.name, s.metadata);\n\n const trace: Plotly.Data = {\n x: s.x,\n y: s.y,\n type: \"scatter\" as const,\n mode: showMarkers ? (\"lines+markers\" as const) : (\"lines\" as const),\n name: s.name,\n line: {\n color: traceColor,\n width: CHROMATOGRAM_TRACE.BASE_LINE_WIDTH,\n },\n hovertemplate: `%{x:.2f} ${xAxisTitle}<br>%{y:.2f} ${yAxisTitle}<extra>${extraContent}</extra>`,\n };\n if (showMarkers) {\n trace.marker = { size: markerSize, color: traceColor };\n }\n return trace;\n });\n\n if (boundaryMarkers !== \"none\") {\n const peaksWithData = collectPeaksWithBoundaryData(\n allDetectedPeaks,\n processedAnnotations,\n processedSeries\n );\n if (peaksWithData.length > 0) {\n plotData.push(...createBoundaryMarkerTraces(peaksWithData));\n }\n }\n\n processedAnnotations.forEach((ann) => {\n if (ann.regionOverlay && processedSeries[0]) {\n plotData.push(...createRegionOverlayTraces([ann], 0, processedSeries[0]));\n }\n });\n allDetectedPeaks.forEach(({ peaks, seriesIndex }) => {\n if (peaks.some((p) => p.regionOverlay) && processedSeries[seriesIndex]) {\n plotData.push(...createRegionOverlayTraces(peaks, seriesIndex, processedSeries[seriesIndex]));\n }\n });\n\n if (allPeaksForInteraction.length > 0) {\n const anyHoverText = allPeaksForInteraction.some((p) => p.peak.hoverText);\n const hitAreaTrace: Plotly.Data = {\n x: allPeaksForInteraction.map((p) => p.peak.x),\n y: allPeaksForInteraction.map((p) => p.peak.y),\n type: \"scatter\" as const,\n mode: \"markers\" as const,\n marker: { size: 14, opacity: 0 },\n showlegend: false,\n name: \"\",\n customdata: allPeaksForInteraction.map((p) => ({\n id: p.peak.id,\n peak: p.peak,\n seriesIndex: p.seriesIndex,\n seriesName: p.seriesName,\n isAutoDetected: p.isAutoDetected,\n })) as unknown as Plotly.Datum[],\n ...(anyHoverText\n ? {\n hovertemplate: allPeaksForInteraction.map((p) =>\n p.peak.hoverText ? `${p.peak.hoverText}<extra></extra>` : \"<extra></extra>\"\n ),\n }\n : { hovertemplate: \"<extra></extra>\" }),\n };\n plotData.push(hitAreaTrace);\n }\n\n return plotData;\n}\n\nexport function buildLayout(params: BuildLayoutParams): Partial<Plotly.Layout> {\n const {\n title,\n titleFontSize,\n titleTopMargin,\n width,\n height,\n xAxisTitle,\n yAxisTitle,\n xRange,\n yRange,\n showLegend,\n seriesCount,\n showGridX,\n showGridY,\n showCrosshairs,\n theme,\n peakAnnotations,\n } = params;\n\n return {\n title: title\n ? {\n text: title,\n font: { size: titleFontSize, family: CHART_FONT_FAMILY, color: theme.textColor },\n }\n : undefined,\n width,\n height,\n margin: {\n l: CHROMATOGRAM_LAYOUT.MARGIN_LEFT,\n r: CHROMATOGRAM_LAYOUT.MARGIN_RIGHT,\n b: CHROMATOGRAM_LAYOUT.MARGIN_BOTTOM,\n t: title\n ? (titleTopMargin ?? CHROMATOGRAM_LAYOUT.MARGIN_TOP_WITH_TITLE)\n : CHROMATOGRAM_LAYOUT.MARGIN_TOP_NO_TITLE,\n pad: CHROMATOGRAM_LAYOUT.MARGIN_PAD,\n },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: { family: CHART_FONT_FAMILY },\n hovermode: showCrosshairs ? (\"x\" as const) : (\"x unified\" as const),\n dragmode: \"zoom\" as const,\n xaxis: {\n title: {\n text: xAxisTitle,\n font: { size: 14, color: theme.textSecondary, family: CHART_FONT_FAMILY },\n standoff: 15,\n },\n showgrid: showGridX,\n gridcolor: theme.gridColor,\n linecolor: theme.lineColor,\n linewidth: 1,\n range: xRange,\n autorange: !xRange,\n zeroline: false,\n tickfont: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n showspikes: showCrosshairs,\n spikemode: \"across\" as const,\n spikesnap: \"cursor\" as const,\n spikecolor: theme.spikeColor,\n spikethickness: 1,\n spikedash: \"dot\" as const,\n },\n yaxis: {\n title: {\n text: yAxisTitle,\n font: { size: 14, color: theme.textSecondary, family: CHART_FONT_FAMILY },\n standoff: 10,\n },\n showgrid: showGridY,\n gridcolor: theme.gridColor,\n linecolor: theme.lineColor,\n linewidth: 1,\n range: yRange,\n autorange: !yRange,\n zeroline: false,\n tickfont: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n showspikes: showCrosshairs,\n spikemode: \"across\" as const,\n spikesnap: \"cursor\" as const,\n spikecolor: theme.spikeColor,\n spikethickness: 1,\n spikedash: \"dot\" as const,\n },\n legend: {\n x: 0.5,\n y: -0.15,\n xanchor: \"center\" as const,\n yanchor: \"top\" as const,\n orientation: \"h\" as const,\n font: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n },\n showlegend: showLegend && seriesCount > 1,\n annotations: peakAnnotations,\n };\n}\n\nexport function buildConfig(params: BuildConfigParams): Partial<Plotly.Config> {\n const { showExportButton, width, height } = params;\n return {\n responsive: true,\n displayModeBar: true,\n displaylogo: false,\n modeBarButtonsToRemove: [\n \"lasso2d\",\n \"select2d\",\n ...(showExportButton ? [] : ([\"toImage\"] as Plotly.ModeBarDefaultButtons[])),\n ] as Plotly.ModeBarDefaultButtons[],\n ...(showExportButton && {\n toImageButtonOptions: {\n format: \"png\",\n filename: \"chromatogram\",\n width,\n height,\n },\n }),\n };\n}\n\ntype MutableRef<T> = { current: T };\n\nexport function createHoverHandler(\n domElement: HTMLElement,\n processedSeriesLength: number,\n thickenedSeriesRef: MutableRef<number | null>,\n onPeakHoverRef: MutableRef<((event: PeakSelectEvent | null) => void) | undefined>,\n hoverLineWidthMultiplier: number\n): (eventData: Plotly.PlotHoverEvent) => void {\n return (eventData) => {\n const pt = eventData.points[0];\n if (pt && pt.curveNumber < processedSeriesLength) {\n const targetIdx = pt.curveNumber;\n if (thickenedSeriesRef.current !== targetIdx) {\n // Hover events can only fire once the plot is drawn, so the\n // lazily-loaded Plotly module is guaranteed to be available here.\n if (thickenedSeriesRef.current !== null) {\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH } as Plotly.Data, [thickenedSeriesRef.current]);\n }\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH * hoverLineWidthMultiplier } as Plotly.Data, [targetIdx]);\n thickenedSeriesRef.current = targetIdx;\n }\n }\n const peakPoint = eventData.points.find((p) => p.customdata != null);\n if (peakPoint) {\n onPeakHoverRef.current?.(peakPoint.customdata as unknown as PeakSelectEvent);\n }\n };\n}\n\nexport function createClickHandler(\n onPeakClickRef: MutableRef<((event: PeakSelectEvent) => void) | undefined>\n): (eventData: Plotly.PlotMouseEvent) => void {\n return (eventData) => {\n if (!onPeakClickRef.current) return;\n const peakPoint = eventData.points.find((p) => p.customdata != null);\n if (!peakPoint) return;\n onPeakClickRef.current(peakPoint.customdata as unknown as PeakSelectEvent);\n };\n}\n\nexport function createUnhoverHandler(\n domElement: HTMLElement,\n thickenedSeriesRef: MutableRef<number | null>,\n onPeakHoverRef: MutableRef<((event: PeakSelectEvent | null) => void) | undefined>\n): () => void {\n return () => {\n onPeakHoverRef.current?.(null);\n if (thickenedSeriesRef.current !== null) {\n // Unhover events can only fire once the plot is drawn, so the\n // lazily-loaded Plotly module is guaranteed to be available here.\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH } as Plotly.Data, [thickenedSeriesRef.current]);\n thickenedSeriesRef.current = null;\n }\n };\n}\n"],"names":["buildTraceData","params","processedSeries","processedAnnotations","allDetectedPeaks","allPeaksForInteraction","showMarkers","markerSize","xAxisTitle","yAxisTitle","boundaryMarkers","plotData","s","index","traceColor","CHART_COLORS","extraContent","buildHoverExtraContent","trace","CHROMATOGRAM_TRACE","peaksWithData","collectPeaksWithBoundaryData","createBoundaryMarkerTraces","ann","createRegionOverlayTraces","peaks","seriesIndex","p","anyHoverText","hitAreaTrace","buildLayout","title","titleFontSize","titleTopMargin","width","height","xRange","yRange","showLegend","seriesCount","showGridX","showGridY","showCrosshairs","theme","peakAnnotations","CHART_FONT_FAMILY","CHROMATOGRAM_LAYOUT","buildConfig","showExportButton","createHoverHandler","domElement","processedSeriesLength","thickenedSeriesRef","onPeakHoverRef","hoverLineWidthMultiplier","eventData","pt","targetIdx","getLoadedPlotly","peakPoint","createClickHandler","onPeakClickRef","createUnhoverHandler"],"mappings":"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"}
1
+ {"version":3,"file":"plotBuilder.cjs","sources":["../../../../src/components/charts/Chromatogram/plotBuilder.ts"],"sourcesContent":["import { CHART_COLORS } from \"../../../utils/colors\";\nimport { getLoadedPlotly } from \"../plotly-loader\";\n\nimport { createBoundaryMarkerTraces } from \"./boundaryMarkers\";\nimport { CHROMATOGRAM_LAYOUT, CHROMATOGRAM_TRACE } from \"./constants\";\nimport { buildHoverExtraContent, collectPeaksWithBoundaryData } from \"./dataProcessing\";\nimport { createRegionOverlayTraces } from \"./regionOverlays\";\n\nimport type { ChromatogramSeries, PeakAnnotation, BoundaryMarkerStyle, PeakSelectEvent } from \"./types\";\nimport type { ChartTooltipHoverPoint } from \"../ChartTooltip\";\nimport type Plotly from \"plotly.js-dist\";\n\nimport { CHART_FONT_FAMILY, type PlotlyThemeColors } from \"@/hooks/use-plotly-theme\";\n\ntype PeakForInteraction = {\n peak: PeakAnnotation & { id: string };\n seriesIndex: number;\n seriesName: string;\n isAutoDetected: boolean;\n};\n\ntype BuildTraceDataParams = {\n processedSeries: ChromatogramSeries[];\n processedAnnotations: PeakAnnotation[];\n allDetectedPeaks: { peaks: PeakAnnotation[]; seriesIndex: number }[];\n allPeaksForInteraction: PeakForInteraction[];\n showMarkers: boolean;\n markerSize: number;\n boundaryMarkers: BoundaryMarkerStyle;\n};\n\ntype BuildLayoutParams = {\n title?: string;\n titleFontSize: number;\n titleTopMargin?: number;\n width: number;\n height: number;\n xAxisTitle: string;\n yAxisTitle: string;\n xRange?: [number, number];\n yRange?: [number, number];\n showLegend: boolean;\n seriesCount: number;\n showGridX: boolean;\n showGridY: boolean;\n showCrosshairs: boolean;\n theme: PlotlyThemeColors;\n peakAnnotations: Partial<Plotly.Annotations>[];\n};\n\ntype BuildConfigParams = {\n showExportButton: boolean;\n width: number;\n height: number;\n};\n\nexport type { PeakForInteraction, BuildTraceDataParams, BuildLayoutParams, BuildConfigParams };\n\nexport function buildTraceData(params: BuildTraceDataParams): Plotly.Data[] {\n const {\n processedSeries,\n processedAnnotations,\n allDetectedPeaks,\n allPeaksForInteraction,\n showMarkers,\n markerSize,\n boundaryMarkers,\n } = params;\n\n const plotData: Plotly.Data[] = processedSeries.map((s, index) => {\n const traceColor = s.color || CHART_COLORS[index % CHART_COLORS.length];\n\n const trace: Plotly.Data = {\n x: s.x,\n y: s.y,\n type: \"scatter\" as const,\n mode: showMarkers ? (\"lines+markers\" as const) : (\"lines\" as const),\n name: s.name,\n line: {\n color: traceColor,\n width: CHROMATOGRAM_TRACE.BASE_LINE_WIDTH,\n },\n // Hover is rendered by the shared ChartTooltip (bound in Chromatogram);\n // \"none\" keeps plotly_hover firing without also drawing Plotly's own\n // label on top of it (SW-2298).\n hoverinfo: \"none\" as const,\n };\n if (showMarkers) {\n trace.marker = { size: markerSize, color: traceColor };\n }\n return trace;\n });\n\n if (boundaryMarkers !== \"none\") {\n const peaksWithData = collectPeaksWithBoundaryData(\n allDetectedPeaks,\n processedAnnotations,\n processedSeries\n );\n if (peaksWithData.length > 0) {\n plotData.push(...createBoundaryMarkerTraces(peaksWithData));\n }\n }\n\n processedAnnotations.forEach((ann) => {\n if (ann.regionOverlay && processedSeries[0]) {\n plotData.push(...createRegionOverlayTraces([ann], 0, processedSeries[0]));\n }\n });\n allDetectedPeaks.forEach(({ peaks, seriesIndex }) => {\n if (peaks.some((p) => p.regionOverlay) && processedSeries[seriesIndex]) {\n plotData.push(...createRegionOverlayTraces(peaks, seriesIndex, processedSeries[seriesIndex]));\n }\n });\n\n if (allPeaksForInteraction.length > 0) {\n const hitAreaTrace: Plotly.Data = {\n x: allPeaksForInteraction.map((p) => p.peak.x),\n y: allPeaksForInteraction.map((p) => p.peak.y),\n type: \"scatter\" as const,\n mode: \"markers\" as const,\n marker: { size: 14, opacity: 0 },\n showlegend: false,\n name: \"\",\n customdata: allPeaksForInteraction.map((p) => ({\n id: p.peak.id,\n peak: p.peak,\n seriesIndex: p.seriesIndex,\n seriesName: p.seriesName,\n isAutoDetected: p.isAutoDetected,\n })) as unknown as Plotly.Datum[],\n // Peak hoverText reaches the shared ChartTooltip through customdata\n // (see buildChromatogramTooltipLines), not Plotly's native label.\n hoverinfo: \"none\" as const,\n };\n plotData.push(hitAreaTrace);\n }\n\n return plotData;\n}\n\ntype ChromatogramTooltipPoint = ChartTooltipHoverPoint;\n\ntype ChromatogramTooltipParams = {\n series: ChromatogramSeries[];\n xAxisTitle: string;\n yAxisTitle: string;\n};\n\nconst formatTooltipNumber = (value: number | string): string =>\n typeof value === \"number\" ? value.toFixed(2) : String(value);\n\n/**\n * The ChartTooltip renders plain text, so `hoverText` written for Plotly's\n * hovertemplate is normalised for display: `<br>` splits lines and the rest is\n * reduced to its text content via the HTML parser (entities decoded, tags\n * dropped). This is presentation clean-up, not security sanitisation — the\n * lines are rendered as React text nodes, never as markup.\n */\nexport function htmlToTooltipLines(text?: string): string[] {\n if (!text) return [];\n const withBreaks = text.replace(/<br\\s*\\/?>/gi, \"\\n\");\n const plain =\n typeof DOMParser === \"undefined\"\n ? withBreaks\n : (new DOMParser().parseFromString(withBreaks, \"text/html\").body.textContent ?? \"\");\n return plain\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean);\n}\n\n/**\n * Split \"Signal (mAU)\" into { label: \"Signal\", unit: \"mAU\" } so values read\n * \"Signal: 12.00 mAU\"; a title without a trailing parenthesised unit keeps\n * its full text as the label and an empty unit.\n */\nexport function splitAxisTitle(axisTitle: string): { label: string; unit: string } {\n // Plain string scanning rather than a regex: the title is consumer input and\n // a backtracking pattern over its whitespace was flagged by CodeQL.\n const trimmed = axisTitle.trim();\n const fallback = { label: trimmed, unit: \"\" };\n if (!trimmed.endsWith(\")\")) return fallback;\n const open = trimmed.lastIndexOf(\"(\");\n if (open <= 0) return fallback;\n const unit = trimmed.slice(open + 1, -1).trim();\n const label = trimmed.slice(0, open).trim();\n if (!unit || !label || unit.includes(\"(\") || unit.includes(\")\")) return fallback;\n return { label, unit };\n}\n\nconst withUnit = (value: string, unit: string): string => (unit ? `${value} ${unit}` : value);\n\n/** \"<name>: <y> <unit>\" followed by the series metadata lines */\nfunction seriesTooltipLines(\n seriesEntry: ChromatogramSeries,\n y: number | string,\n unit: string\n): string[] {\n // buildHoverExtraContent yields \"<name><br>Key: value…\"; keep only the metadata\n const metadata = buildHoverExtraContent(seriesEntry.name, seriesEntry.metadata).split(\"<br>\").slice(1);\n return [`${seriesEntry.name}: ${withUnit(formatTooltipNumber(y), unit)}`, ...metadata];\n}\n\n/** Peak text for a hit-area point (customdata) or a region overlay (trace text) */\nfunction peakTooltipLines(point: ChromatogramTooltipPoint): string[] {\n const peak = (point.customdata as { peak?: PeakAnnotation } | null | undefined)?.peak;\n if (peak) return htmlToTooltipLines(peak.hoverText ?? peak.text);\n return typeof point.text === \"string\" ? htmlToTooltipLines(point.text) : [];\n}\n\n/**\n * Lines for the shared ChartTooltip: the shared x value, one line per hovered\n * series (plus its metadata), then any peak text/hoverText the cursor is on —\n * peaks arrive as the invisible hit-area trace's customdata, overlays carry\n * their text on the trace. The same peak can be reported by both in one\n * \"x unified\" hover, so identical peak blocks are emitted once.\n */\nexport function buildChromatogramTooltipLines(\n points: ChromatogramTooltipPoint[],\n params: ChromatogramTooltipParams\n): string[] {\n const { series, xAxisTitle, yAxisTitle } = params;\n // Both axes are split the same way so the x and y lines read alike:\n // \"Retention Time: 5.80 min\" over \"Sample A: 420.00 mAU\".\n const xAxis = splitAxisTitle(xAxisTitle);\n const { unit } = splitAxisTitle(yAxisTitle);\n const lines: string[] = [];\n const first = points.find((p) => p.x !== undefined);\n if (first?.x !== undefined) {\n lines.push(`${xAxis.label}: ${withUnit(formatTooltipNumber(first.x), xAxis.unit)}`);\n }\n\n const seenPeakBlocks = new Set<string>();\n for (const point of points) {\n const seriesEntry = point.curveNumber === undefined ? undefined : series[point.curveNumber];\n if (seriesEntry) {\n if (point.y !== undefined) lines.push(...seriesTooltipLines(seriesEntry, point.y, unit));\n continue;\n }\n const peakLines = peakTooltipLines(point);\n const key = peakLines.join(\"\\n\");\n if (peakLines.length > 0 && !seenPeakBlocks.has(key)) {\n seenPeakBlocks.add(key);\n lines.push(...peakLines);\n }\n }\n return lines;\n}\n\nexport function buildLayout(params: BuildLayoutParams): Partial<Plotly.Layout> {\n const {\n title,\n titleFontSize,\n titleTopMargin,\n width,\n height,\n xAxisTitle,\n yAxisTitle,\n xRange,\n yRange,\n showLegend,\n seriesCount,\n showGridX,\n showGridY,\n showCrosshairs,\n theme,\n peakAnnotations,\n } = params;\n\n return {\n title: title\n ? {\n text: title,\n font: { size: titleFontSize, family: CHART_FONT_FAMILY, color: theme.textColor },\n }\n : undefined,\n width,\n height,\n margin: {\n l: CHROMATOGRAM_LAYOUT.MARGIN_LEFT,\n r: CHROMATOGRAM_LAYOUT.MARGIN_RIGHT,\n b: CHROMATOGRAM_LAYOUT.MARGIN_BOTTOM,\n t: title\n ? (titleTopMargin ?? CHROMATOGRAM_LAYOUT.MARGIN_TOP_WITH_TITLE)\n : CHROMATOGRAM_LAYOUT.MARGIN_TOP_NO_TITLE,\n pad: CHROMATOGRAM_LAYOUT.MARGIN_PAD,\n },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: { family: CHART_FONT_FAMILY },\n hovermode: showCrosshairs ? (\"x\" as const) : (\"x unified\" as const),\n dragmode: \"zoom\" as const,\n xaxis: {\n title: {\n text: xAxisTitle,\n font: { size: 14, color: theme.textSecondary, family: CHART_FONT_FAMILY },\n standoff: 15,\n },\n showgrid: showGridX,\n gridcolor: theme.gridColor,\n linecolor: theme.lineColor,\n linewidth: 1,\n range: xRange,\n autorange: !xRange,\n zeroline: false,\n tickfont: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n showspikes: showCrosshairs,\n spikemode: \"across\" as const,\n spikesnap: \"cursor\" as const,\n spikecolor: theme.spikeColor,\n spikethickness: 1,\n spikedash: \"dot\" as const,\n },\n yaxis: {\n title: {\n text: yAxisTitle,\n font: { size: 14, color: theme.textSecondary, family: CHART_FONT_FAMILY },\n standoff: 10,\n },\n showgrid: showGridY,\n gridcolor: theme.gridColor,\n linecolor: theme.lineColor,\n linewidth: 1,\n range: yRange,\n autorange: !yRange,\n zeroline: false,\n tickfont: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n showspikes: showCrosshairs,\n spikemode: \"across\" as const,\n spikesnap: \"cursor\" as const,\n spikecolor: theme.spikeColor,\n spikethickness: 1,\n spikedash: \"dot\" as const,\n },\n legend: {\n x: 0.5,\n y: -0.15,\n xanchor: \"center\" as const,\n yanchor: \"top\" as const,\n orientation: \"h\" as const,\n font: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n },\n showlegend: showLegend && seriesCount > 1,\n annotations: peakAnnotations,\n };\n}\n\nexport function buildConfig(params: BuildConfigParams): Partial<Plotly.Config> {\n const { showExportButton, width, height } = params;\n return {\n responsive: true,\n displayModeBar: true,\n displaylogo: false,\n modeBarButtonsToRemove: [\n \"lasso2d\",\n \"select2d\",\n ...(showExportButton ? [] : ([\"toImage\"] as Plotly.ModeBarDefaultButtons[])),\n ] as Plotly.ModeBarDefaultButtons[],\n ...(showExportButton && {\n toImageButtonOptions: {\n format: \"png\",\n filename: \"chromatogram\",\n width,\n height,\n },\n }),\n };\n}\n\ntype MutableRef<T> = { current: T };\n\nexport function createHoverHandler(\n domElement: HTMLElement,\n processedSeriesLength: number,\n thickenedSeriesRef: MutableRef<number | null>,\n onPeakHoverRef: MutableRef<((event: PeakSelectEvent | null) => void) | undefined>,\n hoverLineWidthMultiplier: number\n): (eventData: Plotly.PlotHoverEvent) => void {\n return (eventData) => {\n const pt = eventData.points[0];\n if (pt && pt.curveNumber < processedSeriesLength) {\n const targetIdx = pt.curveNumber;\n if (thickenedSeriesRef.current !== targetIdx) {\n // Hover events can only fire once the plot is drawn, so the\n // lazily-loaded Plotly module is guaranteed to be available here.\n if (thickenedSeriesRef.current !== null) {\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH } as Plotly.Data, [thickenedSeriesRef.current]);\n }\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH * hoverLineWidthMultiplier } as Plotly.Data, [targetIdx]);\n thickenedSeriesRef.current = targetIdx;\n }\n }\n const peakPoint = eventData.points.find((p) => p.customdata != null);\n if (peakPoint) {\n onPeakHoverRef.current?.(peakPoint.customdata as unknown as PeakSelectEvent);\n }\n };\n}\n\nexport function createClickHandler(\n onPeakClickRef: MutableRef<((event: PeakSelectEvent) => void) | undefined>\n): (eventData: Plotly.PlotMouseEvent) => void {\n return (eventData) => {\n if (!onPeakClickRef.current) return;\n const peakPoint = eventData.points.find((p) => p.customdata != null);\n if (!peakPoint) return;\n onPeakClickRef.current(peakPoint.customdata as unknown as PeakSelectEvent);\n };\n}\n\nexport function createUnhoverHandler(\n domElement: HTMLElement,\n thickenedSeriesRef: MutableRef<number | null>,\n onPeakHoverRef: MutableRef<((event: PeakSelectEvent | null) => void) | undefined>\n): () => void {\n return () => {\n onPeakHoverRef.current?.(null);\n if (thickenedSeriesRef.current !== null) {\n // Unhover events can only fire once the plot is drawn, so the\n // lazily-loaded Plotly module is guaranteed to be available here.\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH } as Plotly.Data, [thickenedSeriesRef.current]);\n thickenedSeriesRef.current = null;\n }\n };\n}\n"],"names":["buildTraceData","params","processedSeries","processedAnnotations","allDetectedPeaks","allPeaksForInteraction","showMarkers","markerSize","boundaryMarkers","plotData","s","index","traceColor","CHART_COLORS","trace","CHROMATOGRAM_TRACE","peaksWithData","collectPeaksWithBoundaryData","createBoundaryMarkerTraces","ann","createRegionOverlayTraces","peaks","seriesIndex","p","hitAreaTrace","formatTooltipNumber","value","htmlToTooltipLines","text","withBreaks","line","splitAxisTitle","axisTitle","trimmed","fallback","open","unit","label","withUnit","seriesTooltipLines","seriesEntry","y","metadata","buildHoverExtraContent","peakTooltipLines","point","peak","buildChromatogramTooltipLines","points","series","xAxisTitle","yAxisTitle","xAxis","lines","first","seenPeakBlocks","peakLines","key","buildLayout","title","titleFontSize","titleTopMargin","width","height","xRange","yRange","showLegend","seriesCount","showGridX","showGridY","showCrosshairs","theme","peakAnnotations","CHART_FONT_FAMILY","CHROMATOGRAM_LAYOUT","buildConfig","showExportButton","createHoverHandler","domElement","processedSeriesLength","thickenedSeriesRef","onPeakHoverRef","hoverLineWidthMultiplier","eventData","pt","targetIdx","getLoadedPlotly","peakPoint","createClickHandler","onPeakClickRef","createUnhoverHandler"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { ChromatogramSeries, PeakAnnotation, BoundaryMarkerStyle, PeakSelectEvent } from './types';
2
+ import { ChartTooltipHoverPoint } from '../ChartTooltip';
2
3
  import { default as Plotly } from 'plotly.js-dist';
3
4
  import { PlotlyThemeColors } from '../../../hooks/use-plotly-theme';
4
5
  type PeakForInteraction = {
@@ -19,8 +20,6 @@ type BuildTraceDataParams = {
19
20
  allPeaksForInteraction: PeakForInteraction[];
20
21
  showMarkers: boolean;
21
22
  markerSize: number;
22
- xAxisTitle: string;
23
- yAxisTitle: string;
24
23
  boundaryMarkers: BoundaryMarkerStyle;
25
24
  };
26
25
  type BuildLayoutParams = {
@@ -48,6 +47,37 @@ type BuildConfigParams = {
48
47
  };
49
48
  export type { PeakForInteraction, BuildTraceDataParams, BuildLayoutParams, BuildConfigParams };
50
49
  export declare function buildTraceData(params: BuildTraceDataParams): Plotly.Data[];
50
+ type ChromatogramTooltipPoint = ChartTooltipHoverPoint;
51
+ type ChromatogramTooltipParams = {
52
+ series: ChromatogramSeries[];
53
+ xAxisTitle: string;
54
+ yAxisTitle: string;
55
+ };
56
+ /**
57
+ * The ChartTooltip renders plain text, so `hoverText` written for Plotly's
58
+ * hovertemplate is normalised for display: `<br>` splits lines and the rest is
59
+ * reduced to its text content via the HTML parser (entities decoded, tags
60
+ * dropped). This is presentation clean-up, not security sanitisation — the
61
+ * lines are rendered as React text nodes, never as markup.
62
+ */
63
+ export declare function htmlToTooltipLines(text?: string): string[];
64
+ /**
65
+ * Split "Signal (mAU)" into { label: "Signal", unit: "mAU" } so values read
66
+ * "Signal: 12.00 mAU"; a title without a trailing parenthesised unit keeps
67
+ * its full text as the label and an empty unit.
68
+ */
69
+ export declare function splitAxisTitle(axisTitle: string): {
70
+ label: string;
71
+ unit: string;
72
+ };
73
+ /**
74
+ * Lines for the shared ChartTooltip: the shared x value, one line per hovered
75
+ * series (plus its metadata), then any peak text/hoverText the cursor is on —
76
+ * peaks arrive as the invisible hit-area trace's customdata, overlays carry
77
+ * their text on the trace. The same peak can be reported by both in one
78
+ * "x unified" hover, so identical peak blocks are emitted once.
79
+ */
80
+ export declare function buildChromatogramTooltipLines(points: ChromatogramTooltipPoint[], params: ChromatogramTooltipParams): string[];
51
81
  export declare function buildLayout(params: BuildLayoutParams): Partial<Plotly.Layout>;
52
82
  export declare function buildConfig(params: BuildConfigParams): Partial<Plotly.Config>;
53
83
  type MutableRef<T> = {
@@ -1,151 +1,191 @@
1
- import { CHART_COLORS as k } from "../../../utils/colors.js";
2
- import { getLoadedPlotly as T } from "../plotly-loader.js";
3
- import { createBoundaryMarkerTraces as I } from "./boundaryMarkers.js";
4
- import { CHROMATOGRAM_TRACE as y, CHROMATOGRAM_LAYOUT as h } from "./constants.js";
5
- import { buildHoverExtraContent as C, collectPeaksWithBoundaryData as w } from "./dataProcessing.js";
1
+ import { CHART_COLORS as T } from "../../../utils/colors.js";
2
+ import { getLoadedPlotly as k } from "../plotly-loader.js";
3
+ import { createBoundaryMarkerTraces as O } from "./boundaryMarkers.js";
4
+ import { CHROMATOGRAM_TRACE as x, CHROMATOGRAM_LAYOUT as h } from "./constants.js";
5
+ import { collectPeaksWithBoundaryData as M, buildHoverExtraContent as N } from "./dataProcessing.js";
6
6
  import { createRegionOverlayTraces as A } from "./regionOverlays.js";
7
- import { CHART_FONT_FAMILY as m } from "../../../hooks/use-plotly-theme.js";
8
- function z(i) {
7
+ import { CHART_FONT_FAMILY as f } from "../../../hooks/use-plotly-theme.js";
8
+ function P(e) {
9
9
  const {
10
- processedSeries: e,
11
- processedAnnotations: a,
12
- allDetectedPeaks: n,
13
- allPeaksForInteraction: s,
14
- showMarkers: p,
10
+ processedSeries: t,
11
+ processedAnnotations: o,
12
+ allDetectedPeaks: i,
13
+ allPeaksForInteraction: a,
14
+ showMarkers: c,
15
15
  markerSize: d,
16
- xAxisTitle: f,
17
- yAxisTitle: l,
18
- boundaryMarkers: g
19
- } = i, u = e.map((o, c) => {
20
- const t = o.color || k[c % k.length], x = C(o.name, o.metadata), r = {
21
- x: o.x,
22
- y: o.y,
16
+ boundaryMarkers: u
17
+ } = e, s = t.map((n, r) => {
18
+ const p = n.color || T[r % T.length], m = {
19
+ x: n.x,
20
+ y: n.y,
23
21
  type: "scatter",
24
- mode: p ? "lines+markers" : "lines",
25
- name: o.name,
22
+ mode: c ? "lines+markers" : "lines",
23
+ name: n.name,
26
24
  line: {
27
- color: t,
28
- width: y.BASE_LINE_WIDTH
25
+ color: p,
26
+ width: x.BASE_LINE_WIDTH
29
27
  },
30
- hovertemplate: `%{x:.2f} ${f}<br>%{y:.2f} ${l}<extra>${x}</extra>`
28
+ // Hover is rendered by the shared ChartTooltip (bound in Chromatogram);
29
+ // "none" keeps plotly_hover firing without also drawing Plotly's own
30
+ // label on top of it (SW-2298).
31
+ hoverinfo: "none"
31
32
  };
32
- return p && (r.marker = { size: d, color: t }), r;
33
+ return c && (m.marker = { size: d, color: p }), m;
33
34
  });
34
- if (g !== "none") {
35
- const o = w(
36
- n,
37
- a,
38
- e
35
+ if (u !== "none") {
36
+ const n = M(
37
+ i,
38
+ o,
39
+ t
39
40
  );
40
- o.length > 0 && u.push(...I(o));
41
+ n.length > 0 && s.push(...O(n));
41
42
  }
42
- if (a.forEach((o) => {
43
- o.regionOverlay && e[0] && u.push(...A([o], 0, e[0]));
44
- }), n.forEach(({ peaks: o, seriesIndex: c }) => {
45
- o.some((t) => t.regionOverlay) && e[c] && u.push(...A(o, c, e[c]));
46
- }), s.length > 0) {
47
- const o = s.some((t) => t.peak.hoverText), c = {
48
- x: s.map((t) => t.peak.x),
49
- y: s.map((t) => t.peak.y),
43
+ if (o.forEach((n) => {
44
+ n.regionOverlay && t[0] && s.push(...A([n], 0, t[0]));
45
+ }), i.forEach(({ peaks: n, seriesIndex: r }) => {
46
+ n.some((p) => p.regionOverlay) && t[r] && s.push(...A(n, r, t[r]));
47
+ }), a.length > 0) {
48
+ const n = {
49
+ x: a.map((r) => r.peak.x),
50
+ y: a.map((r) => r.peak.y),
50
51
  type: "scatter",
51
52
  mode: "markers",
52
53
  marker: { size: 14, opacity: 0 },
53
54
  showlegend: !1,
54
55
  name: "",
55
- customdata: s.map((t) => ({
56
- id: t.peak.id,
57
- peak: t.peak,
58
- seriesIndex: t.seriesIndex,
59
- seriesName: t.seriesName,
60
- isAutoDetected: t.isAutoDetected
56
+ customdata: a.map((r) => ({
57
+ id: r.peak.id,
58
+ peak: r.peak,
59
+ seriesIndex: r.seriesIndex,
60
+ seriesName: r.seriesName,
61
+ isAutoDetected: r.isAutoDetected
61
62
  })),
62
- ...o ? {
63
- hovertemplate: s.map(
64
- (t) => t.peak.hoverText ? `${t.peak.hoverText}<extra></extra>` : "<extra></extra>"
65
- )
66
- } : { hovertemplate: "<extra></extra>" }
63
+ // Peak hoverText reaches the shared ChartTooltip through customdata
64
+ // (see buildChromatogramTooltipLines), not Plotly's native label.
65
+ hoverinfo: "none"
67
66
  };
68
- u.push(c);
67
+ s.push(n);
68
+ }
69
+ return s;
70
+ }
71
+ const I = (e) => typeof e == "number" ? e.toFixed(2) : String(e);
72
+ function _(e) {
73
+ if (!e) return [];
74
+ const t = e.replace(/<br\s*\/?>/gi, `
75
+ `);
76
+ return (typeof DOMParser > "u" ? t : new DOMParser().parseFromString(t, "text/html").body.textContent ?? "").split(`
77
+ `).map((i) => i.trim()).filter(Boolean);
78
+ }
79
+ function w(e) {
80
+ const t = e.trim(), o = { label: t, unit: "" };
81
+ if (!t.endsWith(")")) return o;
82
+ const i = t.lastIndexOf("(");
83
+ if (i <= 0) return o;
84
+ const a = t.slice(i + 1, -1).trim(), c = t.slice(0, i).trim();
85
+ return !a || !c || a.includes("(") || a.includes(")") ? o : { label: c, unit: a };
86
+ }
87
+ const C = (e, t) => t ? `${e} ${t}` : e;
88
+ function v(e, t, o) {
89
+ const i = N(e.name, e.metadata).split("<br>").slice(1);
90
+ return [`${e.name}: ${C(I(t), o)}`, ...i];
91
+ }
92
+ function L(e) {
93
+ const t = e.customdata?.peak;
94
+ return t ? _(t.hoverText ?? t.text) : typeof e.text == "string" ? _(e.text) : [];
95
+ }
96
+ function S(e, t) {
97
+ const { series: o, xAxisTitle: i, yAxisTitle: a } = t, c = w(i), { unit: d } = w(a), u = [], s = e.find((r) => r.x !== void 0);
98
+ s?.x !== void 0 && u.push(`${c.label}: ${C(I(s.x), c.unit)}`);
99
+ const n = /* @__PURE__ */ new Set();
100
+ for (const r of e) {
101
+ const p = r.curveNumber === void 0 ? void 0 : o[r.curveNumber];
102
+ if (p) {
103
+ r.y !== void 0 && u.push(...v(p, r.y, d));
104
+ continue;
105
+ }
106
+ const m = L(r), g = m.join(`
107
+ `);
108
+ m.length > 0 && !n.has(g) && (n.add(g), u.push(...m));
69
109
  }
70
110
  return u;
71
111
  }
72
- function L(i) {
112
+ function W(e) {
73
113
  const {
74
- title: e,
75
- titleFontSize: a,
76
- titleTopMargin: n,
77
- width: s,
78
- height: p,
114
+ title: t,
115
+ titleFontSize: o,
116
+ titleTopMargin: i,
117
+ width: a,
118
+ height: c,
79
119
  xAxisTitle: d,
80
- yAxisTitle: f,
81
- xRange: l,
82
- yRange: g,
83
- showLegend: u,
84
- seriesCount: o,
85
- showGridX: c,
86
- showGridY: t,
87
- showCrosshairs: x,
88
- theme: r,
89
- peakAnnotations: _
90
- } = i;
120
+ yAxisTitle: u,
121
+ xRange: s,
122
+ yRange: n,
123
+ showLegend: r,
124
+ seriesCount: p,
125
+ showGridX: m,
126
+ showGridY: g,
127
+ showCrosshairs: y,
128
+ theme: l,
129
+ peakAnnotations: b
130
+ } = e;
91
131
  return {
92
- title: e ? {
93
- text: e,
94
- font: { size: a, family: m, color: r.textColor }
132
+ title: t ? {
133
+ text: t,
134
+ font: { size: o, family: f, color: l.textColor }
95
135
  } : void 0,
96
- width: s,
97
- height: p,
136
+ width: a,
137
+ height: c,
98
138
  margin: {
99
139
  l: h.MARGIN_LEFT,
100
140
  r: h.MARGIN_RIGHT,
101
141
  b: h.MARGIN_BOTTOM,
102
- t: e ? n ?? h.MARGIN_TOP_WITH_TITLE : h.MARGIN_TOP_NO_TITLE,
142
+ t: t ? i ?? h.MARGIN_TOP_WITH_TITLE : h.MARGIN_TOP_NO_TITLE,
103
143
  pad: h.MARGIN_PAD
104
144
  },
105
- paper_bgcolor: r.paperBg,
106
- plot_bgcolor: r.plotBg,
107
- font: { family: m },
108
- hovermode: x ? "x" : "x unified",
145
+ paper_bgcolor: l.paperBg,
146
+ plot_bgcolor: l.plotBg,
147
+ font: { family: f },
148
+ hovermode: y ? "x" : "x unified",
109
149
  dragmode: "zoom",
110
150
  xaxis: {
111
151
  title: {
112
152
  text: d,
113
- font: { size: 14, color: r.textSecondary, family: m },
153
+ font: { size: 14, color: l.textSecondary, family: f },
114
154
  standoff: 15
115
155
  },
116
- showgrid: c,
117
- gridcolor: r.gridColor,
118
- linecolor: r.lineColor,
156
+ showgrid: m,
157
+ gridcolor: l.gridColor,
158
+ linecolor: l.lineColor,
119
159
  linewidth: 1,
120
- range: l,
121
- autorange: !l,
160
+ range: s,
161
+ autorange: !s,
122
162
  zeroline: !1,
123
- tickfont: { size: 12, color: r.textColor, family: m },
124
- showspikes: x,
163
+ tickfont: { size: 12, color: l.textColor, family: f },
164
+ showspikes: y,
125
165
  spikemode: "across",
126
166
  spikesnap: "cursor",
127
- spikecolor: r.spikeColor,
167
+ spikecolor: l.spikeColor,
128
168
  spikethickness: 1,
129
169
  spikedash: "dot"
130
170
  },
131
171
  yaxis: {
132
172
  title: {
133
- text: f,
134
- font: { size: 14, color: r.textSecondary, family: m },
173
+ text: u,
174
+ font: { size: 14, color: l.textSecondary, family: f },
135
175
  standoff: 10
136
176
  },
137
- showgrid: t,
138
- gridcolor: r.gridColor,
139
- linecolor: r.lineColor,
177
+ showgrid: g,
178
+ gridcolor: l.gridColor,
179
+ linecolor: l.lineColor,
140
180
  linewidth: 1,
141
- range: g,
142
- autorange: !g,
181
+ range: n,
182
+ autorange: !n,
143
183
  zeroline: !1,
144
- tickfont: { size: 12, color: r.textColor, family: m },
145
- showspikes: x,
184
+ tickfont: { size: 12, color: l.textColor, family: f },
185
+ showspikes: y,
146
186
  spikemode: "across",
147
187
  spikesnap: "cursor",
148
- spikecolor: r.spikeColor,
188
+ spikecolor: l.spikeColor,
149
189
  spikethickness: 1,
150
190
  spikedash: "dot"
151
191
  },
@@ -155,14 +195,14 @@ function L(i) {
155
195
  xanchor: "center",
156
196
  yanchor: "top",
157
197
  orientation: "h",
158
- font: { size: 12, color: r.textColor, family: m }
198
+ font: { size: 12, color: l.textColor, family: f }
159
199
  },
160
- showlegend: u && o > 1,
161
- annotations: _
200
+ showlegend: r && p > 1,
201
+ annotations: b
162
202
  };
163
203
  }
164
- function R(i) {
165
- const { showExportButton: e, width: a, height: n } = i;
204
+ function F(e) {
205
+ const { showExportButton: t, width: o, height: i } = e;
166
206
  return {
167
207
  responsive: !0,
168
208
  displayModeBar: !0,
@@ -170,47 +210,50 @@ function R(i) {
170
210
  modeBarButtonsToRemove: [
171
211
  "lasso2d",
172
212
  "select2d",
173
- ...e ? [] : ["toImage"]
213
+ ...t ? [] : ["toImage"]
174
214
  ],
175
- ...e && {
215
+ ...t && {
176
216
  toImageButtonOptions: {
177
217
  format: "png",
178
218
  filename: "chromatogram",
179
- width: a,
180
- height: n
219
+ width: o,
220
+ height: i
181
221
  }
182
222
  }
183
223
  };
184
224
  }
185
- function b(i, e, a, n, s) {
186
- return (p) => {
187
- const d = p.points[0];
188
- if (d && d.curveNumber < e) {
189
- const l = d.curveNumber;
190
- a.current !== l && (a.current !== null && T().restyle(i, { "line.width": y.BASE_LINE_WIDTH }, [a.current]), T().restyle(i, { "line.width": y.BASE_LINE_WIDTH * s }, [l]), a.current = l);
225
+ function $(e, t, o, i, a) {
226
+ return (c) => {
227
+ const d = c.points[0];
228
+ if (d && d.curveNumber < t) {
229
+ const s = d.curveNumber;
230
+ o.current !== s && (o.current !== null && k().restyle(e, { "line.width": x.BASE_LINE_WIDTH }, [o.current]), k().restyle(e, { "line.width": x.BASE_LINE_WIDTH * a }, [s]), o.current = s);
191
231
  }
192
- const f = p.points.find((l) => l.customdata != null);
193
- f && n.current?.(f.customdata);
232
+ const u = c.points.find((s) => s.customdata != null);
233
+ u && i.current?.(u.customdata);
194
234
  };
195
235
  }
196
- function D(i) {
197
- return (e) => {
198
- if (!i.current) return;
199
- const a = e.points.find((n) => n.customdata != null);
200
- a && i.current(a.customdata);
236
+ function U(e) {
237
+ return (t) => {
238
+ if (!e.current) return;
239
+ const o = t.points.find((i) => i.customdata != null);
240
+ o && e.current(o.customdata);
201
241
  };
202
242
  }
203
- function G(i, e, a) {
243
+ function Y(e, t, o) {
204
244
  return () => {
205
- a.current?.(null), e.current !== null && (T().restyle(i, { "line.width": y.BASE_LINE_WIDTH }, [e.current]), e.current = null);
245
+ o.current?.(null), t.current !== null && (k().restyle(e, { "line.width": x.BASE_LINE_WIDTH }, [t.current]), t.current = null);
206
246
  };
207
247
  }
208
248
  export {
209
- R as buildConfig,
210
- L as buildLayout,
211
- z as buildTraceData,
212
- D as createClickHandler,
213
- b as createHoverHandler,
214
- G as createUnhoverHandler
249
+ S as buildChromatogramTooltipLines,
250
+ F as buildConfig,
251
+ W as buildLayout,
252
+ P as buildTraceData,
253
+ U as createClickHandler,
254
+ $ as createHoverHandler,
255
+ Y as createUnhoverHandler,
256
+ _ as htmlToTooltipLines,
257
+ w as splitAxisTitle
215
258
  };
216
259
  //# sourceMappingURL=plotBuilder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plotBuilder.js","sources":["../../../../src/components/charts/Chromatogram/plotBuilder.ts"],"sourcesContent":["import { CHART_COLORS } from \"../../../utils/colors\";\nimport { getLoadedPlotly } from \"../plotly-loader\";\n\nimport { createBoundaryMarkerTraces } from \"./boundaryMarkers\";\nimport { CHROMATOGRAM_LAYOUT, CHROMATOGRAM_TRACE } from \"./constants\";\nimport { buildHoverExtraContent, collectPeaksWithBoundaryData } from \"./dataProcessing\";\nimport { createRegionOverlayTraces } from \"./regionOverlays\";\n\nimport type { ChromatogramSeries, PeakAnnotation, BoundaryMarkerStyle, PeakSelectEvent } from \"./types\";\nimport type Plotly from \"plotly.js-dist\";\n\nimport { CHART_FONT_FAMILY, type PlotlyThemeColors } from \"@/hooks/use-plotly-theme\";\n\ntype PeakForInteraction = {\n peak: PeakAnnotation & { id: string };\n seriesIndex: number;\n seriesName: string;\n isAutoDetected: boolean;\n};\n\ntype BuildTraceDataParams = {\n processedSeries: ChromatogramSeries[];\n processedAnnotations: PeakAnnotation[];\n allDetectedPeaks: { peaks: PeakAnnotation[]; seriesIndex: number }[];\n allPeaksForInteraction: PeakForInteraction[];\n showMarkers: boolean;\n markerSize: number;\n xAxisTitle: string;\n yAxisTitle: string;\n boundaryMarkers: BoundaryMarkerStyle;\n};\n\ntype BuildLayoutParams = {\n title?: string;\n titleFontSize: number;\n titleTopMargin?: number;\n width: number;\n height: number;\n xAxisTitle: string;\n yAxisTitle: string;\n xRange?: [number, number];\n yRange?: [number, number];\n showLegend: boolean;\n seriesCount: number;\n showGridX: boolean;\n showGridY: boolean;\n showCrosshairs: boolean;\n theme: PlotlyThemeColors;\n peakAnnotations: Partial<Plotly.Annotations>[];\n};\n\ntype BuildConfigParams = {\n showExportButton: boolean;\n width: number;\n height: number;\n};\n\nexport type { PeakForInteraction, BuildTraceDataParams, BuildLayoutParams, BuildConfigParams };\n\nexport function buildTraceData(params: BuildTraceDataParams): Plotly.Data[] {\n const {\n processedSeries,\n processedAnnotations,\n allDetectedPeaks,\n allPeaksForInteraction,\n showMarkers,\n markerSize,\n xAxisTitle,\n yAxisTitle,\n boundaryMarkers,\n } = params;\n\n const plotData: Plotly.Data[] = processedSeries.map((s, index) => {\n const traceColor = s.color || CHART_COLORS[index % CHART_COLORS.length];\n const extraContent = buildHoverExtraContent(s.name, s.metadata);\n\n const trace: Plotly.Data = {\n x: s.x,\n y: s.y,\n type: \"scatter\" as const,\n mode: showMarkers ? (\"lines+markers\" as const) : (\"lines\" as const),\n name: s.name,\n line: {\n color: traceColor,\n width: CHROMATOGRAM_TRACE.BASE_LINE_WIDTH,\n },\n hovertemplate: `%{x:.2f} ${xAxisTitle}<br>%{y:.2f} ${yAxisTitle}<extra>${extraContent}</extra>`,\n };\n if (showMarkers) {\n trace.marker = { size: markerSize, color: traceColor };\n }\n return trace;\n });\n\n if (boundaryMarkers !== \"none\") {\n const peaksWithData = collectPeaksWithBoundaryData(\n allDetectedPeaks,\n processedAnnotations,\n processedSeries\n );\n if (peaksWithData.length > 0) {\n plotData.push(...createBoundaryMarkerTraces(peaksWithData));\n }\n }\n\n processedAnnotations.forEach((ann) => {\n if (ann.regionOverlay && processedSeries[0]) {\n plotData.push(...createRegionOverlayTraces([ann], 0, processedSeries[0]));\n }\n });\n allDetectedPeaks.forEach(({ peaks, seriesIndex }) => {\n if (peaks.some((p) => p.regionOverlay) && processedSeries[seriesIndex]) {\n plotData.push(...createRegionOverlayTraces(peaks, seriesIndex, processedSeries[seriesIndex]));\n }\n });\n\n if (allPeaksForInteraction.length > 0) {\n const anyHoverText = allPeaksForInteraction.some((p) => p.peak.hoverText);\n const hitAreaTrace: Plotly.Data = {\n x: allPeaksForInteraction.map((p) => p.peak.x),\n y: allPeaksForInteraction.map((p) => p.peak.y),\n type: \"scatter\" as const,\n mode: \"markers\" as const,\n marker: { size: 14, opacity: 0 },\n showlegend: false,\n name: \"\",\n customdata: allPeaksForInteraction.map((p) => ({\n id: p.peak.id,\n peak: p.peak,\n seriesIndex: p.seriesIndex,\n seriesName: p.seriesName,\n isAutoDetected: p.isAutoDetected,\n })) as unknown as Plotly.Datum[],\n ...(anyHoverText\n ? {\n hovertemplate: allPeaksForInteraction.map((p) =>\n p.peak.hoverText ? `${p.peak.hoverText}<extra></extra>` : \"<extra></extra>\"\n ),\n }\n : { hovertemplate: \"<extra></extra>\" }),\n };\n plotData.push(hitAreaTrace);\n }\n\n return plotData;\n}\n\nexport function buildLayout(params: BuildLayoutParams): Partial<Plotly.Layout> {\n const {\n title,\n titleFontSize,\n titleTopMargin,\n width,\n height,\n xAxisTitle,\n yAxisTitle,\n xRange,\n yRange,\n showLegend,\n seriesCount,\n showGridX,\n showGridY,\n showCrosshairs,\n theme,\n peakAnnotations,\n } = params;\n\n return {\n title: title\n ? {\n text: title,\n font: { size: titleFontSize, family: CHART_FONT_FAMILY, color: theme.textColor },\n }\n : undefined,\n width,\n height,\n margin: {\n l: CHROMATOGRAM_LAYOUT.MARGIN_LEFT,\n r: CHROMATOGRAM_LAYOUT.MARGIN_RIGHT,\n b: CHROMATOGRAM_LAYOUT.MARGIN_BOTTOM,\n t: title\n ? (titleTopMargin ?? CHROMATOGRAM_LAYOUT.MARGIN_TOP_WITH_TITLE)\n : CHROMATOGRAM_LAYOUT.MARGIN_TOP_NO_TITLE,\n pad: CHROMATOGRAM_LAYOUT.MARGIN_PAD,\n },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: { family: CHART_FONT_FAMILY },\n hovermode: showCrosshairs ? (\"x\" as const) : (\"x unified\" as const),\n dragmode: \"zoom\" as const,\n xaxis: {\n title: {\n text: xAxisTitle,\n font: { size: 14, color: theme.textSecondary, family: CHART_FONT_FAMILY },\n standoff: 15,\n },\n showgrid: showGridX,\n gridcolor: theme.gridColor,\n linecolor: theme.lineColor,\n linewidth: 1,\n range: xRange,\n autorange: !xRange,\n zeroline: false,\n tickfont: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n showspikes: showCrosshairs,\n spikemode: \"across\" as const,\n spikesnap: \"cursor\" as const,\n spikecolor: theme.spikeColor,\n spikethickness: 1,\n spikedash: \"dot\" as const,\n },\n yaxis: {\n title: {\n text: yAxisTitle,\n font: { size: 14, color: theme.textSecondary, family: CHART_FONT_FAMILY },\n standoff: 10,\n },\n showgrid: showGridY,\n gridcolor: theme.gridColor,\n linecolor: theme.lineColor,\n linewidth: 1,\n range: yRange,\n autorange: !yRange,\n zeroline: false,\n tickfont: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n showspikes: showCrosshairs,\n spikemode: \"across\" as const,\n spikesnap: \"cursor\" as const,\n spikecolor: theme.spikeColor,\n spikethickness: 1,\n spikedash: \"dot\" as const,\n },\n legend: {\n x: 0.5,\n y: -0.15,\n xanchor: \"center\" as const,\n yanchor: \"top\" as const,\n orientation: \"h\" as const,\n font: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n },\n showlegend: showLegend && seriesCount > 1,\n annotations: peakAnnotations,\n };\n}\n\nexport function buildConfig(params: BuildConfigParams): Partial<Plotly.Config> {\n const { showExportButton, width, height } = params;\n return {\n responsive: true,\n displayModeBar: true,\n displaylogo: false,\n modeBarButtonsToRemove: [\n \"lasso2d\",\n \"select2d\",\n ...(showExportButton ? [] : ([\"toImage\"] as Plotly.ModeBarDefaultButtons[])),\n ] as Plotly.ModeBarDefaultButtons[],\n ...(showExportButton && {\n toImageButtonOptions: {\n format: \"png\",\n filename: \"chromatogram\",\n width,\n height,\n },\n }),\n };\n}\n\ntype MutableRef<T> = { current: T };\n\nexport function createHoverHandler(\n domElement: HTMLElement,\n processedSeriesLength: number,\n thickenedSeriesRef: MutableRef<number | null>,\n onPeakHoverRef: MutableRef<((event: PeakSelectEvent | null) => void) | undefined>,\n hoverLineWidthMultiplier: number\n): (eventData: Plotly.PlotHoverEvent) => void {\n return (eventData) => {\n const pt = eventData.points[0];\n if (pt && pt.curveNumber < processedSeriesLength) {\n const targetIdx = pt.curveNumber;\n if (thickenedSeriesRef.current !== targetIdx) {\n // Hover events can only fire once the plot is drawn, so the\n // lazily-loaded Plotly module is guaranteed to be available here.\n if (thickenedSeriesRef.current !== null) {\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH } as Plotly.Data, [thickenedSeriesRef.current]);\n }\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH * hoverLineWidthMultiplier } as Plotly.Data, [targetIdx]);\n thickenedSeriesRef.current = targetIdx;\n }\n }\n const peakPoint = eventData.points.find((p) => p.customdata != null);\n if (peakPoint) {\n onPeakHoverRef.current?.(peakPoint.customdata as unknown as PeakSelectEvent);\n }\n };\n}\n\nexport function createClickHandler(\n onPeakClickRef: MutableRef<((event: PeakSelectEvent) => void) | undefined>\n): (eventData: Plotly.PlotMouseEvent) => void {\n return (eventData) => {\n if (!onPeakClickRef.current) return;\n const peakPoint = eventData.points.find((p) => p.customdata != null);\n if (!peakPoint) return;\n onPeakClickRef.current(peakPoint.customdata as unknown as PeakSelectEvent);\n };\n}\n\nexport function createUnhoverHandler(\n domElement: HTMLElement,\n thickenedSeriesRef: MutableRef<number | null>,\n onPeakHoverRef: MutableRef<((event: PeakSelectEvent | null) => void) | undefined>\n): () => void {\n return () => {\n onPeakHoverRef.current?.(null);\n if (thickenedSeriesRef.current !== null) {\n // Unhover events can only fire once the plot is drawn, so the\n // lazily-loaded Plotly module is guaranteed to be available here.\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH } as Plotly.Data, [thickenedSeriesRef.current]);\n thickenedSeriesRef.current = null;\n }\n };\n}\n"],"names":["buildTraceData","params","processedSeries","processedAnnotations","allDetectedPeaks","allPeaksForInteraction","showMarkers","markerSize","xAxisTitle","yAxisTitle","boundaryMarkers","plotData","s","index","traceColor","CHART_COLORS","extraContent","buildHoverExtraContent","trace","CHROMATOGRAM_TRACE","peaksWithData","collectPeaksWithBoundaryData","createBoundaryMarkerTraces","ann","createRegionOverlayTraces","peaks","seriesIndex","p","anyHoverText","hitAreaTrace","buildLayout","title","titleFontSize","titleTopMargin","width","height","xRange","yRange","showLegend","seriesCount","showGridX","showGridY","showCrosshairs","theme","peakAnnotations","CHART_FONT_FAMILY","CHROMATOGRAM_LAYOUT","buildConfig","showExportButton","createHoverHandler","domElement","processedSeriesLength","thickenedSeriesRef","onPeakHoverRef","hoverLineWidthMultiplier","eventData","pt","targetIdx","getLoadedPlotly","peakPoint","createClickHandler","onPeakClickRef","createUnhoverHandler"],"mappings":"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"}
1
+ {"version":3,"file":"plotBuilder.js","sources":["../../../../src/components/charts/Chromatogram/plotBuilder.ts"],"sourcesContent":["import { CHART_COLORS } from \"../../../utils/colors\";\nimport { getLoadedPlotly } from \"../plotly-loader\";\n\nimport { createBoundaryMarkerTraces } from \"./boundaryMarkers\";\nimport { CHROMATOGRAM_LAYOUT, CHROMATOGRAM_TRACE } from \"./constants\";\nimport { buildHoverExtraContent, collectPeaksWithBoundaryData } from \"./dataProcessing\";\nimport { createRegionOverlayTraces } from \"./regionOverlays\";\n\nimport type { ChromatogramSeries, PeakAnnotation, BoundaryMarkerStyle, PeakSelectEvent } from \"./types\";\nimport type { ChartTooltipHoverPoint } from \"../ChartTooltip\";\nimport type Plotly from \"plotly.js-dist\";\n\nimport { CHART_FONT_FAMILY, type PlotlyThemeColors } from \"@/hooks/use-plotly-theme\";\n\ntype PeakForInteraction = {\n peak: PeakAnnotation & { id: string };\n seriesIndex: number;\n seriesName: string;\n isAutoDetected: boolean;\n};\n\ntype BuildTraceDataParams = {\n processedSeries: ChromatogramSeries[];\n processedAnnotations: PeakAnnotation[];\n allDetectedPeaks: { peaks: PeakAnnotation[]; seriesIndex: number }[];\n allPeaksForInteraction: PeakForInteraction[];\n showMarkers: boolean;\n markerSize: number;\n boundaryMarkers: BoundaryMarkerStyle;\n};\n\ntype BuildLayoutParams = {\n title?: string;\n titleFontSize: number;\n titleTopMargin?: number;\n width: number;\n height: number;\n xAxisTitle: string;\n yAxisTitle: string;\n xRange?: [number, number];\n yRange?: [number, number];\n showLegend: boolean;\n seriesCount: number;\n showGridX: boolean;\n showGridY: boolean;\n showCrosshairs: boolean;\n theme: PlotlyThemeColors;\n peakAnnotations: Partial<Plotly.Annotations>[];\n};\n\ntype BuildConfigParams = {\n showExportButton: boolean;\n width: number;\n height: number;\n};\n\nexport type { PeakForInteraction, BuildTraceDataParams, BuildLayoutParams, BuildConfigParams };\n\nexport function buildTraceData(params: BuildTraceDataParams): Plotly.Data[] {\n const {\n processedSeries,\n processedAnnotations,\n allDetectedPeaks,\n allPeaksForInteraction,\n showMarkers,\n markerSize,\n boundaryMarkers,\n } = params;\n\n const plotData: Plotly.Data[] = processedSeries.map((s, index) => {\n const traceColor = s.color || CHART_COLORS[index % CHART_COLORS.length];\n\n const trace: Plotly.Data = {\n x: s.x,\n y: s.y,\n type: \"scatter\" as const,\n mode: showMarkers ? (\"lines+markers\" as const) : (\"lines\" as const),\n name: s.name,\n line: {\n color: traceColor,\n width: CHROMATOGRAM_TRACE.BASE_LINE_WIDTH,\n },\n // Hover is rendered by the shared ChartTooltip (bound in Chromatogram);\n // \"none\" keeps plotly_hover firing without also drawing Plotly's own\n // label on top of it (SW-2298).\n hoverinfo: \"none\" as const,\n };\n if (showMarkers) {\n trace.marker = { size: markerSize, color: traceColor };\n }\n return trace;\n });\n\n if (boundaryMarkers !== \"none\") {\n const peaksWithData = collectPeaksWithBoundaryData(\n allDetectedPeaks,\n processedAnnotations,\n processedSeries\n );\n if (peaksWithData.length > 0) {\n plotData.push(...createBoundaryMarkerTraces(peaksWithData));\n }\n }\n\n processedAnnotations.forEach((ann) => {\n if (ann.regionOverlay && processedSeries[0]) {\n plotData.push(...createRegionOverlayTraces([ann], 0, processedSeries[0]));\n }\n });\n allDetectedPeaks.forEach(({ peaks, seriesIndex }) => {\n if (peaks.some((p) => p.regionOverlay) && processedSeries[seriesIndex]) {\n plotData.push(...createRegionOverlayTraces(peaks, seriesIndex, processedSeries[seriesIndex]));\n }\n });\n\n if (allPeaksForInteraction.length > 0) {\n const hitAreaTrace: Plotly.Data = {\n x: allPeaksForInteraction.map((p) => p.peak.x),\n y: allPeaksForInteraction.map((p) => p.peak.y),\n type: \"scatter\" as const,\n mode: \"markers\" as const,\n marker: { size: 14, opacity: 0 },\n showlegend: false,\n name: \"\",\n customdata: allPeaksForInteraction.map((p) => ({\n id: p.peak.id,\n peak: p.peak,\n seriesIndex: p.seriesIndex,\n seriesName: p.seriesName,\n isAutoDetected: p.isAutoDetected,\n })) as unknown as Plotly.Datum[],\n // Peak hoverText reaches the shared ChartTooltip through customdata\n // (see buildChromatogramTooltipLines), not Plotly's native label.\n hoverinfo: \"none\" as const,\n };\n plotData.push(hitAreaTrace);\n }\n\n return plotData;\n}\n\ntype ChromatogramTooltipPoint = ChartTooltipHoverPoint;\n\ntype ChromatogramTooltipParams = {\n series: ChromatogramSeries[];\n xAxisTitle: string;\n yAxisTitle: string;\n};\n\nconst formatTooltipNumber = (value: number | string): string =>\n typeof value === \"number\" ? value.toFixed(2) : String(value);\n\n/**\n * The ChartTooltip renders plain text, so `hoverText` written for Plotly's\n * hovertemplate is normalised for display: `<br>` splits lines and the rest is\n * reduced to its text content via the HTML parser (entities decoded, tags\n * dropped). This is presentation clean-up, not security sanitisation — the\n * lines are rendered as React text nodes, never as markup.\n */\nexport function htmlToTooltipLines(text?: string): string[] {\n if (!text) return [];\n const withBreaks = text.replace(/<br\\s*\\/?>/gi, \"\\n\");\n const plain =\n typeof DOMParser === \"undefined\"\n ? withBreaks\n : (new DOMParser().parseFromString(withBreaks, \"text/html\").body.textContent ?? \"\");\n return plain\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean);\n}\n\n/**\n * Split \"Signal (mAU)\" into { label: \"Signal\", unit: \"mAU\" } so values read\n * \"Signal: 12.00 mAU\"; a title without a trailing parenthesised unit keeps\n * its full text as the label and an empty unit.\n */\nexport function splitAxisTitle(axisTitle: string): { label: string; unit: string } {\n // Plain string scanning rather than a regex: the title is consumer input and\n // a backtracking pattern over its whitespace was flagged by CodeQL.\n const trimmed = axisTitle.trim();\n const fallback = { label: trimmed, unit: \"\" };\n if (!trimmed.endsWith(\")\")) return fallback;\n const open = trimmed.lastIndexOf(\"(\");\n if (open <= 0) return fallback;\n const unit = trimmed.slice(open + 1, -1).trim();\n const label = trimmed.slice(0, open).trim();\n if (!unit || !label || unit.includes(\"(\") || unit.includes(\")\")) return fallback;\n return { label, unit };\n}\n\nconst withUnit = (value: string, unit: string): string => (unit ? `${value} ${unit}` : value);\n\n/** \"<name>: <y> <unit>\" followed by the series metadata lines */\nfunction seriesTooltipLines(\n seriesEntry: ChromatogramSeries,\n y: number | string,\n unit: string\n): string[] {\n // buildHoverExtraContent yields \"<name><br>Key: value…\"; keep only the metadata\n const metadata = buildHoverExtraContent(seriesEntry.name, seriesEntry.metadata).split(\"<br>\").slice(1);\n return [`${seriesEntry.name}: ${withUnit(formatTooltipNumber(y), unit)}`, ...metadata];\n}\n\n/** Peak text for a hit-area point (customdata) or a region overlay (trace text) */\nfunction peakTooltipLines(point: ChromatogramTooltipPoint): string[] {\n const peak = (point.customdata as { peak?: PeakAnnotation } | null | undefined)?.peak;\n if (peak) return htmlToTooltipLines(peak.hoverText ?? peak.text);\n return typeof point.text === \"string\" ? htmlToTooltipLines(point.text) : [];\n}\n\n/**\n * Lines for the shared ChartTooltip: the shared x value, one line per hovered\n * series (plus its metadata), then any peak text/hoverText the cursor is on —\n * peaks arrive as the invisible hit-area trace's customdata, overlays carry\n * their text on the trace. The same peak can be reported by both in one\n * \"x unified\" hover, so identical peak blocks are emitted once.\n */\nexport function buildChromatogramTooltipLines(\n points: ChromatogramTooltipPoint[],\n params: ChromatogramTooltipParams\n): string[] {\n const { series, xAxisTitle, yAxisTitle } = params;\n // Both axes are split the same way so the x and y lines read alike:\n // \"Retention Time: 5.80 min\" over \"Sample A: 420.00 mAU\".\n const xAxis = splitAxisTitle(xAxisTitle);\n const { unit } = splitAxisTitle(yAxisTitle);\n const lines: string[] = [];\n const first = points.find((p) => p.x !== undefined);\n if (first?.x !== undefined) {\n lines.push(`${xAxis.label}: ${withUnit(formatTooltipNumber(first.x), xAxis.unit)}`);\n }\n\n const seenPeakBlocks = new Set<string>();\n for (const point of points) {\n const seriesEntry = point.curveNumber === undefined ? undefined : series[point.curveNumber];\n if (seriesEntry) {\n if (point.y !== undefined) lines.push(...seriesTooltipLines(seriesEntry, point.y, unit));\n continue;\n }\n const peakLines = peakTooltipLines(point);\n const key = peakLines.join(\"\\n\");\n if (peakLines.length > 0 && !seenPeakBlocks.has(key)) {\n seenPeakBlocks.add(key);\n lines.push(...peakLines);\n }\n }\n return lines;\n}\n\nexport function buildLayout(params: BuildLayoutParams): Partial<Plotly.Layout> {\n const {\n title,\n titleFontSize,\n titleTopMargin,\n width,\n height,\n xAxisTitle,\n yAxisTitle,\n xRange,\n yRange,\n showLegend,\n seriesCount,\n showGridX,\n showGridY,\n showCrosshairs,\n theme,\n peakAnnotations,\n } = params;\n\n return {\n title: title\n ? {\n text: title,\n font: { size: titleFontSize, family: CHART_FONT_FAMILY, color: theme.textColor },\n }\n : undefined,\n width,\n height,\n margin: {\n l: CHROMATOGRAM_LAYOUT.MARGIN_LEFT,\n r: CHROMATOGRAM_LAYOUT.MARGIN_RIGHT,\n b: CHROMATOGRAM_LAYOUT.MARGIN_BOTTOM,\n t: title\n ? (titleTopMargin ?? CHROMATOGRAM_LAYOUT.MARGIN_TOP_WITH_TITLE)\n : CHROMATOGRAM_LAYOUT.MARGIN_TOP_NO_TITLE,\n pad: CHROMATOGRAM_LAYOUT.MARGIN_PAD,\n },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: { family: CHART_FONT_FAMILY },\n hovermode: showCrosshairs ? (\"x\" as const) : (\"x unified\" as const),\n dragmode: \"zoom\" as const,\n xaxis: {\n title: {\n text: xAxisTitle,\n font: { size: 14, color: theme.textSecondary, family: CHART_FONT_FAMILY },\n standoff: 15,\n },\n showgrid: showGridX,\n gridcolor: theme.gridColor,\n linecolor: theme.lineColor,\n linewidth: 1,\n range: xRange,\n autorange: !xRange,\n zeroline: false,\n tickfont: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n showspikes: showCrosshairs,\n spikemode: \"across\" as const,\n spikesnap: \"cursor\" as const,\n spikecolor: theme.spikeColor,\n spikethickness: 1,\n spikedash: \"dot\" as const,\n },\n yaxis: {\n title: {\n text: yAxisTitle,\n font: { size: 14, color: theme.textSecondary, family: CHART_FONT_FAMILY },\n standoff: 10,\n },\n showgrid: showGridY,\n gridcolor: theme.gridColor,\n linecolor: theme.lineColor,\n linewidth: 1,\n range: yRange,\n autorange: !yRange,\n zeroline: false,\n tickfont: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n showspikes: showCrosshairs,\n spikemode: \"across\" as const,\n spikesnap: \"cursor\" as const,\n spikecolor: theme.spikeColor,\n spikethickness: 1,\n spikedash: \"dot\" as const,\n },\n legend: {\n x: 0.5,\n y: -0.15,\n xanchor: \"center\" as const,\n yanchor: \"top\" as const,\n orientation: \"h\" as const,\n font: { size: 12, color: theme.textColor, family: CHART_FONT_FAMILY },\n },\n showlegend: showLegend && seriesCount > 1,\n annotations: peakAnnotations,\n };\n}\n\nexport function buildConfig(params: BuildConfigParams): Partial<Plotly.Config> {\n const { showExportButton, width, height } = params;\n return {\n responsive: true,\n displayModeBar: true,\n displaylogo: false,\n modeBarButtonsToRemove: [\n \"lasso2d\",\n \"select2d\",\n ...(showExportButton ? [] : ([\"toImage\"] as Plotly.ModeBarDefaultButtons[])),\n ] as Plotly.ModeBarDefaultButtons[],\n ...(showExportButton && {\n toImageButtonOptions: {\n format: \"png\",\n filename: \"chromatogram\",\n width,\n height,\n },\n }),\n };\n}\n\ntype MutableRef<T> = { current: T };\n\nexport function createHoverHandler(\n domElement: HTMLElement,\n processedSeriesLength: number,\n thickenedSeriesRef: MutableRef<number | null>,\n onPeakHoverRef: MutableRef<((event: PeakSelectEvent | null) => void) | undefined>,\n hoverLineWidthMultiplier: number\n): (eventData: Plotly.PlotHoverEvent) => void {\n return (eventData) => {\n const pt = eventData.points[0];\n if (pt && pt.curveNumber < processedSeriesLength) {\n const targetIdx = pt.curveNumber;\n if (thickenedSeriesRef.current !== targetIdx) {\n // Hover events can only fire once the plot is drawn, so the\n // lazily-loaded Plotly module is guaranteed to be available here.\n if (thickenedSeriesRef.current !== null) {\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH } as Plotly.Data, [thickenedSeriesRef.current]);\n }\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH * hoverLineWidthMultiplier } as Plotly.Data, [targetIdx]);\n thickenedSeriesRef.current = targetIdx;\n }\n }\n const peakPoint = eventData.points.find((p) => p.customdata != null);\n if (peakPoint) {\n onPeakHoverRef.current?.(peakPoint.customdata as unknown as PeakSelectEvent);\n }\n };\n}\n\nexport function createClickHandler(\n onPeakClickRef: MutableRef<((event: PeakSelectEvent) => void) | undefined>\n): (eventData: Plotly.PlotMouseEvent) => void {\n return (eventData) => {\n if (!onPeakClickRef.current) return;\n const peakPoint = eventData.points.find((p) => p.customdata != null);\n if (!peakPoint) return;\n onPeakClickRef.current(peakPoint.customdata as unknown as PeakSelectEvent);\n };\n}\n\nexport function createUnhoverHandler(\n domElement: HTMLElement,\n thickenedSeriesRef: MutableRef<number | null>,\n onPeakHoverRef: MutableRef<((event: PeakSelectEvent | null) => void) | undefined>\n): () => void {\n return () => {\n onPeakHoverRef.current?.(null);\n if (thickenedSeriesRef.current !== null) {\n // Unhover events can only fire once the plot is drawn, so the\n // lazily-loaded Plotly module is guaranteed to be available here.\n getLoadedPlotly().restyle(domElement, { \"line.width\": CHROMATOGRAM_TRACE.BASE_LINE_WIDTH } as Plotly.Data, [thickenedSeriesRef.current]);\n thickenedSeriesRef.current = null;\n }\n };\n}\n"],"names":["buildTraceData","params","processedSeries","processedAnnotations","allDetectedPeaks","allPeaksForInteraction","showMarkers","markerSize","boundaryMarkers","plotData","s","index","traceColor","CHART_COLORS","trace","CHROMATOGRAM_TRACE","peaksWithData","collectPeaksWithBoundaryData","createBoundaryMarkerTraces","ann","createRegionOverlayTraces","peaks","seriesIndex","hitAreaTrace","p","formatTooltipNumber","value","htmlToTooltipLines","text","withBreaks","line","splitAxisTitle","axisTitle","trimmed","fallback","open","unit","label","withUnit","seriesTooltipLines","seriesEntry","y","metadata","buildHoverExtraContent","peakTooltipLines","point","peak","buildChromatogramTooltipLines","points","series","xAxisTitle","yAxisTitle","xAxis","lines","first","seenPeakBlocks","peakLines","key","buildLayout","title","titleFontSize","titleTopMargin","width","height","xRange","yRange","showLegend","seriesCount","showGridX","showGridY","showCrosshairs","theme","peakAnnotations","CHART_FONT_FAMILY","CHROMATOGRAM_LAYOUT","buildConfig","showExportButton","createHoverHandler","domElement","processedSeriesLength","thickenedSeriesRef","onPeakHoverRef","hoverLineWidthMultiplier","eventData","pt","targetIdx","getLoadedPlotly","peakPoint","createClickHandler","onPeakClickRef","createUnhoverHandler"],"mappings":"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"}