spectraview 1.0.0 → 1.2.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
  *
@@ -741,6 +763,75 @@ interface LTTBPoint {
741
763
  */
742
764
  declare function lttbDownsample(x: Float64Array | number[], y: Float64Array | number[], startIdx: number, endIdx: number, xScale: (v: number) => number, yScale: (v: number) => number, targetCount: number): LTTBPoint[];
743
765
 
766
+ /**
767
+ * Spectral processing utilities.
768
+ *
769
+ * Pure functions for common spectral data transformations:
770
+ * - Baseline correction (rubber-band)
771
+ * - Normalization (min-max, area, SNV)
772
+ * - Smoothing (Savitzky-Golay)
773
+ * - Numerical derivatives (1st, 2nd)
774
+ *
775
+ * All functions return new arrays, never mutating inputs.
776
+ *
777
+ * @module processing
778
+ */
779
+ /**
780
+ * Rubber-band baseline correction.
781
+ *
782
+ * Computes the convex hull of the spectrum from below, then subtracts
783
+ * the interpolated baseline. This is a simple, robust method for
784
+ * removing broad fluorescence backgrounds.
785
+ *
786
+ * @param y - Input Y values
787
+ * @returns Baseline-corrected Y values
788
+ */
789
+ declare function baselineRubberBand(y: Float64Array | number[]): Float64Array;
790
+ /**
791
+ * Min-max normalization to [0, 1] range.
792
+ */
793
+ declare function normalizeMinMax(y: Float64Array | number[]): Float64Array;
794
+ /**
795
+ * Area normalization: divide by total area under the curve.
796
+ *
797
+ * Uses trapezoidal integration. The resulting spectrum has unit area.
798
+ */
799
+ declare function normalizeArea(x: Float64Array | number[], y: Float64Array | number[]): Float64Array;
800
+ /**
801
+ * Standard Normal Variate (SNV) normalization.
802
+ *
803
+ * Centers the spectrum by subtracting the mean, then divides by
804
+ * the standard deviation. Common in chemometrics.
805
+ */
806
+ declare function normalizeSNV(y: Float64Array | number[]): Float64Array;
807
+ /**
808
+ * Savitzky-Golay smoothing (polynomial order 2).
809
+ *
810
+ * Uses pre-computed convolution coefficients for common window sizes.
811
+ * Falls back to a simple moving average for unsupported window sizes.
812
+ *
813
+ * @param y - Input Y values
814
+ * @param windowSize - Must be odd and >= 3. Defaults to 5.
815
+ * @returns Smoothed Y values
816
+ */
817
+ declare function smoothSavitzkyGolay(y: Float64Array | number[], windowSize?: number): Float64Array;
818
+ /**
819
+ * First derivative using central differences.
820
+ *
821
+ * @param x - X values (for spacing)
822
+ * @param y - Y values
823
+ * @returns First derivative dy/dx
824
+ */
825
+ declare function derivative1st(x: Float64Array | number[], y: Float64Array | number[]): Float64Array;
826
+ /**
827
+ * Second derivative using central differences.
828
+ *
829
+ * @param x - X values (for spacing)
830
+ * @param y - Y values
831
+ * @returns Second derivative d²y/dx²
832
+ */
833
+ declare function derivative2nd(x: Float64Array | number[], y: Float64Array | number[]): Float64Array;
834
+
744
835
  /**
745
836
  * JCAMP-DX parser for spectral data.
746
837
  *
@@ -862,4 +953,4 @@ declare function createXScale(domain: [number, number], width: number, margin: M
862
953
  */
863
954
  declare function createYScale(domain: [number, number], height: number, margin: Margin): d3_scale.ScaleLinear<number, number, never>;
864
955
 
865
- 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, binarySearchClosest, computeXExtent, computeYExtent, createXScale, createYScale, detectPeaks, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, lttbDownsample, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, snapToNearestSpectrum, useExport, useKeyboardNavigation, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
956
+ 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, 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 };
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
  *
