spectraview 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -632,6 +632,31 @@ interface UseKeyboardNavigationOptions {
632
632
  }
633
633
  declare function useKeyboardNavigation(options: UseKeyboardNavigationOptions): (event: React.KeyboardEvent) => void;
634
634
 
635
+ /**
636
+ * Hook for applying spectral normalization/processing transformations.
637
+ *
638
+ * Takes raw spectra and a normalization mode, returns transformed spectra
639
+ * ready for rendering. All transformations are memoized.
640
+ */
641
+
642
+ /** Available normalization/processing modes. */
643
+ type NormalizationMode = "none" | "min-max" | "area" | "snv" | "baseline" | "smooth" | "derivative";
644
+ interface UseNormalizationOptions {
645
+ /** Input spectra. */
646
+ spectra: Spectrum[];
647
+ /** Active normalization mode. */
648
+ mode: NormalizationMode;
649
+ /** Smoothing window size (for "smooth" mode). Defaults to 7. */
650
+ smoothWindow?: number;
651
+ }
652
+ interface UseNormalizationReturn {
653
+ /** Transformed spectra. */
654
+ spectra: Spectrum[];
655
+ /** Current mode label for display. */
656
+ modeLabel: string;
657
+ }
658
+ declare function useNormalization({ spectra, mode, smoothWindow, }: UseNormalizationOptions): UseNormalizationReturn;
659
+
635
660
  interface StackedViewProps {
636
661
  /** Spectra to display. */
637
662
  spectra: Spectrum[];
@@ -1031,4 +1056,4 @@ declare function createXScale(domain: [number, number], width: number, margin: M
1031
1056
  */
1032
1057
  declare function createYScale(domain: [number, number], height: number, margin: Margin): d3_scale.ScaleLinear<number, number, never>;
1033
1058
 
1034
- export { type Annotation, AnnotationLayer, type AnnotationLayerProps, AxisLayer, Crosshair, type CrosshairPosition, type CrosshairProps, type CsvParseOptions, DARK_THEME, type DisplayMode, DropZone, type DropZoneProps, ExportMenu, type ExportMenuProps, KEYBOARD_SHORTCUTS, LIGHT_THEME, LINE_DASH_PATTERNS, type LTTBPoint, Legend, type LegendPosition$1 as LegendPosition, type LegendProps, type LineStyle, type Margin, Minimap, type MinimapProps, type Peak, type PeakDetectionOptions, PeakMarkers, type Region, RegionSelector, type ResolvedConfig, SPECTRUM_COLORS, type SnapPoint, type SnapResult, SpectraView, type SpectraViewProps, type Spectrum, SpectrumCanvas, type SpectrumType, StackedView, type SvgExportOptions, type Theme, Toolbar, Tooltip, type TooltipData, type TooltipProps, type UseExportReturn, type UseKeyboardNavigationOptions, type UsePeakPickingOptions, type UseRegionSelectOptions, type UseRegionSelectReturn, type UseSpectrumDataReturn, type UseZoomPanOptions, type UseZoomPanReturn, type ViewState, type ZoomPanState, addSpectra, baselineRubberBand, binarySearchClosest, computeXExtent, computeYExtent, correlationCoefficient, createXScale, createYScale, derivative1st, derivative2nd, detectPeaks, differenceSpectrum, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, interpolateToGrid, lttbDownsample, normalizeArea, normalizeMinMax, normalizeSNV, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, residualSpectrum, scaleSpectrum, smoothSavitzkyGolay, snapToNearestSpectrum, useExport, useKeyboardNavigation, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
1059
+ export { type Annotation, AnnotationLayer, type AnnotationLayerProps, AxisLayer, Crosshair, type CrosshairPosition, type CrosshairProps, type CsvParseOptions, DARK_THEME, type DisplayMode, DropZone, type DropZoneProps, ExportMenu, type ExportMenuProps, KEYBOARD_SHORTCUTS, LIGHT_THEME, LINE_DASH_PATTERNS, type LTTBPoint, Legend, type LegendPosition$1 as LegendPosition, type LegendProps, type LineStyle, type Margin, Minimap, type MinimapProps, type NormalizationMode, type Peak, type PeakDetectionOptions, PeakMarkers, type Region, RegionSelector, type ResolvedConfig, SPECTRUM_COLORS, type SnapPoint, type SnapResult, SpectraView, type SpectraViewProps, type Spectrum, SpectrumCanvas, type SpectrumType, StackedView, type SvgExportOptions, type Theme, Toolbar, Tooltip, type TooltipData, type TooltipProps, type UseExportReturn, type UseKeyboardNavigationOptions, type UseNormalizationOptions, type UseNormalizationReturn, type UsePeakPickingOptions, type UseRegionSelectOptions, type UseRegionSelectReturn, type UseSpectrumDataReturn, type UseZoomPanOptions, type UseZoomPanReturn, type ViewState, type ZoomPanState, addSpectra, baselineRubberBand, binarySearchClosest, computeXExtent, computeYExtent, correlationCoefficient, createXScale, createYScale, derivative1st, derivative2nd, detectPeaks, differenceSpectrum, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, interpolateToGrid, lttbDownsample, normalizeArea, normalizeMinMax, normalizeSNV, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, residualSpectrum, scaleSpectrum, smoothSavitzkyGolay, snapToNearestSpectrum, useExport, useKeyboardNavigation, useNormalization, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
package/dist/index.d.ts CHANGED
@@ -632,6 +632,31 @@ interface UseKeyboardNavigationOptions {
632
632
  }
633
633
  declare function useKeyboardNavigation(options: UseKeyboardNavigationOptions): (event: React.KeyboardEvent) => void;
634
634
 
635
+ /**
636
+ * Hook for applying spectral normalization/processing transformations.
637
+ *
638
+ * Takes raw spectra and a normalization mode, returns transformed spectra
639
+ * ready for rendering. All transformations are memoized.
640
+ */
641
+
642
+ /** Available normalization/processing modes. */
643
+ type NormalizationMode = "none" | "min-max" | "area" | "snv" | "baseline" | "smooth" | "derivative";
644
+ interface UseNormalizationOptions {
645
+ /** Input spectra. */
646
+ spectra: Spectrum[];
647
+ /** Active normalization mode. */
648
+ mode: NormalizationMode;
649
+ /** Smoothing window size (for "smooth" mode). Defaults to 7. */
650
+ smoothWindow?: number;
651
+ }
652
+ interface UseNormalizationReturn {
653
+ /** Transformed spectra. */
654
+ spectra: Spectrum[];
655
+ /** Current mode label for display. */
656
+ modeLabel: string;
657
+ }
658
+ declare function useNormalization({ spectra, mode, smoothWindow, }: UseNormalizationOptions): UseNormalizationReturn;
659
+
635
660
  interface StackedViewProps {
636
661
  /** Spectra to display. */
637
662
  spectra: Spectrum[];
@@ -1031,4 +1056,4 @@ declare function createXScale(domain: [number, number], width: number, margin: M
1031
1056
  */
1032
1057
  declare function createYScale(domain: [number, number], height: number, margin: Margin): d3_scale.ScaleLinear<number, number, never>;
1033
1058
 
1034
- export { type Annotation, AnnotationLayer, type AnnotationLayerProps, AxisLayer, Crosshair, type CrosshairPosition, type CrosshairProps, type CsvParseOptions, DARK_THEME, type DisplayMode, DropZone, type DropZoneProps, ExportMenu, type ExportMenuProps, KEYBOARD_SHORTCUTS, LIGHT_THEME, LINE_DASH_PATTERNS, type LTTBPoint, Legend, type LegendPosition$1 as LegendPosition, type LegendProps, type LineStyle, type Margin, Minimap, type MinimapProps, type Peak, type PeakDetectionOptions, PeakMarkers, type Region, RegionSelector, type ResolvedConfig, SPECTRUM_COLORS, type SnapPoint, type SnapResult, SpectraView, type SpectraViewProps, type Spectrum, SpectrumCanvas, type SpectrumType, StackedView, type SvgExportOptions, type Theme, Toolbar, Tooltip, type TooltipData, type TooltipProps, type UseExportReturn, type UseKeyboardNavigationOptions, type UsePeakPickingOptions, type UseRegionSelectOptions, type UseRegionSelectReturn, type UseSpectrumDataReturn, type UseZoomPanOptions, type UseZoomPanReturn, type ViewState, type ZoomPanState, addSpectra, baselineRubberBand, binarySearchClosest, computeXExtent, computeYExtent, correlationCoefficient, createXScale, createYScale, derivative1st, derivative2nd, detectPeaks, differenceSpectrum, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, interpolateToGrid, lttbDownsample, normalizeArea, normalizeMinMax, normalizeSNV, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, residualSpectrum, scaleSpectrum, smoothSavitzkyGolay, snapToNearestSpectrum, useExport, useKeyboardNavigation, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
1059
+ export { type Annotation, AnnotationLayer, type AnnotationLayerProps, AxisLayer, Crosshair, type CrosshairPosition, type CrosshairProps, type CsvParseOptions, DARK_THEME, type DisplayMode, DropZone, type DropZoneProps, ExportMenu, type ExportMenuProps, KEYBOARD_SHORTCUTS, LIGHT_THEME, LINE_DASH_PATTERNS, type LTTBPoint, Legend, type LegendPosition$1 as LegendPosition, type LegendProps, type LineStyle, type Margin, Minimap, type MinimapProps, type NormalizationMode, type Peak, type PeakDetectionOptions, PeakMarkers, type Region, RegionSelector, type ResolvedConfig, SPECTRUM_COLORS, type SnapPoint, type SnapResult, SpectraView, type SpectraViewProps, type Spectrum, SpectrumCanvas, type SpectrumType, StackedView, type SvgExportOptions, type Theme, Toolbar, Tooltip, type TooltipData, type TooltipProps, type UseExportReturn, type UseKeyboardNavigationOptions, type UseNormalizationOptions, type UseNormalizationReturn, type UsePeakPickingOptions, type UseRegionSelectOptions, type UseRegionSelectReturn, type UseSpectrumDataReturn, type UseZoomPanOptions, type UseZoomPanReturn, type ViewState, type ZoomPanState, addSpectra, baselineRubberBand, binarySearchClosest, computeXExtent, computeYExtent, correlationCoefficient, createXScale, createYScale, derivative1st, derivative2nd, detectPeaks, differenceSpectrum, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, interpolateToGrid, lttbDownsample, normalizeArea, normalizeMinMax, normalizeSNV, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, residualSpectrum, scaleSpectrum, smoothSavitzkyGolay, snapToNearestSpectrum, useExport, useKeyboardNavigation, useNormalization, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import{useCallback as Mt,useId as Fr,useMemo as U,useRef as Tt,useState as Xe}from"react";import{scaleLinear as pt}from"d3-scale";import{extent as ft}from"d3-array";var Kt=.05;function xe(e){let t=1/0,r=-1/0;for(let o of e){if(o.visible===!1)continue;let[n,s]=ft(o.x);n<t&&(t=n),s>r&&(r=s)}return isFinite(t)?[t,r]:[0,1]}function H(e){let t=1/0,r=-1/0;for(let s of e){if(s.visible===!1)continue;let[a,i]=ft(s.y);a<t&&(t=a),i>r&&(r=i)}if(!isFinite(t))return[0,1];let n=(r-t)*Kt;return[t-n,r+n]}function Se(e,t,r,o){let n=t-r.left-r.right,s=o?[e[1],e[0]]:e;return pt().domain(s).range([0,n])}function X(e,t,r){let o=t-r.top-r.bottom;return pt().domain(e).range([o,0])}var ve=["#2563eb","#dc2626","#16a34a","#9333ea","#ea580c","#0891b2","#be185d","#854d0e","#4f46e5","#65a30d"],dt={background:"#ffffff",axisColor:"#374151",gridColor:"#e5e7eb",tickColor:"#6b7280",labelColor:"#111827",crosshairColor:"#9ca3af",regionFill:"rgba(37, 99, 235, 0.1)",regionStroke:"rgba(37, 99, 235, 0.4)",tooltipBg:"#ffffff",tooltipBorder:"#d1d5db",tooltipText:"#111827"},bt={background:"#111827",axisColor:"#d1d5db",gridColor:"#374151",tickColor:"#9ca3af",labelColor:"#f9fafb",crosshairColor:"#6b7280",regionFill:"rgba(96, 165, 250, 0.15)",regionStroke:"rgba(96, 165, 250, 0.5)",tooltipBg:"#1f2937",tooltipBorder:"#4b5563",tooltipText:"#f9fafb"};function A(e){return ve[e%ve.length]}function z(e){return e==="dark"?bt:dt}import{useCallback as we,useEffect as Jt,useMemo as gt,useRef as ne,useState as qt}from"react";import{zoom as Qt,zoomIdentity as Ce}from"d3-zoom";import{select as V}from"d3-selection";import"d3-transition";var ht=1.5;function ke(e){let{plotWidth:t,plotHeight:r,xScale:o,yScale:n,scaleExtent:s=[1,50],enabled:a=!0,onViewChange:i}=e,l=ne(null),c=ne(null),u=ne(i);u.current=i;let p=ne(s);p.current=s;let[f,m]=qt(Ce),d=gt(()=>f.rescaleX(o.copy()),[f,o]),g=gt(()=>f.rescaleY(n.copy()),[f,n]);Jt(()=>{let S=l.current;if(!S||!a)return;let v=Qt().scaleExtent(p.current).extent([[0,0],[t,r]]).translateExtent([[-1/0,-1/0],[1/0,1/0]]).on("zoom",x=>{let w=x.transform;if(m(w),u.current){let C=w.rescaleX(o.copy()),k=w.rescaleY(n.copy());u.current(C.domain(),k.domain())}});return c.current=v,V(S).call(v),V(S).on("dblclick.zoom",()=>{V(S).transition().duration(300).call(v.transform,Ce)}),()=>{V(S).on(".zoom",null)}},[t,r,a,o,n]);let h=we(()=>{!l.current||!c.current||V(l.current).transition().duration(300).call(c.current.transform,Ce)},[]),b=we(()=>{!l.current||!c.current||V(l.current).transition().duration(200).call(c.current.scaleBy,ht)},[]),y=we(()=>{!l.current||!c.current||V(l.current).transition().duration(200).call(c.current.scaleBy,1/ht)},[]);return{zoomRef:l,state:{transform:f,isZoomed:f.k!==1||f.x!==0||f.y!==0},zoomedXScale:d,zoomedYScale:g,resetZoom:h,zoomIn:b,zoomOut:y}}import{forwardRef as ir,useEffect as xt,useImperativeHandle as ar,useRef as St}from"react";function Re(e,t,r,o,n,s,a){let i=o-r;if(i<=a){let f=[];for(let m=r;m<o;m++)f.push({px:n(e[m]),py:s(t[m]),index:m});return f}let l=[];l.push({px:n(e[r]),py:s(t[r]),index:r});let c=a-2,u=(i-2)/c,p=r;for(let f=0;f<c;f++){let m=r+1+Math.floor(f*u),d=r+1+Math.min(Math.floor((f+1)*u),i-2),g=d,h=r+1+Math.min(Math.floor((f+2)*u),i-2),b,y;if(f===c-1)b=n(e[o-1]),y=s(t[o-1]);else{b=0,y=0;let C=h-g;for(let k=g;k<h;k++)b+=n(e[k]),y+=s(t[k]);C>0&&(b/=C,y/=C)}let S=n(e[p]),v=s(t[p]),x=-1,w=m;for(let C=m;C<d;C++){let k=n(e[C]),R=s(t[C]),T=Math.abs((S-b)*(R-v)-(S-k)*(y-v));T>x&&(x=T,w=C)}l.push({px:n(e[w]),py:s(t[w]),index:w}),p=w}return l.push({px:n(e[o-1]),py:s(t[o-1]),index:o-1}),l}var er=1.5,tr={solid:[],dashed:[8,4],dotted:[2,2],"dash-dot":[8,4,2,4]},rr=2e3;function nr(e,t,r){e.clearRect(0,0,t,r)}function or(e,t,r,o,n,s,a){let{highlighted:i=!1,opacity:l=1}=a??{},c=Math.min(t.x.length,t.y.length);if(c<2)return;let u=t.color??A(r),p=t.lineWidth??er,f=i?p+1:p,m=tr[t.lineStyle??"solid"]??[],[d,g]=o.domain(),h=Math.min(d,g),b=Math.max(d,g),y=0,S=c;for(let x=0;x<c;x++)if(t.x[x]>=h||x<c-1&&t.x[x+1]>=h){y=Math.max(0,x-1);break}for(let x=c-1;x>=0;x--)if(t.x[x]<=b||x>0&&t.x[x-1]<=b){S=Math.min(c,x+2);break}let v=S-y;if(e.save(),e.beginPath(),e.strokeStyle=u,e.lineWidth=f,e.globalAlpha=l,e.lineJoin="round",e.setLineDash(m),v>rr){let x=Math.max(Math.ceil(s*2),200),w=Re(t.x,t.y,y,S,o,n,x);if(w.length>0){e.moveTo(w[0].px,w[0].py);for(let C=1;C<w.length;C++)e.lineTo(w[C].px,w[C].py)}}else{let x=!1;for(let w=y;w<S;w++){let C=o(t.x[w]),k=n(t.y[w]);x?e.lineTo(C,k):(e.moveTo(C,k),x=!0)}}e.stroke(),e.restore()}function yt(e,t,r,o,n,s,a){nr(e,n,s),t.forEach((i,l)=>{i.visible!==!1&&or(e,i,l,r,o,n,{highlighted:i.id===a,opacity:a&&i.id!==a?.3:1})})}import{jsx as sr}from"react/jsx-runtime";var W=ir(function({spectra:t,xScale:r,yScale:o,width:n,height:s,highlightedId:a},i){let l=St(null),c=St(1);return ar(i,()=>l.current,[]),xt(()=>{let u=l.current;if(!u)return;let p=window.devicePixelRatio||1;c.current=p,u.width=n*p,u.height=s*p},[n,s]),xt(()=>{let u=l.current;if(!u)return;let p=u.getContext("2d");if(!p)return;let f=c.current;p.setTransform(f,0,0,f,0,0),yt(p,t,r,o,n,s,a)},[t,r,o,n,s,a]),sr("canvas",{ref:l,style:{width:n,height:s,position:"absolute",top:0,left:0,pointerEvents:"none"}})});import{jsx as F,jsxs as B}from"react/jsx-runtime";function vt(e,t){let[r,o]=e.domain(),n=Math.min(r,o),a=(Math.max(r,o)-n)/(t-1);return Array.from({length:t},(i,l)=>n+l*a)}function wt(e){return Math.abs(e)>=1e3?Math.round(e).toString():Math.abs(e)>=1?e.toFixed(1):Math.abs(e)>=.01?e.toFixed(3):e.toExponential(1)}function Y({xScale:e,yScale:t,width:r,height:o,xLabel:n,yLabel:s,showGrid:a=!0,colors:i}){let l=vt(e,8),c=vt(t,6);return B("g",{children:[a&&B("g",{children:[l.map(u=>F("line",{x1:e(u),x2:e(u),y1:0,y2:o,stroke:i.gridColor,strokeWidth:.5},`xgrid-${u}`)),c.map(u=>F("line",{x1:0,x2:r,y1:t(u),y2:t(u),stroke:i.gridColor,strokeWidth:.5},`ygrid-${u}`))]}),B("g",{transform:`translate(0, ${o})`,children:[F("line",{x1:0,x2:r,y1:0,y2:0,stroke:i.axisColor}),l.map(u=>B("g",{transform:`translate(${e(u)}, 0)`,children:[F("line",{y1:0,y2:6,stroke:i.axisColor}),F("text",{y:20,textAnchor:"middle",fill:i.tickColor,fontSize:11,fontFamily:"system-ui, sans-serif",children:wt(u)})]},`xtick-${u}`)),n&&F("text",{x:r/2,y:42,textAnchor:"middle",fill:i.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:n})]}),B("g",{children:[F("line",{x1:0,x2:0,y1:0,y2:o,stroke:i.axisColor}),c.map(u=>B("g",{transform:`translate(0, ${t(u)})`,children:[F("line",{x1:-6,x2:0,stroke:i.axisColor}),F("text",{x:-10,textAnchor:"end",dominantBaseline:"middle",fill:i.tickColor,fontSize:11,fontFamily:"system-ui, sans-serif",children:wt(u)})]},`ytick-${u}`)),s&&F("text",{transform:`translate(-50, ${o/2}) rotate(-90)`,textAnchor:"middle",fill:i.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:s})]})]})}import{jsx as Me,jsxs as lr}from"react/jsx-runtime";function Te({peaks:e,xScale:t,yScale:r,colors:o,onPeakClick:n}){let[s,a]=t.domain(),i=Math.min(s,a),l=Math.max(s,a),c=e.filter(u=>u.x>=i&&u.x<=l);return Me("g",{className:"spectraview-peaks",children:c.map((u,p)=>{let f=t(u.x),m=r(u.y);return lr("g",{transform:`translate(${f}, ${m})`,style:{cursor:n?"pointer":"default"},onClick:()=>n?.(u),children:[Me("polygon",{points:`0,-5 -5,${-5*2.5} 5,${-5*2.5}`,fill:o.labelColor,opacity:.8}),u.label&&Me("text",{y:-5*2.5-14,textAnchor:"middle",fill:o.labelColor,fontSize:10,fontFamily:"system-ui, sans-serif",fontWeight:500,children:u.label})]},`peak-${u.x}-${p}`)})})}import{jsx as Ae,jsxs as cr}from"react/jsx-runtime";function Ee({regions:e,xScale:t,height:r,colors:o}){return Ae("g",{className:"spectraview-regions",children:e.map((n,s)=>{let a=t(n.xStart),i=t(n.xEnd),l=Math.min(a,i),c=Math.abs(i-a);return cr("g",{children:[Ae("rect",{x:l,y:0,width:c,height:r,fill:n.color??o.regionFill,stroke:o.regionStroke,strokeWidth:1}),n.label&&Ae("text",{x:l+c/2,y:12,textAnchor:"middle",fill:o.labelColor,fontSize:10,fontFamily:"system-ui, sans-serif",children:n.label})]},`region-${s}`)})})}import{jsx as oe,jsxs as Pe}from"react/jsx-runtime";function Le({position:e,width:t,height:r,colors:o,snapPoint:n}){return e?Pe("g",{className:"spectraview-crosshair",pointerEvents:"none",children:[oe("line",{x1:e.px,x2:e.px,y1:0,y2:r,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),oe("line",{x1:0,x2:t,y1:e.py,y2:e.py,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),n&&oe("circle",{cx:n.px,cy:n.py,r:4,fill:n.color??o.crosshairColor,stroke:o.background,strokeWidth:1.5}),Pe("g",{transform:`translate(${Math.min(e.px+10,t-100)}, ${Math.max(e.py-10,20)})`,children:[oe("rect",{x:0,y:-14,width:90,height:18,rx:3,fill:o.tooltipBg,stroke:o.tooltipBorder,strokeWidth:.5,opacity:.9}),Pe("text",{x:5,y:0,fill:o.tooltipText,fontSize:10,fontFamily:"monospace",children:[Ct(n?.dataX??e.dataX),","," ",Ct(n?.dataY??e.dataY)]})]})]}):null}function Ct(e){return Math.abs(e)>=100?Math.round(e).toString():Math.abs(e)>=1?e.toFixed(1):e.toFixed(4)}import{jsx as j,jsxs as mr}from"react/jsx-runtime";function Fe({annotations:e,xScale:t,yScale:r,colors:o}){return e.length===0?null:j("g",{className:"spectraview-annotations",pointerEvents:"none",children:e.map(n=>{let s=t(n.x),a=r(n.y),[i,l]=n.offset??[0,-20],c=s+i,u=a+l,p=n.fontSize??11,f=n.color??o.tickColor,m=n.showAnchorLine!==!1;return mr("g",{children:[m&&j("line",{x1:s,y1:a,x2:c,y2:u,stroke:f,strokeWidth:.75,strokeDasharray:"3 2",opacity:.6}),j("circle",{cx:s,cy:a,r:2.5,fill:f,opacity:.8}),j("text",{x:c,y:u,fill:o.background,fontSize:p,fontFamily:"system-ui, sans-serif",textAnchor:"middle",dominantBaseline:"auto",stroke:o.background,strokeWidth:3,strokeLinejoin:"round",children:n.text}),j("text",{x:c,y:u,fill:f,fontSize:p,fontFamily:"system-ui, sans-serif",textAnchor:"middle",dominantBaseline:"auto",children:n.text})]},n.id)})})}function ie(e,t,r){if(r===0)return-1;if(r===1)return 0;let o=e[r-1]>=e[0],n=0,s=r-1;for(;n<s-1;){let l=n+s>>>1,c=e[l];o?c<=t?n=l:s=l:c>=t?n=l:s=l}let a=Math.abs(e[n]-t),i=Math.abs(e[s]-t);return a<=i?n:s}function De(e,t,r,o,n){let s=null;for(let a of e){if(a.visible===!1)continue;let i=Math.min(a.x.length,a.y.length);if(i<2)continue;let l=ie(a.x,t,i);if(l<0)continue;let c=a.x[l],u=a.y[l],p=Math.abs(o(c)-o(t)),f=Math.abs(n(u)-r),m=Math.sqrt(p*p+f*f);(!s||m<s.distance)&&(s={spectrumId:a.id,index:l,x:c,y:u,distance:m})}return s}import{memo as ur}from"react";import{jsx as Ie,jsxs as fr}from"react/jsx-runtime";var Ue=e=>({display:"inline-flex",alignItems:"center",justifyContent:"center",width:28,height:28,border:`1px solid ${e==="dark"?"#4b5563":"#d1d5db"}`,borderRadius:4,background:e==="dark"?"#1f2937":"#ffffff",color:e==="dark"?"#d1d5db":"#374151",fontSize:14,cursor:"pointer",padding:0,lineHeight:1}),pr=e=>({display:"flex",gap:4,padding:"4px 0",borderBottom:`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`}),$e=ur(function({onZoomIn:t,onZoomOut:r,onReset:o,isZoomed:n,theme:s}){return fr("div",{style:pr(s),className:"spectraview-toolbar",children:[Ie("button",{type:"button",style:Ue(s),onClick:t,title:"Zoom in","aria-label":"Zoom in",children:"+"}),Ie("button",{type:"button",style:Ue(s),onClick:r,title:"Zoom out","aria-label":"Zoom out",children:"\u2212"}),Ie("button",{type:"button",style:{...Ue(s),opacity:n?1:.4},onClick:o,disabled:!n,title:"Reset zoom","aria-label":"Reset zoom",children:"\u21BA"})]})});import{memo as dr}from"react";import{jsx as Ne,jsxs as yr}from"react/jsx-runtime";var br=(e,t)=>({display:"flex",flexDirection:t==="left"||t==="right"?"column":"row",flexWrap:"wrap",gap:6,padding:"4px 8px",fontSize:12,fontFamily:"system-ui, sans-serif",borderTop:t==="bottom"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0,borderBottom:t==="top"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0,borderLeft:t==="right"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0,borderRight:t==="left"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0}),gr=(e,t,r)=>({display:"inline-flex",alignItems:"center",gap:4,cursor:"pointer",opacity:t?.4:1,fontWeight:r?600:400,color:e==="dark"?"#e5e7eb":"#374151",userSelect:"none",padding:"2px 4px",borderRadius:3,background:r?e==="dark"?"rgba(255,255,255,0.08)":"rgba(0,0,0,0.04)":"transparent",transition:"background 0.15s, opacity 0.15s"}),hr=(e,t)=>({width:12,height:3,borderRadius:1,background:e,opacity:t?.4:1,flexShrink:0}),ae=dr(function({spectra:t,theme:r,position:o,onToggleVisibility:n,onHighlight:s,highlightedId:a}){return t.length<=1?null:Ne("div",{className:"spectraview-legend",style:br(r,o),role:"list","aria-label":"Spectrum legend",children:t.map((i,l)=>{let c=i.color??A(l),u=i.visible===!1,p=a===i.id;return yr("div",{role:"listitem",style:gr(r,u,p),onClick:()=>n?.(i.id),onMouseEnter:()=>s?.(i.id),onMouseLeave:()=>s?.(null),title:u?`Show ${i.label}`:`Hide ${i.label}`,children:[Ne("span",{style:hr(c,u)}),Ne("span",{style:{textDecoration:u?"line-through":"none",maxWidth:120,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:i.label})]},i.id)})})});import{useCallback as se,useState as xr}from"react";import{jsx as Sr,jsxs as vr}from"react/jsx-runtime";function Oe({enabled:e,theme:t,width:r,height:o,onDrop:n,children:s}){let[a,i]=xr(!1),l={current:0},c=se(m=>{e&&(m.preventDefault(),l.current++,i(!0))},[e]),u=se(m=>{e&&(m.preventDefault(),l.current--,l.current<=0&&(l.current=0,i(!1)))},[e]),p=se(m=>{e&&(m.preventDefault(),m.dataTransfer.dropEffect="copy")},[e]),f=se(m=>{if(!e)return;m.preventDefault(),l.current=0,i(!1);let d=Array.from(m.dataTransfer.files);d.length>0&&n?.(d)},[e,n]);return vr("div",{style:{position:"relative",width:r,height:o},onDragEnter:c,onDragLeave:u,onDragOver:p,onDrop:f,children:[s,a&&Sr("div",{"data-testid":"dropzone-overlay",style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",background:t==="dark"?"rgba(30, 58, 138, 0.6)":"rgba(59, 130, 246, 0.15)",border:`2px dashed ${t==="dark"?"#60a5fa":"#3b82f6"}`,borderRadius:4,zIndex:100,pointerEvents:"none",fontSize:14,fontFamily:"system-ui, sans-serif",color:t==="dark"?"#93c5fd":"#1d4ed8",fontWeight:500},children:"Drop spectrum files here"})]})}import{useMemo as wr}from"react";import{jsx as Z,jsxs as Cr}from"react/jsx-runtime";var kt=8;function ze({spectra:e,xScale:t,plotWidth:r,plotHeight:o,margin:n,theme:s,showGrid:a,xLabel:i,yLabel:l}){let c=e.filter(d=>d.visible!==!1),u=wr(()=>z(s),[s]),p=c.length,f=(p-1)*kt,m=Math.max(40,Math.floor((o-f)/Math.max(p,1)));return Z("g",{className:"spectraview-stacked",children:c.map((d,g)=>{let h=g*(m+kt),b=H([d]),y=X(b,m+n.top+n.bottom,{...n,top:0,bottom:0}),S=d.color??A(g),v={...d,color:S};return Cr("g",{transform:`translate(0, ${h})`,children:[Z("rect",{x:0,y:0,width:r,height:m,fill:"transparent",stroke:u.gridColor,strokeWidth:.5,rx:2}),Z(Y,{xScale:t,yScale:y,width:r,height:m,xLabel:g===p-1?i:"",yLabel:l,showGrid:a,colors:u}),Z("text",{x:4,y:14,fill:S,fontSize:11,fontFamily:"system-ui, sans-serif",fontWeight:500,children:d.label}),Z("foreignObject",{x:0,y:0,width:r,height:m,children:Z(W,{spectra:[v],xScale:t,yScale:y,width:r,height:m})})]},d.id)})})}import{useCallback as Ve,useRef as kr,useState as Rr}from"react";function Be(e){let{enabled:t,xScale:r,onRegionSelect:o}=e,[n,s]=Rr(null),a=kr(null),i=Ve(u=>{if(!t||!u.shiftKey)return;u.preventDefault();let p=u.currentTarget.getBoundingClientRect(),f=u.clientX-p.left,m=r.invert(f);a.current=m,s({xStart:m,xEnd:m})},[t,r]),l=Ve(u=>{if(a.current===null)return;let p=u.currentTarget.getBoundingClientRect(),f=u.clientX-p.left,m=r.invert(f),d=a.current;s({xStart:Math.min(d,m),xEnd:Math.max(d,m)})},[r]),c=Ve(()=>{if(a.current===null||!n)return;Math.abs(n.xEnd-n.xStart)>0&&o?.(n),a.current=null,s(null)},[n,o]);return{pendingRegion:n,handleMouseDown:i,handleMouseMove:l,handleMouseUp:c}}import{useCallback as Mr,useEffect as Tr,useRef as Rt,useState as Ar}from"react";function Ze(){let[e,t]=Ar(null),r=Rt(null),o=Rt(null),n=Mr(s=>{if(r.current&&(r.current.disconnect(),r.current=null),o.current=s,!s)return;let a=new ResizeObserver(c=>{let u=c[0];if(!u)return;let{width:p,height:f}=u.contentRect;t({width:Math.round(p),height:Math.round(f)})});a.observe(s),r.current=a;let{width:i,height:l}=s.getBoundingClientRect();t({width:Math.round(i),height:Math.round(l)})},[]);return Tr(()=>()=>{r.current?.disconnect()},[]),{ref:n,size:e}}import{useCallback as Er}from"react";function _e(e){let{onZoomIn:t,onZoomOut:r,onReset:o,enabled:n=!0}=e;return Er(a=>{if(n)switch(a.key){case"+":case"=":a.preventDefault(),t();break;case"-":a.preventDefault(),r();break;case"Escape":a.preventDefault(),o();break}},[n,t,r,o])}function Pr(){return typeof window>"u"?!1:window.matchMedia("(prefers-reduced-motion: reduce)").matches}function He(e,t,r){return e===0?"Empty spectrum viewer":`Interactive spectrum viewer showing ${e} ${e===1?"spectrum":"spectra"}. X-axis: ${t}. Y-axis: ${r}. Use arrow keys to pan, +/- to zoom, Escape to reset.`}var Lr={PAN_LEFT:"ArrowLeft",PAN_RIGHT:"ArrowRight",PAN_UP:"ArrowUp",PAN_DOWN:"ArrowDown",ZOOM_IN:"+",ZOOM_IN_ALT:"=",ZOOM_OUT:"-",RESET:"Escape",NEXT_PEAK:"Tab",PREV_PEAK:"Shift+Tab"};import{Fragment as zr,jsx as M,jsxs as G}from"react/jsx-runtime";var Dr={top:20,right:20,bottom:50,left:65},Ur=800,Ir=400;function $r(e){return{width:e.width??Ur,height:e.height??Ir,reverseX:e.reverseX??!1,showGrid:e.showGrid??!0,showCrosshair:e.showCrosshair??!0,showToolbar:e.showToolbar??!0,showLegend:e.showLegend??!0,legendPosition:e.legendPosition??"bottom",displayMode:e.displayMode??"overlay",margin:{...Dr,...e.margin},theme:e.theme??"light",responsive:e.responsive??!1,enableDragDrop:e.enableDragDrop??!1,enableRegionSelect:e.enableRegionSelect??!1}}function Nr(e,t,r){let o=e[0];return{xLabel:t??o?.xUnit??"x",yLabel:r??o?.yUnit??"y"}}function Or(e){let{spectra:t,peaks:r=[],regions:o=[],annotations:n=[],onPeakClick:s,onViewChange:a,onCrosshairMove:i,onToggleVisibility:l,onFileDrop:c,onRegionSelect:u,canvasRef:p,snapCrosshair:f=!0}=e,{ref:m,size:d}=Ze(),h=`spectraview-clip-${Fr().replace(/:/g,"")}`,b=U(()=>$r(e),[e.width,e.height,e.reverseX,e.showGrid,e.showCrosshair,e.showToolbar,e.showLegend,e.legendPosition,e.displayMode,e.margin,e.theme,e.responsive,e.enableDragDrop,e.enableRegionSelect]),y=b.responsive&&d?d.width:b.width,{height:S,margin:v,reverseX:x,theme:w}=b,C=y-v.left-v.right,k=S-v.top-v.bottom,R=U(()=>z(w),[w]),T=U(()=>Nr(t,e.xLabel,e.yLabel),[t,e.xLabel,e.yLabel]),P=U(()=>xe(t),[t]),L=U(()=>H(t),[t]),fe=U(()=>Se(P,y,v,x),[P,y,v,x]),N=U(()=>X(L,S,v),[L,S,v]),O=Tt(a);O.current=a;let zt=U(()=>($,te)=>{O.current?.({xDomain:$,yDomain:te})},[]),{zoomRef:rt,state:Vt,zoomedXScale:E,zoomedYScale:I,resetZoom:nt,zoomIn:ot,zoomOut:it}=ke({plotWidth:C,plotHeight:k,xScale:fe,yScale:N,onViewChange:a?zt:void 0}),{pendingRegion:Q,handleMouseDown:Bt,handleMouseMove:Zt,handleMouseUp:_t}=Be({enabled:b.enableRegionSelect,xScale:E,onRegionSelect:u}),[de,at]=Xe(null),[Ht,st]=Xe(null),[Xt,be]=Xe(null),ee=Tt(i);ee.current=i;let lt=Mt($=>{if(!b.showCrosshair)return;let te=$.currentTarget.getBoundingClientRect(),mt=$.clientX-te.left,he=$.clientY-te.top,re=E.invert(mt),ye=I.invert(he);if(st({px:mt,py:he,dataX:re,dataY:ye}),f&&t.length>0){let D=De(t,re,he,E,I);if(D&&D.distance<50){let ut=t.findIndex(Gt=>Gt.id===D.spectrumId);be({px:E(D.x),py:I(D.y),dataX:D.x,dataY:D.y,color:t[ut]?.color??A(ut)}),ee.current?.(D.x,D.y)}else be(null),ee.current?.(re,ye)}else ee.current?.(re,ye)},[E,I,b.showCrosshair,f,t]),ct=Mt(()=>{st(null),be(null)},[]),Wt=_e({onZoomIn:ot,onZoomOut:it,onReset:nt}),Yt=U(()=>He(t.length,T.xLabel,T.yLabel),[t.length,T.xLabel,T.yLabel]),jt=b.displayMode==="stacked";if(t.length===0)return M("div",{ref:b.responsive?m:void 0,style:{width:b.responsive?"100%":y,height:S,display:"flex",alignItems:"center",justifyContent:"center",border:`1px dashed ${R.gridColor}`,borderRadius:8,color:R.tickColor,fontFamily:"system-ui, sans-serif",fontSize:14},className:e.className,children:"No spectra loaded"});let ge=b.showToolbar?37:0;return G("div",{ref:b.responsive?m:void 0,style:{width:b.responsive?"100%":y,background:R.background,borderRadius:4,overflow:"hidden"},className:e.className,role:"img","aria-label":Yt,tabIndex:0,onKeyDown:Wt,children:[b.showToolbar&&M($e,{onZoomIn:ot,onZoomOut:it,onReset:nt,isZoomed:Vt.isZoomed,theme:w}),b.showLegend&&b.legendPosition==="top"&&M(ae,{spectra:t,theme:w,position:"top",onToggleVisibility:l,onHighlight:at,highlightedId:de}),M(Oe,{enabled:b.enableDragDrop,theme:w,width:y,height:S-ge,onDrop:c,children:jt?M("svg",{width:y,height:S-ge,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${v.left}, ${v.top})`,children:[M(ze,{spectra:t,xScale:E,plotWidth:C,plotHeight:k,margin:v,theme:w,showGrid:b.showGrid,xLabel:T.xLabel,yLabel:T.yLabel}),M("rect",{ref:rt,x:0,y:0,width:C,height:k,fill:"transparent",style:{cursor:"grab"},onMouseMove:lt,onMouseLeave:ct})]})}):G(zr,{children:[M("div",{style:{position:"absolute",top:v.top,left:v.left,width:C,height:k,overflow:"hidden"},children:M(W,{ref:p,spectra:t,xScale:E,yScale:I,width:C,height:k,highlightedId:de??void 0})}),M("svg",{width:y,height:S-ge,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${v.left}, ${v.top})`,children:[M(Y,{xScale:E,yScale:I,width:C,height:k,xLabel:T.xLabel,yLabel:T.yLabel,showGrid:b.showGrid,colors:R}),M("defs",{children:M("clipPath",{id:h,children:M("rect",{x:0,y:0,width:C,height:k})})}),G("g",{clipPath:`url(#${h})`,children:[o.length>0&&M(Ee,{regions:o,xScale:E,height:k,colors:R}),r.length>0&&M(Te,{peaks:r,xScale:E,yScale:I,colors:R,onPeakClick:s})]}),n.length>0&&M(Fe,{annotations:n,xScale:E,yScale:I,colors:R}),b.showCrosshair&&M(Le,{position:Ht,width:C,height:k,colors:R,snapPoint:Xt}),Q&&M("rect",{x:E(Q.xStart),y:0,width:Math.abs(E(Q.xEnd)-E(Q.xStart)),height:k,fill:R.regionFill,stroke:R.regionStroke,strokeWidth:1,pointerEvents:"none"}),M("rect",{ref:rt,x:0,y:0,width:C,height:k,fill:"transparent",style:{cursor:b.showCrosshair?"crosshair":"grab"},onMouseDown:Bt,onMouseMove:$=>{lt($),Zt($)},onMouseUp:_t,onMouseLeave:ct})]})})]})}),b.showLegend&&b.legendPosition==="bottom"&&M(ae,{spectra:t,theme:w,position:"bottom",onToggleVisibility:l,onHighlight:at,highlightedId:de})]})}import{memo as Vr,useEffect as Br,useRef as Zr,useMemo as We}from"react";import{scaleLinear as At}from"d3-scale";import{jsx as le,jsxs as Et}from"react/jsx-runtime";var _r=Vr(function({spectra:t,xExtent:r,yExtent:o,visibleXDomain:n,width:s=200,height:a=50,theme:i="light",isZoomed:l=!1}){let c=Zr(null),u=We(()=>z(i),[i]),p=We(()=>At().domain(r).range([0,s]),[r,s]),f=We(()=>At().domain(o).range([a-2,2]),[o,a]);Br(()=>{let h=c.current?.getContext("2d");if(h){h.clearRect(0,0,s,a);for(let b=0;b<t.length;b++){let y=t[b];if(y.visible===!1)continue;let S=Math.min(y.x.length,y.y.length);if(S<2)continue;let v=y.color??A(b);h.beginPath(),h.strokeStyle=v,h.lineWidth=1,h.globalAlpha=.7;let x=Math.max(1,Math.floor(S/s)),w=!1;for(let C=0;C<S;C+=x){let k=p(y.x[C]),R=f(y.y[C]);w?h.lineTo(k,R):(h.moveTo(k,R),w=!0)}h.stroke()}}},[t,p,f,s,a]);let m=p(Math.min(n[0],n[1])),d=p(Math.max(n[0],n[1])),g=Math.max(d-m,2);return Et("div",{className:"spectraview-minimap",style:{position:"relative",width:s,height:a,border:`1px solid ${u.gridColor}`,borderRadius:3,overflow:"hidden",background:u.background},children:[le("canvas",{ref:c,width:s,height:a,style:{position:"absolute",top:0,left:0}}),l&&Et("svg",{width:s,height:a,style:{position:"absolute",top:0,left:0},children:[le("rect",{x:0,y:0,width:m,height:a,fill:u.background,opacity:.6}),le("rect",{x:m+g,y:0,width:s-m-g,height:a,fill:u.background,opacity:.6}),le("rect",{x:m,y:0,width:g,height:a,fill:"none",stroke:i==="dark"?"#60a5fa":"#3b82f6",strokeWidth:1.5,rx:1})]})]})});import{memo as Hr,useMemo as Pt}from"react";import{jsx as Lt,jsxs as K}from"react/jsx-runtime";function Ye(e,t){switch(t){case"fixed2":return e.toFixed(2);case"fixed4":return e.toFixed(4);case"scientific":return e.toExponential(2);default:return Math.abs(e)>=100?Math.round(e).toString():Math.abs(e)>=1?e.toFixed(2):Math.abs(e)>=.01?e.toFixed(4):e.toExponential(2)}}var Xr=Hr(function({data:t,spectra:r,peaks:o=[],plotWidth:n,plotHeight:s,colors:a,numberFormat:i="auto"}){if(!t)return null;let l=Pt(()=>t?r.filter(b=>b.visible!==!1).map((b,y)=>{let S=Math.min(b.x.length,b.y.length);if(S<1)return null;let v=ie(b.x,t.dataX,S);return v<0?null:{label:b.label,color:b.color??A(y),value:b.y[v],x:b.x[v]}}).filter(Boolean):[],[t?.dataX,r]),c=Pt(()=>{if(!t||o.length===0)return null;let b=null,y=1/0;for(let S of o){let v=Math.abs(S.x-t.dataX);v<y&&(y=v,b=S)}return b},[t?.dataX,o]),u=16,p=18,f=c?u:0,m=p+l.length*u+f+8,d=160,g=t.px+15,h=t.py-m/2;return g+d>n&&(g=t.px-d-15),h<0&&(h=4),h+m>s&&(h=s-m-4),K("g",{className:"spectraview-tooltip",transform:`translate(${g}, ${h})`,pointerEvents:"none",children:[Lt("rect",{x:0,y:0,width:d,height:m,rx:4,fill:a.tooltipBg,stroke:a.tooltipBorder,strokeWidth:.5,opacity:.95}),K("text",{x:8,y:14,fill:a.tooltipText,fontSize:10,fontFamily:"monospace",fontWeight:600,children:["x = ",Ye(t.dataX,i)]}),l.map((b,y)=>K("g",{transform:`translate(0, ${p+y*u})`,children:[Lt("circle",{cx:12,cy:8,r:3,fill:b.color}),K("text",{x:20,y:11,fill:a.tooltipText,fontSize:9,fontFamily:"monospace",children:[b.label.slice(0,10),": ",Ye(b.value,i)]})]},b.label)),c&&K("text",{x:8,y:p+l.length*u+12,fill:a.labelColor,fontSize:9,fontFamily:"monospace",fontStyle:"italic",children:["Peak: ",c.label??Ye(c.x,i)]})]})});import{useMemo as Gr}from"react";function je(e,t,r={}){let{prominence:o=.01,minDistance:n=5,maxPeaks:s}=r;if(e.length<3||t.length<3)return[];let a=1/0,i=-1/0;for(let m=0;m<t.length;m++)t[m]<a&&(a=t[m]),t[m]>i&&(i=t[m]);let l=i-a;if(l===0)return[];let c=o*l,u=[];for(let m=1;m<t.length-1;m++)if(t[m]>t[m-1]&&t[m]>t[m+1]){let d=Wr(t,m),g=Yr(t,m),h=t[m]-Math.max(d,g);h>=c&&u.push({index:m,prom:h})}u.sort((m,d)=>d.prom-m.prom);let p=[];for(let m of u)p.some(g=>Math.abs(g.index-m.index)<n)||p.push(m);return(s?p.slice(0,s):p).map(m=>({x:e[m.index],y:t[m.index],label:jr(e[m.index])})).sort((m,d)=>m.x-d.x)}function Wr(e,t){let r=e[t];for(let o=t-1;o>=0&&!(e[o]>e[t]);o--)e[o]<r&&(r=e[o]);return r}function Yr(e,t){let r=e[t];for(let o=t+1;o<e.length&&!(e[o]>e[t]);o++)e[o]<r&&(r=e[o]);return r}function jr(e){return Math.round(e).toString()}function Kr(e,t={}){let{enabled:r=!0,spectrumIds:o,prominence:n,minDistance:s,maxPeaks:a}=t;return Gr(()=>{if(!r)return[];let i=o?e.filter(c=>o.includes(c.id)):e,l=[];for(let c of i){if(c.visible===!1)continue;let u=je(c.x,c.y,{prominence:n,minDistance:s,maxPeaks:a});for(let p of u)l.push({...p,spectrumId:c.id})}return l},[e,r,o,n,s,a])}import{useCallback as _,useState as Qe}from"react";var Ft=0,Jr=[" ",",",";"," "];function Dt(e){let t=e.trim().split(/\r?\n/).slice(0,5),r=",",o=0;for(let n of Jr){let s=t.map(i=>i.split(n).length-1),a=Math.min(...s);a>0&&a>=o&&(s.every(l=>l===s[0])||a>o)&&(o=a,r=n)}return r}function Ge(e,t={}){let{xColumn:r=0,yColumn:o=1,hasHeader:n=!0,label:s="CSV Spectrum"}=t,a=t.delimiter??Dt(e),i=e.trim().split(/\r?\n/);if(i.length<2)throw new Error("CSV file must contain at least 2 lines");let l=s,c=0;if(n){let f=i[0].split(a).map(m=>m.trim());!t.label&&f[o]&&(l=f[o]),c=1}let u=[],p=[];for(let f=c;f<i.length;f++){let m=i[f].trim();if(m===""||m.startsWith("#"))continue;let d=m.split(a),g=parseFloat(d[r]),h=parseFloat(d[o]);!isNaN(g)&&!isNaN(h)&&(u.push(g),p.push(h))}if(u.length===0)throw new Error("No valid numeric data found in CSV");return{id:`csv-${++Ft}`,label:l,x:new Float64Array(u),y:new Float64Array(p)}}function qr(e,t={}){let{hasHeader:r=!0,label:o}=t,n=t.delimiter??Dt(e),s=e.trim().split(/\r?\n/);if(s.length<2)throw new Error("CSV file must contain at least 2 lines");let i=s[r?1:0].split(n).length;if(i<2)throw new Error("CSV must have at least 2 columns (x + y)");let l,c=0;r&&(l=s[0].split(n).map(m=>m.trim()),c=1);let u=[],p=Array.from({length:i-1},()=>[]);for(let m=c;m<s.length;m++){let d=s[m].trim();if(d===""||d.startsWith("#"))continue;let g=d.split(n),h=parseFloat(g[0]);if(!isNaN(h)){u.push(h);for(let b=1;b<i;b++){let y=parseFloat(g[b]);p[b-1].push(isNaN(y)?0:y)}}}let f=new Float64Array(u);return p.map((m,d)=>({id:`csv-${++Ft}`,label:o??l?.[d+1]??`Spectrum ${d+1}`,x:f,y:new Float64Array(m)}))}var Qr=0;function Je(e){let t;try{t=JSON.parse(e)}catch{throw new Error("Invalid JSON: failed to parse input")}if(Array.isArray(t))return t.map((r,o)=>Ke(r,o));if(typeof t=="object"&&t!==null){let r=t;return Array.isArray(r.spectra)?r.spectra.map((o,n)=>Ke(o,n)):[Ke(t,0)]}throw new Error("Invalid JSON structure: expected an object or array")}function Ke(e,t){let r=e.x??e.wavenumbers??e.wavelengths;if(!r||!Array.isArray(r))throw new Error(`Spectrum ${t}: missing x-axis data (expected "x", "wavenumbers", or "wavelengths")`);let o=e.y??e.intensities??e.absorbance;if(!o||!Array.isArray(o))throw new Error(`Spectrum ${t}: missing y-axis data (expected "y", "intensities", or "absorbance")`);if(r.length!==o.length)throw new Error(`Spectrum ${t}: x and y arrays must have the same length (got ${r.length} and ${o.length})`);let n=e.label??e.title??e.name??`Spectrum ${t+1}`;return{id:`json-${++Qr}`,label:n,x:new Float64Array(r),y:new Float64Array(o),xUnit:e.xUnit,yUnit:e.yUnit,type:e.type,meta:e.meta}}var It=0,ce=null,Ut=!1;async function en(){if(Ut)return ce;Ut=!0;try{ce=await import("jcampconverter")}catch{ce=null}return ce}function $t(e){let t=(e["DATA TYPE"]??e.DATATYPE??"").toLowerCase();return t.includes("infrared")||t.includes("ir")?"IR":t.includes("raman")?"Raman":t.includes("nir")||t.includes("near")?"NIR":t.includes("uv")||t.includes("vis")?"UV-Vis":t.includes("fluor")?"fluorescence":"other"}async function qe(e){let t=await en();return t?tn(e,t):[rn(e)]}function tn(e,t){return t.convert(e,{keepRecordsRegExp:/.*/}).flatten.map((o,n)=>{let s=o.spectra?.[0]?.data?.[0];if(!s)throw new Error(`JCAMP block ${n}: no spectral data found`);return{id:`jcamp-${++It}`,label:o.info?.TITLE??`Spectrum ${n+1}`,x:new Float64Array(s.x),y:new Float64Array(s.y),xUnit:o.info?.XUNITS??"cm\u207B\xB9",yUnit:o.info?.YUNITS??"Absorbance",type:$t(o.info),meta:o.info}})}function rn(e){let t=e.split(/\r?\n/),r={},o=[],n=[],s=!1;for(let a of t){let i=a.trim();if(i.startsWith("##")){let l=i.match(/^##(.+?)=\s*(.*)$/);if(l){let c=l[1].trim().toUpperCase(),u=l[2].trim();if(c==="XYDATA"||c==="XYPOINTS"){s=!0;continue}if(c==="END"){s=!1;continue}r[c]=u}continue}if(s&&i!==""){let l=i.split(/[\s,]+/).map(Number);if(l.length>=2&&!l.some(isNaN)){let c=l[0],u=parseFloat(r.FIRSTX??"0"),p=parseFloat(r.LASTX??"0"),f=parseInt(r.NPOINTS??"0",10);if(f>0&&l.length===2)o.push(l[0]),n.push(l[1]);else if(l.length>1){let m=f>1?(p-u)/(f-1):0;for(let d=1;d<l.length;d++)o.push(c+(d-1)*m),n.push(l[d])}}}}if(o.length===0)throw new Error("Failed to parse JCAMP-DX: no data found. Install jcampconverter for full format support.");return{id:`jcamp-${++It}`,label:r.TITLE??"JCAMP Spectrum",x:new Float64Array(o),y:new Float64Array(n),xUnit:r.XUNITS??"cm\u207B\xB9",yUnit:r.YUNITS??"Absorbance",type:$t(r),meta:r}}function nn(e){switch(e.toLowerCase().split(".").pop()){case"dx":case"jdx":case"jcamp":return"jcamp";case"csv":case"tsv":case"txt":return"csv";case"json":return"json";default:return null}}function on(e=[]){let[t,r]=Qe(e),[o,n]=Qe(!1),[s,a]=Qe(null),i=_(async(m,d)=>{n(!0),a(null);try{let g;switch(d){case"jcamp":g=await qe(m);break;case"csv":g=[Ge(m)];break;case"json":g=Je(m);break}r(h=>[...h,...g])}catch(g){let h=g instanceof Error?g.message:"Failed to parse file";a(h)}finally{n(!1)}},[]),l=_(async m=>{let d=nn(m.name);if(!d){a(`Unsupported file format: ${m.name}`);return}let g=await m.text();await i(g,d)},[i]),c=_(m=>{r(d=>[...d,m])},[]),u=_(m=>{r(d=>d.filter(g=>g.id!==m))},[]),p=_(m=>{r(d=>d.map(g=>g.id===m?{...g,visible:g.visible===!1}:g))},[]),f=_(()=>{r([]),a(null)},[]);return{spectra:t,loading:o,error:s,loadFile:l,loadText:i,addSpectrum:c,removeSpectrum:u,toggleVisibility:p,clear:f}}import{useCallback as me}from"react";var Nt={solid:"",dashed:"8 4",dotted:"2 2","dash-dot":"8 4 2 4"};function et(e,t,r,o){let{width:n,height:s,background:a="#ffffff",title:i}=o,l=e.filter(c=>c.visible!==!1).map((c,u)=>{let p=c.color??A(u),f=c.lineStyle??"solid",m=c.lineWidth??1.5,d=Nt[f]??"",g=Math.min(c.x.length,c.y.length);if(g<2)return"";let h=[];for(let b=0;b<g;b++){let y=t(c.x[b]).toFixed(2),S=r(c.y[b]).toFixed(2);h.push(`${b===0?"M":"L"}${y},${S}`)}return`<path d="${h.join(" ")}" fill="none" stroke="${p}" stroke-width="${m}"${d?` stroke-dasharray="${d}"`:""}/>
2
+ import{useCallback as Ft,useId as zr,useMemo as U,useRef as Dt,useState as Xe}from"react";import{scaleLinear as yt}from"d3-scale";import{extent as xt}from"d3-array";var rr=.05;function xe(e){let t=1/0,r=-1/0;for(let o of e){if(o.visible===!1)continue;let[n,s]=xt(o.x);n<t&&(t=n),s>r&&(r=s)}return isFinite(t)?[t,r]:[0,1]}function H(e){let t=1/0,r=-1/0;for(let s of e){if(s.visible===!1)continue;let[a,i]=xt(s.y);a<t&&(t=a),i>r&&(r=i)}if(!isFinite(t))return[0,1];let n=(r-t)*rr;return[t-n,r+n]}function Se(e,t,r,o){let n=t-r.left-r.right,s=o?[e[1],e[0]]:e;return yt().domain(s).range([0,n])}function X(e,t,r){let o=t-r.top-r.bottom;return yt().domain(e).range([o,0])}var ve=["#2563eb","#dc2626","#16a34a","#9333ea","#ea580c","#0891b2","#be185d","#854d0e","#4f46e5","#65a30d"],St={background:"#ffffff",axisColor:"#374151",gridColor:"#e5e7eb",tickColor:"#6b7280",labelColor:"#111827",crosshairColor:"#9ca3af",regionFill:"rgba(37, 99, 235, 0.1)",regionStroke:"rgba(37, 99, 235, 0.4)",tooltipBg:"#ffffff",tooltipBorder:"#d1d5db",tooltipText:"#111827"},vt={background:"#111827",axisColor:"#d1d5db",gridColor:"#374151",tickColor:"#9ca3af",labelColor:"#f9fafb",crosshairColor:"#6b7280",regionFill:"rgba(96, 165, 250, 0.15)",regionStroke:"rgba(96, 165, 250, 0.5)",tooltipBg:"#1f2937",tooltipBorder:"#4b5563",tooltipText:"#f9fafb"};function T(e){return ve[e%ve.length]}function O(e){return e==="dark"?vt:St}import{useCallback as ke,useEffect as nr,useMemo as kt,useRef as ne,useState as or}from"react";import{zoom as ir,zoomIdentity as we}from"d3-zoom";import{select as V}from"d3-selection";import"d3-transition";var wt=1.5;function Ce(e){let{plotWidth:t,plotHeight:r,xScale:o,yScale:n,scaleExtent:s=[1,50],enabled:a=!0,onViewChange:i}=e,l=ne(null),c=ne(null),u=ne(i);u.current=i;let p=ne(s);p.current=s;let[f,m]=or(we),d=kt(()=>f.rescaleX(o.copy()),[f,o]),g=kt(()=>f.rescaleY(n.copy()),[f,n]);nr(()=>{let S=l.current;if(!S||!a)return;let v=ir().scaleExtent(p.current).extent([[0,0],[t,r]]).translateExtent([[-1/0,-1/0],[1/0,1/0]]).on("zoom",x=>{let k=x.transform;if(m(k),u.current){let w=k.rescaleX(o.copy()),C=k.rescaleY(n.copy());u.current(w.domain(),C.domain())}});return c.current=v,V(S).call(v),V(S).on("dblclick.zoom",()=>{V(S).transition().duration(300).call(v.transform,we)}),()=>{V(S).on(".zoom",null)}},[t,r,a,o,n]);let h=ke(()=>{!l.current||!c.current||V(l.current).transition().duration(300).call(c.current.transform,we)},[]),b=ke(()=>{!l.current||!c.current||V(l.current).transition().duration(200).call(c.current.scaleBy,wt)},[]),y=ke(()=>{!l.current||!c.current||V(l.current).transition().duration(200).call(c.current.scaleBy,1/wt)},[]);return{zoomRef:l,state:{transform:f,isZoomed:f.k!==1||f.x!==0||f.y!==0},zoomedXScale:d,zoomedYScale:g,resetZoom:h,zoomIn:b,zoomOut:y}}import{forwardRef as ur,useEffect as Mt,useImperativeHandle as pr,useRef as Rt}from"react";function Me(e,t,r,o,n,s,a){let i=o-r;if(i<=a){let f=[];for(let m=r;m<o;m++)f.push({px:n(e[m]),py:s(t[m]),index:m});return f}let l=[];l.push({px:n(e[r]),py:s(t[r]),index:r});let c=a-2,u=(i-2)/c,p=r;for(let f=0;f<c;f++){let m=r+1+Math.floor(f*u),d=r+1+Math.min(Math.floor((f+1)*u),i-2),g=d,h=r+1+Math.min(Math.floor((f+2)*u),i-2),b,y;if(f===c-1)b=n(e[o-1]),y=s(t[o-1]);else{b=0,y=0;let w=h-g;for(let C=g;C<h;C++)b+=n(e[C]),y+=s(t[C]);w>0&&(b/=w,y/=w)}let S=n(e[p]),v=s(t[p]),x=-1,k=m;for(let w=m;w<d;w++){let C=n(e[w]),M=s(t[w]),A=Math.abs((S-b)*(M-v)-(S-C)*(y-v));A>x&&(x=A,k=w)}l.push({px:n(e[k]),py:s(t[k]),index:k}),p=k}return l.push({px:n(e[o-1]),py:s(t[o-1]),index:o-1}),l}var ar=1.5,sr={solid:[],dashed:[8,4],dotted:[2,2],"dash-dot":[8,4,2,4]},lr=2e3;function cr(e,t,r){e.clearRect(0,0,t,r)}function mr(e,t,r,o,n,s,a){let{highlighted:i=!1,opacity:l=1}=a??{},c=Math.min(t.x.length,t.y.length);if(c<2)return;let u=t.color??T(r),p=t.lineWidth??ar,f=i?p+1:p,m=sr[t.lineStyle??"solid"]??[],[d,g]=o.domain(),h=Math.min(d,g),b=Math.max(d,g),y=0,S=c;for(let x=0;x<c;x++)if(t.x[x]>=h||x<c-1&&t.x[x+1]>=h){y=Math.max(0,x-1);break}for(let x=c-1;x>=0;x--)if(t.x[x]<=b||x>0&&t.x[x-1]<=b){S=Math.min(c,x+2);break}let v=S-y;if(e.save(),e.beginPath(),e.strokeStyle=u,e.lineWidth=f,e.globalAlpha=l,e.lineJoin="round",e.setLineDash(m),v>lr){let x=Math.max(Math.ceil(s*2),200),k=Me(t.x,t.y,y,S,o,n,x);if(k.length>0){e.moveTo(k[0].px,k[0].py);for(let w=1;w<k.length;w++)e.lineTo(k[w].px,k[w].py)}}else{let x=!1;for(let k=y;k<S;k++){let w=o(t.x[k]),C=n(t.y[k]);x?e.lineTo(w,C):(e.moveTo(w,C),x=!0)}}e.stroke(),e.restore()}function Ct(e,t,r,o,n,s,a){cr(e,n,s),t.forEach((i,l)=>{i.visible!==!1&&mr(e,i,l,r,o,n,{highlighted:i.id===a,opacity:a&&i.id!==a?.3:1})})}import{jsx as fr}from"react/jsx-runtime";var W=ur(function({spectra:t,xScale:r,yScale:o,width:n,height:s,highlightedId:a},i){let l=Rt(null),c=Rt(1);return pr(i,()=>l.current,[]),Mt(()=>{let u=l.current;if(!u)return;let p=window.devicePixelRatio||1;c.current=p,u.width=n*p,u.height=s*p},[n,s]),Mt(()=>{let u=l.current;if(!u)return;let p=u.getContext("2d");if(!p)return;let f=c.current;p.setTransform(f,0,0,f,0,0),Ct(p,t,r,o,n,s,a)},[t,r,o,n,s,a]),fr("canvas",{ref:l,style:{width:n,height:s,position:"absolute",top:0,left:0,pointerEvents:"none"}})});import{jsx as F,jsxs as B}from"react/jsx-runtime";function At(e,t){let[r,o]=e.domain(),n=Math.min(r,o),a=(Math.max(r,o)-n)/(t-1);return Array.from({length:t},(i,l)=>n+l*a)}function Tt(e){return Math.abs(e)>=1e3?Math.round(e).toString():Math.abs(e)>=1?e.toFixed(1):Math.abs(e)>=.01?e.toFixed(3):e.toExponential(1)}function Y({xScale:e,yScale:t,width:r,height:o,xLabel:n,yLabel:s,showGrid:a=!0,colors:i}){let l=At(e,8),c=At(t,6);return B("g",{children:[a&&B("g",{children:[l.map(u=>F("line",{x1:e(u),x2:e(u),y1:0,y2:o,stroke:i.gridColor,strokeWidth:.5},`xgrid-${u}`)),c.map(u=>F("line",{x1:0,x2:r,y1:t(u),y2:t(u),stroke:i.gridColor,strokeWidth:.5},`ygrid-${u}`))]}),B("g",{transform:`translate(0, ${o})`,children:[F("line",{x1:0,x2:r,y1:0,y2:0,stroke:i.axisColor}),l.map(u=>B("g",{transform:`translate(${e(u)}, 0)`,children:[F("line",{y1:0,y2:6,stroke:i.axisColor}),F("text",{y:20,textAnchor:"middle",fill:i.tickColor,fontSize:11,fontFamily:"system-ui, sans-serif",children:Tt(u)})]},`xtick-${u}`)),n&&F("text",{x:r/2,y:42,textAnchor:"middle",fill:i.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:n})]}),B("g",{children:[F("line",{x1:0,x2:0,y1:0,y2:o,stroke:i.axisColor}),c.map(u=>B("g",{transform:`translate(0, ${t(u)})`,children:[F("line",{x1:-6,x2:0,stroke:i.axisColor}),F("text",{x:-10,textAnchor:"end",dominantBaseline:"middle",fill:i.tickColor,fontSize:11,fontFamily:"system-ui, sans-serif",children:Tt(u)})]},`ytick-${u}`)),s&&F("text",{transform:`translate(-50, ${o/2}) rotate(-90)`,textAnchor:"middle",fill:i.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:s})]})]})}import{jsx as Re,jsxs as dr}from"react/jsx-runtime";function Ae({peaks:e,xScale:t,yScale:r,colors:o,onPeakClick:n}){let[s,a]=t.domain(),i=Math.min(s,a),l=Math.max(s,a),c=e.filter(u=>u.x>=i&&u.x<=l);return Re("g",{className:"spectraview-peaks",children:c.map((u,p)=>{let f=t(u.x),m=r(u.y);return dr("g",{transform:`translate(${f}, ${m})`,style:{cursor:n?"pointer":"default"},onClick:()=>n?.(u),children:[Re("polygon",{points:`0,-5 -5,${-5*2.5} 5,${-5*2.5}`,fill:o.labelColor,opacity:.8}),u.label&&Re("text",{y:-5*2.5-14,textAnchor:"middle",fill:o.labelColor,fontSize:10,fontFamily:"system-ui, sans-serif",fontWeight:500,children:u.label})]},`peak-${u.x}-${p}`)})})}import{jsx as Te,jsxs as br}from"react/jsx-runtime";function Ee({regions:e,xScale:t,height:r,colors:o}){return Te("g",{className:"spectraview-regions",children:e.map((n,s)=>{let a=t(n.xStart),i=t(n.xEnd),l=Math.min(a,i),c=Math.abs(i-a);return br("g",{children:[Te("rect",{x:l,y:0,width:c,height:r,fill:n.color??o.regionFill,stroke:o.regionStroke,strokeWidth:1}),n.label&&Te("text",{x:l+c/2,y:12,textAnchor:"middle",fill:o.labelColor,fontSize:10,fontFamily:"system-ui, sans-serif",children:n.label})]},`region-${s}`)})})}import{jsx as oe,jsxs as Pe}from"react/jsx-runtime";function Le({position:e,width:t,height:r,colors:o,snapPoint:n}){return e?Pe("g",{className:"spectraview-crosshair",pointerEvents:"none",children:[oe("line",{x1:e.px,x2:e.px,y1:0,y2:r,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),oe("line",{x1:0,x2:t,y1:e.py,y2:e.py,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),n&&oe("circle",{cx:n.px,cy:n.py,r:4,fill:n.color??o.crosshairColor,stroke:o.background,strokeWidth:1.5}),Pe("g",{transform:`translate(${Math.min(e.px+10,t-100)}, ${Math.max(e.py-10,20)})`,children:[oe("rect",{x:0,y:-14,width:90,height:18,rx:3,fill:o.tooltipBg,stroke:o.tooltipBorder,strokeWidth:.5,opacity:.9}),Pe("text",{x:5,y:0,fill:o.tooltipText,fontSize:10,fontFamily:"monospace",children:[Et(n?.dataX??e.dataX),","," ",Et(n?.dataY??e.dataY)]})]})]}):null}function Et(e){return Math.abs(e)>=100?Math.round(e).toString():Math.abs(e)>=1?e.toFixed(1):e.toFixed(4)}import{jsx as j,jsxs as gr}from"react/jsx-runtime";function Fe({annotations:e,xScale:t,yScale:r,colors:o}){return e.length===0?null:j("g",{className:"spectraview-annotations",pointerEvents:"none",children:e.map(n=>{let s=t(n.x),a=r(n.y),[i,l]=n.offset??[0,-20],c=s+i,u=a+l,p=n.fontSize??11,f=n.color??o.tickColor,m=n.showAnchorLine!==!1;return gr("g",{children:[m&&j("line",{x1:s,y1:a,x2:c,y2:u,stroke:f,strokeWidth:.75,strokeDasharray:"3 2",opacity:.6}),j("circle",{cx:s,cy:a,r:2.5,fill:f,opacity:.8}),j("text",{x:c,y:u,fill:o.background,fontSize:p,fontFamily:"system-ui, sans-serif",textAnchor:"middle",dominantBaseline:"auto",stroke:o.background,strokeWidth:3,strokeLinejoin:"round",children:n.text}),j("text",{x:c,y:u,fill:f,fontSize:p,fontFamily:"system-ui, sans-serif",textAnchor:"middle",dominantBaseline:"auto",children:n.text})]},n.id)})})}function ie(e,t,r){if(r===0)return-1;if(r===1)return 0;let o=e[r-1]>=e[0],n=0,s=r-1;for(;n<s-1;){let l=n+s>>>1,c=e[l];o?c<=t?n=l:s=l:c>=t?n=l:s=l}let a=Math.abs(e[n]-t),i=Math.abs(e[s]-t);return a<=i?n:s}function De(e,t,r,o,n){let s=null;for(let a of e){if(a.visible===!1)continue;let i=Math.min(a.x.length,a.y.length);if(i<2)continue;let l=ie(a.x,t,i);if(l<0)continue;let c=a.x[l],u=a.y[l],p=Math.abs(o(c)-o(t)),f=Math.abs(n(u)-r),m=Math.sqrt(p*p+f*f);(!s||m<s.distance)&&(s={spectrumId:a.id,index:l,x:c,y:u,distance:m})}return s}import{memo as hr}from"react";import{jsx as Ie,jsxs as xr}from"react/jsx-runtime";var Ue=e=>({display:"inline-flex",alignItems:"center",justifyContent:"center",width:28,height:28,border:`1px solid ${e==="dark"?"#4b5563":"#d1d5db"}`,borderRadius:4,background:e==="dark"?"#1f2937":"#ffffff",color:e==="dark"?"#d1d5db":"#374151",fontSize:14,cursor:"pointer",padding:0,lineHeight:1}),yr=e=>({display:"flex",gap:4,padding:"4px 0",borderBottom:`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`}),Ne=hr(function({onZoomIn:t,onZoomOut:r,onReset:o,isZoomed:n,theme:s}){return xr("div",{style:yr(s),className:"spectraview-toolbar",children:[Ie("button",{type:"button",style:Ue(s),onClick:t,title:"Zoom in","aria-label":"Zoom in",children:"+"}),Ie("button",{type:"button",style:Ue(s),onClick:r,title:"Zoom out","aria-label":"Zoom out",children:"\u2212"}),Ie("button",{type:"button",style:{...Ue(s),opacity:n?1:.4},onClick:o,disabled:!n,title:"Reset zoom","aria-label":"Reset zoom",children:"\u21BA"})]})});import{memo as Sr}from"react";import{jsx as $e,jsxs as Cr}from"react/jsx-runtime";var vr=(e,t)=>({display:"flex",flexDirection:t==="left"||t==="right"?"column":"row",flexWrap:"wrap",gap:6,padding:"4px 8px",fontSize:12,fontFamily:"system-ui, sans-serif",borderTop:t==="bottom"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0,borderBottom:t==="top"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0,borderLeft:t==="right"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0,borderRight:t==="left"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0}),kr=(e,t,r)=>({display:"inline-flex",alignItems:"center",gap:4,cursor:"pointer",opacity:t?.4:1,fontWeight:r?600:400,color:e==="dark"?"#e5e7eb":"#374151",userSelect:"none",padding:"2px 4px",borderRadius:3,background:r?e==="dark"?"rgba(255,255,255,0.08)":"rgba(0,0,0,0.04)":"transparent",transition:"background 0.15s, opacity 0.15s"}),wr=(e,t)=>({width:12,height:3,borderRadius:1,background:e,opacity:t?.4:1,flexShrink:0}),ae=Sr(function({spectra:t,theme:r,position:o,onToggleVisibility:n,onHighlight:s,highlightedId:a}){return t.length<=1?null:$e("div",{className:"spectraview-legend",style:vr(r,o),role:"list","aria-label":"Spectrum legend",children:t.map((i,l)=>{let c=i.color??T(l),u=i.visible===!1,p=a===i.id;return Cr("div",{role:"listitem",style:kr(r,u,p),onClick:()=>n?.(i.id),onMouseEnter:()=>s?.(i.id),onMouseLeave:()=>s?.(null),title:u?`Show ${i.label}`:`Hide ${i.label}`,children:[$e("span",{style:wr(c,u)}),$e("span",{style:{textDecoration:u?"line-through":"none",maxWidth:120,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:i.label})]},i.id)})})});import{useCallback as se,useState as Mr}from"react";import{jsx as Rr,jsxs as Ar}from"react/jsx-runtime";function ze({enabled:e,theme:t,width:r,height:o,onDrop:n,children:s}){let[a,i]=Mr(!1),l={current:0},c=se(m=>{e&&(m.preventDefault(),l.current++,i(!0))},[e]),u=se(m=>{e&&(m.preventDefault(),l.current--,l.current<=0&&(l.current=0,i(!1)))},[e]),p=se(m=>{e&&(m.preventDefault(),m.dataTransfer.dropEffect="copy")},[e]),f=se(m=>{if(!e)return;m.preventDefault(),l.current=0,i(!1);let d=Array.from(m.dataTransfer.files);d.length>0&&n?.(d)},[e,n]);return Ar("div",{style:{position:"relative",width:r,height:o},onDragEnter:c,onDragLeave:u,onDragOver:p,onDrop:f,children:[s,a&&Rr("div",{"data-testid":"dropzone-overlay",style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",background:t==="dark"?"rgba(30, 58, 138, 0.6)":"rgba(59, 130, 246, 0.15)",border:`2px dashed ${t==="dark"?"#60a5fa":"#3b82f6"}`,borderRadius:4,zIndex:100,pointerEvents:"none",fontSize:14,fontFamily:"system-ui, sans-serif",color:t==="dark"?"#93c5fd":"#1d4ed8",fontWeight:500},children:"Drop spectrum files here"})]})}import{useMemo as Tr}from"react";import{jsx as Z,jsxs as Er}from"react/jsx-runtime";var Pt=8;function Oe({spectra:e,xScale:t,plotWidth:r,plotHeight:o,margin:n,theme:s,showGrid:a,xLabel:i,yLabel:l}){let c=e.filter(d=>d.visible!==!1),u=Tr(()=>O(s),[s]),p=c.length,f=(p-1)*Pt,m=Math.max(40,Math.floor((o-f)/Math.max(p,1)));return Z("g",{className:"spectraview-stacked",children:c.map((d,g)=>{let h=g*(m+Pt),b=H([d]),y=X(b,m+n.top+n.bottom,{...n,top:0,bottom:0}),S=d.color??T(g),v={...d,color:S};return Er("g",{transform:`translate(0, ${h})`,children:[Z("rect",{x:0,y:0,width:r,height:m,fill:"transparent",stroke:u.gridColor,strokeWidth:.5,rx:2}),Z(Y,{xScale:t,yScale:y,width:r,height:m,xLabel:g===p-1?i:"",yLabel:l,showGrid:a,colors:u}),Z("text",{x:4,y:14,fill:S,fontSize:11,fontFamily:"system-ui, sans-serif",fontWeight:500,children:d.label}),Z("foreignObject",{x:0,y:0,width:r,height:m,children:Z(W,{spectra:[v],xScale:t,yScale:y,width:r,height:m})})]},d.id)})})}import{useCallback as Ve,useRef as Pr,useState as Lr}from"react";function Be(e){let{enabled:t,xScale:r,onRegionSelect:o}=e,[n,s]=Lr(null),a=Pr(null),i=Ve(u=>{if(!t||!u.shiftKey)return;u.preventDefault();let p=u.currentTarget.getBoundingClientRect(),f=u.clientX-p.left,m=r.invert(f);a.current=m,s({xStart:m,xEnd:m})},[t,r]),l=Ve(u=>{if(a.current===null)return;let p=u.currentTarget.getBoundingClientRect(),f=u.clientX-p.left,m=r.invert(f),d=a.current;s({xStart:Math.min(d,m),xEnd:Math.max(d,m)})},[r]),c=Ve(()=>{if(a.current===null||!n)return;Math.abs(n.xEnd-n.xStart)>0&&o?.(n),a.current=null,s(null)},[n,o]);return{pendingRegion:n,handleMouseDown:i,handleMouseMove:l,handleMouseUp:c}}import{useCallback as Fr,useEffect as Dr,useRef as Lt,useState as Ur}from"react";function Ze(){let[e,t]=Ur(null),r=Lt(null),o=Lt(null),n=Fr(s=>{if(r.current&&(r.current.disconnect(),r.current=null),o.current=s,!s)return;let a=new ResizeObserver(c=>{let u=c[0];if(!u)return;let{width:p,height:f}=u.contentRect;t({width:Math.round(p),height:Math.round(f)})});a.observe(s),r.current=a;let{width:i,height:l}=s.getBoundingClientRect();t({width:Math.round(i),height:Math.round(l)})},[]);return Dr(()=>()=>{r.current?.disconnect()},[]),{ref:n,size:e}}import{useCallback as Ir}from"react";function _e(e){let{onZoomIn:t,onZoomOut:r,onReset:o,enabled:n=!0}=e;return Ir(a=>{if(n)switch(a.key){case"+":case"=":a.preventDefault(),t();break;case"-":a.preventDefault(),r();break;case"Escape":a.preventDefault(),o();break}},[n,t,r,o])}function Nr(){return typeof window>"u"?!1:window.matchMedia("(prefers-reduced-motion: reduce)").matches}function He(e,t,r){return e===0?"Empty spectrum viewer":`Interactive spectrum viewer showing ${e} ${e===1?"spectrum":"spectra"}. X-axis: ${t}. Y-axis: ${r}. Use arrow keys to pan, +/- to zoom, Escape to reset.`}var $r={PAN_LEFT:"ArrowLeft",PAN_RIGHT:"ArrowRight",PAN_UP:"ArrowUp",PAN_DOWN:"ArrowDown",ZOOM_IN:"+",ZOOM_IN_ALT:"=",ZOOM_OUT:"-",RESET:"Escape",NEXT_PEAK:"Tab",PREV_PEAK:"Shift+Tab"};import{Fragment as Xr,jsx as R,jsxs as G}from"react/jsx-runtime";var Or={top:20,right:20,bottom:50,left:65},Vr=800,Br=400;function Zr(e){return{width:e.width??Vr,height:e.height??Br,reverseX:e.reverseX??!1,showGrid:e.showGrid??!0,showCrosshair:e.showCrosshair??!0,showToolbar:e.showToolbar??!0,showLegend:e.showLegend??!0,legendPosition:e.legendPosition??"bottom",displayMode:e.displayMode??"overlay",margin:{...Or,...e.margin},theme:e.theme??"light",responsive:e.responsive??!1,enableDragDrop:e.enableDragDrop??!1,enableRegionSelect:e.enableRegionSelect??!1}}function _r(e,t,r){let o=e[0];return{xLabel:t??o?.xUnit??"x",yLabel:r??o?.yUnit??"y"}}function Hr(e){let{spectra:t,peaks:r=[],regions:o=[],annotations:n=[],onPeakClick:s,onViewChange:a,onCrosshairMove:i,onToggleVisibility:l,onFileDrop:c,onRegionSelect:u,canvasRef:p,snapCrosshair:f=!0}=e,{ref:m,size:d}=Ze(),h=`spectraview-clip-${zr().replace(/:/g,"")}`,b=U(()=>Zr(e),[e.width,e.height,e.reverseX,e.showGrid,e.showCrosshair,e.showToolbar,e.showLegend,e.legendPosition,e.displayMode,e.margin,e.theme,e.responsive,e.enableDragDrop,e.enableRegionSelect]),y=b.responsive&&d?d.width:b.width,{height:S,margin:v,reverseX:x,theme:k}=b,w=y-v.left-v.right,C=S-v.top-v.bottom,M=U(()=>O(k),[k]),A=U(()=>_r(t,e.xLabel,e.yLabel),[t,e.xLabel,e.yLabel]),P=U(()=>xe(t),[t]),L=U(()=>H(t),[t]),fe=U(()=>Se(P,y,v,x),[P,y,v,x]),$=U(()=>X(L,S,v),[L,S,v]),z=Dt(a);z.current=a;let Xt=U(()=>(N,te)=>{z.current?.({xDomain:N,yDomain:te})},[]),{zoomRef:lt,state:Wt,zoomedXScale:E,zoomedYScale:I,resetZoom:ct,zoomIn:mt,zoomOut:ut}=Ce({plotWidth:w,plotHeight:C,xScale:fe,yScale:$,onViewChange:a?Xt:void 0}),{pendingRegion:Q,handleMouseDown:Yt,handleMouseMove:jt,handleMouseUp:Gt}=Be({enabled:b.enableRegionSelect,xScale:E,onRegionSelect:u}),[de,pt]=Xe(null),[Kt,ft]=Xe(null),[Jt,be]=Xe(null),ee=Dt(i);ee.current=i;let dt=Ft(N=>{if(!b.showCrosshair)return;let te=N.currentTarget.getBoundingClientRect(),gt=N.clientX-te.left,he=N.clientY-te.top,re=E.invert(gt),ye=I.invert(he);if(ft({px:gt,py:he,dataX:re,dataY:ye}),f&&t.length>0){let D=De(t,re,he,E,I);if(D&&D.distance<50){let ht=t.findIndex(tr=>tr.id===D.spectrumId);be({px:E(D.x),py:I(D.y),dataX:D.x,dataY:D.y,color:t[ht]?.color??T(ht)}),ee.current?.(D.x,D.y)}else be(null),ee.current?.(re,ye)}else ee.current?.(re,ye)},[E,I,b.showCrosshair,f,t]),bt=Ft(()=>{ft(null),be(null)},[]),qt=_e({onZoomIn:mt,onZoomOut:ut,onReset:ct}),Qt=U(()=>He(t.length,A.xLabel,A.yLabel),[t.length,A.xLabel,A.yLabel]),er=b.displayMode==="stacked";if(t.length===0)return R("div",{ref:b.responsive?m:void 0,style:{width:b.responsive?"100%":y,height:S,display:"flex",alignItems:"center",justifyContent:"center",border:`1px dashed ${M.gridColor}`,borderRadius:8,color:M.tickColor,fontFamily:"system-ui, sans-serif",fontSize:14},className:e.className,children:"No spectra loaded"});let ge=b.showToolbar?37:0;return G("div",{ref:b.responsive?m:void 0,style:{width:b.responsive?"100%":y,background:M.background,borderRadius:4,overflow:"hidden"},className:e.className,role:"img","aria-label":Qt,tabIndex:0,onKeyDown:qt,children:[b.showToolbar&&R(Ne,{onZoomIn:mt,onZoomOut:ut,onReset:ct,isZoomed:Wt.isZoomed,theme:k}),b.showLegend&&b.legendPosition==="top"&&R(ae,{spectra:t,theme:k,position:"top",onToggleVisibility:l,onHighlight:pt,highlightedId:de}),R(ze,{enabled:b.enableDragDrop,theme:k,width:y,height:S-ge,onDrop:c,children:er?R("svg",{width:y,height:S-ge,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${v.left}, ${v.top})`,children:[R(Oe,{spectra:t,xScale:E,plotWidth:w,plotHeight:C,margin:v,theme:k,showGrid:b.showGrid,xLabel:A.xLabel,yLabel:A.yLabel}),R("rect",{ref:lt,x:0,y:0,width:w,height:C,fill:"transparent",style:{cursor:"grab"},onMouseMove:dt,onMouseLeave:bt})]})}):G(Xr,{children:[R("div",{style:{position:"absolute",top:v.top,left:v.left,width:w,height:C,overflow:"hidden"},children:R(W,{ref:p,spectra:t,xScale:E,yScale:I,width:w,height:C,highlightedId:de??void 0})}),R("svg",{width:y,height:S-ge,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${v.left}, ${v.top})`,children:[R(Y,{xScale:E,yScale:I,width:w,height:C,xLabel:A.xLabel,yLabel:A.yLabel,showGrid:b.showGrid,colors:M}),R("defs",{children:R("clipPath",{id:h,children:R("rect",{x:0,y:0,width:w,height:C})})}),G("g",{clipPath:`url(#${h})`,children:[o.length>0&&R(Ee,{regions:o,xScale:E,height:C,colors:M}),r.length>0&&R(Ae,{peaks:r,xScale:E,yScale:I,colors:M,onPeakClick:s})]}),n.length>0&&R(Fe,{annotations:n,xScale:E,yScale:I,colors:M}),b.showCrosshair&&R(Le,{position:Kt,width:w,height:C,colors:M,snapPoint:Jt}),Q&&R("rect",{x:E(Q.xStart),y:0,width:Math.abs(E(Q.xEnd)-E(Q.xStart)),height:C,fill:M.regionFill,stroke:M.regionStroke,strokeWidth:1,pointerEvents:"none"}),R("rect",{ref:lt,x:0,y:0,width:w,height:C,fill:"transparent",style:{cursor:b.showCrosshair?"crosshair":"grab"},onMouseDown:Yt,onMouseMove:N=>{dt(N),jt(N)},onMouseUp:Gt,onMouseLeave:bt})]})})]})}),b.showLegend&&b.legendPosition==="bottom"&&R(ae,{spectra:t,theme:k,position:"bottom",onToggleVisibility:l,onHighlight:pt,highlightedId:de})]})}import{memo as Wr,useEffect as Yr,useRef as jr,useMemo as We}from"react";import{scaleLinear as Ut}from"d3-scale";import{jsx as le,jsxs as It}from"react/jsx-runtime";var Gr=Wr(function({spectra:t,xExtent:r,yExtent:o,visibleXDomain:n,width:s=200,height:a=50,theme:i="light",isZoomed:l=!1}){let c=jr(null),u=We(()=>O(i),[i]),p=We(()=>Ut().domain(r).range([0,s]),[r,s]),f=We(()=>Ut().domain(o).range([a-2,2]),[o,a]);Yr(()=>{let h=c.current?.getContext("2d");if(h){h.clearRect(0,0,s,a);for(let b=0;b<t.length;b++){let y=t[b];if(y.visible===!1)continue;let S=Math.min(y.x.length,y.y.length);if(S<2)continue;let v=y.color??T(b);h.beginPath(),h.strokeStyle=v,h.lineWidth=1,h.globalAlpha=.7;let x=Math.max(1,Math.floor(S/s)),k=!1;for(let w=0;w<S;w+=x){let C=p(y.x[w]),M=f(y.y[w]);k?h.lineTo(C,M):(h.moveTo(C,M),k=!0)}h.stroke()}}},[t,p,f,s,a]);let m=p(Math.min(n[0],n[1])),d=p(Math.max(n[0],n[1])),g=Math.max(d-m,2);return It("div",{className:"spectraview-minimap",style:{position:"relative",width:s,height:a,border:`1px solid ${u.gridColor}`,borderRadius:3,overflow:"hidden",background:u.background},children:[le("canvas",{ref:c,width:s,height:a,style:{position:"absolute",top:0,left:0}}),l&&It("svg",{width:s,height:a,style:{position:"absolute",top:0,left:0},children:[le("rect",{x:0,y:0,width:m,height:a,fill:u.background,opacity:.6}),le("rect",{x:m+g,y:0,width:s-m-g,height:a,fill:u.background,opacity:.6}),le("rect",{x:m,y:0,width:g,height:a,fill:"none",stroke:i==="dark"?"#60a5fa":"#3b82f6",strokeWidth:1.5,rx:1})]})]})});import{memo as Kr,useMemo as Nt}from"react";import{jsx as $t,jsxs as K}from"react/jsx-runtime";function Ye(e,t){switch(t){case"fixed2":return e.toFixed(2);case"fixed4":return e.toFixed(4);case"scientific":return e.toExponential(2);default:return Math.abs(e)>=100?Math.round(e).toString():Math.abs(e)>=1?e.toFixed(2):Math.abs(e)>=.01?e.toFixed(4):e.toExponential(2)}}var Jr=Kr(function({data:t,spectra:r,peaks:o=[],plotWidth:n,plotHeight:s,colors:a,numberFormat:i="auto"}){if(!t)return null;let l=Nt(()=>t?r.filter(b=>b.visible!==!1).map((b,y)=>{let S=Math.min(b.x.length,b.y.length);if(S<1)return null;let v=ie(b.x,t.dataX,S);return v<0?null:{label:b.label,color:b.color??T(y),value:b.y[v],x:b.x[v]}}).filter(Boolean):[],[t?.dataX,r]),c=Nt(()=>{if(!t||o.length===0)return null;let b=null,y=1/0;for(let S of o){let v=Math.abs(S.x-t.dataX);v<y&&(y=v,b=S)}return b},[t?.dataX,o]),u=16,p=18,f=c?u:0,m=p+l.length*u+f+8,d=160,g=t.px+15,h=t.py-m/2;return g+d>n&&(g=t.px-d-15),h<0&&(h=4),h+m>s&&(h=s-m-4),K("g",{className:"spectraview-tooltip",transform:`translate(${g}, ${h})`,pointerEvents:"none",children:[$t("rect",{x:0,y:0,width:d,height:m,rx:4,fill:a.tooltipBg,stroke:a.tooltipBorder,strokeWidth:.5,opacity:.95}),K("text",{x:8,y:14,fill:a.tooltipText,fontSize:10,fontFamily:"monospace",fontWeight:600,children:["x = ",Ye(t.dataX,i)]}),l.map((b,y)=>K("g",{transform:`translate(0, ${p+y*u})`,children:[$t("circle",{cx:12,cy:8,r:3,fill:b.color}),K("text",{x:20,y:11,fill:a.tooltipText,fontSize:9,fontFamily:"monospace",children:[b.label.slice(0,10),": ",Ye(b.value,i)]})]},b.label)),c&&K("text",{x:8,y:p+l.length*u+12,fill:a.labelColor,fontSize:9,fontFamily:"monospace",fontStyle:"italic",children:["Peak: ",c.label??Ye(c.x,i)]})]})});import{useMemo as tn}from"react";function je(e,t,r={}){let{prominence:o=.01,minDistance:n=5,maxPeaks:s}=r;if(e.length<3||t.length<3)return[];let a=1/0,i=-1/0;for(let m=0;m<t.length;m++)t[m]<a&&(a=t[m]),t[m]>i&&(i=t[m]);let l=i-a;if(l===0)return[];let c=o*l,u=[];for(let m=1;m<t.length-1;m++)if(t[m]>t[m-1]&&t[m]>t[m+1]){let d=qr(t,m),g=Qr(t,m),h=t[m]-Math.max(d,g);h>=c&&u.push({index:m,prom:h})}u.sort((m,d)=>d.prom-m.prom);let p=[];for(let m of u)p.some(g=>Math.abs(g.index-m.index)<n)||p.push(m);return(s?p.slice(0,s):p).map(m=>({x:e[m.index],y:t[m.index],label:en(e[m.index])})).sort((m,d)=>m.x-d.x)}function qr(e,t){let r=e[t];for(let o=t-1;o>=0&&!(e[o]>e[t]);o--)e[o]<r&&(r=e[o]);return r}function Qr(e,t){let r=e[t];for(let o=t+1;o<e.length&&!(e[o]>e[t]);o++)e[o]<r&&(r=e[o]);return r}function en(e){return Math.round(e).toString()}function rn(e,t={}){let{enabled:r=!0,spectrumIds:o,prominence:n,minDistance:s,maxPeaks:a}=t;return tn(()=>{if(!r)return[];let i=o?e.filter(c=>o.includes(c.id)):e,l=[];for(let c of i){if(c.visible===!1)continue;let u=je(c.x,c.y,{prominence:n,minDistance:s,maxPeaks:a});for(let p of u)l.push({...p,spectrumId:c.id})}return l},[e,r,o,n,s,a])}import{useCallback as _,useState as Qe}from"react";var zt=0,nn=[" ",",",";"," "];function Ot(e){let t=e.trim().split(/\r?\n/).slice(0,5),r=",",o=0;for(let n of nn){let s=t.map(i=>i.split(n).length-1),a=Math.min(...s);a>0&&a>=o&&(s.every(l=>l===s[0])||a>o)&&(o=a,r=n)}return r}function Ge(e,t={}){let{xColumn:r=0,yColumn:o=1,hasHeader:n=!0,label:s="CSV Spectrum"}=t,a=t.delimiter??Ot(e),i=e.trim().split(/\r?\n/);if(i.length<2)throw new Error("CSV file must contain at least 2 lines");let l=s,c=0;if(n){let f=i[0].split(a).map(m=>m.trim());!t.label&&f[o]&&(l=f[o]),c=1}let u=[],p=[];for(let f=c;f<i.length;f++){let m=i[f].trim();if(m===""||m.startsWith("#"))continue;let d=m.split(a),g=parseFloat(d[r]),h=parseFloat(d[o]);!isNaN(g)&&!isNaN(h)&&(u.push(g),p.push(h))}if(u.length===0)throw new Error("No valid numeric data found in CSV");return{id:`csv-${++zt}`,label:l,x:new Float64Array(u),y:new Float64Array(p)}}function on(e,t={}){let{hasHeader:r=!0,label:o}=t,n=t.delimiter??Ot(e),s=e.trim().split(/\r?\n/);if(s.length<2)throw new Error("CSV file must contain at least 2 lines");let i=s[r?1:0].split(n).length;if(i<2)throw new Error("CSV must have at least 2 columns (x + y)");let l,c=0;r&&(l=s[0].split(n).map(m=>m.trim()),c=1);let u=[],p=Array.from({length:i-1},()=>[]);for(let m=c;m<s.length;m++){let d=s[m].trim();if(d===""||d.startsWith("#"))continue;let g=d.split(n),h=parseFloat(g[0]);if(!isNaN(h)){u.push(h);for(let b=1;b<i;b++){let y=parseFloat(g[b]);p[b-1].push(isNaN(y)?0:y)}}}let f=new Float64Array(u);return p.map((m,d)=>({id:`csv-${++zt}`,label:o??l?.[d+1]??`Spectrum ${d+1}`,x:f,y:new Float64Array(m)}))}var an=0;function Je(e){let t;try{t=JSON.parse(e)}catch{throw new Error("Invalid JSON: failed to parse input")}if(Array.isArray(t))return t.map((r,o)=>Ke(r,o));if(typeof t=="object"&&t!==null){let r=t;return Array.isArray(r.spectra)?r.spectra.map((o,n)=>Ke(o,n)):[Ke(t,0)]}throw new Error("Invalid JSON structure: expected an object or array")}function Ke(e,t){let r=e.x??e.wavenumbers??e.wavelengths;if(!r||!Array.isArray(r))throw new Error(`Spectrum ${t}: missing x-axis data (expected "x", "wavenumbers", or "wavelengths")`);let o=e.y??e.intensities??e.absorbance;if(!o||!Array.isArray(o))throw new Error(`Spectrum ${t}: missing y-axis data (expected "y", "intensities", or "absorbance")`);if(r.length!==o.length)throw new Error(`Spectrum ${t}: x and y arrays must have the same length (got ${r.length} and ${o.length})`);let n=e.label??e.title??e.name??`Spectrum ${t+1}`;return{id:`json-${++an}`,label:n,x:new Float64Array(r),y:new Float64Array(o),xUnit:e.xUnit,yUnit:e.yUnit,type:e.type,meta:e.meta}}var Bt=0,ce=null,Vt=!1;async function sn(){if(Vt)return ce;Vt=!0;try{ce=await import("jcampconverter")}catch{ce=null}return ce}function Zt(e){let t=(e["DATA TYPE"]??e.DATATYPE??"").toLowerCase();return t.includes("infrared")||t.includes("ir")?"IR":t.includes("raman")?"Raman":t.includes("nir")||t.includes("near")?"NIR":t.includes("uv")||t.includes("vis")?"UV-Vis":t.includes("fluor")?"fluorescence":"other"}async function qe(e){let t=await sn();return t?ln(e,t):[cn(e)]}function ln(e,t){return t.convert(e,{keepRecordsRegExp:/.*/}).flatten.map((o,n)=>{let s=o.spectra?.[0]?.data?.[0];if(!s)throw new Error(`JCAMP block ${n}: no spectral data found`);return{id:`jcamp-${++Bt}`,label:o.info?.TITLE??`Spectrum ${n+1}`,x:new Float64Array(s.x),y:new Float64Array(s.y),xUnit:o.info?.XUNITS??"cm\u207B\xB9",yUnit:o.info?.YUNITS??"Absorbance",type:Zt(o.info),meta:o.info}})}function cn(e){let t=e.split(/\r?\n/),r={},o=[],n=[],s=!1;for(let a of t){let i=a.trim();if(i.startsWith("##")){let l=i.match(/^##(.+?)=\s*(.*)$/);if(l){let c=l[1].trim().toUpperCase(),u=l[2].trim();if(c==="XYDATA"||c==="XYPOINTS"){s=!0;continue}if(c==="END"){s=!1;continue}r[c]=u}continue}if(s&&i!==""){let l=i.split(/[\s,]+/).map(Number);if(l.length>=2&&!l.some(isNaN)){let c=l[0],u=parseFloat(r.FIRSTX??"0"),p=parseFloat(r.LASTX??"0"),f=parseInt(r.NPOINTS??"0",10);if(f>0&&l.length===2)o.push(l[0]),n.push(l[1]);else if(l.length>1){let m=f>1?(p-u)/(f-1):0;for(let d=1;d<l.length;d++)o.push(c+(d-1)*m),n.push(l[d])}}}}if(o.length===0)throw new Error("Failed to parse JCAMP-DX: no data found. Install jcampconverter for full format support.");return{id:`jcamp-${++Bt}`,label:r.TITLE??"JCAMP Spectrum",x:new Float64Array(o),y:new Float64Array(n),xUnit:r.XUNITS??"cm\u207B\xB9",yUnit:r.YUNITS??"Absorbance",type:Zt(r),meta:r}}function mn(e){switch(e.toLowerCase().split(".").pop()){case"dx":case"jdx":case"jcamp":return"jcamp";case"csv":case"tsv":case"txt":return"csv";case"json":return"json";default:return null}}function un(e=[]){let[t,r]=Qe(e),[o,n]=Qe(!1),[s,a]=Qe(null),i=_(async(m,d)=>{n(!0),a(null);try{let g;switch(d){case"jcamp":g=await qe(m);break;case"csv":g=[Ge(m)];break;case"json":g=Je(m);break}r(h=>[...h,...g])}catch(g){let h=g instanceof Error?g.message:"Failed to parse file";a(h)}finally{n(!1)}},[]),l=_(async m=>{let d=mn(m.name);if(!d){a(`Unsupported file format: ${m.name}`);return}let g=await m.text();await i(g,d)},[i]),c=_(m=>{r(d=>[...d,m])},[]),u=_(m=>{r(d=>d.filter(g=>g.id!==m))},[]),p=_(m=>{r(d=>d.map(g=>g.id===m?{...g,visible:g.visible===!1}:g))},[]),f=_(()=>{r([]),a(null)},[]);return{spectra:t,loading:o,error:s,loadFile:l,loadText:i,addSpectrum:c,removeSpectrum:u,toggleVisibility:p,clear:f}}import{useCallback as me}from"react";var _t={solid:"",dashed:"8 4",dotted:"2 2","dash-dot":"8 4 2 4"};function et(e,t,r,o){let{width:n,height:s,background:a="#ffffff",title:i}=o,l=e.filter(c=>c.visible!==!1).map((c,u)=>{let p=c.color??T(u),f=c.lineStyle??"solid",m=c.lineWidth??1.5,d=_t[f]??"",g=Math.min(c.x.length,c.y.length);if(g<2)return"";let h=[];for(let b=0;b<g;b++){let y=t(c.x[b]).toFixed(2),S=r(c.y[b]).toFixed(2);h.push(`${b===0?"M":"L"}${y},${S}`)}return`<path d="${h.join(" ")}" fill="none" stroke="${p}" stroke-width="${m}"${d?` stroke-dasharray="${d}"`:""}/>
3
3
  <!-- ${c.label} -->`}).filter(Boolean).join(`
4
4
  `);return`<?xml version="1.0" encoding="UTF-8"?>
5
5
  <svg xmlns="http://www.w3.org/2000/svg" width="${n}" height="${s}" viewBox="0 0 ${n} ${s}">
@@ -8,9 +8,9 @@ import{useCallback as Mt,useId as Fr,useMemo as U,useRef as Tt,useState as Xe}fr
8
8
  <g>
9
9
  ${l}
10
10
  </g>
11
- </svg>`}function tt(e,t="spectrum.svg"){let r=new Blob([e],{type:"image/svg+xml"}),o=URL.createObjectURL(r),n=document.createElement("a");n.href=o,n.download=t,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(o)}function ue(e,t){let r=URL.createObjectURL(e),o=document.createElement("a");o.href=r,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(r)}function an(){let e=me((n,s="spectrum.png")=>{n.toBlob(a=>{a&&ue(a,s)},"image/png")},[]),t=me((n,s="spectra.csv")=>{let a=n.filter(i=>i.visible!==!1);if(a.length!==0)if(a.length===1){let i=a[0],l=`${i.xUnit??"x"},${i.yUnit??"y"}
11
+ </svg>`}function tt(e,t="spectrum.svg"){let r=new Blob([e],{type:"image/svg+xml"}),o=URL.createObjectURL(r),n=document.createElement("a");n.href=o,n.download=t,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(o)}function ue(e,t){let r=URL.createObjectURL(e),o=document.createElement("a");o.href=r,o.download=t,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(r)}function pn(){let e=me((n,s="spectrum.png")=>{n.toBlob(a=>{a&&ue(a,s)},"image/png")},[]),t=me((n,s="spectra.csv")=>{let a=n.filter(i=>i.visible!==!1);if(a.length!==0)if(a.length===1){let i=a[0],l=`${i.xUnit??"x"},${i.yUnit??"y"}
12
12
  `,c=Array.from(i.x).map((p,f)=>`${p},${i.y[f]}`),u=l+c.join(`
13
13
  `);ue(new Blob([u],{type:"text/csv"}),s)}else{let i=Math.max(...a.map(p=>p.x.length)),l=a.map(p=>`${p.label}_x,${p.label}_y`).join(","),c=[];for(let p=0;p<i;p++){let f=a.map(m=>p<m.x.length?`${m.x[p]},${m.y[p]}`:",");c.push(f.join(","))}let u=l+`
14
14
  `+c.join(`
15
- `);ue(new Blob([u],{type:"text/csv"}),s)}},[]),r=me((n,s="spectra.json")=>{let i=n.filter(c=>c.visible!==!1).map(c=>({label:c.label,x:Array.from(c.x),y:Array.from(c.y),xUnit:c.xUnit,yUnit:c.yUnit,type:c.type})),l=JSON.stringify(i,null,2);ue(new Blob([l],{type:"application/json"}),s)},[]),o=me((n,s,a,i,l,c="spectrum.svg")=>{let u=et(n,s,a,{width:i,height:l});tt(u,c)},[]);return{exportPng:e,exportSvg:o,exportCsv:t,exportJson:r}}import{useCallback as sn,useState as ln}from"react";import{jsx as J,jsxs as Ot}from"react/jsx-runtime";var cn=e=>({display:"inline-flex",alignItems:"center",justifyContent:"center",height:28,padding:"0 8px",border:`1px solid ${e==="dark"?"#4b5563":"#d1d5db"}`,borderRadius:4,background:e==="dark"?"#1f2937":"#ffffff",color:e==="dark"?"#d1d5db":"#374151",fontSize:12,cursor:"pointer",lineHeight:1,position:"relative"}),mn=e=>({position:"absolute",top:30,left:0,background:e==="dark"?"#1f2937":"#ffffff",border:`1px solid ${e==="dark"?"#4b5563":"#d1d5db"}`,borderRadius:4,boxShadow:"0 2px 8px rgba(0,0,0,0.15)",zIndex:200,minWidth:100,overflow:"hidden"}),pe=e=>({display:"block",width:"100%",padding:"6px 12px",border:"none",background:"transparent",color:e==="dark"?"#d1d5db":"#374151",fontSize:12,textAlign:"left",cursor:"pointer"});function un({theme:e,onExportPng:t,onExportSvg:r,onExportCsv:o,onExportJson:n}){let[s,a]=ln(!1),i=sn(l=>{a(!1),l?.()},[]);return Ot("div",{style:{position:"relative",display:"inline-block"},children:[J("button",{type:"button",style:cn(e),onClick:()=>a(!s),"aria-label":"Export","aria-expanded":s,"aria-haspopup":"true",children:"Export"}),s&&Ot("div",{style:mn(e),role:"menu",children:[t&&J("button",{type:"button",role:"menuitem",style:pe(e),onClick:()=>i(t),children:"PNG Image"}),r&&J("button",{type:"button",role:"menuitem",style:pe(e),onClick:()=>i(r),children:"SVG Vector"}),o&&J("button",{type:"button",role:"menuitem",style:pe(e),onClick:()=>i(o),children:"CSV Data"}),n&&J("button",{type:"button",role:"menuitem",style:pe(e),onClick:()=>i(n),children:"JSON Data"})]})]})}function pn(e){let t=e.length;if(t<3)return new Float64Array(e);let r=[0];for(let a=1;a<t;a++){for(;r.length>=2;){let i=r.length-1,l=r[i-1],c=r[i];if((a-l)*(e[c]-e[l])-(c-l)*(e[a]-e[l])>=0)r.pop();else break}r.push(a)}let o=new Float64Array(t),n=0;for(let a=0;a<t;a++){for(;n<r.length-1&&r[n+1]<=a;)n++;if(n>=r.length-1)o[a]=e[r[r.length-1]];else{let i=r[n],l=r[n+1],c=(a-i)/(l-i);o[a]=e[i]*(1-c)+e[l]*c}}let s=new Float64Array(t);for(let a=0;a<t;a++)s[a]=e[a]-o[a];return s}function fn(e){let t=e.length,r=new Float64Array(t),o=1/0,n=-1/0;for(let a=0;a<t;a++){let i=e[a];i<o&&(o=i),i>n&&(n=i)}let s=n-o;if(s===0)return r;for(let a=0;a<t;a++)r[a]=(e[a]-o)/s;return r}function dn(e,t){let r=Math.min(e.length,t.length);if(r<2)return new Float64Array(t);let o=0;for(let s=1;s<r;s++)o+=Math.abs(e[s]-e[s-1])*(Math.abs(t[s])+Math.abs(t[s-1]))*.5;if(o===0)return new Float64Array(t);let n=new Float64Array(r);for(let s=0;s<r;s++)n[s]=t[s]/o;return n}function bn(e){let t=e.length;if(t===0)return new Float64Array(0);let r=0;for(let i=0;i<t;i++)r+=e[i];let o=r/t,n=0;for(let i=0;i<t;i++){let l=e[i]-o;n+=l*l}let s=Math.sqrt(n/t);if(s===0)return new Float64Array(t);let a=new Float64Array(t);for(let i=0;i<t;i++)a[i]=(e[i]-o)/s;return a}function gn(e,t=5){let r=e.length;if(r<t||t<3)return new Float64Array(e);let o=t%2===0?t+1:t,n=(o-1)/2,s=hn(o),a=new Float64Array(r);for(let i=0;i<n;i++)a[i]=e[i],a[r-1-i]=e[r-1-i];for(let i=n;i<r-n;i++){let l=0;for(let c=-n;c<=n;c++)l+=s[c+n]*e[i+c];a[i]=l}return a}function hn(e){let t={5:[-3,12,17,12,-3].map(r=>r/35),7:[-2,3,6,7,6,3,-2].map(r=>r/21),9:[-21,14,39,54,59,54,39,14,-21].map(r=>r/231),11:[-36,9,44,69,84,89,84,69,44,9,-36].map(r=>r/429)};return t[e]?t[e]:Array(e).fill(1/e)}function yn(e,t){let r=Math.min(e.length,t.length);if(r<2)return new Float64Array(r);let o=new Float64Array(r);o[0]=(t[1]-t[0])/(e[1]-e[0]);for(let n=1;n<r-1;n++)o[n]=(t[n+1]-t[n-1])/(e[n+1]-e[n-1]);return o[r-1]=(t[r-1]-t[r-2])/(e[r-1]-e[r-2]),o}function xn(e,t){let r=Math.min(e.length,t.length);if(r<3)return new Float64Array(r);let o=new Float64Array(r);for(let n=1;n<r-1;n++){let s=e[n]-e[n-1],a=e[n+1]-e[n],i=(s+a)/2;o[n]=(t[n+1]-2*t[n]+t[n-1])/(i*i)}return o[0]=o[1],o[r-1]=o[r-2],o}var q=0;function Sn(e,t){let r=Math.min(e.y.length,t.y.length),o=new Float64Array(r);for(let n=0;n<r;n++)o[n]=e.y[n]-t.y[n];return{id:`diff-${++q}`,label:`${e.label} \u2212 ${t.label}`,x:e.x.length<=t.x.length?new Float64Array(e.x):new Float64Array(t.x),y:o,xUnit:e.xUnit,yUnit:e.yUnit,color:"#ef4444",type:e.type}}function vn(e,t){let r=Math.min(e.y.length,t.y.length),o=new Float64Array(r);for(let n=0;n<r;n++)o[n]=e.y[n]+t.y[n];return{id:`add-${++q}`,label:`${e.label} + ${t.label}`,x:e.x.length<=t.x.length?new Float64Array(e.x):new Float64Array(t.x),y:o,xUnit:e.xUnit,yUnit:e.yUnit,type:e.type}}function wn(e,t){let r=e.y.length,o=new Float64Array(r);for(let n=0;n<r;n++)o[n]=e.y[n]*t;return{id:`scaled-${++q}`,label:`${e.label} \xD7 ${t}`,x:new Float64Array(e.x),y:o,xUnit:e.xUnit,yUnit:e.yUnit,color:e.color,type:e.type}}function Cn(e,t){let r=Math.min(e.y.length,t.y.length);if(r===0)return 0;let o=0,n=0;for(let p=0;p<r;p++)o+=e.y[p],n+=t.y[p];let s=o/r,a=n/r,i=0,l=0,c=0;for(let p=0;p<r;p++){let f=e.y[p]-s,m=t.y[p]-a;i+=f*m,l+=f*f,c+=m*m}let u=Math.sqrt(l*c);return u===0?0:i/u}function kn(e,t){let r=Math.min(e.y.length,t.y.length),o=new Float64Array(r);for(let n=0;n<r;n++)o[n]=Math.abs(e.y[n]-t.y[n]);return{id:`residual-${++q}`,label:`|${e.label} \u2212 ${t.label}|`,x:e.x.length<=t.x.length?new Float64Array(e.x):new Float64Array(t.x),y:o,xUnit:e.xUnit,yUnit:e.yUnit,color:"#f97316",lineStyle:"dashed",type:e.type}}function Rn(e,t){let r=Math.min(e.x.length,e.y.length),o=t.length,n=new Float64Array(o);if(r<2)return{...e,x:new Float64Array(t),y:n};let s=e.x[r-1]>e.x[0];for(let a=0;a<o;a++){let i=t[a],l=0,c=r-1;for(;l<c-1;){let d=l+c>>>1;s?e.x[d]<=i?l=d:c=d:e.x[d]>=i?l=d:c=d}let u=e.x[l],p=e.x[c],f=e.y[l],m=e.y[c];if(u===p)n[a]=f;else{let d=(i-u)/(p-u);n[a]=f+d*(m-f)}}return{...e,id:`interp-${++q}`,x:new Float64Array(t),y:n}}var Mn=0,Tn=1,An=4,En=128,Pn={0:"Arbitrary",1:"cm\u207B\xB9",2:"\xB5m",3:"nm",4:"s",5:"min",6:"Hz",7:"kHz",8:"MHz",9:"m/z",10:"Da",11:"ppm",12:"days",13:"years",14:"Raman shift (cm\u207B\xB9)",15:"eV",16:"Text label",255:"Double interferogram"},Ln={0:"Arbitrary",1:"Interferogram",2:"Absorbance",3:"Kubelka-Munk",4:"Counts",5:"V",6:"\xB0",7:"mA",8:"mm",9:"mV",10:"log(1/R)",11:"%",12:"Intensity",13:"Relative intensity",14:"Energy",16:"dB",19:"\xB0C",20:"\xB0F",21:"K",22:"Index of refraction [n]",23:"Extinction coeff. [k]",24:"Real",25:"Imaginary",26:"Complex",128:"Transmittance",129:"Reflectance",130:"Arbitrary (Valley to peak)",131:"Emission"};function Fn(e,t){return e===1?"IR":e===14?"Raman":e===3&&(t===2||t===128)?"UV-Vis":e===2?"NIR":t===131?"fluorescence":"other"}function Dn(e){let t=new DataView(e);if(e.byteLength<512)throw new Error("Invalid SPC file: too small for SPC header");let o=t.getUint8(0),n=t.getUint8(1);if(n!==75&&n!==77)throw new Error(`Unsupported SPC version: 0x${n.toString(16)}. Expected 0x4B or 0x4D.`);let s=t.getUint8(2),a=t.getUint8(3),i=t.getUint32(4,!0),l=t.getFloat64(8,!0),c=t.getFloat64(16,!0),u=t.getUint32(24,!0),p=Pn[s]??"Arbitrary",f=Ln[a]??"Arbitrary",m=new Uint8Array(e,30,130),d=Un(m),g=(o&An)!==0,h=(o&En)!==0,b=(o&Tn)!==0,y=Fn(s,a),S=null;if(!h&&i>0){S=new Float64Array(i);let k=i>1?(c-l)/(i-1):0;for(let R=0;R<i;R++)S[R]=l+R*k}let v=[],x=512,w=null;if(h&&!g){w=new Float64Array(i);for(let k=0;k<i;k++)w[k]=t.getFloat32(x,!0),x+=4}let C=g?u:1;for(let k=0;k<C;k++){let R,T,P=i;if(g){if(x+32>e.byteLength)break;let L=t.getFloat32(x+4,!0),fe=t.getFloat32(x+8,!0);if(P=t.getUint32(x+12,!0)||i,x+=32,h){R=new Float64Array(P);for(let N=0;N<P&&!(x+4>e.byteLength);N++)R[N]=t.getFloat32(x,!0),x+=4}else if(S)R=S;else{R=new Float64Array(P);let N=P>1?(fe-L)/(P-1):0;for(let O=0;O<P;O++)R[O]=L+O*N}}else R=w??S??new Float64Array(0);if(T=new Float64Array(P),b)for(let L=0;L<P&&!(x+2>e.byteLength);L++)T[L]=t.getInt16(x,!0),x+=2;else for(let L=0;L<P&&!(x+4>e.byteLength);L++)T[L]=t.getFloat32(x,!0),x+=4;v.push({id:`spc-${++Mn}`,label:d||`SPC Spectrum ${k+1}`,x:R,y:T,xUnit:p,yUnit:f,type:y,meta:{format:"SPC",version:n===75?"new":"old",xType:s.toString(),yType:a.toString()}})}if(v.length===0)throw new Error("Invalid SPC file: no spectra found");return v}function Un(e){let t=e.indexOf(0),r=t>=0?e.slice(0,t):e;return new TextDecoder("ascii").decode(r).trim()}export{Fe as AnnotationLayer,Y as AxisLayer,Le as Crosshair,bt as DARK_THEME,Oe as DropZone,un as ExportMenu,Lr as KEYBOARD_SHORTCUTS,dt as LIGHT_THEME,Nt as LINE_DASH_PATTERNS,ae as Legend,_r as Minimap,Te as PeakMarkers,Ee as RegionSelector,ve as SPECTRUM_COLORS,Or as SpectraView,W as SpectrumCanvas,ze as StackedView,$e as Toolbar,Xr as Tooltip,vn as addSpectra,pn as baselineRubberBand,ie as binarySearchClosest,xe as computeXExtent,H as computeYExtent,Cn as correlationCoefficient,Se as createXScale,X as createYScale,yn as derivative1st,xn as derivative2nd,je as detectPeaks,Sn as differenceSpectrum,tt as downloadSvg,He as generateChartDescription,et as generateSvg,A as getSpectrumColor,z as getThemeColors,Rn as interpolateToGrid,Re as lttbDownsample,dn as normalizeArea,fn as normalizeMinMax,bn as normalizeSNV,Ge as parseCsv,qr as parseCsvMulti,qe as parseJcamp,Je as parseJson,Dn as parseSpc,Pr as prefersReducedMotion,kn as residualSpectrum,wn as scaleSpectrum,gn as smoothSavitzkyGolay,De as snapToNearestSpectrum,an as useExport,_e as useKeyboardNavigation,Kr as usePeakPicking,Be as useRegionSelect,Ze as useResizeObserver,on as useSpectrumData,ke as useZoomPan};
15
+ `);ue(new Blob([u],{type:"text/csv"}),s)}},[]),r=me((n,s="spectra.json")=>{let i=n.filter(c=>c.visible!==!1).map(c=>({label:c.label,x:Array.from(c.x),y:Array.from(c.y),xUnit:c.xUnit,yUnit:c.yUnit,type:c.type})),l=JSON.stringify(i,null,2);ue(new Blob([l],{type:"application/json"}),s)},[]),o=me((n,s,a,i,l,c="spectrum.svg")=>{let u=et(n,s,a,{width:i,height:l});tt(u,c)},[]);return{exportPng:e,exportSvg:o,exportCsv:t,exportJson:r}}import{useMemo as bn}from"react";function rt(e){let t=e.length;if(t<3)return new Float64Array(e);let r=[0];for(let a=1;a<t;a++){for(;r.length>=2;){let i=r.length-1,l=r[i-1],c=r[i];if((a-l)*(e[c]-e[l])-(c-l)*(e[a]-e[l])>=0)r.pop();else break}r.push(a)}let o=new Float64Array(t),n=0;for(let a=0;a<t;a++){for(;n<r.length-1&&r[n+1]<=a;)n++;if(n>=r.length-1)o[a]=e[r[r.length-1]];else{let i=r[n],l=r[n+1],c=(a-i)/(l-i);o[a]=e[i]*(1-c)+e[l]*c}}let s=new Float64Array(t);for(let a=0;a<t;a++)s[a]=e[a]-o[a];return s}function nt(e){let t=e.length,r=new Float64Array(t),o=1/0,n=-1/0;for(let a=0;a<t;a++){let i=e[a];i<o&&(o=i),i>n&&(n=i)}let s=n-o;if(s===0)return r;for(let a=0;a<t;a++)r[a]=(e[a]-o)/s;return r}function ot(e,t){let r=Math.min(e.length,t.length);if(r<2)return new Float64Array(t);let o=0;for(let s=1;s<r;s++)o+=Math.abs(e[s]-e[s-1])*(Math.abs(t[s])+Math.abs(t[s-1]))*.5;if(o===0)return new Float64Array(t);let n=new Float64Array(r);for(let s=0;s<r;s++)n[s]=t[s]/o;return n}function it(e){let t=e.length;if(t===0)return new Float64Array(0);let r=0;for(let i=0;i<t;i++)r+=e[i];let o=r/t,n=0;for(let i=0;i<t;i++){let l=e[i]-o;n+=l*l}let s=Math.sqrt(n/t);if(s===0)return new Float64Array(t);let a=new Float64Array(t);for(let i=0;i<t;i++)a[i]=(e[i]-o)/s;return a}function at(e,t=5){let r=e.length;if(r<t||t<3)return new Float64Array(e);let o=t%2===0?t+1:t,n=(o-1)/2,s=fn(o),a=new Float64Array(r);for(let i=0;i<n;i++)a[i]=e[i],a[r-1-i]=e[r-1-i];for(let i=n;i<r-n;i++){let l=0;for(let c=-n;c<=n;c++)l+=s[c+n]*e[i+c];a[i]=l}return a}function fn(e){let t={5:[-3,12,17,12,-3].map(r=>r/35),7:[-2,3,6,7,6,3,-2].map(r=>r/21),9:[-21,14,39,54,59,54,39,14,-21].map(r=>r/231),11:[-36,9,44,69,84,89,84,69,44,9,-36].map(r=>r/429)};return t[e]?t[e]:Array(e).fill(1/e)}function st(e,t){let r=Math.min(e.length,t.length);if(r<2)return new Float64Array(r);let o=new Float64Array(r);o[0]=(t[1]-t[0])/(e[1]-e[0]);for(let n=1;n<r-1;n++)o[n]=(t[n+1]-t[n-1])/(e[n+1]-e[n-1]);return o[r-1]=(t[r-1]-t[r-2])/(e[r-1]-e[r-2]),o}function dn(e,t){let r=Math.min(e.length,t.length);if(r<3)return new Float64Array(r);let o=new Float64Array(r);for(let n=1;n<r-1;n++){let s=e[n]-e[n-1],a=e[n+1]-e[n],i=(s+a)/2;o[n]=(t[n+1]-2*t[n]+t[n-1])/(i*i)}return o[0]=o[1],o[r-1]=o[r-2],o}var gn={none:"Raw","min-max":"Min-Max",area:"Area Normalized",snv:"SNV",baseline:"Baseline Corrected",smooth:"Smoothed",derivative:"1st Derivative"};function hn(e,t,r){if(t==="none")return e;let o;switch(t){case"min-max":o=nt(e.y);break;case"area":o=ot(e.x,e.y);break;case"snv":o=it(e.y);break;case"baseline":o=rt(e.y);break;case"smooth":o=at(e.y,r);break;case"derivative":o=st(e.x,e.y);break;default:return e}return{...e,y:o}}function yn({spectra:e,mode:t,smoothWindow:r=7}){return{spectra:bn(()=>e.map(n=>hn(n,t,r)),[e,t,r]),modeLabel:gn[t]}}import{useCallback as xn,useState as Sn}from"react";import{jsx as J,jsxs as Ht}from"react/jsx-runtime";var vn=e=>({display:"inline-flex",alignItems:"center",justifyContent:"center",height:28,padding:"0 8px",border:`1px solid ${e==="dark"?"#4b5563":"#d1d5db"}`,borderRadius:4,background:e==="dark"?"#1f2937":"#ffffff",color:e==="dark"?"#d1d5db":"#374151",fontSize:12,cursor:"pointer",lineHeight:1,position:"relative"}),kn=e=>({position:"absolute",top:30,left:0,background:e==="dark"?"#1f2937":"#ffffff",border:`1px solid ${e==="dark"?"#4b5563":"#d1d5db"}`,borderRadius:4,boxShadow:"0 2px 8px rgba(0,0,0,0.15)",zIndex:200,minWidth:100,overflow:"hidden"}),pe=e=>({display:"block",width:"100%",padding:"6px 12px",border:"none",background:"transparent",color:e==="dark"?"#d1d5db":"#374151",fontSize:12,textAlign:"left",cursor:"pointer"});function wn({theme:e,onExportPng:t,onExportSvg:r,onExportCsv:o,onExportJson:n}){let[s,a]=Sn(!1),i=xn(l=>{a(!1),l?.()},[]);return Ht("div",{style:{position:"relative",display:"inline-block"},children:[J("button",{type:"button",style:vn(e),onClick:()=>a(!s),"aria-label":"Export","aria-expanded":s,"aria-haspopup":"true",children:"Export"}),s&&Ht("div",{style:kn(e),role:"menu",children:[t&&J("button",{type:"button",role:"menuitem",style:pe(e),onClick:()=>i(t),children:"PNG Image"}),r&&J("button",{type:"button",role:"menuitem",style:pe(e),onClick:()=>i(r),children:"SVG Vector"}),o&&J("button",{type:"button",role:"menuitem",style:pe(e),onClick:()=>i(o),children:"CSV Data"}),n&&J("button",{type:"button",role:"menuitem",style:pe(e),onClick:()=>i(n),children:"JSON Data"})]})]})}var q=0;function Cn(e,t){let r=Math.min(e.y.length,t.y.length),o=new Float64Array(r);for(let n=0;n<r;n++)o[n]=e.y[n]-t.y[n];return{id:`diff-${++q}`,label:`${e.label} \u2212 ${t.label}`,x:e.x.length<=t.x.length?new Float64Array(e.x):new Float64Array(t.x),y:o,xUnit:e.xUnit,yUnit:e.yUnit,color:"#ef4444",type:e.type}}function Mn(e,t){let r=Math.min(e.y.length,t.y.length),o=new Float64Array(r);for(let n=0;n<r;n++)o[n]=e.y[n]+t.y[n];return{id:`add-${++q}`,label:`${e.label} + ${t.label}`,x:e.x.length<=t.x.length?new Float64Array(e.x):new Float64Array(t.x),y:o,xUnit:e.xUnit,yUnit:e.yUnit,type:e.type}}function Rn(e,t){let r=e.y.length,o=new Float64Array(r);for(let n=0;n<r;n++)o[n]=e.y[n]*t;return{id:`scaled-${++q}`,label:`${e.label} \xD7 ${t}`,x:new Float64Array(e.x),y:o,xUnit:e.xUnit,yUnit:e.yUnit,color:e.color,type:e.type}}function An(e,t){let r=Math.min(e.y.length,t.y.length);if(r===0)return 0;let o=0,n=0;for(let p=0;p<r;p++)o+=e.y[p],n+=t.y[p];let s=o/r,a=n/r,i=0,l=0,c=0;for(let p=0;p<r;p++){let f=e.y[p]-s,m=t.y[p]-a;i+=f*m,l+=f*f,c+=m*m}let u=Math.sqrt(l*c);return u===0?0:i/u}function Tn(e,t){let r=Math.min(e.y.length,t.y.length),o=new Float64Array(r);for(let n=0;n<r;n++)o[n]=Math.abs(e.y[n]-t.y[n]);return{id:`residual-${++q}`,label:`|${e.label} \u2212 ${t.label}|`,x:e.x.length<=t.x.length?new Float64Array(e.x):new Float64Array(t.x),y:o,xUnit:e.xUnit,yUnit:e.yUnit,color:"#f97316",lineStyle:"dashed",type:e.type}}function En(e,t){let r=Math.min(e.x.length,e.y.length),o=t.length,n=new Float64Array(o);if(r<2)return{...e,x:new Float64Array(t),y:n};let s=e.x[r-1]>e.x[0];for(let a=0;a<o;a++){let i=t[a],l=0,c=r-1;for(;l<c-1;){let d=l+c>>>1;s?e.x[d]<=i?l=d:c=d:e.x[d]>=i?l=d:c=d}let u=e.x[l],p=e.x[c],f=e.y[l],m=e.y[c];if(u===p)n[a]=f;else{let d=(i-u)/(p-u);n[a]=f+d*(m-f)}}return{...e,id:`interp-${++q}`,x:new Float64Array(t),y:n}}var Pn=0,Ln=1,Fn=4,Dn=128,Un={0:"Arbitrary",1:"cm\u207B\xB9",2:"\xB5m",3:"nm",4:"s",5:"min",6:"Hz",7:"kHz",8:"MHz",9:"m/z",10:"Da",11:"ppm",12:"days",13:"years",14:"Raman shift (cm\u207B\xB9)",15:"eV",16:"Text label",255:"Double interferogram"},In={0:"Arbitrary",1:"Interferogram",2:"Absorbance",3:"Kubelka-Munk",4:"Counts",5:"V",6:"\xB0",7:"mA",8:"mm",9:"mV",10:"log(1/R)",11:"%",12:"Intensity",13:"Relative intensity",14:"Energy",16:"dB",19:"\xB0C",20:"\xB0F",21:"K",22:"Index of refraction [n]",23:"Extinction coeff. [k]",24:"Real",25:"Imaginary",26:"Complex",128:"Transmittance",129:"Reflectance",130:"Arbitrary (Valley to peak)",131:"Emission"};function Nn(e,t){return e===1?"IR":e===14?"Raman":e===3&&(t===2||t===128)?"UV-Vis":e===2?"NIR":t===131?"fluorescence":"other"}function $n(e){let t=new DataView(e);if(e.byteLength<512)throw new Error("Invalid SPC file: too small for SPC header");let o=t.getUint8(0),n=t.getUint8(1);if(n!==75&&n!==77)throw new Error(`Unsupported SPC version: 0x${n.toString(16)}. Expected 0x4B or 0x4D.`);let s=t.getUint8(2),a=t.getUint8(3),i=t.getUint32(4,!0),l=t.getFloat64(8,!0),c=t.getFloat64(16,!0),u=t.getUint32(24,!0),p=Un[s]??"Arbitrary",f=In[a]??"Arbitrary",m=new Uint8Array(e,30,130),d=zn(m),g=(o&Fn)!==0,h=(o&Dn)!==0,b=(o&Ln)!==0,y=Nn(s,a),S=null;if(!h&&i>0){S=new Float64Array(i);let C=i>1?(c-l)/(i-1):0;for(let M=0;M<i;M++)S[M]=l+M*C}let v=[],x=512,k=null;if(h&&!g){k=new Float64Array(i);for(let C=0;C<i;C++)k[C]=t.getFloat32(x,!0),x+=4}let w=g?u:1;for(let C=0;C<w;C++){let M,A,P=i;if(g){if(x+32>e.byteLength)break;let L=t.getFloat32(x+4,!0),fe=t.getFloat32(x+8,!0);if(P=t.getUint32(x+12,!0)||i,x+=32,h){M=new Float64Array(P);for(let $=0;$<P&&!(x+4>e.byteLength);$++)M[$]=t.getFloat32(x,!0),x+=4}else if(S)M=S;else{M=new Float64Array(P);let $=P>1?(fe-L)/(P-1):0;for(let z=0;z<P;z++)M[z]=L+z*$}}else M=k??S??new Float64Array(0);if(A=new Float64Array(P),b)for(let L=0;L<P&&!(x+2>e.byteLength);L++)A[L]=t.getInt16(x,!0),x+=2;else for(let L=0;L<P&&!(x+4>e.byteLength);L++)A[L]=t.getFloat32(x,!0),x+=4;v.push({id:`spc-${++Pn}`,label:d||`SPC Spectrum ${C+1}`,x:M,y:A,xUnit:p,yUnit:f,type:y,meta:{format:"SPC",version:n===75?"new":"old",xType:s.toString(),yType:a.toString()}})}if(v.length===0)throw new Error("Invalid SPC file: no spectra found");return v}function zn(e){let t=e.indexOf(0),r=t>=0?e.slice(0,t):e;return new TextDecoder("ascii").decode(r).trim()}export{Fe as AnnotationLayer,Y as AxisLayer,Le as Crosshair,vt as DARK_THEME,ze as DropZone,wn as ExportMenu,$r as KEYBOARD_SHORTCUTS,St as LIGHT_THEME,_t as LINE_DASH_PATTERNS,ae as Legend,Gr as Minimap,Ae as PeakMarkers,Ee as RegionSelector,ve as SPECTRUM_COLORS,Hr as SpectraView,W as SpectrumCanvas,Oe as StackedView,Ne as Toolbar,Jr as Tooltip,Mn as addSpectra,rt as baselineRubberBand,ie as binarySearchClosest,xe as computeXExtent,H as computeYExtent,An as correlationCoefficient,Se as createXScale,X as createYScale,st as derivative1st,dn as derivative2nd,je as detectPeaks,Cn as differenceSpectrum,tt as downloadSvg,He as generateChartDescription,et as generateSvg,T as getSpectrumColor,O as getThemeColors,En as interpolateToGrid,Me as lttbDownsample,ot as normalizeArea,nt as normalizeMinMax,it as normalizeSNV,Ge as parseCsv,on as parseCsvMulti,qe as parseJcamp,Je as parseJson,$n as parseSpc,Nr as prefersReducedMotion,Tn as residualSpectrum,Rn as scaleSpectrum,at as smoothSavitzkyGolay,De as snapToNearestSpectrum,pn as useExport,_e as useKeyboardNavigation,yn as useNormalization,rn as usePeakPicking,Be as useRegionSelect,Ze as useResizeObserver,un as useSpectrumData,Ce as useZoomPan};
16
16
  //# sourceMappingURL=index.js.map