spectraview 1.4.0 → 1.6.0

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