@@ -741,6 +763,75 @@ interface LTTBPoint {
741
763
  */
742
764
  declare function lttbDownsample(x: Float64Array | number[], y: Float64Array | number[], startIdx: number, endIdx: number, xScale: (v: number) => number, yScale: (v: number) => number, targetCount: number): LTTBPoint[];
743
765
 
766
+ /**
767
+ * Spectral processing utilities.
768
+ *
769
+ * Pure functions for common spectral data transformations:
770
+ * - Baseline correction (rubber-band)
771
+ * - Normalization (min-max, area, SNV)
772
+ * - Smoothing (Savitzky-Golay)
773
+ * - Numerical derivatives (1st, 2nd)
774
+ *
775
+ * All functions return new arrays, never mutating inputs.
776
+ *
777
+ * @module processing
778
+ */
779
+ /**
780
+ * Rubber-band baseline correction.
781
+ *
782
+ * Computes the convex hull of the spectrum from below, then subtracts
783
+ * the interpolated baseline. This is a simple, robust method for
784
+ * removing broad fluorescence backgrounds.
785
+ *
786
+ * @param y - Input Y values
787
+ * @returns Baseline-corrected Y values
788
+ */
789
+ declare function baselineRubberBand(y: Float64Array | number[]): Float64Array;
790
+ /**
791
+ * Min-max normalization to [0, 1] range.
792
+ */
793
+ declare function normalizeMinMax(y: Float64Array | number[]): Float64Array;
794
+ /**
795
+ * Area normalization: divide by total area under the curve.
796
+ *
797
+ * Uses trapezoidal integration. The resulting spectrum has unit area.
798
+ */
799
+ declare function normalizeArea(x: Float64Array | number[], y: Float64Array | number[]): Float64Array;
800
+ /**
801
+ * Standard Normal Variate (SNV) normalization.
802
+ *
803
+ * Centers the spectrum by subtracting the mean, then divides by
804
+ * the standard deviation. Common in chemometrics.
805
+ */
806
+ declare function normalizeSNV(y: Float64Array | number[]): Float64Array;
807
+ /**
808
+ * Savitzky-Golay smoothing (polynomial order 2).
809
+ *
810
+ * Uses pre-computed convolution coefficients for common window sizes.
811
+ * Falls back to a simple moving average for unsupported window sizes.
812
+ *
813
+ * @param y - Input Y values
814
+ * @param windowSize - Must be odd and >= 3. Defaults to 5.
815
+ * @returns Smoothed Y values
816
+ */
817
+ declare function smoothSavitzkyGolay(y: Float64Array | number[], windowSize?: number): Float64Array;
818
+ /**
819
+ * First derivative using central differences.
820
+ *
821
+ * @param x - X values (for spacing)
822
+ * @param y - Y values
823
+ * @returns First derivative dy/dx
824
+ */
825
+ declare function derivative1st(x: Float64Array | number[], y: Float64Array | number[]): Float64Array;
826
+ /**
827
+ * Second derivative using central differences.
828
+ *
829
+ * @param x - X values (for spacing)
830
+ * @param y - Y values
831
+ * @returns Second derivative d²y/dx²
832
+ */
833
+ declare function derivative2nd(x: Float64Array | number[], y: Float64Array | number[]): Float64Array;
834
+
744
835
  /**
745
836
  * JCAMP-DX parser for spectral data.
746
837
  *
@@ -862,4 +953,4 @@ declare function createXScale(domain: [number, number], width: number, margin: M
862
953
  */
863
954
  declare function createYScale(domain: [number, number], height: number, margin: Margin): d3_scale.ScaleLinear<number, number, never>;
864
955
 
865
- 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, binarySearchClosest, computeXExtent, computeYExtent, createXScale, createYScale, detectPeaks, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, lttbDownsample, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, snapToNearestSpectrum, useExport, useKeyboardNavigation, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
956
+ 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, 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 };
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  "use client";
2
- import{useCallback as vt,useId as wr,useMemo as U,useRef as Ct,useState as Ze}from"react";import{scaleLinear as st}from"d3-scale";import{extent as at}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,i]=at(o.x);n<t&&(t=n),i>r&&(r=i)}return isFinite(t)?[t,r]:[0,1]}function B(e){let t=1/0,r=-1/0;for(let i of e){if(i.visible===!1)continue;let[l,s]=at(i.y);l<t&&(t=l),s>r&&(r=s)}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,i=o?[e[1],e[0]]:e;return st().domain(i).range([0,n])}function X(e,t,r){let o=t-r.top-r.bottom;return st().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 A(e){return he[e%he.length]}function H(e){return e==="dark"?ct:lt}import{useCallback as ye,useEffect as _t,useMemo as mt,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 ut=1.5;function Se(e){let{plotWidth:t,plotHeight:r,xScale:o,yScale:n,scaleExtent:i=[1,50],enabled:l=!0,onViewChange:s}=e,c=te(null),m=te(null),u=te(s);u.current=s;let p=te(i);p.current=i;let[f,a]=Bt(xe),d=mt(()=>f.rescaleX(o.copy()),[f,o]),g=mt(()=>f.rescaleY(n.copy()),[f,n]);_t(()=>{let v=c.current;if(!v||!l)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(a(y),u.current){let w=y.rescaleX(o.copy()),x=y.rescaleY(n.copy());u.current(w.domain(),x.domain())}});return m.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,l,o,n]);let S=ye(()=>{!c.current||!m.current||z(c.current).transition().duration(300).call(m.current.transform,xe)},[]),b=ye(()=>{!c.current||!m.current||z(c.current).transition().duration(200).call(m.current.scaleBy,ut)},[]),C=ye(()=>{!c.current||!m.current||z(c.current).transition().duration(200).call(m.current.scaleBy,1/ut)},[]);return{zoomRef:c,state:{transform:f,isZoomed:f.k!==1||f.x!==0||f.y!==0},zoomedXScale:d,zoomedYScale:g,resetZoom:S,zoomIn:b,zoomOut:C}}import{forwardRef as Gt,useEffect as ft,useImperativeHandle as Jt,useRef as dt}from"react";function ve(e,t,r,o,n,i,l){let s=o-r;if(s<=l){let f=[];for(let a=r;a<o;a++)f.push({px:n(e[a]),py:i(t[a]),index:a});return f}let c=[];c.push({px:n(e[r]),py:i(t[r]),index:r});let m=l-2,u=(s-2)/m,p=r;for(let f=0;f<m;f++){let a=r+1+Math.floor(f*u),d=r+1+Math.min(Math.floor((f+1)*u),s-2),g=d,S=r+1+Math.min(Math.floor((f+2)*u),s-2),b,C;if(f===m-1)b=n(e[o-1]),C=i(t[o-1]);else{b=0,C=0;let w=S-g;for(let x=g;x<S;x++)b+=n(e[x]),C+=i(t[x]);w>0&&(b/=w,C/=w)}let v=n(e[p]),k=i(t[p]),h=-1,y=a;for(let w=a;w<d;w++){let x=n(e[w]),R=i(t[w]),E=Math.abs((v-b)*(R-k)-(v-x)*(C-k));E>h&&(h=E,y=w)}c.push({px:n(e[y]),py:i(t[y]),index:y}),p=y}return c.push({px:n(e[o-1]),py:i(t[o-1]),index:o-1}),c}var Ht=1.5,Wt={solid:[],dashed:[8,4],dotted:[2,2],"dash-dot":[8,4,2,4]},Yt=2e3;function jt(e,t,r){e.clearRect(0,0,t,r)}function Kt(e,t,r,o,n,i,l){let{highlighted:s=!1,opacity:c=1}=l??{},m=Math.min(t.x.length,t.y.length);if(m<2)return;let u=t.color??A(r),p=t.lineWidth??Ht,f=s?p+1:p,a=Wt[t.lineStyle??"solid"]??[],[d,g]=o.domain(),S=Math.min(d,g),b=Math.max(d,g),C=0,v=m;for(let h=0;h<m;h++)if(t.x[h]>=S||h<m-1&&t.x[h+1]>=S){C=Math.max(0,h-1);break}for(let h=m-1;h>=0;h--)if(t.x[h]<=b||h>0&&t.x[h-1]<=b){v=Math.min(m,h+2);break}let k=v-C;if(e.save(),e.beginPath(),e.strokeStyle=u,e.lineWidth=f,e.globalAlpha=c,e.lineJoin="round",e.setLineDash(a),k>Yt){let h=Math.max(Math.ceil(i*2),200),y=ve(t.x,t.y,C,v,o,n,h);if(y.length>0){e.moveTo(y[0].px,y[0].py);for(let w=1;w<y.length;w++)e.lineTo(y[w].px,y[w].py)}}else{let h=!1;for(let y=C;y<v;y++){let w=o(t.x[y]),x=n(t.y[y]);h?e.lineTo(w,x):(e.moveTo(w,x),h=!0)}}e.stroke(),e.restore()}function pt(e,t,r,o,n,i,l){jt(e,n,i),t.forEach((s,c)=>{s.visible!==!1&&Kt(e,s,c,r,o,n,{highlighted:s.id===l,opacity:l&&s.id!==l?.3:1})})}import{jsx as qt}from"react/jsx-runtime";var W=Gt(function({spectra:t,xScale:r,yScale:o,width:n,height:i,highlightedId:l},s){let c=dt(null),m=dt(1);return Jt(s,()=>c.current,[]),ft(()=>{let u=c.current;if(!u)return;let p=window.devicePixelRatio||1;m.current=p,u.width=n*p,u.height=i*p},[n,i]),ft(()=>{let u=c.current;if(!u)return;let p=u.getContext("2d");if(!p)return;let f=m.current;p.setTransform(f,0,0,f,0,0),pt(p,t,r,o,n,i,l)},[t,r,o,n,i,l]),qt("canvas",{ref:c,style:{width:n,height:i,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),l=(Math.max(r,o)-n)/(t-1);return Array.from({length:t},(s,c)=>n+c*l)}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 Y({xScale:e,yScale:t,width:r,height:o,xLabel:n,yLabel:i,showGrid:l=!0,colors:s}){let c=bt(e,8),m=bt(t,6);return V("g",{children:[l&&V("g",{children:[c.map(u=>D("line",{x1:e(u),x2:e(u),y1:0,y2:o,stroke:s.gridColor,strokeWidth:.5},`xgrid-${u}`)),m.map(u=>D("line",{x1:0,x2:r,y1:t(u),y2:t(u),stroke:s.gridColor,strokeWidth:.5},`ygrid-${u}`))]}),V("g",{transform:`translate(0, ${o})`,children:[D("line",{x1:0,x2:r,y1:0,y2:0,stroke:s.axisColor}),c.map(u=>V("g",{transform:`translate(${e(u)}, 0)`,children:[D("line",{y1:0,y2:6,stroke:s.axisColor}),D("text",{y:20,textAnchor:"middle",fill:s.tickColor,fontSize:11,fontFamily:"system-ui, sans-serif",children:gt(u)})]},`xtick-${u}`)),n&&D("text",{x:r/2,y:42,textAnchor:"middle",fill:s.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:n})]}),V("g",{children:[D("line",{x1:0,x2:0,y1:0,y2:o,stroke:s.axisColor}),m.map(u=>V("g",{transform:`translate(0, ${t(u)})`,children:[D("line",{x1:-6,x2:0,stroke:s.axisColor}),D("text",{x:-10,textAnchor:"end",dominantBaseline:"middle",fill:s.tickColor,fontSize:11,fontFamily:"system-ui, sans-serif",children:gt(u)})]},`ytick-${u}`)),i&&D("text",{transform:`translate(-50, ${o/2}) rotate(-90)`,textAnchor:"middle",fill:s.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:i})]})]})}import{jsx as Ce,jsxs as Qt}from"react/jsx-runtime";function we({peaks:e,xScale:t,yScale:r,colors:o,onPeakClick:n}){let[i,l]=t.domain(),s=Math.min(i,l),c=Math.max(i,l),m=e.filter(u=>u.x>=s&&u.x<=c);return Ce("g",{className:"spectraview-peaks",children:m.map((u,p)=>{let f=t(u.x),a=r(u.y);return Qt("g",{transform:`translate(${f}, ${a})`,style:{cursor:n?"pointer":"default"},onClick:()=>n?.(u),children:[Ce("polygon",{points:`0,-5 -5,${-5*2.5} 5,${-5*2.5}`,fill:o.labelColor,opacity:.8}),u.label&&Ce("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 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,i)=>{let l=t(n.xStart),s=t(n.xEnd),c=Math.min(l,s),m=Math.abs(s-l);return er("g",{children:[ke("rect",{x:c,y:0,width:m,height:r,fill:n.color??o.regionFill,stroke:o.regionStroke,strokeWidth:1}),n.label&&ke("text",{x:c+m/2,y:12,textAnchor:"middle",fill:o.labelColor,fontSize:10,fontFamily:"system-ui, sans-serif",children:n.label})]},`region-${i}`)})})}import{jsx as re,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:[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}),Te("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}),Te("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 j,jsxs as tr}from"react/jsx-runtime";function Me({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 i=t(n.x),l=r(n.y),[s,c]=n.offset??[0,-20],m=i+s,u=l+c,p=n.fontSize??11,f=n.color??o.tickColor,a=n.showAnchorLine!==!1;return tr("g",{children:[a&&j("line",{x1:i,y1:l,x2:m,y2:u,stroke:f,strokeWidth:.75,strokeDasharray:"3 2",opacity:.6}),j("circle",{cx:i,cy:l,r:2.5,fill:f,opacity:.8}),j("text",{x:m,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:m,y:u,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,i=r-1;for(;n<i-1;){let c=n+i>>>1,m=e[c];o?m<=t?n=c:i=c:m>=t?n=c:i=c}let l=Math.abs(e[n]-t),s=Math.abs(e[i]-t);return l<=s?n:i}function Le(e,t,r,o,n){let i=null;for(let l of e){if(l.visible===!1)continue;let s=Math.min(l.x.length,l.y.length);if(s<2)continue;let c=yt(l.x,t,s);if(c<0)continue;let m=l.x[c],u=l.y[c],p=Math.abs(o(m)-o(t)),f=Math.abs(n(u)-r),a=Math.sqrt(p*p+f*f);(!i||a<i.distance)&&(i={spectrumId:l.id,index:c,x:m,y:u,distance:a})}return i}import{memo as rr}from"react";import{jsx as Ae,jsxs as nr}from"react/jsx-runtime";var Pe=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}),or=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:i}){return nr("div",{style:or(i),className:"spectraview-toolbar",children:[Ae("button",{type:"button",style:Pe(i),onClick:t,title:"Zoom in","aria-label":"Zoom in",children:"+"}),Ae("button",{type:"button",style:Pe(i),onClick:r,title:"Zoom out","aria-label":"Zoom out",children:"\u2212"}),Ae("button",{type:"button",style:{...Pe(i),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 Ie,jsxs as cr}from"react/jsx-runtime";var sr=(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}),ar=(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}),oe=ir(function({spectra:t,theme:r,position:o,onToggleVisibility:n,onHighlight:i,highlightedId:l}){return t.length<=1?null:Ie("div",{className:"spectraview-legend",style:sr(r,o),role:"list","aria-label":"Spectrum legend",children:t.map((s,c)=>{let m=s.color??A(c),u=s.visible===!1,p=l===s.id;return cr("div",{role:"listitem",style:ar(r,u,p),onClick:()=>n?.(s.id),onMouseEnter:()=>i?.(s.id),onMouseLeave:()=>i?.(null),title:u?`Show ${s.label}`:`Hide ${s.label}`,children:[Ie("span",{style:lr(m,u)}),Ie("span",{style:{textDecoration:u?"line-through":"none",maxWidth:120,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:s.label})]},s.id)})})});import{useCallback as ne,useState as mr}from"react";import{jsx as ur,jsxs as pr}from"react/jsx-runtime";function Ue({enabled:e,theme:t,width:r,height:o,onDrop:n,children:i}){let[l,s]=mr(!1),c={current:0},m=ne(a=>{e&&(a.preventDefault(),c.current++,s(!0))},[e]),u=ne(a=>{e&&(a.preventDefault(),c.current--,c.current<=0&&(c.current=0,s(!1)))},[e]),p=ne(a=>{e&&(a.preventDefault(),a.dataTransfer.dropEffect="copy")},[e]),f=ne(a=>{if(!e)return;a.preventDefault(),c.current=0,s(!1);let d=Array.from(a.dataTransfer.files);d.length>0&&n?.(d)},[e,n]);return pr("div",{style:{position:"relative",width:r,height:o},onDragEnter:m,onDragLeave:u,onDragOver:p,onDrop:f,children:[i,l&&ur("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 Fe({spectra:e,xScale:t,plotWidth:r,plotHeight:o,margin:n,theme:i,showGrid:l,xLabel:s,yLabel:c}){let m=e.filter(d=>d.visible!==!1),u=fr(()=>H(i),[i]),p=m.length,f=(p-1)*xt,a=Math.max(40,Math.floor((o-f)/Math.max(p,1)));return Z("g",{className:"spectraview-stacked",children:m.map((d,g)=>{let S=g*(a+xt),b=B([d]),C=X(b,a+n.top+n.bottom,{...n,top:0,bottom:0}),v=d.color??A(g),k={...d,color:v};return dr("g",{transform:`translate(0, ${S})`,children:[Z("rect",{x:0,y:0,width:r,height:a,fill:"transparent",stroke:u.gridColor,strokeWidth:.5,rx:2}),Z(Y,{xScale:t,yScale:C,width:r,height:a,xLabel:g===p-1?s:"",yLabel:c,showGrid:l,colors:u}),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:a,children:Z(W,{spectra:[k],xScale:t,yScale:C,width:r,height:a})})]},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,i]=gr(null),l=br(null),s=$e(u=>{if(!t||!u.shiftKey)return;u.preventDefault();let p=u.currentTarget.getBoundingClientRect(),f=u.clientX-p.left,a=r.invert(f);l.current=a,i({xStart:a,xEnd:a})},[t,r]),c=$e(u=>{if(l.current===null)return;let p=u.currentTarget.getBoundingClientRect(),f=u.clientX-p.left,a=r.invert(f),d=l.current;i({xStart:Math.min(d,a),xEnd:Math.max(d,a)})},[r]),m=$e(()=>{if(l.current===null||!n)return;Math.abs(n.xEnd-n.xStart)>0&&o?.(n),l.current=null,i(null)},[n,o]);return{pendingRegion:n,handleMouseDown:s,handleMouseMove:c,handleMouseUp:m}}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(i=>{if(r.current&&(r.current.disconnect(),r.current=null),o.current=i,!i)return;let l=new ResizeObserver(m=>{let u=m[0];if(!u)return;let{width:p,height:f}=u.contentRect;t({width:Math.round(p),height:Math.round(f)})});l.observe(i),r.current=l;let{width:s,height:c}=i.getBoundingClientRect();t({width:Math.round(s),height:Math.round(c)})},[]);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(l=>{if(n)switch(l.key){case"+":case"=":l.preventDefault(),t();break;case"-":l.preventDefault(),r();break;case"Escape":l.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 Cr={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 Pr,jsx as T,jsxs as K}from"react/jsx-runtime";var kr={top:20,right:20,bottom:50,left:65},Rr=800,Tr=400;function Er(e){return{width:e.width??Rr,height:e.height??Tr,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 Mr(e,t,r){let o=e[0];return{xLabel:t??o?.xUnit??"x",yLabel:r??o?.yUnit??"y"}}function Lr(e){let{spectra:t,peaks:r=[],regions:o=[],annotations:n=[],onPeakClick:i,onViewChange:l,onCrosshairMove:s,onToggleVisibility:c,onFileDrop:m,onRegionSelect:u,canvasRef:p,snapCrosshair:f=!0}=e,{ref:a,size:d}=Ne(),S=`spectraview-clip-${wr().replace(/:/g,"")}`,b=U(()=>Er(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]),C=b.responsive&&d?d.width:b.width,{height:v,margin:k,reverseX:h,theme:y}=b,w=C-k.left-k.right,x=v-k.top-k.bottom,R=U(()=>H(y),[y]),E=U(()=>Mr(t,e.xLabel,e.yLabel),[t,e.xLabel,e.yLabel]),L=U(()=>be(t),[t]),P=U(()=>B(t),[t]),ce=U(()=>ge(L,C,k,h),[L,C,k,h]),O=U(()=>X(P,v,k),[P,v,k]),N=Ct(l);N.current=l;let Pt=U(()=>($,Q)=>{N.current?.({xDomain:$,yDomain:Q})},[]),{zoomRef:Ge,state:At,zoomedXScale:M,zoomedYScale:F,resetZoom:Je,zoomIn:qe,zoomOut:Qe}=Se({plotWidth:w,plotHeight:x,xScale:ce,yScale:O,onViewChange:l?Pt:void 0}),{pendingRegion:J,handleMouseDown:Dt,handleMouseMove:It,handleMouseUp:Ut}=Oe({enabled:b.enableRegionSelect,xScale:M,onRegionSelect:u}),[me,et]=Ze(null),[Ft,tt]=Ze(null),[$t,ue]=Ze(null),q=Ct(s);q.current=s;let rt=vt($=>{if(!b.showCrosshair)return;let Q=$.currentTarget.getBoundingClientRect(),nt=$.clientX-Q.left,fe=$.clientY-Q.top,ee=M.invert(nt),de=F.invert(fe);if(tt({px:nt,py:fe,dataX:ee,dataY:de}),f&&t.length>0){let I=Le(t,ee,fe,M,F);if(I&&I.distance<50){let it=t.findIndex(Vt=>Vt.id===I.spectrumId);ue({px:M(I.x),py:F(I.y),dataX:I.x,dataY:I.y,color:t[it]?.color??A(it)}),q.current?.(I.x,I.y)}else ue(null),q.current?.(ee,de)}else q.current?.(ee,de)},[M,F,b.showCrosshair,f,t]),ot=vt(()=>{tt(null),ue(null)},[]),Ot=ze({onZoomIn:qe,onZoomOut:Qe,onReset:Je}),Nt=U(()=>Ve(t.length,E.xLabel,E.yLabel),[t.length,E.xLabel,E.yLabel]),zt=b.displayMode==="stacked";if(t.length===0)return T("div",{ref:b.responsive?a:void 0,style:{width:b.responsive?"100%":C,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 K("div",{ref:b.responsive?a:void 0,style:{width:b.responsive?"100%":C,background:R.background,borderRadius:4,overflow:"hidden"},className:e.className,role:"img","aria-label":Nt,tabIndex:0,onKeyDown:Ot,children:[b.showToolbar&&T(De,{onZoomIn:qe,onZoomOut:Qe,onReset:Je,isZoomed:At.isZoomed,theme:y}),b.showLegend&&b.legendPosition==="top"&&T(oe,{spectra:t,theme:y,position:"top",onToggleVisibility:c,onHighlight:et,highlightedId:me}),T(Ue,{enabled:b.enableDragDrop,theme:y,width:C,height:v-pe,onDrop:m,children:zt?T("svg",{width:C,height:v-pe,style:{position:"absolute",top:0,left:0},children:K("g",{transform:`translate(${k.left}, ${k.top})`,children:[T(Fe,{spectra:t,xScale:M,plotWidth:w,plotHeight:x,margin:k,theme:y,showGrid:b.showGrid,xLabel:E.xLabel,yLabel:E.yLabel}),T("rect",{ref:Ge,x:0,y:0,width:w,height:x,fill:"transparent",style:{cursor:"grab"},onMouseMove:rt,onMouseLeave:ot})]})}):K(Pr,{children:[T("div",{style:{position:"absolute",top:k.top,left:k.left,width:w,height:x,overflow:"hidden"},children:T(W,{ref:p,spectra:t,xScale:M,yScale:F,width:w,height:x,highlightedId:me??void 0})}),T("svg",{width:C,height:v-pe,style:{position:"absolute",top:0,left:0},children:K("g",{transform:`translate(${k.left}, ${k.top})`,children:[T(Y,{xScale:M,yScale:F,width:w,height:x,xLabel:E.xLabel,yLabel:E.yLabel,showGrid:b.showGrid,colors:R}),T("defs",{children:T("clipPath",{id:S,children:T("rect",{x:0,y:0,width:w,height:x})})}),K("g",{clipPath:`url(#${S})`,children:[o.length>0&&T(Re,{regions:o,xScale:M,height:x,colors:R}),r.length>0&&T(we,{peaks:r,xScale:M,yScale:F,colors:R,onPeakClick:i})]}),n.length>0&&T(Me,{annotations:n,xScale:M,yScale:F,colors:R}),b.showCrosshair&&T(Ee,{position:Ft,width:w,height:x,colors:R,snapPoint:$t}),J&&T("rect",{x:M(J.xStart),y:0,width:Math.abs(M(J.xEnd)-M(J.xStart)),height:x,fill:R.regionFill,stroke:R.regionStroke,strokeWidth:1,pointerEvents:"none"}),T("rect",{ref:Ge,x:0,y:0,width:w,height:x,fill:"transparent",style:{cursor:b.showCrosshair?"crosshair":"grab"},onMouseDown:Dt,onMouseMove:$=>{rt($),It($)},onMouseUp:Ut,onMouseLeave:ot})]})})]})}),b.showLegend&&b.legendPosition==="bottom"&&T(oe,{spectra:t,theme:y,position:"bottom",onToggleVisibility:c,onHighlight:et,highlightedId:me})]})}import{useMemo as Ur}from"react";function _e(e,t,r={}){let{prominence:o=.01,minDistance:n=5,maxPeaks:i}=r;if(e.length<3||t.length<3)return[];let l=1/0,s=-1/0;for(let a=0;a<t.length;a++)t[a]<l&&(l=t[a]),t[a]>s&&(s=t[a]);let c=s-l;if(c===0)return[];let m=o*c,u=[];for(let a=1;a<t.length-1;a++)if(t[a]>t[a-1]&&t[a]>t[a+1]){let d=Ar(t,a),g=Dr(t,a),S=t[a]-Math.max(d,g);S>=m&&u.push({index:a,prom:S})}u.sort((a,d)=>d.prom-a.prom);let p=[];for(let a of u)p.some(g=>Math.abs(g.index-a.index)<n)||p.push(a);return(i?p.slice(0,i):p).map(a=>({x:e[a.index],y:t[a.index],label:Ir(e[a.index])})).sort((a,d)=>a.x-d.x)}function Ar(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 Ir(e){return Math.round(e).toString()}function Fr(e,t={}){let{enabled:r=!0,spectrumIds:o,prominence:n,minDistance:i,maxPeaks:l}=t;return Ur(()=>{if(!r)return[];let s=o?e.filter(m=>o.includes(m.id)):e,c=[];for(let m of s){if(m.visible===!1)continue;let u=_e(m.x,m.y,{prominence:n,minDistance:i,maxPeaks:l});for(let p of u)c.push({...p,spectrumId:m.id})}return c},[e,r,o,n,i,l])}import{useCallback as _,useState as Ye}from"react";var wt=0,$r=[" ",",",";"," "];function kt(e){let t=e.trim().split(/\r?\n/).slice(0,5),r=",",o=0;for(let n of $r){let i=t.map(s=>s.split(n).length-1),l=Math.min(...i);l>0&&l>=o&&(i.every(c=>c===i[0])||l>o)&&(o=l,r=n)}return r}function Be(e,t={}){let{xColumn:r=0,yColumn:o=1,hasHeader:n=!0,label:i="CSV Spectrum"}=t,l=t.delimiter??kt(e),s=e.trim().split(/\r?\n/);if(s.length<2)throw new Error("CSV file must contain at least 2 lines");let c=i,m=0;if(n){let f=s[0].split(l).map(a=>a.trim());!t.label&&f[o]&&(c=f[o]),m=1}let u=[],p=[];for(let f=m;f<s.length;f++){let a=s[f].trim();if(a===""||a.startsWith("#"))continue;let d=a.split(l),g=parseFloat(d[r]),S=parseFloat(d[o]);!isNaN(g)&&!isNaN(S)&&(u.push(g),p.push(S))}if(u.length===0)throw new Error("No valid numeric data found in CSV");return{id:`csv-${++wt}`,label:c,x:new Float64Array(u),y:new Float64Array(p)}}function Or(e,t={}){let{hasHeader:r=!0,label:o}=t,n=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 s=i[r?1:0].split(n).length;if(s<2)throw new Error("CSV must have at least 2 columns (x + y)");let c,m=0;r&&(c=i[0].split(n).map(a=>a.trim()),m=1);let u=[],p=Array.from({length:s-1},()=>[]);for(let a=m;a<i.length;a++){let d=i[a].trim();if(d===""||d.startsWith("#"))continue;let g=d.split(n),S=parseFloat(g[0]);if(!isNaN(S)){u.push(S);for(let b=1;b<s;b++){let C=parseFloat(g[b]);p[b-1].push(isNaN(C)?0:C)}}}let f=new Float64Array(u);return p.map((a,d)=>({id:`csv-${++wt}`,label:o??c?.[d+1]??`Spectrum ${d+1}`,x:f,y:new Float64Array(a)}))}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 Tt=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 Et(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 i=o.spectra?.[0]?.data?.[0];if(!i)throw new Error(`JCAMP block ${n}: no spectral data found`);return{id:`jcamp-${++Tt}`,label:o.info?.TITLE??`Spectrum ${n+1}`,x:new Float64Array(i.x),y:new Float64Array(i.y),xUnit:o.info?.XUNITS??"cm\u207B\xB9",yUnit:o.info?.YUNITS??"Absorbance",type:Et(o.info),meta:o.info}})}function Zr(e){let t=e.split(/\r?\n/),r={},o=[],n=[],i=!1;for(let l of t){let s=l.trim();if(s.startsWith("##")){let c=s.match(/^##(.+?)=\s*(.*)$/);if(c){let m=c[1].trim().toUpperCase(),u=c[2].trim();if(m==="XYDATA"||m==="XYPOINTS"){i=!0;continue}if(m==="END"){i=!1;continue}r[m]=u}continue}if(i&&s!==""){let c=s.split(/[\s,]+/).map(Number);if(c.length>=2&&!c.some(isNaN)){let m=c[0],u=parseFloat(r.FIRSTX??"0"),p=parseFloat(r.LASTX??"0"),f=parseInt(r.NPOINTS??"0",10);if(f>0&&c.length===2)o.push(c[0]),n.push(c[1]);else if(c.length>1){let a=f>1?(p-u)/(f-1):0;for(let d=1;d<c.length;d++)o.push(m+(d-1)*a),n.push(c[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-${++Tt}`,label:r.TITLE??"JCAMP Spectrum",x:new Float64Array(o),y:new Float64Array(n),xUnit:r.XUNITS??"cm\u207B\xB9",yUnit:r.YUNITS??"Absorbance",type:Et(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]=Ye(e),[o,n]=Ye(!1),[i,l]=Ye(null),s=_(async(a,d)=>{n(!0),l(null);try{let g;switch(d){case"jcamp":g=await We(a);break;case"csv":g=[Be(a)];break;case"json":g=He(a);break}r(S=>[...S,...g])}catch(g){let S=g instanceof Error?g.message:"Failed to parse file";l(S)}finally{n(!1)}},[]),c=_(async a=>{let d=_r(a.name);if(!d){l(`Unsupported file format: ${a.name}`);return}let g=await a.text();await s(g,d)},[s]),m=_(a=>{r(d=>[...d,a])},[]),u=_(a=>{r(d=>d.filter(g=>g.id!==a))},[]),p=_(a=>{r(d=>d.map(g=>g.id===a?{...g,visible:g.visible===!1}:g))},[]),f=_(()=>{r([]),l(null)},[]);return{spectra:t,loading:o,error:i,loadFile:c,loadText:s,addSpectrum:m,removeSpectrum:u,toggleVisibility:p,clear:f}}import{useCallback as se}from"react";var Mt={solid:"",dashed:"8 4",dotted:"2 2","dash-dot":"8 4 2 4"};function je(e,t,r,o){let{width:n,height:i,background:l="#ffffff",title:s}=o,c=e.filter(m=>m.visible!==!1).map((m,u)=>{let p=m.color??A(u),f=m.lineStyle??"solid",a=m.lineWidth??1.5,d=Mt[f]??"",g=Math.min(m.x.length,m.y.length);if(g<2)return"";let S=[];for(let b=0;b<g;b++){let C=t(m.x[b]).toFixed(2),v=r(m.y[b]).toFixed(2);S.push(`${b===0?"M":"L"}${C},${v}`)}return`<path d="${S.join(" ")}" fill="none" stroke="${p}" stroke-width="${a}"${d?` stroke-dasharray="${d}"`:""}/>
3
- <!-- ${m.label} -->`}).filter(Boolean).join(`
2
+ import{useCallback as kt,useId as Tr,useMemo as I,useRef as wt,useState as _e}from"react";import{scaleLinear as lt}from"d3-scale";import{extent as ct}from"d3-array";var Xt=.05;function ge(e){let t=1/0,r=-1/0;for(let o of e){if(o.visible===!1)continue;let[n,a]=ct(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]=ct(a.y);s<t&&(t=s),i>r&&(r=i)}if(!isFinite(t))return[0,1];let n=(r-t)*Xt;return[t-n,r+n]}function he(e,t,r,o){let n=t-r.left-r.right,a=o?[e[1],e[0]]:e;return lt().domain(a).range([0,n])}function X(e,t,r){let o=t-r.top-r.bottom;return lt().domain(e).range([o,0])}var ye=["#2563eb","#dc2626","#16a34a","#9333ea","#ea580c","#0891b2","#be185d","#854d0e","#4f46e5","#65a30d"],mt={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"},ut={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 ye[e%ye.length]}function z(e){return e==="dark"?ut:mt}import{useCallback as xe,useEffect as Wt,useMemo as pt,useRef as te,useState as Yt}from"react";import{zoom as jt,zoomIdentity as Se}from"d3-zoom";import{select as V}from"d3-selection";import"d3-transition";var ft=1.5;function ve(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),u=te(i);u.current=i;let p=te(a);p.current=a;let[f,m]=Yt(Se),d=pt(()=>f.rescaleX(o.copy()),[f,o]),g=pt(()=>f.rescaleY(n.copy()),[f,n]);Wt(()=>{let S=l.current;if(!S||!s)return;let w=jt().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 C=v.rescaleX(o.copy()),k=v.rescaleY(n.copy());u.current(C.domain(),k.domain())}});return c.current=w,V(S).call(w),V(S).on("dblclick.zoom",()=>{V(S).transition().duration(300).call(w.transform,Se)}),()=>{V(S).on(".zoom",null)}},[t,r,s,o,n]);let y=xe(()=>{!l.current||!c.current||V(l.current).transition().duration(300).call(c.current.transform,Se)},[]),b=xe(()=>{!l.current||!c.current||V(l.current).transition().duration(200).call(c.current.scaleBy,ft)},[]),x=xe(()=>{!l.current||!c.current||V(l.current).transition().duration(200).call(c.current.scaleBy,1/ft)},[]);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 er,useEffect as bt,useImperativeHandle as tr,useRef as gt}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 C=y-g;for(let k=g;k<y;k++)b+=n(e[k]),x+=a(t[k]);C>0&&(b/=C,x/=C)}let S=n(e[p]),w=a(t[p]),h=-1,v=m;for(let C=m;C<d;C++){let k=n(e[C]),R=a(t[C]),T=Math.abs((S-b)*(R-w)-(S-k)*(x-w));T>h&&(h=T,v=C)}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 Gt=1.5,Kt={solid:[],dashed:[8,4],dotted:[2,2],"dash-dot":[8,4,2,4]},Jt=2e3;function qt(e,t,r){e.clearRect(0,0,t,r)}function Qt(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??Gt,f=i?p+1:p,m=Kt[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 w=S-x;if(e.save(),e.beginPath(),e.strokeStyle=u,e.lineWidth=f,e.globalAlpha=l,e.lineJoin="round",e.setLineDash(m),w>Jt){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 C=1;C<v.length;C++)e.lineTo(v[C].px,v[C].py)}}else{let h=!1;for(let v=x;v<S;v++){let C=o(t.x[v]),k=n(t.y[v]);h?e.lineTo(C,k):(e.moveTo(C,k),h=!0)}}e.stroke(),e.restore()}function dt(e,t,r,o,n,a,s){qt(e,n,a),t.forEach((i,l)=>{i.visible!==!1&&Qt(e,i,l,r,o,n,{highlighted:i.id===s,opacity:s&&i.id!==s?.3:1})})}import{jsx as rr}from"react/jsx-runtime";var W=er(function({spectra:t,xScale:r,yScale:o,width:n,height:a,highlightedId:s},i){let l=gt(null),c=gt(1);return tr(i,()=>l.current,[]),bt(()=>{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]),bt(()=>{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),dt(p,t,r,o,n,a,s)},[t,r,o,n,a,s]),rr("canvas",{ref:l,style:{width:n,height:a,position:"absolute",top:0,left:0,pointerEvents:"none"}})});import{jsx as D,jsxs as Z}from"react/jsx-runtime";function ht(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 yt(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=ht(e,8),c=ht(t,6);return Z("g",{children:[s&&Z("g",{children:[l.map(u=>D("line",{x1:e(u),x2:e(u),y1:0,y2:o,stroke:i.gridColor,strokeWidth:.5},`xgrid-${u}`)),c.map(u=>D("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:[D("line",{x1:0,x2:r,y1:0,y2:0,stroke:i.axisColor}),l.map(u=>Z("g",{transform:`translate(${e(u)}, 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:yt(u)})]},`xtick-${u}`)),n&&D("text",{x:r/2,y:42,textAnchor:"middle",fill:i.labelColor,fontSize:13,fontFamily:"system-ui, sans-serif",children:n})]}),Z("g",{children:[D("line",{x1:0,x2:0,y1:0,y2:o,stroke:i.axisColor}),c.map(u=>Z("g",{transform:`translate(0, ${t(u)})`,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:yt(u)})]},`ytick-${u}`)),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 ke,jsxs as nr}from"react/jsx-runtime";function we({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 nr("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 Re,jsxs as or}from"react/jsx-runtime";function Me({regions:e,xScale:t,height:r,colors:o}){return Re("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 or("g",{children:[Re("rect",{x:l,y:0,width:c,height:r,fill:n.color??o.regionFill,stroke:o.regionStroke,strokeWidth:1}),n.label&&Re("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 Te}from"react/jsx-runtime";function Ae({position:e,width:t,height:r,colors:o,snapPoint:n}){return e?Te("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}),Te("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}),Te("text",{x:5,y:0,fill:o.tooltipText,fontSize:10,fontFamily:"monospace",children:[xt(n?.dataX??e.dataX),","," ",xt(n?.dataY??e.dataY)]})]})]}):null}function xt(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 ir}from"react/jsx-runtime";function Ee({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 ir("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 St(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 Le(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=St(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 ar}from"react";import{jsx as De,jsxs as lr}from"react/jsx-runtime";var Pe=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}),sr=e=>({display:"flex",gap:4,padding:"4px 0",borderBottom:`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`}),Fe=ar(function({onZoomIn:t,onZoomOut:r,onReset:o,isZoomed:n,theme:a}){return lr("div",{style:sr(a),className:"spectraview-toolbar",children:[De("button",{type:"button",style:Pe(a),onClick:t,title:"Zoom in","aria-label":"Zoom in",children:"+"}),De("button",{type:"button",style:Pe(a),onClick:r,title:"Zoom out","aria-label":"Zoom out",children:"\u2212"}),De("button",{type:"button",style:{...Pe(a),opacity:n?1:.4},onClick:o,disabled:!n,title:"Reset zoom","aria-label":"Reset zoom",children:"\u21BA"})]})});import{memo as cr}from"react";import{jsx as Ie,jsxs as fr}from"react/jsx-runtime";var mr=(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}),ur=(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"}),pr=(e,t)=>({width:12,height:3,borderRadius:1,background:e,opacity:t?.4:1,flexShrink:0}),ne=cr(function({spectra:t,theme:r,position:o,onToggleVisibility:n,onHighlight:a,highlightedId:s}){return t.length<=1?null:Ie("div",{className:"spectraview-legend",style:mr(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 fr("div",{role:"listitem",style:ur(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:pr(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 oe,useState as dr}from"react";import{jsx as br,jsxs as gr}from"react/jsx-runtime";function Ue({enabled:e,theme:t,width:r,height:o,onDrop:n,children:a}){let[s,i]=dr(!1),l={current:0},c=oe(m=>{e&&(m.preventDefault(),l.current++,i(!0))},[e]),u=oe(m=>{e&&(m.preventDefault(),l.current--,l.current<=0&&(l.current=0,i(!1)))},[e]),p=oe(m=>{e&&(m.preventDefault(),m.dataTransfer.dropEffect="copy")},[e]),f=oe(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 gr("div",{style:{position:"relative",width:r,height:o},onDragEnter:c,onDragLeave:u,onDragOver:p,onDrop:f,children:[a,s&&br("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 hr}from"react";import{jsx as _,jsxs as yr}from"react/jsx-runtime";var vt=8;function $e({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=hr(()=>z(a),[a]),p=c.length,f=(p-1)*vt,m=Math.max(40,Math.floor((o-f)/Math.max(p,1)));return _("g",{className:"spectraview-stacked",children:c.map((d,g)=>{let y=g*(m+vt),b=H([d]),x=X(b,m+n.top+n.bottom,{...n,top:0,bottom:0}),S=d.color??P(g),w={...d,color:S};return yr("g",{transform:`translate(0, ${y})`,children:[_("rect",{x:0,y:0,width:r,height:m,fill:"transparent",stroke:u.gridColor,strokeWidth:.5,rx:2}),_(Y,{xScale:t,yScale:x,width:r,height:m,xLabel:g===p-1?i:"",yLabel:l,showGrid:s,colors:u}),_("text",{x:4,y:14,fill:S,fontSize:11,fontFamily:"system-ui, sans-serif",fontWeight:500,children:d.label}),_("foreignObject",{x:0,y:0,width:r,height:m,children:_(W,{spectra:[w],xScale:t,yScale:x,width:r,height:m})})]},d.id)})})}import{useCallback as Ne,useRef as xr,useState as Sr}from"react";function Oe(e){let{enabled:t,xScale:r,onRegionSelect:o}=e,[n,a]=Sr(null),s=xr(null),i=Ne(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=Ne(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=Ne(()=>{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 vr,useEffect as Cr,useRef as Ct,useState as kr}from"react";function ze(){let[e,t]=kr(null),r=Ct(null),o=Ct(null),n=vr(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 wr}from"react";function Ve(e){let{onZoomIn:t,onZoomOut:r,onReset:o,enabled:n=!0}=e;return wr(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 Rr(){return typeof window>"u"?!1:window.matchMedia("(prefers-reduced-motion: reduce)").matches}function Ze(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 Mr={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 Ar={top:20,right:20,bottom:50,left:65},Er=800,Lr=400;function Pr(e){return{width:e.width??Er,height:e.height??Lr,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:{...Ar,...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 Fr(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}=ze(),y=`spectraview-clip-${Tr().replace(/:/g,"")}`,b=I(()=>Pr(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:w,reverseX:h,theme:v}=b,C=x-w.left-w.right,k=S-w.top-w.bottom,R=I(()=>z(v),[v]),T=I(()=>Dr(t,e.xLabel,e.yLabel),[t,e.xLabel,e.yLabel]),E=I(()=>ge(t),[t]),L=I(()=>H(t),[t]),me=I(()=>he(E,x,w,h),[E,x,w,h]),N=I(()=>X(L,S,w),[L,S,w]),O=wt(s);O.current=s;let It=I(()=>($,Q)=>{O.current?.({xDomain:$,yDomain:Q})},[]),{zoomRef:qe,state:Ut,zoomedXScale:A,zoomedYScale:U,resetZoom:Qe,zoomIn:et,zoomOut:tt}=ve({plotWidth:C,plotHeight:k,xScale:me,yScale:N,onViewChange:s?It:void 0}),{pendingRegion:J,handleMouseDown:$t,handleMouseMove:Nt,handleMouseUp:Ot}=Oe({enabled:b.enableRegionSelect,xScale:A,onRegionSelect:u}),[ue,rt]=_e(null),[zt,nt]=_e(null),[Vt,pe]=_e(null),q=wt(i);q.current=i;let ot=kt($=>{if(!b.showCrosshair)return;let Q=$.currentTarget.getBoundingClientRect(),at=$.clientX-Q.left,de=$.clientY-Q.top,ee=A.invert(at),be=U.invert(de);if(nt({px:at,py:de,dataX:ee,dataY:be}),f&&t.length>0){let F=Le(t,ee,de,A,U);if(F&&F.distance<50){let st=t.findIndex(Ht=>Ht.id===F.spectrumId);pe({px:A(F.x),py:U(F.y),dataX:F.x,dataY:F.y,color:t[st]?.color??P(st)}),q.current?.(F.x,F.y)}else pe(null),q.current?.(ee,be)}else q.current?.(ee,be)},[A,U,b.showCrosshair,f,t]),it=kt(()=>{nt(null),pe(null)},[]),Zt=Ve({onZoomIn:et,onZoomOut:tt,onReset:Qe}),_t=I(()=>Ze(t.length,T.xLabel,T.yLabel),[t.length,T.xLabel,T.yLabel]),Bt=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 fe=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:Zt,children:[b.showToolbar&&M(Fe,{onZoomIn:et,onZoomOut:tt,onReset:Qe,isZoomed:Ut.isZoomed,theme:v}),b.showLegend&&b.legendPosition==="top"&&M(ne,{spectra:t,theme:v,position:"top",onToggleVisibility:l,onHighlight:rt,highlightedId:ue}),M(Ue,{enabled:b.enableDragDrop,theme:v,width:x,height:S-fe,onDrop:c,children:Bt?M("svg",{width:x,height:S-fe,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${w.left}, ${w.top})`,children:[M($e,{spectra:t,xScale:A,plotWidth:C,plotHeight:k,margin:w,theme:v,showGrid:b.showGrid,xLabel:T.xLabel,yLabel:T.yLabel}),M("rect",{ref:qe,x:0,y:0,width:C,height:k,fill:"transparent",style:{cursor:"grab"},onMouseMove:ot,onMouseLeave:it})]})}):G(Ir,{children:[M("div",{style:{position:"absolute",top:w.top,left:w.left,width:C,height:k,overflow:"hidden"},children:M(W,{ref:p,spectra:t,xScale:A,yScale:U,width:C,height:k,highlightedId:ue??void 0})}),M("svg",{width:x,height:S-fe,style:{position:"absolute",top:0,left:0},children:G("g",{transform:`translate(${w.left}, ${w.top})`,children:[M(Y,{xScale:A,yScale:U,width:C,height:k,xLabel:T.xLabel,yLabel:T.yLabel,showGrid:b.showGrid,colors:R}),M("defs",{children:M("clipPath",{id:y,children:M("rect",{x:0,y:0,width:C,height:k})})}),G("g",{clipPath:`url(#${y})`,children:[o.length>0&&M(Me,{regions:o,xScale:A,height:k,colors:R}),r.length>0&&M(we,{peaks:r,xScale:A,yScale:U,colors:R,onPeakClick:a})]}),n.length>0&&M(Ee,{annotations:n,xScale:A,yScale:U,colors:R}),b.showCrosshair&&M(Ae,{position:zt,width:C,height:k,colors:R,snapPoint:Vt}),J&&M("rect",{x:A(J.xStart),y:0,width:Math.abs(A(J.xEnd)-A(J.xStart)),height:k,fill:R.regionFill,stroke:R.regionStroke,strokeWidth:1,pointerEvents:"none"}),M("rect",{ref:qe,x:0,y:0,width:C,height:k,fill:"transparent",style:{cursor:b.showCrosshair?"crosshair":"grab"},onMouseDown:$t,onMouseMove:$=>{ot($),Nt($)},onMouseUp:Ot,onMouseLeave:it})]})})]})}),b.showLegend&&b.legendPosition==="bottom"&&M(ne,{spectra:t,theme:v,position:"bottom",onToggleVisibility:l,onHighlight:rt,highlightedId:ue})]})}import{memo as Ur,useEffect as $r,useRef as Nr,useMemo as Be}from"react";import{scaleLinear as Rt}from"d3-scale";import{jsx as ie,jsxs as Mt}from"react/jsx-runtime";var Or=Ur(function({spectra:t,xExtent:r,yExtent:o,visibleXDomain:n,width:a=200,height:s=50,theme:i="light",isZoomed:l=!1}){let c=Nr(null),u=Be(()=>z(i),[i]),p=Be(()=>Rt().domain(r).range([0,a]),[r,a]),f=Be(()=>Rt().domain(o).range([s-2,2]),[o,s]);$r(()=>{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 w=x.color??P(b);y.beginPath(),y.strokeStyle=w,y.lineWidth=1,y.globalAlpha=.7;let h=Math.max(1,Math.floor(S/a)),v=!1;for(let C=0;C<S;C+=h){let k=p(x.x[C]),R=f(x.y[C]);v?y.lineTo(k,R):(y.moveTo(k,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 Mt("div",{className:"spectraview-minimap",style:{position:"relative",width:a,height:s,border:`1px solid ${u.gridColor}`,borderRadius:3,overflow:"hidden",background:u.background},children:[ie("canvas",{ref:c,width:a,height:s,style:{position:"absolute",top:0,left:0}}),l&&Mt("svg",{width:a,height:s,style:{position:"absolute",top:0,left:0},children:[ie("rect",{x:0,y:0,width:m,height:s,fill:u.background,opacity:.6}),ie("rect",{x:m+g,y:0,width:a-m-g,height:s,fill:u.background,opacity:.6}),ie("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 He(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=zr(t,m),g=Vr(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:Zr(e[m.index])})).sort((m,d)=>m.x-d.x)}function zr(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 Vr(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 Zr(e){return Math.round(e).toString()}function Br(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=He(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 B,useState as Ge}from"react";var Tt=0,Hr=[" ",",",";"," "];function At(e){let t=e.trim().split(/\r?\n/).slice(0,5),r=",",o=0;for(let n of Hr){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 Xe(e,t={}){let{xColumn:r=0,yColumn:o=1,hasHeader:n=!0,label:a="CSV Spectrum"}=t,s=t.delimiter??At(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 Xr(e,t={}){let{hasHeader:r=!0,label:o}=t,n=t.delimiter??At(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 Wr=0;function Ye(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)=>We(r,o));if(typeof t=="object"&&t!==null){let r=t;return Array.isArray(r.spectra)?r.spectra.map((o,n)=>We(o,n)):[We(t,0)]}throw new Error("Invalid JSON structure: expected an object or array")}function We(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-${++Wr}`,label:n,x:new Float64Array(r),y:new Float64Array(o),xUnit:e.xUnit,yUnit:e.yUnit,type:e.type,meta:e.meta}}var Lt=0,ae=null,Et=!1;async function Yr(){if(Et)return ae;Et=!0;try{ae=await import("jcampconverter")}catch{ae=null}return ae}function Pt(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 je(e){let t=await Yr();return t?jr(e,t):[Gr(e)]}function jr(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-${++Lt}`,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:Pt(o.info),meta:o.info}})}function Gr(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-${++Lt}`,label:r.TITLE??"JCAMP Spectrum",x:new Float64Array(o),y:new Float64Array(n),xUnit:r.XUNITS??"cm\u207B\xB9",yUnit:r.YUNITS??"Absorbance",type:Pt(r),meta:r}}function Kr(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 Jr(e=[]){let[t,r]=Ge(e),[o,n]=Ge(!1),[a,s]=Ge(null),i=B(async(m,d)=>{n(!0),s(null);try{let g;switch(d){case"jcamp":g=await je(m);break;case"csv":g=[Xe(m)];break;case"json":g=Ye(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=B(async m=>{let d=Kr(m.name);if(!d){s(`Unsupported file format: ${m.name}`);return}let g=await m.text();await i(g,d)},[i]),c=B(m=>{r(d=>[...d,m])},[]),u=B(m=>{r(d=>d.filter(g=>g.id!==m))},[]),p=B(m=>{r(d=>d.map(g=>g.id===m?{...g,visible:g.visible===!1}:g))},[]),f=B(()=>{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 se}from"react";var Dt={solid:"",dashed:"8 4",dotted:"2 2","dash-dot":"8 4 2 4"};function Ke(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
+ <!-- ${c.label} -->`}).filter(Boolean).join(`
4
4
  `);return`<?xml version="1.0" encoding="UTF-8"?>
5
- <svg xmlns="http://www.w3.org/2000/svg" width="${n}" height="${i}" viewBox="0 0 ${n} ${i}">
6
- <rect width="${n}" height="${i}" fill="${l}"/>
7
- ${s?`<text x="${n/2}" y="20" text-anchor="middle" font-family="system-ui" font-size="14">${s}</text>`:""}
5
+ <svg xmlns="http://www.w3.org/2000/svg" width="${n}" height="${a}" viewBox="0 0 ${n} ${a}">
6
+ <rect width="${n}" height="${a}" fill="${s}"/>
7
+ ${i?`<text x="${n/2}" y="20" text-anchor="middle" font-family="system-ui" font-size="14">${i}</text>`:""}
8
8
  <g>
9
- ${c}
9
+ ${l}
10
10
  </g>
11
- </svg>`}function Ke(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 ae(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=se((n,i="spectrum.png")=>{n.toBlob(l=>{l&&ae(l,i)},"image/png")},[]),t=se((n,i="spectra.csv")=>{let l=n.filter(s=>s.visible!==!1);if(l.length!==0)if(l.length===1){let s=l[0],c=`${s.xUnit??"x"},${s.yUnit??"y"}
12
- `,m=Array.from(s.x).map((p,f)=>`${p},${s.y[f]}`),u=c+m.join(`
13
- `);ae(new Blob([u],{type:"text/csv"}),i)}else{let s=Math.max(...l.map(p=>p.x.length)),c=l.map(p=>`${p.label}_x,${p.label}_y`).join(","),m=[];for(let p=0;p<s;p++){let f=l.map(a=>p<a.x.length?`${a.x[p]},${a.y[p]}`:",");m.push(f.join(","))}let u=c+`
14
- `+m.join(`
15
- `);ae(new Blob([u],{type:"text/csv"}),i)}},[]),r=se((n,i="spectra.json")=>{let s=n.filter(m=>m.visible!==!1).map(m=>({label:m.label,x:Array.from(m.x),y:Array.from(m.y),xUnit:m.xUnit,yUnit:m.yUnit,type:m.type})),c=JSON.stringify(s,null,2);ae(new Blob([c],{type:"application/json"}),i)},[]),o=se((n,i,l,s,c,m="spectrum.svg")=>{let u=je(n,i,l,{width:s,height:c});Ke(u,m)},[]);return{exportPng:e,exportSvg:o,exportCsv:t,exportJson:r}}import{useCallback as Hr,useState as Wr}from"react";import{jsx as G,jsxs as Lt}from"react/jsx-runtime";var Yr=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"}),jr=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 Kr({theme:e,onExportPng:t,onExportSvg:r,onExportCsv:o,onExportJson:n}){let[i,l]=Wr(!1),s=Hr(c=>{l(!1),c?.()},[]);return Lt("div",{style:{position:"relative",display:"inline-block"},children:[G("button",{type:"button",style:Yr(e),onClick:()=>l(!i),"aria-label":"Export","aria-expanded":i,"aria-haspopup":"true",children:"Export"}),i&&Lt("div",{style:jr(e),role:"menu",children:[t&&G("button",{type:"button",role:"menuitem",style:le(e),onClick:()=>s(t),children:"PNG Image"}),r&&G("button",{type:"button",role:"menuitem",style:le(e),onClick:()=>s(r),children:"SVG Vector"}),o&&G("button",{type:"button",role:"menuitem",style:le(e),onClick:()=>s(o),children:"CSV Data"}),n&&G("button",{type:"button",role:"menuitem",style:le(e),onClick:()=>s(n),children:"JSON Data"})]})]})}var Gr=0,Jr=1,qr=4,Qr=128,eo={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"},to={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 ro(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 oo(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 i=t.getUint8(2),l=t.getUint8(3),s=t.getUint32(4,!0),c=t.getFloat64(8,!0),m=t.getFloat64(16,!0),u=t.getUint32(24,!0),p=eo[i]??"Arbitrary",f=to[l]??"Arbitrary",a=new Uint8Array(e,30,130),d=no(a),g=(o&qr)!==0,S=(o&Qr)!==0,b=(o&Jr)!==0,C=ro(i,l),v=null;if(!S&&s>0){v=new Float64Array(s);let x=s>1?(m-c)/(s-1):0;for(let R=0;R<s;R++)v[R]=c+R*x}let k=[],h=512,y=null;if(S&&!g){y=new Float64Array(s);for(let x=0;x<s;x++)y[x]=t.getFloat32(h,!0),h+=4}let w=g?u:1;for(let x=0;x<w;x++){let R,E,L=s;if(g){if(h+32>e.byteLength)break;let P=t.getFloat32(h+4,!0),ce=t.getFloat32(h+8,!0);if(L=t.getUint32(h+12,!0)||s,h+=32,S){R=new Float64Array(L);for(let O=0;O<L&&!(h+4>e.byteLength);O++)R[O]=t.getFloat32(h,!0),h+=4}else if(v)R=v;else{R=new Float64Array(L);let O=L>1?(ce-P)/(L-1):0;for(let N=0;N<L;N++)R[N]=P+N*O}}else R=y??v??new Float64Array(0);if(E=new Float64Array(L),b)for(let P=0;P<L&&!(h+2>e.byteLength);P++)E[P]=t.getInt16(h,!0),h+=2;else for(let P=0;P<L&&!(h+4>e.byteLength);P++)E[P]=t.getFloat32(h,!0),h+=4;k.push({id:`spc-${++Gr}`,label:d||`SPC Spectrum ${x+1}`,x:R,y:E,xUnit:p,yUnit:f,type:C,meta:{format:"SPC",version:n===75?"new":"old",xType:i.toString(),yType:l.toString()}})}if(k.length===0)throw new Error("Invalid SPC file: no spectra found");return k}function no(e){let t=e.indexOf(0),r=t>=0?e.slice(0,t):e;return new TextDecoder("ascii").decode(r).trim()}export{Me as AnnotationLayer,Y as AxisLayer,Ee as Crosshair,ct as DARK_THEME,Ue as DropZone,Kr as ExportMenu,Cr as KEYBOARD_SHORTCUTS,lt as LIGHT_THEME,Mt as LINE_DASH_PATTERNS,oe as Legend,we as PeakMarkers,Re as RegionSelector,he as SPECTRUM_COLORS,Lr as SpectraView,W as SpectrumCanvas,Fe as StackedView,De as Toolbar,yt as binarySearchClosest,be as computeXExtent,B as computeYExtent,ge as createXScale,X as createYScale,_e as detectPeaks,Ke as downloadSvg,Ve as generateChartDescription,je as generateSvg,A as getSpectrumColor,H as getThemeColors,ve as lttbDownsample,Be as parseCsv,Or as parseCsvMulti,We as parseJcamp,He as parseJson,oo as parseSpc,vr as prefersReducedMotion,Le as snapToNearestSpectrum,Xr as useExport,ze as useKeyboardNavigation,Fr as usePeakPicking,Oe as useRegionSelect,Ne as useResizeObserver,Br as useSpectrumData,Se as useZoomPan};
11
+ </svg>`}function Je(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 le(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=se((n,a="spectrum.png")=>{n.toBlob(s=>{s&&le(s,a)},"image/png")},[]),t=se((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
+ `);le(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
+ `+c.join(`
15
+ `);le(new Blob([u],{type:"text/csv"}),a)}},[]),r=se((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);le(new Blob([l],{type:"application/json"}),a)},[]),o=se((n,a,s,i,l,c="spectrum.svg")=>{let u=Ke(n,a,s,{width:i,height:l});Je(u,c)},[]);return{exportPng:e,exportSvg:o,exportCsv:t,exportJson:r}}import{useCallback as Qr,useState as en}from"react";import{jsx as K,jsxs as Ft}from"react/jsx-runtime";var tn=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"}),rn=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"}),ce=e=>({display:"block",width:"100%",padding:"6px 12px",border:"none",background:"transparent",color:e==="dark"?"#d1d5db":"#374151",fontSize:12,textAlign:"left",cursor:"pointer"});function nn({theme:e,onExportPng:t,onExportSvg:r,onExportCsv:o,onExportJson:n}){let[a,s]=en(!1),i=Qr(l=>{s(!1),l?.()},[]);return Ft("div",{style:{position:"relative",display:"inline-block"},children:[K("button",{type:"button",style:tn(e),onClick:()=>s(!a),"aria-label":"Export","aria-expanded":a,"aria-haspopup":"true",children:"Export"}),a&&Ft("div",{style:rn(e),role:"menu",children:[t&&K("button",{type:"button",role:"menuitem",style:ce(e),onClick:()=>i(t),children:"PNG Image"}),r&&K("button",{type:"button",role:"menuitem",style:ce(e),onClick:()=>i(r),children:"SVG Vector"}),o&&K("button",{type:"button",role:"menuitem",style:ce(e),onClick:()=>i(o),children:"CSV Data"}),n&&K("button",{type:"button",role:"menuitem",style:ce(e),onClick:()=>i(n),children:"JSON Data"})]})]})}function on(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 an(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 sn(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 ln(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 cn(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=mn(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 mn(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 un(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 pn(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 fn=0,dn=1,bn=4,gn=128,hn={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"},yn={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 xn(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 Sn(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=hn[a]??"Arbitrary",f=yn[s]??"Arbitrary",m=new Uint8Array(e,30,130),d=vn(m),g=(o&bn)!==0,y=(o&gn)!==0,b=(o&dn)!==0,x=xn(a,s),S=null;if(!y&&i>0){S=new Float64Array(i);let k=i>1?(c-l)/(i-1):0;for(let R=0;R<i;R++)S[R]=l+R*k}let w=[],h=512,v=null;if(y&&!g){v=new Float64Array(i);for(let k=0;k<i;k++)v[k]=t.getFloat32(h,!0),h+=4}let C=g?u:1;for(let k=0;k<C;k++){let R,T,E=i;if(g){if(h+32>e.byteLength)break;let L=t.getFloat32(h+4,!0),me=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?(me-L)/(E-1):0;for(let O=0;O<E;O++)R[O]=L+O*N}}else R=v??S??new Float64Array(0);if(T=new Float64Array(E),b)for(let L=0;L<E&&!(h+2>e.byteLength);L++)T[L]=t.getInt16(h,!0),h+=2;else for(let L=0;L<E&&!(h+4>e.byteLength);L++)T[L]=t.getFloat32(h,!0),h+=4;w.push({id:`spc-${++fn}`,label:d||`SPC Spectrum ${k+1}`,x:R,y:T,xUnit:p,yUnit:f,type:x,meta:{format:"SPC",version:n===75?"new":"old",xType:a.toString(),yType:s.toString()}})}if(w.length===0)throw new Error("Invalid SPC file: no spectra found");return w}function vn(e){let t=e.indexOf(0),r=t>=0?e.slice(0,t):e;return new TextDecoder("ascii").decode(r).trim()}export{Ee as AnnotationLayer,Y as AxisLayer,Ae as Crosshair,ut as DARK_THEME,Ue as DropZone,nn as ExportMenu,Mr as KEYBOARD_SHORTCUTS,mt as LIGHT_THEME,Dt as LINE_DASH_PATTERNS,ne as Legend,Or as Minimap,we as PeakMarkers,Me as RegionSelector,ye as SPECTRUM_COLORS,Fr as SpectraView,W as SpectrumCanvas,$e as StackedView,Fe as Toolbar,on as baselineRubberBand,St as binarySearchClosest,ge as computeXExtent,H as computeYExtent,he as createXScale,X as createYScale,un as derivative1st,pn as derivative2nd,He as detectPeaks,Je as downloadSvg,Ze as generateChartDescription,Ke as generateSvg,P as getSpectrumColor,z as getThemeColors,Ce as lttbDownsample,sn as normalizeArea,an as normalizeMinMax,ln as normalizeSNV,Xe as parseCsv,Xr as parseCsvMulti,je as parseJcamp,Ye as parseJson,Sn as parseSpc,Rr as prefersReducedMotion,cn as smoothSavitzkyGolay,Le as snapToNearestSpectrum,qr as useExport,Ve as useKeyboardNavigation,Br as usePeakPicking,Oe as useRegionSelect,ze as useResizeObserver,Jr as useSpectrumData,ve as useZoomPan};
16
16
  //# sourceMappingURL=index.js.map