spectraview 1.1.0 → 1.3.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
@@ -380,6 +380,28 @@ interface AnnotationLayerProps {
380
380
  }
381
381
  declare function AnnotationLayer({ annotations, xScale, yScale, colors, }: AnnotationLayerProps): react_jsx_runtime.JSX.Element | null;
382
382
 
383
+ interface MinimapProps {
384
+ /** Spectra to render in the minimap. */
385
+ spectra: Spectrum[];
386
+ /** Full X extent [min, max] of the data. */
387
+ xExtent: [number, number];
388
+ /** Full Y extent [min, max] of the data. */
389
+ yExtent: [number, number];
390
+ /** Currently visible X domain from the zoomed view. */
391
+ visibleXDomain: [number, number];
392
+ /** Currently visible Y domain from the zoomed view (reserved for future use). */
393
+ visibleYDomain?: [number, number];
394
+ /** Minimap width in pixels. Defaults to 200. */
395
+ width?: number;
396
+ /** Minimap height in pixels. Defaults to 50. */
397
+ height?: number;
398
+ /** Theme. */
399
+ theme?: Theme;
400
+ /** Whether the view is currently zoomed. */
401
+ isZoomed?: boolean;
402
+ }
403
+ declare const Minimap: react.NamedExoticComponent<MinimapProps>;
404
+
383
405
  /**
384
406
  * Hook for zoom and pan behavior backed by d3-zoom.
385
407
  *
@@ -810,6 +832,58 @@ declare function derivative1st(x: Float64Array | number[], y: Float64Array | num
810
832
  */
811
833
  declare function derivative2nd(x: Float64Array | number[], y: Float64Array | number[]): Float64Array;
812
834
 
835
+ /**
836
+ * Spectrum comparison and mathematical operations.
837
+ *
838
+ * Provides functions for computing difference spectra, correlation
839
+ * coefficients, spectral arithmetic, and residuals.
840
+ *
841
+ * All functions assume spectra share the same X-axis (same length
842
+ * and point spacing). Use `interpolateToGrid` to align spectra
843
+ * before comparison if needed.
844
+ *
845
+ * @module comparison
846
+ */
847
+
848
+ /**
849
+ * Compute the difference spectrum (a - b).
850
+ *
851
+ * @param a - First spectrum
852
+ * @param b - Second spectrum
853
+ * @returns New Spectrum representing a - b
854
+ */
855
+ declare function differenceSpectrum(a: Spectrum, b: Spectrum): Spectrum;
856
+ /**
857
+ * Add two spectra element-wise.
858
+ */
859
+ declare function addSpectra(a: Spectrum, b: Spectrum): Spectrum;
860
+ /**
861
+ * Multiply spectrum Y values by a scalar factor.
862
+ */
863
+ declare function scaleSpectrum(spectrum: Spectrum, factor: number): Spectrum;
864
+ /**
865
+ * Pearson correlation coefficient between two spectra's Y values.
866
+ *
867
+ * Returns a value in [-1, 1] where 1 means perfect positive
868
+ * correlation and 0 means no linear correlation.
869
+ */
870
+ declare function correlationCoefficient(a: Spectrum, b: Spectrum): number;
871
+ /**
872
+ * Compute the residual (absolute difference) between two spectra.
873
+ */
874
+ declare function residualSpectrum(a: Spectrum, b: Spectrum): Spectrum;
875
+ /**
876
+ * Interpolate a spectrum to a new X grid using linear interpolation.
877
+ *
878
+ * Useful for aligning two spectra that have different X-axis points
879
+ * before performing comparison operations.
880
+ *
881
+ * @param spectrum - Source spectrum
882
+ * @param newX - Target X values
883
+ * @returns Spectrum interpolated to the new X grid
884
+ */
885
+ declare function interpolateToGrid(spectrum: Spectrum, newX: Float64Array | number[]): Spectrum;
886
+
813
887
  /**
814
888
  * JCAMP-DX parser for spectral data.
815
889
  *
@@ -931,4 +1005,4 @@ declare function createXScale(domain: [number, number], width: number, margin: M
931
1005
  */
932
1006
  declare function createYScale(domain: [number, number], height: number, margin: Margin): d3_scale.ScaleLinear<number, number, never>;
933
1007
 
934
- 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, 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, type UseExportReturn, type UseKeyboardNavigationOptions, type UsePeakPickingOptions, type UseRegionSelectOptions, type UseRegionSelectReturn, type UseSpectrumDataReturn, type UseZoomPanOptions, type UseZoomPanReturn, type ViewState, type ZoomPanState, baselineRubberBand, binarySearchClosest, computeXExtent, computeYExtent, createXScale, createYScale, derivative1st, derivative2nd, detectPeaks, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, lttbDownsample, normalizeArea, normalizeMinMax, normalizeSNV, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, smoothSavitzkyGolay, snapToNearestSpectrum, useExport, useKeyboardNavigation, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
1008
+ 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, 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 };
package/dist/index.d.ts CHANGED
@@ -380,6 +380,28 @@ interface AnnotationLayerProps {
380
380
  }
381
381
  declare function AnnotationLayer({ annotations, xScale, yScale, colors, }: AnnotationLayerProps): react_jsx_runtime.JSX.Element | null;
382
382
 
383
+ interface MinimapProps {
384
+ /** Spectra to render in the minimap. */
385
+ spectra: Spectrum[];
386
+ /** Full X extent [min, max] of the data. */
387
+ xExtent: [number, number];
388
+ /** Full Y extent [min, max] of the data. */
389
+ yExtent: [number, number];
390
+ /** Currently visible X domain from the zoomed view. */
391
+ visibleXDomain: [number, number];
392
+ /** Currently visible Y domain from the zoomed view (reserved for future use). */
393
+ visibleYDomain?: [number, number];
394
+ /** Minimap width in pixels. Defaults to 200. */
395
+ width?: number;
396
+ /** Minimap height in pixels. Defaults to 50. */
397
+ height?: number;
398
+ /** Theme. */
399
+ theme?: Theme;
400
+ /** Whether the view is currently zoomed. */
401
+ isZoomed?: boolean;
402
+ }
403
+ declare const Minimap: react.NamedExoticComponent<MinimapProps>;
404
+
383
405
  /**
384
406
  * Hook for zoom and pan behavior backed by d3-zoom.
385
407
  *
@@ -810,6 +832,58 @@ declare function derivative1st(x: Float64Array | number[], y: Float64Array | num
810
832
  */
811
833
  declare function derivative2nd(x: Float64Array | number[], y: Float64Array | number[]): Float64Array;
812
834
 
835
+ /**
836
+ * Spectrum comparison and mathematical operations.
837
+ *
838
+ * Provides functions for computing difference spectra, correlation
839
+ * coefficients, spectral arithmetic, and residuals.
840
+ *
841
+ * All functions assume spectra share the same X-axis (same length
842
+ * and point spacing). Use `interpolateToGrid` to align spectra
843
+ * before comparison if needed.
844
+ *
845
+ * @module comparison
846
+ */
847
+
848
+ /**
849
+ * Compute the difference spectrum (a - b).
850
+ *
851
+ * @param a - First spectrum
852
+ * @param b - Second spectrum
853
+ * @returns New Spectrum representing a - b
854
+ */
855
+ declare function differenceSpectrum(a: Spectrum, b: Spectrum): Spectrum;
856
+ /**
857
+ * Add two spectra element-wise.
858
+ */
859
+ declare function addSpectra(a: Spectrum, b: Spectrum): Spectrum;
860
+ /**
861
+ * Multiply spectrum Y values by a scalar factor.
862
+ */
863
+ declare function scaleSpectrum(spectrum: Spectrum, factor: number): Spectrum;
864
+ /**
865
+ * Pearson correlation coefficient between two spectra's Y values.
866
+ *
867
+ * Returns a value in [-1, 1] where 1 means perfect positive
868
+ * correlation and 0 means no linear correlation.
869
+ */
870
+ declare function correlationCoefficient(a: Spectrum, b: Spectrum): number;
871
+ /**
872
+ * Compute the residual (absolute difference) between two spectra.
873
+ */
874
+ declare function residualSpectrum(a: Spectrum, b: Spectrum): Spectrum;
875
+ /**
876
+ * Interpolate a spectrum to a new X grid using linear interpolation.
877
+ *
878
+ * Useful for aligning two spectra that have different X-axis points
879
+ * before performing comparison operations.
880
+ *
881
+ * @param spectrum - Source spectrum
882
+ * @param newX - Target X values
883
+ * @returns Spectrum interpolated to the new X grid
884
+ */
885
+ declare function interpolateToGrid(spectrum: Spectrum, newX: Float64Array | number[]): Spectrum;
886
+
813
887
  /**
814
888
  * JCAMP-DX parser for spectral data.
815
889
  *
@@ -931,4 +1005,4 @@ declare function createXScale(domain: [number, number], width: number, margin: M
931
1005
  */
932
1006
  declare function createYScale(domain: [number, number], height: number, margin: Margin): d3_scale.ScaleLinear<number, number, never>;
933
1007
 
