spectraview 1.3.0 → 1.5.0

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