934
- 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, 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, type UseExportReturn, type UseKeyboardNavigationOptions, type UsePeakPickingOptions, type UseRegionSelectOptions, type UseRegionSelectReturn, type UseSpectrumDataReturn, type UseZoomPanOptions, type UseZoomPanReturn, type ViewState, type ZoomPanState, baselineRubberBand, binarySearchClosest, computeXExtent, computeYExtent, createXScale, createYScale, derivative1st, derivative2nd, detectPeaks, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, lttbDownsample, normalizeArea, normalizeMinMax, normalizeSNV, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, smoothSavitzkyGolay, snapToNearestSpectrum, useExport, useKeyboardNavigation, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
1008
+ 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, 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 };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import{useCallback as vt,useId as Cr,useMemo as I,useRef as wt,useState as Ze}from"react";import{scaleLinear as at}from"d3-scale";import{extent as st}from"d3-array";var Zt=.05;function be(e){let t=1/0,r=-1/0;for(let o of e){if(o.visible===!1)continue;let[n,a]=st(o.x);n<t&&(t=n),a>r&&(r=a)}return isFinite(t)?[t,r]:[0,1]}function B(e){let t=1/0,r=-1/0;for(let a of e){if(a.visible===!1)continue;let[s,i]=st(a.y);s<t&&(t=s),i>r&&(r=i)}if(!isFinite(t))return[0,1];let n=(r-t)*Zt;return[t-n,r+n]}function ge(e,t,r,o){let n=t-r.left-r.right,a=o?[e[1],e[0]]:e;return at().domain(a).range([0,n])}function X(e,t,r){let o=t-r.top-r.bottom;return at().domain(e).range([o,0])}var he=["#2563eb","#dc2626","#16a34a","#9333ea","#ea580c","#0891b2","#be185d","#854d0e","#4f46e5","#65a30d"],lt={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"},ct={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 P(e){return he[e%he.length]}function H(e){return e==="dark"?ct:lt}import{useCallback as ye,useEffect as _t,useMemo as ut,useRef as te,useState as Bt}from"react";import{zoom as Xt,zoomIdentity as xe}from"d3-zoom";import{select as z}from"d3-selection";import"d3-transition";var mt=1.5;function Se(e){let{plotWidth:t,plotHeight:r,xScale:o,yScale:n,scaleExtent:a=[1,50],enabled:s=!0,onViewChange:i}=e,l=te(null),c=te(null),m=te(i);m.current=i;let p=te(a);p.current=a;let[f,u]=Bt(xe),d=ut(()=>f.rescaleX(o.copy()),[f,o]),g=ut(()=>f.rescaleY(n.copy()),[f,n]);_t(()=>{let v=l.current;if(!v||!s)return;let k=Xt().scaleExtent(p.current).extent([[0,0],[t,r]]).translateExtent([[-1/0,-1/0],[1/0,1/0]]).on("zoom",h=>{let y=h.transform;if(u(y),m.current){let C=y.rescaleX(o.copy()),x=y.rescaleY(n.copy());m.current(C.domain(),x.domain())}});return c.current=k,z(v).call(k),z(v).on("dblclick.zoom",()=>{z(v).transition().duration(300).call(k.transform,xe)}),()=>{z(v).on(".zoom",null)}},[t,r,s,o,n]);let S=ye(()=>{!l.current||!c.current||z(l.current).transition().duration(300).call(c.current.transform,xe)},[]),b=ye(()=>{!l.current||!c.current||z(l.current).transition().duration(200).call(c.current.scaleBy,mt)},[]),w=ye(()=>{!l.current||!c.current||z(l.current).transition().duration(200).call(c.current.scaleBy,1/mt)},[]);return{zoomRef:l,state:{transform:f,isZoomed:f.k!==1||f.x!==0||f.y!==0},zoomedXScale:d,zoomedYScale:g,resetZoom:S,zoomIn:b,zoomOut:w}}import{forwardRef as Kt,useEffect as ft,useImperativeHandle as Jt,useRef as dt}from"react";function ve(e,t,r,o,n,a,s){let i=o-r;if(i<=s){let f=[];for(let u=r;u<o;u++)f.push({px:n(e[u]),py:a(t[u]),index:u});return f}let l=[];l.push({px:n(e[r]),py:a(t[r]),index:r});let c=s-2,m=(i-2)/c,p=r;for(let f=0;f<c;f++){let u=r+1+Math.floor(f*m),d=r+1+Math.min(Math.floor((f+1)*m),i-2),g=d,S=r+1+Math.min(Math.floor((f+2)*m),i-2),b,w;if(f===c-1)b=n(e[o-1]),w=a(t[o-1]);else{b=0,w=0;let C=S-g;for(let x=g;x<S;x++)b+=n(e[x]),w+=a(t[x]);C>0&&(b/=C,w/=C)}let v=n(e[p]),k=a(t[p]),h=-1,y=u;for(let C=u;C<d;C++){let x=n(e[C]),R=a(t[C]),A=Math.abs((v-b)*(R-k)-(v-x)*(w-k));A>h&&(h=A,y=C)}l.push({px:n(e[y]),py:a(t[y]),index:y}),p=y}return l.push({px:n(e[o-1]),py:a(t[o-1]),index:o-1}),l}var Ht=1.5,Wt={solid:[],dashed:[8,4],dotted:[2,2],"dash-dot":[8,4,2,4]},jt=2e3;function Yt(e,t,r){e.clearRect(0,0,t,r)}function Gt(e,t,r,o,n,a,s){let{highlighted:i=!1,opacity:l=1}=s??{},c=Math.min(t.x.length,t.y.length);if(c<2)return;let m=t.color??P(r),p=t.lineWidth??Ht,f=i?p+1:p,u=Wt[t.lineStyle??"solid"]??[],[d,g]=o.domain(),S=Math.min(d,g),b=Math.max(d,g),w=0,v=c;for(let h=0;h<c;h++)if(t.x[h]>=S||h<c-1&&t.x[h+1]>=S){w=Math.max(0,h-1);break}for(let h=c-1;h>=0;h--)if(t.x[h]<=b||h>0&&t.x[h-1]<=b){v=Math.min(c,h+2);break}let k=v-w;if(e.save(),e.beginPath(),e.strokeStyle=m,e.lineWidth=f,e.globalAlpha=l,e.lineJoin="round",e.setLineDash(u),k>jt){let h=Math.max(Math.ceil(a*2),200),y=ve(t.x,t.y,w,v,o,n,h);if(y.length>0){e.moveTo(y[0].px,y[0].py);for(let C=1;C<y.length;C++)e.lineTo(y[C].px,y[C].py)}}else{let h=!1;for(let y=w;y<v;y++){let C=o(t.x[y]),x=n(t.y[y]);h?e.lineTo(C,x):(e.moveTo(C,x),h=!0)}}e.stroke(),e.restore()}function pt(e,t,r,o,n,a,s){Yt(e,n,a),t.forEach((i,l)=>{i.visible!==!1&&Gt(e,i,l,r,o,n,{highlighted:i.id===s,opacity:s&&i.id!==s?.3:1})})}import{jsx as qt}from"react/jsx-runtime";var W=Kt(function({spectra:t,xScale:r,yScale:o,width:n,height:a,highlightedId:s},i){let l=dt(null),c=dt(1);return Jt(i,()=>l.current,[]),ft(()=>{let m=l.current;if(!m)return;let p=window.devicePixelRatio||1;c.current=p,m.width=n*p,m.height=a*p},[n,a]),ft(()=>{let m=l.current;if(!m)return;let p=m.getContext("2d");if(!p)return;let f=c.current;p.setTransform(f,0,0,f,0,0),pt(p,t,r,o,n,a,s)},[t,r,o,n,a,s]),qt("canvas",{ref:l,style:{width:n,height:a,position:"absolute",top:0,left:0,pointerEvents:"none"}})});import{jsx as D,jsxs as V}from"react/jsx-runtime";function bt(e,t){let[r,o]=e.domain(),n=Math.min(r,o),s=(Math.max(r,o)-n)/(t-1);return Array.from({length:t},(i,l)=>n+l*s)}function gt(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 j({xScale:e,yScale:t,width:r,height:o,xLabel:n,yLabel:a,showGrid:s=!0,colors:i}){let l=bt(e,8),c=bt(t,6);return V("g",{children:[s&&V("g",{children:[l.map(m=>D("line",{x1:e(m),x2:e(m),y1:0,y2:o,stroke:i.gridColor,strokeWidth:.5},`xgrid-${m}`)),c.map(m=>D("line",{x1:0,x2:r,y1:t(m),y2:t(m),stroke:i.gridColor,strokeWidth:.5},`ygrid-${m}`))]}),V("g",{transform:`translate(0, ${o})`,children:[D("line",{x1:0,x2:r,y1:0,y2:0,stroke:i.axisColor}),l.map(m=>V("g",{transform:`translate(${e(m)}, 0)`,children:[D("line",{y1:0,y2:6,stroke:i.axisColor}),D("text",{y:20,textAnchor:"middle",fill:i.tickColor,fontSize:11,fontFamily:"system-ui, sans-serif",children:gt(m)})]},`xtick-${m}`)),n&&D("text",{x:r/2,y:42,textAnchor:"middle",fill:i.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:n})]}),V("g",{children:[D("line",{x1:0,x2:0,y1:0,y2:o,stroke:i.axisColor}),c.map(m=>V("g",{transform:`translate(0, ${t(m)})`,children:[D("line",{x1:-6,x2:0,stroke:i.axisColor}),D("text",{x:-10,textAnchor:"end",dominantBaseline:"middle",fill:i.tickColor,fontSize:11,fontFamily:"system-ui, sans-serif",children:gt(m)})]},`ytick-${m}`)),a&&D("text",{transform:`translate(-50, ${o/2}) rotate(-90)`,textAnchor:"middle",fill:i.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:a})]})]})}import{jsx as we,jsxs as Qt}from"react/jsx-runtime";function Ce({peaks:e,xScale:t,yScale:r,colors:o,onPeakClick:n}){let[a,s]=t.domain(),i=Math.min(a,s),l=Math.max(a,s),c=e.filter(m=>m.x>=i&&m.x<=l);return we("g",{className:"spectraview-peaks",children:c.map((m,p)=>{let f=t(m.x),u=r(m.y);return Qt("g",{transform:`translate(${f}, ${u})`,style:{cursor:n?"pointer":"default"},onClick:()=>n?.(m),children:[we("polygon",{points:`0,-5 -5,${-5*2.5} 5,${-5*2.5}`,fill:o.labelColor,opacity:.8}),m.label&&we("text",{y:-5*2.5-14,textAnchor:"middle",fill:o.labelColor,fontSize:10,fontFamily:"system-ui, sans-serif",fontWeight:500,children:m.label})]},`peak-${m.x}-${p}`)})})}import{jsx as ke,jsxs as er}from"react/jsx-runtime";function Re({regions:e,xScale:t,height:r,colors:o}){return ke("g",{className:"spectraview-regions",children:e.map((n,a)=>{let s=t(n.xStart),i=t(n.xEnd),l=Math.min(s,i),c=Math.abs(i-s);return er("g",{children:[ke("rect",{x:l,y:0,width:c,height:r,fill:n.color??o.regionFill,stroke:o.regionStroke,strokeWidth:1}),n.label&&ke("text",{x:l+c/2,y:12,textAnchor:"middle",fill:o.labelColor,fontSize:10,fontFamily:"system-ui, sans-serif",children:n.label})]},`region-${a}`)})})}import{jsx as re,jsxs as Me}from"react/jsx-runtime";function Ae({position:e,width:t,height:r,colors:o,snapPoint:n}){return e?Me("g",{className:"spectraview-crosshair",pointerEvents:"none",children:[re("line",{x1:e.px,x2:e.px,y1:0,y2:r,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),re("line",{x1:0,x2:t,y1:e.py,y2:e.py,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),n&&re("circle",{cx:n.px,cy:n.py,r:4,fill:n.color??o.crosshairColor,stroke:o.background,strokeWidth:1.5}),Me("g",{transform:`translate(${Math.min(e.px+10,t-100)}, ${Math.max(e.py-10,20)})`,children:[re("rect",{x:0,y:-14,width:90,height:18,rx:3,fill:o.tooltipBg,stroke:o.tooltipBorder,strokeWidth:.5,opacity:.9}),Me("text",{x:5,y:0,fill:o.tooltipText,fontSize:10,fontFamily:"monospace",children:[ht(n?.dataX??e.dataX),","," ",ht(n?.dataY??e.dataY)]})]})]}):null}function ht(e){return Math.abs(e)>=100?Math.round(e).toString():Math.abs(e)>=1?e.toFixed(1):e.toFixed(4)}import{jsx as Y,jsxs as tr}from"react/jsx-runtime";function Te({annotations:e,xScale:t,yScale:r,colors:o}){return e.length===0?null:Y("g",{className:"spectraview-annotations",pointerEvents:"none",children:e.map(n=>{let a=t(n.x),s=r(n.y),[i,l]=n.offset??[0,-20],c=a+i,m=s+l,p=n.fontSize??11,f=n.color??o.tickColor,u=n.showAnchorLine!==!1;return tr("g",{children:[u&&Y("line",{x1:a,y1:s,x2:c,y2:m,stroke:f,strokeWidth:.75,strokeDasharray:"3 2",opacity:.6}),Y("circle",{cx:a,cy:s,r:2.5,fill:f,opacity:.8}),Y("text",{x:c,y:m,fill:o.background,fontSize:p,fontFamily:"system-ui, sans-serif",textAnchor:"middle",dominantBaseline:"auto",stroke:o.background,strokeWidth:3,strokeLinejoin:"round",children:n.text}),Y("text",{x:c,y:m,fill:f,fontSize:p,fontFamily:"system-ui, sans-serif",textAnchor:"middle",dominantBaseline:"auto",children:n.text})]},n.id)})})}function yt(e,t,r){if(r===0)return-1;if(r===1)return 0;let o=e[r-1]>=e[0],n=0,a=r-1;for(;n<a-1;){let l=n+a>>>1,c=e[l];o?c<=t?n=l:a=l:c>=t?n=l:a=l}let s=Math.abs(e[n]-t),i=Math.abs(e[a]-t);return s<=i?n:a}function Ee(e,t,r,o,n){let a=null;for(let s of e){if(s.visible===!1)continue;let i=Math.min(s.x.length,s.y.length);if(i<2)continue;let l=yt(s.x,t,i);if(l<0)continue;let c=s.x[l],m=s.y[l],p=Math.abs(o(c)-o(t)),f=Math.abs(n(m)-r),u=Math.sqrt(p*p+f*f);(!a||u<a.distance)&&(a={spectrumId:s.id,index:l,x:c,y:m,distance:u})}return a}import{memo as rr}from"react";import{jsx as Pe,jsxs as or}from"react/jsx-runtime";var Le=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}),nr=e=>({display:"flex",gap:4,padding:"4px 0",borderBottom:`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`}),De=rr(function({onZoomIn:t,onZoomOut:r,onReset:o,isZoomed:n,theme:a}){return or("div",{style:nr(a),className:"spectraview-toolbar",children:[Pe("button",{type:"button",style:Le(a),onClick:t,title:"Zoom in","aria-label":"Zoom in",children:"+"}),Pe("button",{type:"button",style:Le(a),onClick:r,title:"Zoom out","aria-label":"Zoom out",children:"\u2212"}),Pe("button",{type:"button",style:{...Le(a),opacity:n?1:.4},onClick:o,disabled:!n,title:"Reset zoom","aria-label":"Reset zoom",children:"\u21BA"})]})});import{memo as ir}from"react";import{jsx as Fe,jsxs as cr}from"react/jsx-runtime";var ar=(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}),sr=(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"}),lr=(e,t)=>({width:12,height:3,borderRadius:1,background:e,opacity:t?.4:1,flexShrink:0}),ne=ir(function({spectra:t,theme:r,position:o,onToggleVisibility:n,onHighlight:a,highlightedId:s}){return t.length<=1?null:Fe("div",{className:"spectraview-legend",style:ar(r,o),role:"list","aria-label":"Spectrum legend",children:t.map((i,l)=>{let c=i.color??P(l),m=i.visible===!1,p=s===i.id;return cr("div",{role:"listitem",style:sr(r,m,p),onClick:()=>n?.(i.id),onMouseEnter:()=>a?.(i.id),onMouseLeave:()=>a?.(null),title:m?`Show ${i.label}`:`Hide ${i.label}`,children:[Fe("span",{style:lr(c,m)}),Fe("span",{style:{textDecoration:m?"line-through":"none",maxWidth:120,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:i.label})]},i.id)})})});import{useCallback as oe,useState as ur}from"react";import{jsx as mr,jsxs as pr}from"react/jsx-runtime";function Ie({enabled:e,theme:t,width:r,height:o,onDrop:n,children:a}){let[s,i]=ur(!1),l={current:0},c=oe(u=>{e&&(u.preventDefault(),l.current++,i(!0))},[e]),m=oe(u=>{e&&(u.preventDefault(),l.current--,l.current<=0&&(l.current=0,i(!1)))},[e]),p=oe(u=>{e&&(u.preventDefault(),u.dataTransfer.dropEffect="copy")},[e]),f=oe(u=>{if(!e)return;u.preventDefault(),l.current=0,i(!1);let d=Array.from(u.dataTransfer.files);d.length>0&&n?.(d)},[e,n]);return pr("div",{style:{position:"relative",width:r,height:o},onDragEnter:c,onDragLeave:m,onDragOver:p,onDrop:f,children:[a,s&&mr("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 fr}from"react";import{jsx as Z,jsxs as dr}from"react/jsx-runtime";var xt=8;function Ue({spectra:e,xScale:t,plotWidth:r,plotHeight:o,margin:n,theme:a,showGrid:s,xLabel:i,yLabel:l}){let c=e.filter(d=>d.visible!==!1),m=fr(()=>H(a),[a]),p=c.length,f=(p-1)*xt,u=Math.max(40,Math.floor((o-f)/Math.max(p,1)));return Z("g",{className:"spectraview-stacked",children:c.map((d,g)=>{let S=g*(u+xt),b=B([d]),w=X(b,u+n.top+n.bottom,{...n,top:0,bottom:0}),v=d.color??P(g),k={...d,color:v};return dr("g",{transform:`translate(0, ${S})`,children:[Z("rect",{x:0,y:0,width:r,height:u,fill:"transparent",stroke:m.gridColor,strokeWidth:.5,rx:2}),Z(j,{xScale:t,yScale:w,width:r,height:u,xLabel:g===p-1?i:"",yLabel:l,showGrid:s,colors:m}),Z("text",{x:4,y:14,fill:v,fontSize:11,fontFamily:"system-ui, sans-serif",fontWeight:500,children:d.label}),Z("foreignObject",{x:0,y:0,width:r,height:u,children:Z(W,{spectra:[k],xScale:t,yScale:w,width:r,height:u})})]},d.id)})})}import{useCallback as $e,useRef as br,useState as gr}from"react";function Oe(e){let{enabled:t,xScale:r,onRegionSelect:o}=e,[n,a]=gr(null),s=br(null),i=$e(m=>{if(!t||!m.shiftKey)return;m.preventDefault();let p=m.currentTarget.getBoundingClientRect(),f=m.clientX-p.left,u=r.invert(f);s.current=u,a({xStart:u,xEnd:u})},[t,r]),l=$e(m=>{if(s.current===null)return;let p=m.currentTarget.getBoundingClientRect(),f=m.clientX-p.left,u=r.invert(f),d=s.current;a({xStart:Math.min(d,u),xEnd:Math.max(d,u)})},[r]),c=$e(()=>{if(s.current===null||!n)return;Math.abs(n.xEnd-n.xStart)>0&&o?.(n),s.current=null,a(null)},[n,o]);return{pendingRegion:n,handleMouseDown:i,handleMouseMove:l,handleMouseUp:c}}import{useCallback as hr,useEffect as yr,useRef as St,useState as xr}from"react";function Ne(){let[e,t]=xr(null),r=St(null),o=St(null),n=hr(a=>{if(r.current&&(r.current.disconnect(),r.current=null),o.current=a,!a)return;let s=new ResizeObserver(c=>{let m=c[0];if(!m)return;let{width:p,height:f}=m.contentRect;t({width:Math.round(p),height:Math.round(f)})});s.observe(a),r.current=s;let{width:i,height:l}=a.getBoundingClientRect();t({width:Math.round(i),height:Math.round(l)})},[]);return yr(()=>()=>{r.current?.disconnect()},[]),{ref:n,size:e}}import{useCallback as Sr}from"react";function ze(e){let{onZoomIn:t,onZoomOut:r,onReset:o,enabled:n=!0}=e;return Sr(s=>{if(n)switch(s.key){case"+":case"=":s.preventDefault(),t();break;case"-":s.preventDefault(),r();break;case"Escape":s.preventDefault(),o();break}},[n,t,r,o])}function vr(){return typeof window>"u"?!1:window.matchMedia("(prefers-reduced-motion: reduce)").matches}function Ve(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 wr={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 Lr,jsx as M,jsxs as G}from"react/jsx-runtime";var kr={top:20,right:20,bottom:50,left:65},Rr=800,Mr=400;function Ar(e){return{width:e.width??Rr,height:e.height??Mr,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:{...kr,...e.margin},theme:e.theme??"light",responsive:e.responsive??!1,enableDragDrop:e.enableDragDrop??!1,enableRegionSelect:e.enableRegionSelect??!1}}function Tr(e,t,r){let o=e[0];return{xLabel:t??o?.xUnit??"x",yLabel:r??o?.yUnit??"y"}}function Er(e){let{spectra:t,peaks:r=[],regions:o=[],annotations:n=[],onPeakClick:a,onViewChange:s,onCrosshairMove:i,onToggleVisibility:l,onFileDrop:c,onRegionSelect:m,canvasRef:p,snapCrosshair:f=!0}=e,{ref:u,size:d}=Ne(),S=`spectraview-clip-${Cr().replace(/:/g,"")}`,b=I(()=>Ar(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]),w=b.responsive&&d?d.width:b.width,{height:v,margin:k,reverseX:h,theme:y}=b,C=w-k.left-k.right,x=v-k.top-k.bottom,R=I(()=>H(y),[y]),A=I(()=>Tr(t,e.xLabel,e.yLabel),[t,e.xLabel,e.yLabel]),E=I(()=>be(t),[t]),L=I(()=>B(t),[t]),ce=I(()=>ge(E,w,k,h),[E,w,k,h]),O=I(()=>X(L,v,k),[L,v,k]),N=wt(s);N.current=s;let Lt=I(()=>($,Q)=>{N.current?.({xDomain:$,yDomain:Q})},[]),{zoomRef:Ke,state:Pt,zoomedXScale:T,zoomedYScale:U,resetZoom:Je,zoomIn:qe,zoomOut:Qe}=Se({plotWidth:C,plotHeight:x,xScale:ce,yScale:O,onViewChange:s?Lt:void 0}),{pendingRegion:J,handleMouseDown:Dt,handleMouseMove:Ft,handleMouseUp:It}=Oe({enabled:b.enableRegionSelect,xScale:T,onRegionSelect:m}),[ue,et]=Ze(null),[Ut,tt]=Ze(null),[$t,me]=Ze(null),q=wt(i);q.current=i;let rt=vt($=>{if(!b.showCrosshair)return;let Q=$.currentTarget.getBoundingClientRect(),ot=$.clientX-Q.left,fe=$.clientY-Q.top,ee=T.invert(ot),de=U.invert(fe);if(tt({px:ot,py:fe,dataX:ee,dataY:de}),f&&t.length>0){let F=Ee(t,ee,fe,T,U);if(F&&F.distance<50){let it=t.findIndex(Vt=>Vt.id===F.spectrumId);me({px:T(F.x),py:U(F.y),dataX:F.x,dataY:F.y,color:t[it]?.color??P(it)}),q.current?.(F.x,F.y)}else me(null),q.current?.(ee,de)}else q.current?.(ee,de)},[T,U,b.showCrosshair,f,t]),nt=vt(()=>{tt(null),me(null)},[]),Ot=ze({onZoomIn:qe,onZoomOut:Qe,onReset:Je}),Nt=I(()=>Ve(t.length,A.xLabel,A.yLabel),[t.length,A.xLabel,A.yLabel]),zt=b.displayMode==="stacked";if(t.length===0)return M("div",{ref:b.responsive?u:void 0,style:{width:b.responsive?"100%":w,height:v,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 pe=b.showToolbar?37:0;return G("div",{ref:b.responsive?u:void 0,style:{width:b.responsive?"100%":w,background:R.background,borderRadius:4,overflow:"hidden"},className:e.className,role:"img","aria-label":Nt,tabIndex:0,onKeyDown:Ot,children:[b.showToolbar&&M(De,{onZoomIn:qe,onZoomOut:Qe,onReset:Je,isZoomed:Pt.isZoomed,theme:y}),b.showLegend&&b.legendPosition==="top"&&M(ne,{spectra:t,theme:y,position:"top",onToggleVisibility:l,onHighlight:et,highlightedId:ue}),M(Ie,{enabled:b.enableDragDrop,theme:y,width:w,height:v-pe,onDrop:c,children:zt?M("svg",{width:w,height:v-pe,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${k.left}, ${k.top})`,children:[M(Ue,{spectra:t,xScale:T,plotWidth:C,plotHeight:x,margin:k,theme:y,showGrid:b.showGrid,xLabel:A.xLabel,yLabel:A.yLabel}),M("rect",{ref:Ke,x:0,y:0,width:C,height:x,fill:"transparent",style:{cursor:"grab"},onMouseMove:rt,onMouseLeave:nt})]})}):G(Lr,{children:[M("div",{style:{position:"absolute",top:k.top,left:k.left,width:C,height:x,overflow:"hidden"},children:M(W,{ref:p,spectra:t,xScale:T,yScale:U,width:C,height:x,highlightedId:ue??void 0})}),M("svg",{width:w,height:v-pe,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${k.left}, ${k.top})`,children:[M(j,{xScale:T,yScale:U,width:C,height:x,xLabel:A.xLabel,yLabel:A.yLabel,showGrid:b.showGrid,colors:R}),M("defs",{children:M("clipPath",{id:S,children:M("rect",{x:0,y:0,width:C,height:x})})}),G("g",{clipPath:`url(#${S})`,children:[o.length>0&&M(Re,{regions:o,xScale:T,height:x,colors:R}),r.length>0&&M(Ce,{peaks:r,xScale:T,yScale:U,colors:R,onPeakClick:a})]}),n.length>0&&M(Te,{annotations:n,xScale:T,yScale:U,colors:R}),b.showCrosshair&&M(Ae,{position:Ut,width:C,height:x,colors:R,snapPoint:$t}),J&&M("rect",{x:T(J.xStart),y:0,width:Math.abs(T(J.xEnd)-T(J.xStart)),height:x,fill:R.regionFill,stroke:R.regionStroke,strokeWidth:1,pointerEvents:"none"}),M("rect",{ref:Ke,x:0,y:0,width:C,height:x,fill:"transparent",style:{cursor:b.showCrosshair?"crosshair":"grab"},onMouseDown:Dt,onMouseMove:$=>{rt($),Ft($)},onMouseUp:It,onMouseLeave:nt})]})})]})}),b.showLegend&&b.legendPosition==="bottom"&&M(ne,{spectra:t,theme:y,position:"bottom",onToggleVisibility:l,onHighlight:et,highlightedId:ue})]})}import{useMemo as Ir}from"react";function _e(e,t,r={}){let{prominence:o=.01,minDistance:n=5,maxPeaks:a}=r;if(e.length<3||t.length<3)return[];let s=1/0,i=-1/0;for(let u=0;u<t.length;u++)t[u]<s&&(s=t[u]),t[u]>i&&(i=t[u]);let l=i-s;if(l===0)return[];let c=o*l,m=[];for(let u=1;u<t.length-1;u++)if(t[u]>t[u-1]&&t[u]>t[u+1]){let d=Pr(t,u),g=Dr(t,u),S=t[u]-Math.max(d,g);S>=c&&m.push({index:u,prom:S})}m.sort((u,d)=>d.prom-u.prom);let p=[];for(let u of m)p.some(g=>Math.abs(g.index-u.index)<n)||p.push(u);return(a?p.slice(0,a):p).map(u=>({x:e[u.index],y:t[u.index],label:Fr(e[u.index])})).sort((u,d)=>u.x-d.x)}function Pr(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 Dr(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 Fr(e){return Math.round(e).toString()}function Ur(e,t={}){let{enabled:r=!0,spectrumIds:o,prominence:n,minDistance:a,maxPeaks:s}=t;return Ir(()=>{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 m=_e(c.x,c.y,{prominence:n,minDistance:a,maxPeaks:s});for(let p of m)l.push({...p,spectrumId:c.id})}return l},[e,r,o,n,a,s])}import{useCallback as _,useState as je}from"react";var Ct=0,$r=[" ",",",";"," "];function kt(e){let t=e.trim().split(/\r?\n/).slice(0,5),r=",",o=0;for(let n of $r){let a=t.map(i=>i.split(n).length-1),s=Math.min(...a);s>0&&s>=o&&(a.every(l=>l===a[0])||s>o)&&(o=s,r=n)}return r}function Be(e,t={}){let{xColumn:r=0,yColumn:o=1,hasHeader:n=!0,label:a="CSV Spectrum"}=t,s=t.delimiter??kt(e),i=e.trim().split(/\r?\n/);if(i.length<2)throw new Error("CSV file must contain at least 2 lines");let l=a,c=0;if(n){let f=i[0].split(s).map(u=>u.trim());!t.label&&f[o]&&(l=f[o]),c=1}let m=[],p=[];for(let f=c;f<i.length;f++){let u=i[f].trim();if(u===""||u.startsWith("#"))continue;let d=u.split(s),g=parseFloat(d[r]),S=parseFloat(d[o]);!isNaN(g)&&!isNaN(S)&&(m.push(g),p.push(S))}if(m.length===0)throw new Error("No valid numeric data found in CSV");return{id:`csv-${++Ct}`,label:l,x:new Float64Array(m),y:new Float64Array(p)}}function Or(e,t={}){let{hasHeader:r=!0,label:o}=t,n=t.delimiter??kt(e),a=e.trim().split(/\r?\n/);if(a.length<2)throw new Error("CSV file must contain at least 2 lines");let i=a[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=a[0].split(n).map(u=>u.trim()),c=1);let m=[],p=Array.from({length:i-1},()=>[]);for(let u=c;u<a.length;u++){let d=a[u].trim();if(d===""||d.startsWith("#"))continue;let g=d.split(n),S=parseFloat(g[0]);if(!isNaN(S)){m.push(S);for(let b=1;b<i;b++){let w=parseFloat(g[b]);p[b-1].push(isNaN(w)?0:w)}}}let f=new Float64Array(m);return p.map((u,d)=>({id:`csv-${++Ct}`,label:o??l?.[d+1]??`Spectrum ${d+1}`,x:f,y:new Float64Array(u)}))}var Nr=0;function He(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)=>Xe(r,o));if(typeof t=="object"&&t!==null){let r=t;return Array.isArray(r.spectra)?r.spectra.map((o,n)=>Xe(o,n)):[Xe(t,0)]}throw new Error("Invalid JSON structure: expected an object or array")}function Xe(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-${++Nr}`,label:n,x:new Float64Array(r),y:new Float64Array(o),xUnit:e.xUnit,yUnit:e.yUnit,type:e.type,meta:e.meta}}var Mt=0,ie=null,Rt=!1;async function zr(){if(Rt)return ie;Rt=!0;try{ie=await import("jcampconverter")}catch{ie=null}return ie}function At(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 We(e){let t=await zr();return t?Vr(e,t):[Zr(e)]}function Vr(e,t){return t.convert(e,{keepRecordsRegExp:/.*/}).flatten.map((o,n)=>{let a=o.spectra?.[0]?.data?.[0];if(!a)throw new Error(`JCAMP block ${n}: no spectral data found`);return{id:`jcamp-${++Mt}`,label:o.info?.TITLE??`Spectrum ${n+1}`,x:new Float64Array(a.x),y:new Float64Array(a.y),xUnit:o.info?.XUNITS??"cm\u207B\xB9",yUnit:o.info?.YUNITS??"Absorbance",type:At(o.info),meta:o.info}})}function Zr(e){let t=e.split(/\r?\n/),r={},o=[],n=[],a=!1;for(let s of t){let i=s.trim();if(i.startsWith("##")){let l=i.match(/^##(.+?)=\s*(.*)$/);if(l){let c=l[1].trim().toUpperCase(),m=l[2].trim();if(c==="XYDATA"||c==="XYPOINTS"){a=!0;continue}if(c==="END"){a=!1;continue}r[c]=m}continue}if(a&&i!==""){let l=i.split(/[\s,]+/).map(Number);if(l.length>=2&&!l.some(isNaN)){let c=l[0],m=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 u=f>1?(p-m)/(f-1):0;for(let d=1;d<l.length;d++)o.push(c+(d-1)*u),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-${++Mt}`,label:r.TITLE??"JCAMP Spectrum",x:new Float64Array(o),y:new Float64Array(n),xUnit:r.XUNITS??"cm\u207B\xB9",yUnit:r.YUNITS??"Absorbance",type:At(r),meta:r}}function _r(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 Br(e=[]){let[t,r]=je(e),[o,n]=je(!1),[a,s]=je(null),i=_(async(u,d)=>{n(!0),s(null);try{let g;switch(d){case"jcamp":g=await We(u);break;case"csv":g=[Be(u)];break;case"json":g=He(u);break}r(S=>[...S,...g])}catch(g){let S=g instanceof Error?g.message:"Failed to parse file";s(S)}finally{n(!1)}},[]),l=_(async u=>{let d=_r(u.name);if(!d){s(`Unsupported file format: ${u.name}`);return}let g=await u.text();await i(g,d)},[i]),c=_(u=>{r(d=>[...d,u])},[]),m=_(u=>{r(d=>d.filter(g=>g.id!==u))},[]),p=_(u=>{r(d=>d.map(g=>g.id===u?{...g,visible:g.visible===!1}:g))},[]),f=_(()=>{r([]),s(null)},[]);return{spectra:t,loading:o,error:a,loadFile:l,loadText:i,addSpectrum:c,removeSpectrum:m,toggleVisibility:p,clear:f}}import{useCallback as ae}from"react";var Tt={solid:"",dashed:"8 4",dotted:"2 2","dash-dot":"8 4 2 4"};function Ye(e,t,r,o){let{width:n,height:a,background:s="#ffffff",title:i}=o,l=e.filter(c=>c.visible!==!1).map((c,m)=>{let p=c.color??P(m),f=c.lineStyle??"solid",u=c.lineWidth??1.5,d=Tt[f]??"",g=Math.min(c.x.length,c.y.length);if(g<2)return"";let S=[];for(let b=0;b<g;b++){let w=t(c.x[b]).toFixed(2),v=r(c.y[b]).toFixed(2);S.push(`${b===0?"M":"L"}${w},${v}`)}return`<path d="${S.join(" ")}" fill="none" stroke="${p}" stroke-width="${u}"${d?` stroke-dasharray="${d}"`:""}/>
2
+ import{useCallback as kt,useId as Tr,useMemo as U,useRef as Rt,useState as _e}from"react";import{scaleLinear as ct}from"d3-scale";import{extent as mt}from"d3-array";var Wt=.05;function he(e){let t=1/0,r=-1/0;for(let o of e){if(o.visible===!1)continue;let[n,a]=mt(o.x);n<t&&(t=n),a>r&&(r=a)}return isFinite(t)?[t,r]:[0,1]}function H(e){let t=1/0,r=-1/0;for(let a of e){if(a.visible===!1)continue;let[s,i]=mt(a.y);s<t&&(t=s),i>r&&(r=i)}if(!isFinite(t))return[0,1];let n=(r-t)*Wt;return[t-n,r+n]}function ye(e,t,r,o){let n=t-r.left-r.right,a=o?[e[1],e[0]]:e;return ct().domain(a).range([0,n])}function X(e,t,r){let o=t-r.top-r.bottom;return ct().domain(e).range([o,0])}var xe=["#2563eb","#dc2626","#16a34a","#9333ea","#ea580c","#0891b2","#be185d","#854d0e","#4f46e5","#65a30d"],ut={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"},pt={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 P(e){return xe[e%xe.length]}function z(e){return e==="dark"?pt:ut}import{useCallback as Se,useEffect as Yt,useMemo as ft,useRef as re,useState as jt}from"react";import{zoom as Gt,zoomIdentity as ve}from"d3-zoom";import{select as V}from"d3-selection";import"d3-transition";var dt=1.5;function we(e){let{plotWidth:t,plotHeight:r,xScale:o,yScale:n,scaleExtent:a=[1,50],enabled:s=!0,onViewChange:i}=e,l=re(null),c=re(null),u=re(i);u.current=i;let p=re(a);p.current=a;let[f,m]=jt(ve),d=ft(()=>f.rescaleX(o.copy()),[f,o]),g=ft(()=>f.rescaleY(n.copy()),[f,n]);Yt(()=>{let S=l.current;if(!S||!s)return;let k=Gt().scaleExtent(p.current).extent([[0,0],[t,r]]).translateExtent([[-1/0,-1/0],[1/0,1/0]]).on("zoom",h=>{let v=h.transform;if(m(v),u.current){let w=v.rescaleX(o.copy()),C=v.rescaleY(n.copy());u.current(w.domain(),C.domain())}});return c.current=k,V(S).call(k),V(S).on("dblclick.zoom",()=>{V(S).transition().duration(300).call(k.transform,ve)}),()=>{V(S).on(".zoom",null)}},[t,r,s,o,n]);let y=Se(()=>{!l.current||!c.current||V(l.current).transition().duration(300).call(c.current.transform,ve)},[]),b=Se(()=>{!l.current||!c.current||V(l.current).transition().duration(200).call(c.current.scaleBy,dt)},[]),x=Se(()=>{!l.current||!c.current||V(l.current).transition().duration(200).call(c.current.scaleBy,1/dt)},[]);return{zoomRef:l,state:{transform:f,isZoomed:f.k!==1||f.x!==0||f.y!==0},zoomedXScale:d,zoomedYScale:g,resetZoom:y,zoomIn:b,zoomOut:x}}import{forwardRef as tr,useEffect as gt,useImperativeHandle as rr,useRef as ht}from"react";function Ce(e,t,r,o,n,a,s){let i=o-r;if(i<=s){let f=[];for(let m=r;m<o;m++)f.push({px:n(e[m]),py:a(t[m]),index:m});return f}let l=[];l.push({px:n(e[r]),py:a(t[r]),index:r});let c=s-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,y=r+1+Math.min(Math.floor((f+2)*u),i-2),b,x;if(f===c-1)b=n(e[o-1]),x=a(t[o-1]);else{b=0,x=0;let w=y-g;for(let C=g;C<y;C++)b+=n(e[C]),x+=a(t[C]);w>0&&(b/=w,x/=w)}let S=n(e[p]),k=a(t[p]),h=-1,v=m;for(let w=m;w<d;w++){let C=n(e[w]),R=a(t[w]),A=Math.abs((S-b)*(R-k)-(S-C)*(x-k));A>h&&(h=A,v=w)}l.push({px:n(e[v]),py:a(t[v]),index:v}),p=v}return l.push({px:n(e[o-1]),py:a(t[o-1]),index:o-1}),l}var Kt=1.5,Jt={solid:[],dashed:[8,4],dotted:[2,2],"dash-dot":[8,4,2,4]},qt=2e3;function Qt(e,t,r){e.clearRect(0,0,t,r)}function er(e,t,r,o,n,a,s){let{highlighted:i=!1,opacity:l=1}=s??{},c=Math.min(t.x.length,t.y.length);if(c<2)return;let u=t.color??P(r),p=t.lineWidth??Kt,f=i?p+1:p,m=Jt[t.lineStyle??"solid"]??[],[d,g]=o.domain(),y=Math.min(d,g),b=Math.max(d,g),x=0,S=c;for(let h=0;h<c;h++)if(t.x[h]>=y||h<c-1&&t.x[h+1]>=y){x=Math.max(0,h-1);break}for(let h=c-1;h>=0;h--)if(t.x[h]<=b||h>0&&t.x[h-1]<=b){S=Math.min(c,h+2);break}let k=S-x;if(e.save(),e.beginPath(),e.strokeStyle=u,e.lineWidth=f,e.globalAlpha=l,e.lineJoin="round",e.setLineDash(m),k>qt){let h=Math.max(Math.ceil(a*2),200),v=Ce(t.x,t.y,x,S,o,n,h);if(v.length>0){e.moveTo(v[0].px,v[0].py);for(let w=1;w<v.length;w++)e.lineTo(v[w].px,v[w].py)}}else{let h=!1;for(let v=x;v<S;v++){let w=o(t.x[v]),C=n(t.y[v]);h?e.lineTo(w,C):(e.moveTo(w,C),h=!0)}}e.stroke(),e.restore()}function bt(e,t,r,o,n,a,s){Qt(e,n,a),t.forEach((i,l)=>{i.visible!==!1&&er(e,i,l,r,o,n,{highlighted:i.id===s,opacity:s&&i.id!==s?.3:1})})}import{jsx as nr}from"react/jsx-runtime";var W=tr(function({spectra:t,xScale:r,yScale:o,width:n,height:a,highlightedId:s},i){let l=ht(null),c=ht(1);return rr(i,()=>l.current,[]),gt(()=>{let u=l.current;if(!u)return;let p=window.devicePixelRatio||1;c.current=p,u.width=n*p,u.height=a*p},[n,a]),gt(()=>{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),bt(p,t,r,o,n,a,s)},[t,r,o,n,a,s]),nr("canvas",{ref:l,style:{width:n,height:a,position:"absolute",top:0,left:0,pointerEvents:"none"}})});import{jsx as F,jsxs as Z}from"react/jsx-runtime";function yt(e,t){let[r,o]=e.domain(),n=Math.min(r,o),s=(Math.max(r,o)-n)/(t-1);return Array.from({length:t},(i,l)=>n+l*s)}function xt(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:a,showGrid:s=!0,colors:i}){let l=yt(e,8),c=yt(t,6);return Z("g",{children:[s&&Z("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}`))]}),Z("g",{transform:`translate(0, ${o})`,children:[F("line",{x1:0,x2:r,y1:0,y2:0,stroke:i.axisColor}),l.map(u=>Z("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:xt(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})]}),Z("g",{children:[F("line",{x1:0,x2:0,y1:0,y2:o,stroke:i.axisColor}),c.map(u=>Z("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:xt(u)})]},`ytick-${u}`)),a&&F("text",{transform:`translate(-50, ${o/2}) rotate(-90)`,textAnchor:"middle",fill:i.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:a})]})]})}import{jsx as ke,jsxs as or}from"react/jsx-runtime";function Re({peaks:e,xScale:t,yScale:r,colors:o,onPeakClick:n}){let[a,s]=t.domain(),i=Math.min(a,s),l=Math.max(a,s),c=e.filter(u=>u.x>=i&&u.x<=l);return ke("g",{className:"spectraview-peaks",children:c.map((u,p)=>{let f=t(u.x),m=r(u.y);return or("g",{transform:`translate(${f}, ${m})`,style:{cursor:n?"pointer":"default"},onClick:()=>n?.(u),children:[ke("polygon",{points:`0,-5 -5,${-5*2.5} 5,${-5*2.5}`,fill:o.labelColor,opacity:.8}),u.label&&ke("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 Me,jsxs as ir}from"react/jsx-runtime";function Ae({regions:e,xScale:t,height:r,colors:o}){return Me("g",{className:"spectraview-regions",children:e.map((n,a)=>{let s=t(n.xStart),i=t(n.xEnd),l=Math.min(s,i),c=Math.abs(i-s);return ir("g",{children:[Me("rect",{x:l,y:0,width:c,height:r,fill:n.color??o.regionFill,stroke:o.regionStroke,strokeWidth:1}),n.label&&Me("text",{x:l+c/2,y:12,textAnchor:"middle",fill:o.labelColor,fontSize:10,fontFamily:"system-ui, sans-serif",children:n.label})]},`region-${a}`)})})}import{jsx as ne,jsxs as Te}from"react/jsx-runtime";function Ee({position:e,width:t,height:r,colors:o,snapPoint:n}){return e?Te("g",{className:"spectraview-crosshair",pointerEvents:"none",children:[ne("line",{x1:e.px,x2:e.px,y1:0,y2:r,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),ne("line",{x1:0,x2:t,y1:e.py,y2:e.py,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),n&&ne("circle",{cx:n.px,cy:n.py,r:4,fill:n.color??o.crosshairColor,stroke:o.background,strokeWidth:1.5}),Te("g",{transform:`translate(${Math.min(e.px+10,t-100)}, ${Math.max(e.py-10,20)})`,children:[ne("rect",{x:0,y:-14,width:90,height:18,rx:3,fill:o.tooltipBg,stroke:o.tooltipBorder,strokeWidth:.5,opacity:.9}),Te("text",{x:5,y:0,fill:o.tooltipText,fontSize:10,fontFamily:"monospace",children:[St(n?.dataX??e.dataX),","," ",St(n?.dataY??e.dataY)]})]})]}):null}function St(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 ar}from"react/jsx-runtime";function Le({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 a=t(n.x),s=r(n.y),[i,l]=n.offset??[0,-20],c=a+i,u=s+l,p=n.fontSize??11,f=n.color??o.tickColor,m=n.showAnchorLine!==!1;return ar("g",{children:[m&&j("line",{x1:a,y1:s,x2:c,y2:u,stroke:f,strokeWidth:.75,strokeDasharray:"3 2",opacity:.6}),j("circle",{cx:a,cy:s,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 vt(e,t,r){if(r===0)return-1;if(r===1)return 0;let o=e[r-1]>=e[0],n=0,a=r-1;for(;n<a-1;){let l=n+a>>>1,c=e[l];o?c<=t?n=l:a=l:c>=t?n=l:a=l}let s=Math.abs(e[n]-t),i=Math.abs(e[a]-t);return s<=i?n:a}function Pe(e,t,r,o,n){let a=null;for(let s of e){if(s.visible===!1)continue;let i=Math.min(s.x.length,s.y.length);if(i<2)continue;let l=vt(s.x,t,i);if(l<0)continue;let c=s.x[l],u=s.y[l],p=Math.abs(o(c)-o(t)),f=Math.abs(n(u)-r),m=Math.sqrt(p*p+f*f);(!a||m<a.distance)&&(a={spectrumId:s.id,index:l,x:c,y:u,distance:m})}return a}import{memo as sr}from"react";import{jsx as De,jsxs as cr}from"react/jsx-runtime";var Fe=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}),lr=e=>({display:"flex",gap:4,padding:"4px 0",borderBottom:`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`}),Ue=sr(function({onZoomIn:t,onZoomOut:r,onReset:o,isZoomed:n,theme:a}){return cr("div",{style:lr(a),className:"spectraview-toolbar",children:[De("button",{type:"button",style:Fe(a),onClick:t,title:"Zoom in","aria-label":"Zoom in",children:"+"}),De("button",{type:"button",style:Fe(a),onClick:r,title:"Zoom out","aria-label":"Zoom out",children:"\u2212"}),De("button",{type:"button",style:{...Fe(a),opacity:n?1:.4},onClick:o,disabled:!n,title:"Reset zoom","aria-label":"Reset zoom",children:"\u21BA"})]})});import{memo as mr}from"react";import{jsx as Ie,jsxs as dr}from"react/jsx-runtime";var ur=(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}),pr=(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"}),fr=(e,t)=>({width:12,height:3,borderRadius:1,background:e,opacity:t?.4:1,flexShrink:0}),oe=mr(function({spectra:t,theme:r,position:o,onToggleVisibility:n,onHighlight:a,highlightedId:s}){return t.length<=1?null:Ie("div",{className:"spectraview-legend",style:ur(r,o),role:"list","aria-label":"Spectrum legend",children:t.map((i,l)=>{let c=i.color??P(l),u=i.visible===!1,p=s===i.id;return dr("div",{role:"listitem",style:pr(r,u,p),onClick:()=>n?.(i.id),onMouseEnter:()=>a?.(i.id),onMouseLeave:()=>a?.(null),title:u?`Show ${i.label}`:`Hide ${i.label}`,children:[Ie("span",{style:fr(c,u)}),Ie("span",{style:{textDecoration:u?"line-through":"none",maxWidth:120,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:i.label})]},i.id)})})});import{useCallback as ie,useState as br}from"react";import{jsx as gr,jsxs as hr}from"react/jsx-runtime";function $e({enabled:e,theme:t,width:r,height:o,onDrop:n,children:a}){let[s,i]=br(!1),l={current:0},c=ie(m=>{e&&(m.preventDefault(),l.current++,i(!0))},[e]),u=ie(m=>{e&&(m.preventDefault(),l.current--,l.current<=0&&(l.current=0,i(!1)))},[e]),p=ie(m=>{e&&(m.preventDefault(),m.dataTransfer.dropEffect="copy")},[e]),f=ie(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 hr("div",{style:{position:"relative",width:r,height:o},onDragEnter:c,onDragLeave:u,onDragOver:p,onDrop:f,children:[a,s&&gr("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 yr}from"react";import{jsx as B,jsxs as xr}from"react/jsx-runtime";var wt=8;function Ne({spectra:e,xScale:t,plotWidth:r,plotHeight:o,margin:n,theme:a,showGrid:s,xLabel:i,yLabel:l}){let c=e.filter(d=>d.visible!==!1),u=yr(()=>z(a),[a]),p=c.length,f=(p-1)*wt,m=Math.max(40,Math.floor((o-f)/Math.max(p,1)));return B("g",{className:"spectraview-stacked",children:c.map((d,g)=>{let y=g*(m+wt),b=H([d]),x=X(b,m+n.top+n.bottom,{...n,top:0,bottom:0}),S=d.color??P(g),k={...d,color:S};return xr("g",{transform:`translate(0, ${y})`,children:[B("rect",{x:0,y:0,width:r,height:m,fill:"transparent",stroke:u.gridColor,strokeWidth:.5,rx:2}),B(Y,{xScale:t,yScale:x,width:r,height:m,xLabel:g===p-1?i:"",yLabel:l,showGrid:s,colors:u}),B("text",{x:4,y:14,fill:S,fontSize:11,fontFamily:"system-ui, sans-serif",fontWeight:500,children:d.label}),B("foreignObject",{x:0,y:0,width:r,height:m,children:B(W,{spectra:[k],xScale:t,yScale:x,width:r,height:m})})]},d.id)})})}import{useCallback as Oe,useRef as Sr,useState as vr}from"react";function ze(e){let{enabled:t,xScale:r,onRegionSelect:o}=e,[n,a]=vr(null),s=Sr(null),i=Oe(u=>{if(!t||!u.shiftKey)return;u.preventDefault();let p=u.currentTarget.getBoundingClientRect(),f=u.clientX-p.left,m=r.invert(f);s.current=m,a({xStart:m,xEnd:m})},[t,r]),l=Oe(u=>{if(s.current===null)return;let p=u.currentTarget.getBoundingClientRect(),f=u.clientX-p.left,m=r.invert(f),d=s.current;a({xStart:Math.min(d,m),xEnd:Math.max(d,m)})},[r]),c=Oe(()=>{if(s.current===null||!n)return;Math.abs(n.xEnd-n.xStart)>0&&o?.(n),s.current=null,a(null)},[n,o]);return{pendingRegion:n,handleMouseDown:i,handleMouseMove:l,handleMouseUp:c}}import{useCallback as wr,useEffect as Cr,useRef as Ct,useState as kr}from"react";function Ve(){let[e,t]=kr(null),r=Ct(null),o=Ct(null),n=wr(a=>{if(r.current&&(r.current.disconnect(),r.current=null),o.current=a,!a)return;let s=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)})});s.observe(a),r.current=s;let{width:i,height:l}=a.getBoundingClientRect();t({width:Math.round(i),height:Math.round(l)})},[]);return Cr(()=>()=>{r.current?.disconnect()},[]),{ref:n,size:e}}import{useCallback as Rr}from"react";function Ze(e){let{onZoomIn:t,onZoomOut:r,onReset:o,enabled:n=!0}=e;return Rr(s=>{if(n)switch(s.key){case"+":case"=":s.preventDefault(),t();break;case"-":s.preventDefault(),r();break;case"Escape":s.preventDefault(),o();break}},[n,t,r,o])}function Mr(){return typeof window>"u"?!1:window.matchMedia("(prefers-reduced-motion: reduce)").matches}function Be(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 Ar={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 Ir,jsx as M,jsxs as G}from"react/jsx-runtime";var Er={top:20,right:20,bottom:50,left:65},Lr=800,Pr=400;function Fr(e){return{width:e.width??Lr,height:e.height??Pr,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:{...Er,...e.margin},theme:e.theme??"light",responsive:e.responsive??!1,enableDragDrop:e.enableDragDrop??!1,enableRegionSelect:e.enableRegionSelect??!1}}function Dr(e,t,r){let o=e[0];return{xLabel:t??o?.xUnit??"x",yLabel:r??o?.yUnit??"y"}}function Ur(e){let{spectra:t,peaks:r=[],regions:o=[],annotations:n=[],onPeakClick:a,onViewChange:s,onCrosshairMove:i,onToggleVisibility:l,onFileDrop:c,onRegionSelect:u,canvasRef:p,snapCrosshair:f=!0}=e,{ref:m,size:d}=Ve(),y=`spectraview-clip-${Tr().replace(/:/g,"")}`,b=U(()=>Fr(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]),x=b.responsive&&d?d.width:b.width,{height:S,margin:k,reverseX:h,theme:v}=b,w=x-k.left-k.right,C=S-k.top-k.bottom,R=U(()=>z(v),[v]),A=U(()=>Dr(t,e.xLabel,e.yLabel),[t,e.xLabel,e.yLabel]),E=U(()=>he(t),[t]),L=U(()=>H(t),[t]),ue=U(()=>ye(E,x,k,h),[E,x,k,h]),N=U(()=>X(L,S,k),[L,S,k]),O=Rt(s);O.current=s;let It=U(()=>($,ee)=>{O.current?.({xDomain:$,yDomain:ee})},[]),{zoomRef:Qe,state:$t,zoomedXScale:T,zoomedYScale:I,resetZoom:et,zoomIn:tt,zoomOut:rt}=we({plotWidth:w,plotHeight:C,xScale:ue,yScale:N,onViewChange:s?It:void 0}),{pendingRegion:q,handleMouseDown:Nt,handleMouseMove:Ot,handleMouseUp:zt}=ze({enabled:b.enableRegionSelect,xScale:T,onRegionSelect:u}),[pe,nt]=_e(null),[Vt,ot]=_e(null),[Zt,fe]=_e(null),Q=Rt(i);Q.current=i;let it=kt($=>{if(!b.showCrosshair)return;let ee=$.currentTarget.getBoundingClientRect(),st=$.clientX-ee.left,be=$.clientY-ee.top,te=T.invert(st),ge=I.invert(be);if(ot({px:st,py:be,dataX:te,dataY:ge}),f&&t.length>0){let D=Pe(t,te,be,T,I);if(D&&D.distance<50){let lt=t.findIndex(Xt=>Xt.id===D.spectrumId);fe({px:T(D.x),py:I(D.y),dataX:D.x,dataY:D.y,color:t[lt]?.color??P(lt)}),Q.current?.(D.x,D.y)}else fe(null),Q.current?.(te,ge)}else Q.current?.(te,ge)},[T,I,b.showCrosshair,f,t]),at=kt(()=>{ot(null),fe(null)},[]),Bt=Ze({onZoomIn:tt,onZoomOut:rt,onReset:et}),_t=U(()=>Be(t.length,A.xLabel,A.yLabel),[t.length,A.xLabel,A.yLabel]),Ht=b.displayMode==="stacked";if(t.length===0)return M("div",{ref:b.responsive?m:void 0,style:{width:b.responsive?"100%":x,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 de=b.showToolbar?37:0;return G("div",{ref:b.responsive?m:void 0,style:{width:b.responsive?"100%":x,background:R.background,borderRadius:4,overflow:"hidden"},className:e.className,role:"img","aria-label":_t,tabIndex:0,onKeyDown:Bt,children:[b.showToolbar&&M(Ue,{onZoomIn:tt,onZoomOut:rt,onReset:et,isZoomed:$t.isZoomed,theme:v}),b.showLegend&&b.legendPosition==="top"&&M(oe,{spectra:t,theme:v,position:"top",onToggleVisibility:l,onHighlight:nt,highlightedId:pe}),M($e,{enabled:b.enableDragDrop,theme:v,width:x,height:S-de,onDrop:c,children:Ht?M("svg",{width:x,height:S-de,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${k.left}, ${k.top})`,children:[M(Ne,{spectra:t,xScale:T,plotWidth:w,plotHeight:C,margin:k,theme:v,showGrid:b.showGrid,xLabel:A.xLabel,yLabel:A.yLabel}),M("rect",{ref:Qe,x:0,y:0,width:w,height:C,fill:"transparent",style:{cursor:"grab"},onMouseMove:it,onMouseLeave:at})]})}):G(Ir,{children:[M("div",{style:{position:"absolute",top:k.top,left:k.left,width:w,height:C,overflow:"hidden"},children:M(W,{ref:p,spectra:t,xScale:T,yScale:I,width:w,height:C,highlightedId:pe??void 0})}),M("svg",{width:x,height:S-de,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${k.left}, ${k.top})`,children:[M(Y,{xScale:T,yScale:I,width:w,height:C,xLabel:A.xLabel,yLabel:A.yLabel,showGrid:b.showGrid,colors:R}),M("defs",{children:M("clipPath",{id:y,children:M("rect",{x:0,y:0,width:w,height:C})})}),G("g",{clipPath:`url(#${y})`,children:[o.length>0&&M(Ae,{regions:o,xScale:T,height:C,colors:R}),r.length>0&&M(Re,{peaks:r,xScale:T,yScale:I,colors:R,onPeakClick:a})]}),n.length>0&&M(Le,{annotations:n,xScale:T,yScale:I,colors:R}),b.showCrosshair&&M(Ee,{position:Vt,width:w,height:C,colors:R,snapPoint:Zt}),q&&M("rect",{x:T(q.xStart),y:0,width:Math.abs(T(q.xEnd)-T(q.xStart)),height:C,fill:R.regionFill,stroke:R.regionStroke,strokeWidth:1,pointerEvents:"none"}),M("rect",{ref:Qe,x:0,y:0,width:w,height:C,fill:"transparent",style:{cursor:b.showCrosshair?"crosshair":"grab"},onMouseDown:Nt,onMouseMove:$=>{it($),Ot($)},onMouseUp:zt,onMouseLeave:at})]})})]})}),b.showLegend&&b.legendPosition==="bottom"&&M(oe,{spectra:t,theme:v,position:"bottom",onToggleVisibility:l,onHighlight:nt,highlightedId:pe})]})}import{memo as $r,useEffect as Nr,useRef as Or,useMemo as He}from"react";import{scaleLinear as Mt}from"d3-scale";import{jsx as ae,jsxs as At}from"react/jsx-runtime";var zr=$r(function({spectra:t,xExtent:r,yExtent:o,visibleXDomain:n,width:a=200,height:s=50,theme:i="light",isZoomed:l=!1}){let c=Or(null),u=He(()=>z(i),[i]),p=He(()=>Mt().domain(r).range([0,a]),[r,a]),f=He(()=>Mt().domain(o).range([s-2,2]),[o,s]);Nr(()=>{let y=c.current?.getContext("2d");if(y){y.clearRect(0,0,a,s);for(let b=0;b<t.length;b++){let x=t[b];if(x.visible===!1)continue;let S=Math.min(x.x.length,x.y.length);if(S<2)continue;let k=x.color??P(b);y.beginPath(),y.strokeStyle=k,y.lineWidth=1,y.globalAlpha=.7;let h=Math.max(1,Math.floor(S/a)),v=!1;for(let w=0;w<S;w+=h){let C=p(x.x[w]),R=f(x.y[w]);v?y.lineTo(C,R):(y.moveTo(C,R),v=!0)}y.stroke()}}},[t,p,f,a,s]);let m=p(Math.min(n[0],n[1])),d=p(Math.max(n[0],n[1])),g=Math.max(d-m,2);return At("div",{className:"spectraview-minimap",style:{position:"relative",width:a,height:s,border:`1px solid ${u.gridColor}`,borderRadius:3,overflow:"hidden",background:u.background},children:[ae("canvas",{ref:c,width:a,height:s,style:{position:"absolute",top:0,left:0}}),l&&At("svg",{width:a,height:s,style:{position:"absolute",top:0,left:0},children:[ae("rect",{x:0,y:0,width:m,height:s,fill:u.background,opacity:.6}),ae("rect",{x:m+g,y:0,width:a-m-g,height:s,fill:u.background,opacity:.6}),ae("rect",{x:m,y:0,width:g,height:s,fill:"none",stroke:i==="dark"?"#60a5fa":"#3b82f6",strokeWidth:1.5,rx:1})]})]})});import{useMemo as _r}from"react";function Xe(e,t,r={}){let{prominence:o=.01,minDistance:n=5,maxPeaks:a}=r;if(e.length<3||t.length<3)return[];let s=1/0,i=-1/0;for(let m=0;m<t.length;m++)t[m]<s&&(s=t[m]),t[m]>i&&(i=t[m]);let l=i-s;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=Vr(t,m),g=Zr(t,m),y=t[m]-Math.max(d,g);y>=c&&u.push({index:m,prom:y})}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(a?p.slice(0,a):p).map(m=>({x:e[m.index],y:t[m.index],label:Br(e[m.index])})).sort((m,d)=>m.x-d.x)}function Vr(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 Zr(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 Br(e){return Math.round(e).toString()}function Hr(e,t={}){let{enabled:r=!0,spectrumIds:o,prominence:n,minDistance:a,maxPeaks:s}=t;return _r(()=>{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=Xe(c.x,c.y,{prominence:n,minDistance:a,maxPeaks:s});for(let p of u)l.push({...p,spectrumId:c.id})}return l},[e,r,o,n,a,s])}import{useCallback as _,useState as Ke}from"react";var Tt=0,Xr=[" ",",",";"," "];function Et(e){let t=e.trim().split(/\r?\n/).slice(0,5),r=",",o=0;for(let n of Xr){let a=t.map(i=>i.split(n).length-1),s=Math.min(...a);s>0&&s>=o&&(a.every(l=>l===a[0])||s>o)&&(o=s,r=n)}return r}function We(e,t={}){let{xColumn:r=0,yColumn:o=1,hasHeader:n=!0,label:a="CSV Spectrum"}=t,s=t.delimiter??Et(e),i=e.trim().split(/\r?\n/);if(i.length<2)throw new Error("CSV file must contain at least 2 lines");let l=a,c=0;if(n){let f=i[0].split(s).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(s),g=parseFloat(d[r]),y=parseFloat(d[o]);!isNaN(g)&&!isNaN(y)&&(u.push(g),p.push(y))}if(u.length===0)throw new Error("No valid numeric data found in CSV");return{id:`csv-${++Tt}`,label:l,x:new Float64Array(u),y:new Float64Array(p)}}function Wr(e,t={}){let{hasHeader:r=!0,label:o}=t,n=t.delimiter??Et(e),a=e.trim().split(/\r?\n/);if(a.length<2)throw new Error("CSV file must contain at least 2 lines");let i=a[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=a[0].split(n).map(m=>m.trim()),c=1);let u=[],p=Array.from({length:i-1},()=>[]);for(let m=c;m<a.length;m++){let d=a[m].trim();if(d===""||d.startsWith("#"))continue;let g=d.split(n),y=parseFloat(g[0]);if(!isNaN(y)){u.push(y);for(let b=1;b<i;b++){let x=parseFloat(g[b]);p[b-1].push(isNaN(x)?0:x)}}}let f=new Float64Array(u);return p.map((m,d)=>({id:`csv-${++Tt}`,label:o??l?.[d+1]??`Spectrum ${d+1}`,x:f,y:new Float64Array(m)}))}var Yr=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)=>Ye(r,o));if(typeof t=="object"&&t!==null){let r=t;return Array.isArray(r.spectra)?r.spectra.map((o,n)=>Ye(o,n)):[Ye(t,0)]}throw new Error("Invalid JSON structure: expected an object or array")}function Ye(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-${++Yr}`,label:n,x:new Float64Array(r),y:new Float64Array(o),xUnit:e.xUnit,yUnit:e.yUnit,type:e.type,meta:e.meta}}var Pt=0,se=null,Lt=!1;async function jr(){if(Lt)return se;Lt=!0;try{se=await import("jcampconverter")}catch{se=null}return se}function Ft(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 Ge(e){let t=await jr();return t?Gr(e,t):[Kr(e)]}function Gr(e,t){return t.convert(e,{keepRecordsRegExp:/.*/}).flatten.map((o,n)=>{let a=o.spectra?.[0]?.data?.[0];if(!a)throw new Error(`JCAMP block ${n}: no spectral data found`);return{id:`jcamp-${++Pt}`,label:o.info?.TITLE??`Spectrum ${n+1}`,x:new Float64Array(a.x),y:new Float64Array(a.y),xUnit:o.info?.XUNITS??"cm\u207B\xB9",yUnit:o.info?.YUNITS??"Absorbance",type:Ft(o.info),meta:o.info}})}function Kr(e){let t=e.split(/\r?\n/),r={},o=[],n=[],a=!1;for(let s of t){let i=s.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"){a=!0;continue}if(c==="END"){a=!1;continue}r[c]=u}continue}if(a&&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-${++Pt}`,label:r.TITLE??"JCAMP Spectrum",x:new Float64Array(o),y:new Float64Array(n),xUnit:r.XUNITS??"cm\u207B\xB9",yUnit:r.YUNITS??"Absorbance",type:Ft(r),meta:r}}function Jr(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 qr(e=[]){let[t,r]=Ke(e),[o,n]=Ke(!1),[a,s]=Ke(null),i=_(async(m,d)=>{n(!0),s(null);try{let g;switch(d){case"jcamp":g=await Ge(m);break;case"csv":g=[We(m)];break;case"json":g=je(m);break}r(y=>[...y,...g])}catch(g){let y=g instanceof Error?g.message:"Failed to parse file";s(y)}finally{n(!1)}},[]),l=_(async m=>{let d=Jr(m.name);if(!d){s(`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([]),s(null)},[]);return{spectra:t,loading:o,error:a,loadFile:l,loadText:i,addSpectrum:c,removeSpectrum:u,toggleVisibility:p,clear:f}}import{useCallback as le}from"react";var Dt={solid:"",dashed:"8 4",dotted:"2 2","dash-dot":"8 4 2 4"};function Je(e,t,r,o){let{width:n,height:a,background:s="#ffffff",title:i}=o,l=e.filter(c=>c.visible!==!1).map((c,u)=>{let p=c.color??P(u),f=c.lineStyle??"solid",m=c.lineWidth??1.5,d=Dt[f]??"",g=Math.min(c.x.length,c.y.length);if(g<2)return"";let y=[];for(let b=0;b<g;b++){let x=t(c.x[b]).toFixed(2),S=r(c.y[b]).toFixed(2);y.push(`${b===0?"M":"L"}${x},${S}`)}return`<path d="${y.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="${a}" viewBox="0 0 ${n} ${a}">
@@ -8,9 +8,9 @@ import{useCallback as vt,useId as Cr,useMemo as I,useRef as wt,useState as Ze}fr
8
8
  <g>
9
9
  ${l}
10
10
  </g>
11
- </svg>`}function Ge(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 se(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 Xr(){let e=ae((n,a="spectrum.png")=>{n.toBlob(s=>{s&&se(s,a)},"image/png")},[]),t=ae((n,a="spectra.csv")=>{let s=n.filter(i=>i.visible!==!1);if(s.length!==0)if(s.length===1){let i=s[0],l=`${i.xUnit??"x"},${i.yUnit??"y"}
12
- `,c=Array.from(i.x).map((p,f)=>`${p},${i.y[f]}`),m=l+c.join(`
13
- `);se(new Blob([m],{type:"text/csv"}),a)}else{let i=Math.max(...s.map(p=>p.x.length)),l=s.map(p=>`${p.label}_x,${p.label}_y`).join(","),c=[];for(let p=0;p<i;p++){let f=s.map(u=>p<u.x.length?`${u.x[p]},${u.y[p]}`:",");c.push(f.join(","))}let m=l+`
11
+ </svg>`}function qe(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 ce(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 Qr(){let e=le((n,a="spectrum.png")=>{n.toBlob(s=>{s&&ce(s,a)},"image/png")},[]),t=le((n,a="spectra.csv")=>{let s=n.filter(i=>i.visible!==!1);if(s.length!==0)if(s.length===1){let i=s[0],l=`${i.xUnit??"x"},${i.yUnit??"y"}
12
+ `,c=Array.from(i.x).map((p,f)=>`${p},${i.y[f]}`),u=l+c.join(`
13
+ `);ce(new Blob([u],{type:"text/csv"}),a)}else{let i=Math.max(...s.map(p=>p.x.length)),l=s.map(p=>`${p.label}_x,${p.label}_y`).join(","),c=[];for(let p=0;p<i;p++){let f=s.map(m=>p<m.x.length?`${m.x[p]},${m.y[p]}`:",");c.push(f.join(","))}let u=l+`
14
14
  `+c.join(`
15
- `);se(new Blob([m],{type:"text/csv"}),a)}},[]),r=ae((n,a="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);se(new Blob([l],{type:"application/json"}),a)},[]),o=ae((n,a,s,i,l,c="spectrum.svg")=>{let m=Ye(n,a,s,{width:i,height:l});Ge(m,c)},[]);return{exportPng:e,exportSvg:o,exportCsv:t,exportJson:r}}import{useCallback as Hr,useState as Wr}from"react";import{jsx as K,jsxs as Et}from"react/jsx-runtime";var jr=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"}),Yr=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"}),le=e=>({display:"block",width:"100%",padding:"6px 12px",border:"none",background:"transparent",color:e==="dark"?"#d1d5db":"#374151",fontSize:12,textAlign:"left",cursor:"pointer"});function Gr({theme:e,onExportPng:t,onExportSvg:r,onExportCsv:o,onExportJson:n}){let[a,s]=Wr(!1),i=Hr(l=>{s(!1),l?.()},[]);return Et("div",{style:{position:"relative",display:"inline-block"},children:[K("button",{type:"button",style:jr(e),onClick:()=>s(!a),"aria-label":"Export","aria-expanded":a,"aria-haspopup":"true",children:"Export"}),a&&Et("div",{style:Yr(e),role:"menu",children:[t&&K("button",{type:"button",role:"menuitem",style:le(e),onClick:()=>i(t),children:"PNG Image"}),r&&K("button",{type:"button",role:"menuitem",style:le(e),onClick:()=>i(r),children:"SVG Vector"}),o&&K("button",{type:"button",role:"menuitem",style:le(e),onClick:()=>i(o),children:"CSV Data"}),n&&K("button",{type:"button",role:"menuitem",style:le(e),onClick:()=>i(n),children:"JSON Data"})]})]})}function Kr(e){let t=e.length;if(t<3)return new Float64Array(e);let r=[0];for(let s=1;s<t;s++){for(;r.length>=2;){let i=r.length-1,l=r[i-1],c=r[i];if((s-l)*(e[c]-e[l])-(c-l)*(e[s]-e[l])>=0)r.pop();else break}r.push(s)}let o=new Float64Array(t),n=0;for(let s=0;s<t;s++){for(;n<r.length-1&&r[n+1]<=s;)n++;if(n>=r.length-1)o[s]=e[r[r.length-1]];else{let i=r[n],l=r[n+1],c=(s-i)/(l-i);o[s]=e[i]*(1-c)+e[l]*c}}let a=new Float64Array(t);for(let s=0;s<t;s++)a[s]=e[s]-o[s];return a}function Jr(e){let t=e.length,r=new Float64Array(t),o=1/0,n=-1/0;for(let s=0;s<t;s++){let i=e[s];i<o&&(o=i),i>n&&(n=i)}let a=n-o;if(a===0)return r;for(let s=0;s<t;s++)r[s]=(e[s]-o)/a;return r}function qr(e,t){let r=Math.min(e.length,t.length);if(r<2)return new Float64Array(t);let o=0;for(let a=1;a<r;a++)o+=Math.abs(e[a]-e[a-1])*(Math.abs(t[a])+Math.abs(t[a-1]))*.5;if(o===0)return new Float64Array(t);let n=new Float64Array(r);for(let a=0;a<r;a++)n[a]=t[a]/o;return n}function Qr(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 a=Math.sqrt(n/t);if(a===0)return new Float64Array(t);let s=new Float64Array(t);for(let i=0;i<t;i++)s[i]=(e[i]-o)/a;return s}function en(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,a=tn(o),s=new Float64Array(r);for(let i=0;i<n;i++)s[i]=e[i],s[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+=a[c+n]*e[i+c];s[i]=l}return s}function tn(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 rn(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 nn(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 a=e[n]-e[n-1],s=e[n+1]-e[n],i=(a+s)/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 on=0,an=1,sn=4,ln=128,cn={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"},un={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 mn(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 pn(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 a=t.getUint8(2),s=t.getUint8(3),i=t.getUint32(4,!0),l=t.getFloat64(8,!0),c=t.getFloat64(16,!0),m=t.getUint32(24,!0),p=cn[a]??"Arbitrary",f=un[s]??"Arbitrary",u=new Uint8Array(e,30,130),d=fn(u),g=(o&sn)!==0,S=(o&ln)!==0,b=(o&an)!==0,w=mn(a,s),v=null;if(!S&&i>0){v=new Float64Array(i);let x=i>1?(c-l)/(i-1):0;for(let R=0;R<i;R++)v[R]=l+R*x}let k=[],h=512,y=null;if(S&&!g){y=new Float64Array(i);for(let x=0;x<i;x++)y[x]=t.getFloat32(h,!0),h+=4}let C=g?m:1;for(let x=0;x<C;x++){let R,A,E=i;if(g){if(h+32>e.byteLength)break;let L=t.getFloat32(h+4,!0),ce=t.getFloat32(h+8,!0);if(E=t.getUint32(h+12,!0)||i,h+=32,S){R=new Float64Array(E);for(let O=0;O<E&&!(h+4>e.byteLength);O++)R[O]=t.getFloat32(h,!0),h+=4}else if(v)R=v;else{R=new Float64Array(E);let O=E>1?(ce-L)/(E-1):0;for(let N=0;N<E;N++)R[N]=L+N*O}}else R=y??v??new Float64Array(0);if(A=new Float64Array(E),b)for(let L=0;L<E&&!(h+2>e.byteLength);L++)A[L]=t.getInt16(h,!0),h+=2;else for(let L=0;L<E&&!(h+4>e.byteLength);L++)A[L]=t.getFloat32(h,!0),h+=4;k.push({id:`spc-${++on}`,label:d||`SPC Spectrum ${x+1}`,x:R,y:A,xUnit:p,yUnit:f,type:w,meta:{format:"SPC",version:n===75?"new":"old",xType:a.toString(),yType:s.toString()}})}if(k.length===0)throw new Error("Invalid SPC file: no spectra found");return k}function fn(e){let t=e.indexOf(0),r=t>=0?e.slice(0,t):e;return new TextDecoder("ascii").decode(r).trim()}export{Te as AnnotationLayer,j as AxisLayer,Ae as Crosshair,ct as DARK_THEME,Ie as DropZone,Gr as ExportMenu,wr as KEYBOARD_SHORTCUTS,lt as LIGHT_THEME,Tt as LINE_DASH_PATTERNS,ne as Legend,Ce as PeakMarkers,Re as RegionSelector,he as SPECTRUM_COLORS,Er as SpectraView,W as SpectrumCanvas,Ue as StackedView,De as Toolbar,Kr as baselineRubberBand,yt as binarySearchClosest,be as computeXExtent,B as computeYExtent,ge as createXScale,X as createYScale,rn as derivative1st,nn as derivative2nd,_e as detectPeaks,Ge as downloadSvg,Ve as generateChartDescription,Ye as generateSvg,P as getSpectrumColor,H as getThemeColors,ve as lttbDownsample,qr as normalizeArea,Jr as normalizeMinMax,Qr as normalizeSNV,Be as parseCsv,Or as parseCsvMulti,We as parseJcamp,He as parseJson,pn as parseSpc,vr as prefersReducedMotion,en as smoothSavitzkyGolay,Ee as snapToNearestSpectrum,Xr as useExport,ze as useKeyboardNavigation,Ur as usePeakPicking,Oe as useRegionSelect,Ne as useResizeObserver,Br as useSpectrumData,Se as useZoomPan};
15
+ `);ce(new Blob([u],{type:"text/csv"}),a)}},[]),r=le((n,a="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);ce(new Blob([l],{type:"application/json"}),a)},[]),o=le((n,a,s,i,l,c="spectrum.svg")=>{let u=Je(n,a,s,{width:i,height:l});qe(u,c)},[]);return{exportPng:e,exportSvg:o,exportCsv:t,exportJson:r}}import{useCallback as en,useState as tn}from"react";import{jsx as K,jsxs as Ut}from"react/jsx-runtime";var rn=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"}),nn=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"}),me=e=>({display:"block",width:"100%",padding:"6px 12px",border:"none",background:"transparent",color:e==="dark"?"#d1d5db":"#374151",fontSize:12,textAlign:"left",cursor:"pointer"});function on({theme:e,onExportPng:t,onExportSvg:r,onExportCsv:o,onExportJson:n}){let[a,s]=tn(!1),i=en(l=>{s(!1),l?.()},[]);return Ut("div",{style:{position:"relative",display:"inline-block"},children:[K("button",{type:"button",style:rn(e),onClick:()=>s(!a),"aria-label":"Export","aria-expanded":a,"aria-haspopup":"true",children:"Export"}),a&&Ut("div",{style:nn(e),role:"menu",children:[t&&K("button",{type:"button",role:"menuitem",style:me(e),onClick:()=>i(t),children:"PNG Image"}),r&&K("button",{type:"button",role:"menuitem",style:me(e),onClick:()=>i(r),children:"SVG Vector"}),o&&K("button",{type:"button",role:"menuitem",style:me(e),onClick:()=>i(o),children:"CSV Data"}),n&&K("button",{type:"button",role:"menuitem",style:me(e),onClick:()=>i(n),children:"JSON Data"})]})]})}function an(e){let t=e.length;if(t<3)return new Float64Array(e);let r=[0];for(let s=1;s<t;s++){for(;r.length>=2;){let i=r.length-1,l=r[i-1],c=r[i];if((s-l)*(e[c]-e[l])-(c-l)*(e[s]-e[l])>=0)r.pop();else break}r.push(s)}let o=new Float64Array(t),n=0;for(let s=0;s<t;s++){for(;n<r.length-1&&r[n+1]<=s;)n++;if(n>=r.length-1)o[s]=e[r[r.length-1]];else{let i=r[n],l=r[n+1],c=(s-i)/(l-i);o[s]=e[i]*(1-c)+e[l]*c}}let a=new Float64Array(t);for(let s=0;s<t;s++)a[s]=e[s]-o[s];return a}function sn(e){let t=e.length,r=new Float64Array(t),o=1/0,n=-1/0;for(let s=0;s<t;s++){let i=e[s];i<o&&(o=i),i>n&&(n=i)}let a=n-o;if(a===0)return r;for(let s=0;s<t;s++)r[s]=(e[s]-o)/a;return r}function ln(e,t){let r=Math.min(e.length,t.length);if(r<2)return new Float64Array(t);let o=0;for(let a=1;a<r;a++)o+=Math.abs(e[a]-e[a-1])*(Math.abs(t[a])+Math.abs(t[a-1]))*.5;if(o===0)return new Float64Array(t);let n=new Float64Array(r);for(let a=0;a<r;a++)n[a]=t[a]/o;return n}function cn(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 a=Math.sqrt(n/t);if(a===0)return new Float64Array(t);let s=new Float64Array(t);for(let i=0;i<t;i++)s[i]=(e[i]-o)/a;return s}function mn(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,a=un(o),s=new Float64Array(r);for(let i=0;i<n;i++)s[i]=e[i],s[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+=a[c+n]*e[i+c];s[i]=l}return s}function un(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 pn(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 fn(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 a=e[n]-e[n-1],s=e[n+1]-e[n],i=(a+s)/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 J=0;function dn(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-${++J}`,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 bn(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-${++J}`,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 gn(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-${++J}`,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 hn(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 a=o/r,s=n/r,i=0,l=0,c=0;for(let p=0;p<r;p++){let f=e.y[p]-a,m=t.y[p]-s;i+=f*m,l+=f*f,c+=m*m}let u=Math.sqrt(l*c);return u===0?0:i/u}function yn(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-${++J}`,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 xn(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 a=e.x[r-1]>e.x[0];for(let s=0;s<o;s++){let i=t[s],l=0,c=r-1;for(;l<c-1;){let d=l+c>>>1;a?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[s]=f;else{let d=(i-u)/(p-u);n[s]=f+d*(m-f)}}return{...e,id:`interp-${++J}`,x:new Float64Array(t),y:n}}var Sn=0,vn=1,wn=4,Cn=128,kn={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"},Rn={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 Mn(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 An(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 a=t.getUint8(2),s=t.getUint8(3),i=t.getUint32(4,!0),l=t.getFloat64(8,!0),c=t.getFloat64(16,!0),u=t.getUint32(24,!0),p=kn[a]??"Arbitrary",f=Rn[s]??"Arbitrary",m=new Uint8Array(e,30,130),d=Tn(m),g=(o&wn)!==0,y=(o&Cn)!==0,b=(o&vn)!==0,x=Mn(a,s),S=null;if(!y&&i>0){S=new Float64Array(i);let C=i>1?(c-l)/(i-1):0;for(let R=0;R<i;R++)S[R]=l+R*C}let k=[],h=512,v=null;if(y&&!g){v=new Float64Array(i);for(let C=0;C<i;C++)v[C]=t.getFloat32(h,!0),h+=4}let w=g?u:1;for(let C=0;C<w;C++){let R,A,E=i;if(g){if(h+32>e.byteLength)break;let L=t.getFloat32(h+4,!0),ue=t.getFloat32(h+8,!0);if(E=t.getUint32(h+12,!0)||i,h+=32,y){R=new Float64Array(E);for(let N=0;N<E&&!(h+4>e.byteLength);N++)R[N]=t.getFloat32(h,!0),h+=4}else if(S)R=S;else{R=new Float64Array(E);let N=E>1?(ue-L)/(E-1):0;for(let O=0;O<E;O++)R[O]=L+O*N}}else R=v??S??new Float64Array(0);if(A=new Float64Array(E),b)for(let L=0;L<E&&!(h+2>e.byteLength);L++)A[L]=t.getInt16(h,!0),h+=2;else for(let L=0;L<E&&!(h+4>e.byteLength);L++)A[L]=t.getFloat32(h,!0),h+=4;k.push({id:`spc-${++Sn}`,label:d||`SPC Spectrum ${C+1}`,x:R,y:A,xUnit:p,yUnit:f,type:x,meta:{format:"SPC",version:n===75?"new":"old",xType:a.toString(),yType:s.toString()}})}if(k.length===0)throw new Error("Invalid SPC file: no spectra found");return k}function Tn(e){let t=e.indexOf(0),r=t>=0?e.slice(0,t):e;return new TextDecoder("ascii").decode(r).trim()}export{Le as AnnotationLayer,Y as AxisLayer,Ee as Crosshair,pt as DARK_THEME,$e as DropZone,on as ExportMenu,Ar as KEYBOARD_SHORTCUTS,ut as LIGHT_THEME,Dt as LINE_DASH_PATTERNS,oe as Legend,zr as Minimap,Re as PeakMarkers,Ae as RegionSelector,xe as SPECTRUM_COLORS,Ur as SpectraView,W as SpectrumCanvas,Ne as StackedView,Ue as Toolbar,bn as addSpectra,an as baselineRubberBand,vt as binarySearchClosest,he as computeXExtent,H as computeYExtent,hn as correlationCoefficient,ye as createXScale,X as createYScale,pn as derivative1st,fn as derivative2nd,Xe as detectPeaks,dn as differenceSpectrum,qe as downloadSvg,Be as generateChartDescription,Je as generateSvg,P as getSpectrumColor,z as getThemeColors,xn as interpolateToGrid,Ce as lttbDownsample,ln as normalizeArea,sn as normalizeMinMax,cn as normalizeSNV,We as parseCsv,Wr as parseCsvMulti,Ge as parseJcamp,je as parseJson,An as parseSpc,Mr as prefersReducedMotion,yn as residualSpectrum,gn as scaleSpectrum,mn as smoothSavitzkyGolay,Pe as snapToNearestSpectrum,Qr as useExport,Ze as useKeyboardNavigation,Hr as usePeakPicking,ze as useRegionSelect,Ve as useResizeObserver,qr as useSpectrumData,we as useZoomPan};
16
16
  //# sourceMappingURL=index.js.map