spectraview 1.6.0 → 1.7.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
@@ -428,6 +428,20 @@ interface TooltipProps {
428
428
  }
429
429
  declare const Tooltip: react.NamedExoticComponent<TooltipProps>;
430
430
 
431
+ interface DataTableProps {
432
+ /** Spectrum to display. */
433
+ spectrum: Spectrum;
434
+ /** Theme. */
435
+ theme?: Theme;
436
+ /** Maximum rows to display. Defaults to 200. */
437
+ maxRows?: number;
438
+ /** Height of the table container. Defaults to 300. */
439
+ height?: number;
440
+ /** X-range to highlight [min, max]. */
441
+ highlightRange?: [number, number];
442
+ }
443
+ declare const DataTable: react.NamedExoticComponent<DataTableProps>;
444
+
431
445
  /**
432
446
  * Hook for zoom and pan behavior backed by d3-zoom.
433
447
  *
@@ -815,6 +829,44 @@ declare function binarySearchClosest(arr: Float64Array | number[], target: numbe
815
829
  */
816
830
  declare function snapToNearestSpectrum(spectra: Spectrum[], dataX: number, cursorPy: number, xScale: (v: number) => number, yScale: (v: number) => number): SnapResult | null;
817
831
 
832
+ /**
833
+ * Data export utilities for CSV and JSON output.
834
+ *
835
+ * Supports full spectrum export, region-limited export, and
836
+ * multi-spectrum batch export.
837
+ *
838
+ * @module export-data
839
+ */
840
+
841
+ interface ExportOptions {
842
+ /** Delimiter for CSV. Defaults to ",". */
843
+ delimiter?: string;
844
+ /** Include header row. Defaults to true. */
845
+ includeHeader?: boolean;
846
+ /** X-range to export [min, max]. If undefined, exports all. */
847
+ xRange?: [number, number];
848
+ /** Number of decimal places. Defaults to 6. */
849
+ precision?: number;
850
+ }
851
+ /**
852
+ * Export a single spectrum to CSV string.
853
+ */
854
+ declare function spectrumToCsv(spectrum: Spectrum, options?: ExportOptions): string;
855
+ /**
856
+ * Export multiple spectra to CSV with shared X column.
857
+ *
858
+ * Assumes all spectra share the same X values.
859
+ */
860
+ declare function multiSpectraToCsv(spectra: Spectrum[], options?: ExportOptions): string;
861
+ /**
862
+ * Export spectrum data to JSON string.
863
+ */
864
+ declare function spectrumToJson(spectrum: Spectrum, options?: ExportOptions): string;
865
+ /**
866
+ * Trigger a file download in the browser.
867
+ */
868
+ declare function downloadString(content: string, filename: string, mimeType?: string): void;
869
+
818
870
  /**
819
871
  * Largest-Triangle-Three-Buckets (LTTB) downsampling algorithm.
820
872
  *
@@ -1093,4 +1145,4 @@ declare function createXScale(domain: [number, number], width: number, margin: M
1093
1145
  */
1094
1146
  declare function createYScale(domain: [number, number], height: number, margin: Margin): d3_scale.ScaleLinear<number, number, never>;
1095
1147
 
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 };
1148
+ export { type Annotation, AnnotationLayer, type AnnotationLayerProps, AxisLayer, Crosshair, type CrosshairPosition, type CrosshairProps, type CsvParseOptions, DARK_THEME, DataTable, type DataTableProps, type DisplayMode, DropZone, type DropZoneProps, ExportMenu, type ExportMenuProps, type ExportOptions, 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, downloadString, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, interpolateToGrid, lttbDownsample, multiSpectraToCsv, normalizeArea, normalizeMinMax, normalizeSNV, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, residualSpectrum, scaleSpectrum, smoothSavitzkyGolay, snapToNearestSpectrum, spectrumToCsv, spectrumToJson, useExport, useHistory, useKeyboardNavigation, useNormalization, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
package/dist/index.d.ts CHANGED
@@ -428,6 +428,20 @@ interface TooltipProps {
428
428
  }
429
429
  declare const Tooltip: react.NamedExoticComponent<TooltipProps>;
430
430
 
431
+ interface DataTableProps {
432
+ /** Spectrum to display. */
433
+ spectrum: Spectrum;
434
+ /** Theme. */
435
+ theme?: Theme;
436
+ /** Maximum rows to display. Defaults to 200. */
437
+ maxRows?: number;
438
+ /** Height of the table container. Defaults to 300. */
439
+ height?: number;
440
+ /** X-range to highlight [min, max]. */
441
+ highlightRange?: [number, number];
442
+ }
443
+ declare const DataTable: react.NamedExoticComponent<DataTableProps>;
444
+
431
445
  /**
432
446
  * Hook for zoom and pan behavior backed by d3-zoom.
433
447
  *
@@ -815,6 +829,44 @@ declare function binarySearchClosest(arr: Float64Array | number[], target: numbe
815
829
  */
816
830
  declare function snapToNearestSpectrum(spectra: Spectrum[], dataX: number, cursorPy: number, xScale: (v: number) => number, yScale: (v: number) => number): SnapResult | null;
817
831
 
832
+ /**
833
+ * Data export utilities for CSV and JSON output.
834
+ *
835
+ * Supports full spectrum export, region-limited export, and
836
+ * multi-spectrum batch export.
837
+ *
838
+ * @module export-data
839
+ */
840
+
841
+ interface ExportOptions {
842
+ /** Delimiter for CSV. Defaults to ",". */
843
+ delimiter?: string;
844
+ /** Include header row. Defaults to true. */
845
+ includeHeader?: boolean;
846
+ /** X-range to export [min, max]. If undefined, exports all. */
847
+ xRange?: [number, number];
848
+ /** Number of decimal places. Defaults to 6. */
849
+ precision?: number;
850
+ }
851
+ /**
852
+ * Export a single spectrum to CSV string.
853
+ */
854
+ declare function spectrumToCsv(spectrum: Spectrum, options?: ExportOptions): string;
855
+ /**
856
+ * Export multiple spectra to CSV with shared X column.
857
+ *
858
+ * Assumes all spectra share the same X values.
859
+ */
860
+ declare function multiSpectraToCsv(spectra: Spectrum[], options?: ExportOptions): string;
861
+ /**
862
+ * Export spectrum data to JSON string.
863
+ */
864
+ declare function spectrumToJson(spectrum: Spectrum, options?: ExportOptions): string;
865
+ /**
866
+ * Trigger a file download in the browser.
867
+ */
868
+ declare function downloadString(content: string, filename: string, mimeType?: string): void;
869
+
818
870
  /**
819
871
  * Largest-Triangle-Three-Buckets (LTTB) downsampling algorithm.
820
872
  *
@@ -1093,4 +1145,4 @@ declare function createXScale(domain: [number, number], width: number, margin: M
1093
1145
  */
1094
1146
  declare function createYScale(domain: [number, number], height: number, margin: Margin): d3_scale.ScaleLinear<number, number, never>;
1095
1147
 
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 };
1148
+ export { type Annotation, AnnotationLayer, type AnnotationLayerProps, AxisLayer, Crosshair, type CrosshairPosition, type CrosshairProps, type CsvParseOptions, DARK_THEME, DataTable, type DataTableProps, type DisplayMode, DropZone, type DropZoneProps, ExportMenu, type ExportMenuProps, type ExportOptions, 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, downloadString, downloadSvg, generateChartDescription, generateSvg, getSpectrumColor, getThemeColors, interpolateToGrid, lttbDownsample, multiSpectraToCsv, normalizeArea, normalizeMinMax, normalizeSNV, parseCsv, parseCsvMulti, parseJcamp, parseJson, parseSpc, prefersReducedMotion, residualSpectrum, scaleSpectrum, smoothSavitzkyGolay, snapToNearestSpectrum, spectrumToCsv, spectrumToJson, useExport, useHistory, useKeyboardNavigation, useNormalization, usePeakPicking, useRegionSelect, useResizeObserver, useSpectrumData, useZoomPan };
package/dist/index.js CHANGED
@@ -1,16 +1,18 @@
1
1
  "use client";
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
- <!-- ${c.label} -->`}).filter(Boolean).join(`
2
+ import{useCallback as Nt,useId as _r,useMemo as $,useRef as It,useState as Ye}from"react";import{scaleLinear as kt}from"d3-scale";import{extent as Ct}from"d3-array";var lr=.05;function ke(e){let t=1/0,r=-1/0;for(let o of e){if(o.visible===!1)continue;let[n,a]=Ct(o.x);n<t&&(t=n),a>r&&(r=a)}return isFinite(t)?[t,r]:[0,1]}function W(e){let t=1/0,r=-1/0;for(let a of e){if(a.visible===!1)continue;let[s,i]=Ct(a.y);s<t&&(t=s),i>r&&(r=i)}if(!isFinite(t))return[0,1];let n=(r-t)*lr;return[t-n,r+n]}function Ce(e,t,r,o){let n=t-r.left-r.right,a=o?[e[1],e[0]]:e;return kt().domain(a).range([0,n])}function j(e,t,r){let o=t-r.top-r.bottom;return kt().domain(e).range([o,0])}var we=["#2563eb","#dc2626","#16a34a","#9333ea","#ea580c","#0891b2","#be185d","#854d0e","#4f46e5","#65a30d"],wt={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"},Mt={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 we[e%we.length]}function U(e){return e==="dark"?Mt:wt}import{useCallback as Me,useEffect as cr,useMemo as Rt,useRef as ie,useState as ur}from"react";import{zoom as mr,zoomIdentity as Re}from"d3-zoom";import{select as V}from"d3-selection";import"d3-transition";var Tt=1.5;function Te(e){let{plotWidth:t,plotHeight:r,xScale:o,yScale:n,scaleExtent:a=[1,50],enabled:s=!0,onViewChange:i}=e,c=ie(null),l=ie(null),m=ie(i);m.current=i;let p=ie(a);p.current=a;let[f,u]=ur(Re),d=Rt(()=>f.rescaleX(o.copy()),[f,o]),h=Rt(()=>f.rescaleY(n.copy()),[f,n]);cr(()=>{let S=c.current;if(!S||!s)return;let v=mr().scaleExtent(p.current).extent([[0,0],[t,r]]).translateExtent([[-1/0,-1/0],[1/0,1/0]]).on("zoom",y=>{let k=y.transform;if(u(k),m.current){let C=k.rescaleX(o.copy()),w=k.rescaleY(n.copy());m.current(C.domain(),w.domain())}});return l.current=v,V(S).call(v),V(S).on("dblclick.zoom",()=>{V(S).transition().duration(300).call(v.transform,Re)}),()=>{V(S).on(".zoom",null)}},[t,r,s,o,n]);let g=Me(()=>{!c.current||!l.current||V(c.current).transition().duration(300).call(l.current.transform,Re)},[]),b=Me(()=>{!c.current||!l.current||V(c.current).transition().duration(200).call(l.current.scaleBy,Tt)},[]),x=Me(()=>{!c.current||!l.current||V(c.current).transition().duration(200).call(l.current.scaleBy,1/Tt)},[]);return{zoomRef:c,state:{transform:f,isZoomed:f.k!==1||f.x!==0||f.y!==0},zoomedXScale:d,zoomedYScale:h,resetZoom:g,zoomIn:b,zoomOut:x}}import{forwardRef as hr,useEffect as Et,useImperativeHandle as xr,useRef as Lt}from"react";function Ae(e,t,r,o,n,a,s){let i=o-r;if(i<=s){let f=[];for(let u=r;u<o;u++)f.push({px:n(e[u]),py:a(t[u]),index:u});return f}let c=[];c.push({px:n(e[r]),py:a(t[r]),index:r});let l=s-2,m=(i-2)/l,p=r;for(let f=0;f<l;f++){let u=r+1+Math.floor(f*m),d=r+1+Math.min(Math.floor((f+1)*m),i-2),h=d,g=r+1+Math.min(Math.floor((f+2)*m),i-2),b,x;if(f===l-1)b=n(e[o-1]),x=a(t[o-1]);else{b=0,x=0;let C=g-h;for(let w=h;w<g;w++)b+=n(e[w]),x+=a(t[w]);C>0&&(b/=C,x/=C)}let S=n(e[p]),v=a(t[p]),y=-1,k=u;for(let C=u;C<d;C++){let w=n(e[C]),M=a(t[C]),T=Math.abs((S-b)*(M-v)-(S-w)*(x-v));T>y&&(y=T,k=C)}c.push({px:n(e[k]),py:a(t[k]),index:k}),p=k}return c.push({px:n(e[o-1]),py:a(t[o-1]),index:o-1}),c}var pr=1.5,fr={solid:[],dashed:[8,4],dotted:[2,2],"dash-dot":[8,4,2,4]},dr=2e3;function br(e,t,r){e.clearRect(0,0,t,r)}function gr(e,t,r,o,n,a,s){let{highlighted:i=!1,opacity:c=1}=s??{},l=Math.min(t.x.length,t.y.length);if(l<2)return;let m=t.color??A(r),p=t.lineWidth??pr,f=i?p+1:p,u=fr[t.lineStyle??"solid"]??[],[d,h]=o.domain(),g=Math.min(d,h),b=Math.max(d,h),x=0,S=l;for(let y=0;y<l;y++)if(t.x[y]>=g||y<l-1&&t.x[y+1]>=g){x=Math.max(0,y-1);break}for(let y=l-1;y>=0;y--)if(t.x[y]<=b||y>0&&t.x[y-1]<=b){S=Math.min(l,y+2);break}let v=S-x;if(e.save(),e.beginPath(),e.strokeStyle=m,e.lineWidth=f,e.globalAlpha=c,e.lineJoin="round",e.setLineDash(u),v>dr){let y=Math.max(Math.ceil(a*2),200),k=Ae(t.x,t.y,x,S,o,n,y);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 y=!1;for(let k=x;k<S;k++){let C=o(t.x[k]),w=n(t.y[k]);y?e.lineTo(C,w):(e.moveTo(C,w),y=!0)}}e.stroke(),e.restore()}function At(e,t,r,o,n,a,s){br(e,n,a),t.forEach((i,c)=>{i.visible!==!1&&gr(e,i,c,r,o,n,{highlighted:i.id===s,opacity:s&&i.id!==s?.3:1})})}import{jsx as yr}from"react/jsx-runtime";var Y=hr(function({spectra:t,xScale:r,yScale:o,width:n,height:a,highlightedId:s},i){let c=Lt(null),l=Lt(1);return xr(i,()=>c.current,[]),Et(()=>{let m=c.current;if(!m)return;let p=window.devicePixelRatio||1;l.current=p,m.width=n*p,m.height=a*p},[n,a]),Et(()=>{let m=c.current;if(!m)return;let p=m.getContext("2d");if(!p)return;let f=l.current;p.setTransform(f,0,0,f,0,0),At(p,t,r,o,n,a,s)},[t,r,o,n,a,s]),yr("canvas",{ref:c,style:{width:n,height:a,position:"absolute",top:0,left:0,pointerEvents:"none"}})});import{jsx as F,jsxs as H}from"react/jsx-runtime";function Pt(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,c)=>n+c*s)}function Ft(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 G({xScale:e,yScale:t,width:r,height:o,xLabel:n,yLabel:a,showGrid:s=!0,colors:i}){let c=Pt(e,8),l=Pt(t,6);return H("g",{children:[s&&H("g",{children:[c.map(m=>F("line",{x1:e(m),x2:e(m),y1:0,y2:o,stroke:i.gridColor,strokeWidth:.5},`xgrid-${m}`)),l.map(m=>F("line",{x1:0,x2:r,y1:t(m),y2:t(m),stroke:i.gridColor,strokeWidth:.5},`ygrid-${m}`))]}),H("g",{transform:`translate(0, ${o})`,children:[F("line",{x1:0,x2:r,y1:0,y2:0,stroke:i.axisColor}),c.map(m=>H("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:Ft(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})]}),H("g",{children:[F("line",{x1:0,x2:0,y1:0,y2:o,stroke:i.axisColor}),l.map(m=>H("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:Ft(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 Ee,jsxs as Sr}from"react/jsx-runtime";function Le({peaks:e,xScale:t,yScale:r,colors:o,onPeakClick:n}){let[a,s]=t.domain(),i=Math.min(a,s),c=Math.max(a,s),l=e.filter(m=>m.x>=i&&m.x<=c);return Ee("g",{className:"spectraview-peaks",children:l.map((m,p)=>{let f=t(m.x),u=r(m.y);return Sr("g",{transform:`translate(${f}, ${u})`,style:{cursor:n?"pointer":"default"},onClick:()=>n?.(m),children:[Ee("polygon",{points:`0,-5 -5,${-5*2.5} 5,${-5*2.5}`,fill:o.labelColor,opacity:.8}),m.label&&Ee("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 Pe,jsxs as vr}from"react/jsx-runtime";function Fe({regions:e,xScale:t,height:r,colors:o}){return Pe("g",{className:"spectraview-regions",children:e.map((n,a)=>{let s=t(n.xStart),i=t(n.xEnd),c=Math.min(s,i),l=Math.abs(i-s);return vr("g",{children:[Pe("rect",{x:c,y:0,width:l,height:r,fill:n.color??o.regionFill,stroke:o.regionStroke,strokeWidth:1}),n.label&&Pe("text",{x:c+l/2,y:12,textAnchor:"middle",fill:o.labelColor,fontSize:10,fontFamily:"system-ui, sans-serif",children:n.label})]},`region-${a}`)})})}import{jsx as ae,jsxs as De}from"react/jsx-runtime";function Ue({position:e,width:t,height:r,colors:o,snapPoint:n}){return e?De("g",{className:"spectraview-crosshair",pointerEvents:"none",children:[ae("line",{x1:e.px,x2:e.px,y1:0,y2:r,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),ae("line",{x1:0,x2:t,y1:e.py,y2:e.py,stroke:o.crosshairColor,strokeWidth:1,strokeDasharray:"4 4"}),n&&ae("circle",{cx:n.px,cy:n.py,r:4,fill:n.color??o.crosshairColor,stroke:o.background,strokeWidth:1.5}),De("g",{transform:`translate(${Math.min(e.px+10,t-100)}, ${Math.max(e.py-10,20)})`,children:[ae("rect",{x:0,y:-14,width:90,height:18,rx:3,fill:o.tooltipBg,stroke:o.tooltipBorder,strokeWidth:.5,opacity:.9}),De("text",{x:5,y:0,fill:o.tooltipText,fontSize:10,fontFamily:"monospace",children:[Dt(n?.dataX??e.dataX),","," ",Dt(n?.dataY??e.dataY)]})]})]}):null}function Dt(e){return Math.abs(e)>=100?Math.round(e).toString():Math.abs(e)>=1?e.toFixed(1):e.toFixed(4)}import{jsx as K,jsxs as kr}from"react/jsx-runtime";function $e({annotations:e,xScale:t,yScale:r,colors:o}){return e.length===0?null:K("g",{className:"spectraview-annotations",pointerEvents:"none",children:e.map(n=>{let a=t(n.x),s=r(n.y),[i,c]=n.offset??[0,-20],l=a+i,m=s+c,p=n.fontSize??11,f=n.color??o.tickColor,u=n.showAnchorLine!==!1;return kr("g",{children:[u&&K("line",{x1:a,y1:s,x2:l,y2:m,stroke:f,strokeWidth:.75,strokeDasharray:"3 2",opacity:.6}),K("circle",{cx:a,cy:s,r:2.5,fill:f,opacity:.8}),K("text",{x:l,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}),K("text",{x:l,y:m,fill:f,fontSize:p,fontFamily:"system-ui, sans-serif",textAnchor:"middle",dominantBaseline:"auto",children:n.text})]},n.id)})})}function se(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 c=n+a>>>1,l=e[c];o?l<=t?n=c:a=c:l>=t?n=c:a=c}let s=Math.abs(e[n]-t),i=Math.abs(e[a]-t);return s<=i?n:a}function Ne(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 c=se(s.x,t,i);if(c<0)continue;let l=s.x[c],m=s.y[c],p=Math.abs(o(l)-o(t)),f=Math.abs(n(m)-r),u=Math.sqrt(p*p+f*f);(!a||u<a.distance)&&(a={spectrumId:s.id,index:c,x:l,y:m,distance:u})}return a}import{memo as Cr}from"react";import{jsx as Oe,jsxs as Mr}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}),wr=e=>({display:"flex",gap:4,padding:"4px 0",borderBottom:`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`}),ze=Cr(function({onZoomIn:t,onZoomOut:r,onReset:o,isZoomed:n,theme:a}){return Mr("div",{style:wr(a),className:"spectraview-toolbar",children:[Oe("button",{type:"button",style:Ie(a),onClick:t,title:"Zoom in","aria-label":"Zoom in",children:"+"}),Oe("button",{type:"button",style:Ie(a),onClick:r,title:"Zoom out","aria-label":"Zoom out",children:"\u2212"}),Oe("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 Rr}from"react";import{jsx as Ve,jsxs as Lr}from"react/jsx-runtime";var Tr=(e,t)=>({display:"flex",flexDirection:t==="left"||t==="right"?"column":"row",flexWrap:"wrap",gap:6,padding:"4px 8px",fontSize:12,fontFamily:"system-ui, sans-serif",borderTop:t==="bottom"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0,borderBottom:t==="top"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0,borderLeft:t==="right"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0,borderRight:t==="left"?`1px solid ${e==="dark"?"#374151":"#e5e7eb"}`:void 0}),Ar=(e,t,r)=>({display:"inline-flex",alignItems:"center",gap:4,cursor:"pointer",opacity:t?.4:1,fontWeight:r?600:400,color:e==="dark"?"#e5e7eb":"#374151",userSelect:"none",padding:"2px 4px",borderRadius:3,background:r?e==="dark"?"rgba(255,255,255,0.08)":"rgba(0,0,0,0.04)":"transparent",transition:"background 0.15s, opacity 0.15s"}),Er=(e,t)=>({width:12,height:3,borderRadius:1,background:e,opacity:t?.4:1,flexShrink:0}),le=Rr(function({spectra:t,theme:r,position:o,onToggleVisibility:n,onHighlight:a,highlightedId:s}){return t.length<=1?null:Ve("div",{className:"spectraview-legend",style:Tr(r,o),role:"list","aria-label":"Spectrum legend",children:t.map((i,c)=>{let l=i.color??A(c),m=i.visible===!1,p=s===i.id;return Lr("div",{role:"listitem",style:Ar(r,m,p),onClick:()=>n?.(i.id),onMouseEnter:()=>a?.(i.id),onMouseLeave:()=>a?.(null),title:m?`Show ${i.label}`:`Hide ${i.label}`,children:[Ve("span",{style:Er(l,m)}),Ve("span",{style:{textDecoration:m?"line-through":"none",maxWidth:120,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:i.label})]},i.id)})})});import{useCallback as ce,useState as Pr}from"react";import{jsx as Fr,jsxs as Dr}from"react/jsx-runtime";function Be({enabled:e,theme:t,width:r,height:o,onDrop:n,children:a}){let[s,i]=Pr(!1),c={current:0},l=ce(u=>{e&&(u.preventDefault(),c.current++,i(!0))},[e]),m=ce(u=>{e&&(u.preventDefault(),c.current--,c.current<=0&&(c.current=0,i(!1)))},[e]),p=ce(u=>{e&&(u.preventDefault(),u.dataTransfer.dropEffect="copy")},[e]),f=ce(u=>{if(!e)return;u.preventDefault(),c.current=0,i(!1);let d=Array.from(u.dataTransfer.files);d.length>0&&n?.(d)},[e,n]);return Dr("div",{style:{position:"relative",width:r,height:o},onDragEnter:l,onDragLeave:m,onDragOver:p,onDrop:f,children:[a,s&&Fr("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 Ur}from"react";import{jsx as Z,jsxs as $r}from"react/jsx-runtime";var Ut=8;function He({spectra:e,xScale:t,plotWidth:r,plotHeight:o,margin:n,theme:a,showGrid:s,xLabel:i,yLabel:c}){let l=e.filter(d=>d.visible!==!1),m=Ur(()=>U(a),[a]),p=l.length,f=(p-1)*Ut,u=Math.max(40,Math.floor((o-f)/Math.max(p,1)));return Z("g",{className:"spectraview-stacked",children:l.map((d,h)=>{let g=h*(u+Ut),b=W([d]),x=j(b,u+n.top+n.bottom,{...n,top:0,bottom:0}),S=d.color??A(h),v={...d,color:S};return $r("g",{transform:`translate(0, ${g})`,children:[Z("rect",{x:0,y:0,width:r,height:u,fill:"transparent",stroke:m.gridColor,strokeWidth:.5,rx:2}),Z(G,{xScale:t,yScale:x,width:r,height:u,xLabel:h===p-1?i:"",yLabel:c,showGrid:s,colors:m}),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:u,children:Z(Y,{spectra:[v],xScale:t,yScale:x,width:r,height:u})})]},d.id)})})}import{useCallback as Ze,useRef as Nr,useState as Ir}from"react";function _e(e){let{enabled:t,xScale:r,onRegionSelect:o}=e,[n,a]=Ir(null),s=Nr(null),i=Ze(m=>{if(!t||!m.shiftKey)return;m.preventDefault();let p=m.currentTarget.getBoundingClientRect(),f=m.clientX-p.left,u=r.invert(f);s.current=u,a({xStart:u,xEnd:u})},[t,r]),c=Ze(m=>{if(s.current===null)return;let p=m.currentTarget.getBoundingClientRect(),f=m.clientX-p.left,u=r.invert(f),d=s.current;a({xStart:Math.min(d,u),xEnd:Math.max(d,u)})},[r]),l=Ze(()=>{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:c,handleMouseUp:l}}import{useCallback as Or,useEffect as zr,useRef as $t,useState as Vr}from"react";function Xe(){let[e,t]=Vr(null),r=$t(null),o=$t(null),n=Or(a=>{if(r.current&&(r.current.disconnect(),r.current=null),o.current=a,!a)return;let s=new ResizeObserver(l=>{let m=l[0];if(!m)return;let{width:p,height:f}=m.contentRect;t({width:Math.round(p),height:Math.round(f)})});s.observe(a),r.current=s;let{width:i,height:c}=a.getBoundingClientRect();t({width:Math.round(i),height:Math.round(c)})},[]);return zr(()=>()=>{r.current?.disconnect()},[]),{ref:n,size:e}}import{useCallback as Br}from"react";function We(e){let{onZoomIn:t,onZoomOut:r,onReset:o,enabled:n=!0}=e;return Br(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 Hr(){return typeof window>"u"?!1:window.matchMedia("(prefers-reduced-motion: reduce)").matches}function je(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 Zr={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 R,jsxs as J}from"react/jsx-runtime";var Xr={top:20,right:20,bottom:50,left:65},Wr=800,jr=400;function Yr(e){return{width:e.width??Wr,height:e.height??jr,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:{...Xr,...e.margin},theme:e.theme??"light",responsive:e.responsive??!1,enableDragDrop:e.enableDragDrop??!1,enableRegionSelect:e.enableRegionSelect??!1}}function Gr(e,t,r){let o=e[0];return{xLabel:t??o?.xUnit??"x",yLabel:r??o?.yUnit??"y"}}function Kr(e){let{spectra:t,peaks:r=[],regions:o=[],annotations:n=[],onPeakClick:a,onViewChange:s,onCrosshairMove:i,onToggleVisibility:c,onFileDrop:l,onRegionSelect:m,canvasRef:p,snapCrosshair:f=!0}=e,{ref:u,size:d}=Xe(),g=`spectraview-clip-${_r().replace(/:/g,"")}`,b=$(()=>Yr(e),[e.width,e.height,e.reverseX,e.showGrid,e.showCrosshair,e.showToolbar,e.showLegend,e.legendPosition,e.displayMode,e.margin,e.theme,e.responsive,e.enableDragDrop,e.enableRegionSelect]),x=b.responsive&&d?d.width:b.width,{height:S,margin:v,reverseX:y,theme:k}=b,C=x-v.left-v.right,w=S-v.top-v.bottom,M=$(()=>U(k),[k]),T=$(()=>Gr(t,e.xLabel,e.yLabel),[t,e.xLabel,e.yLabel]),L=$(()=>ke(t),[t]),P=$(()=>W(t),[t]),ge=$(()=>Ce(L,x,v,y),[L,x,v,y]),O=$(()=>j(P,S,v),[P,S,v]),z=It(s);z.current=s;let Jt=$(()=>(I,ne)=>{z.current?.({xDomain:I,yDomain:ne})},[]),{zoomRef:pt,state:qt,zoomedXScale:E,zoomedYScale:N,resetZoom:ft,zoomIn:dt,zoomOut:bt}=Te({plotWidth:C,plotHeight:w,xScale:ge,yScale:O,onViewChange:s?Jt:void 0}),{pendingRegion:te,handleMouseDown:Qt,handleMouseMove:er,handleMouseUp:tr}=_e({enabled:b.enableRegionSelect,xScale:E,onRegionSelect:m}),[he,gt]=Ye(null),[rr,ht]=Ye(null),[nr,xe]=Ye(null),re=It(i);re.current=i;let xt=Nt(I=>{if(!b.showCrosshair)return;let ne=I.currentTarget.getBoundingClientRect(),St=I.clientX-ne.left,Se=I.clientY-ne.top,oe=E.invert(St),ve=N.invert(Se);if(ht({px:St,py:Se,dataX:oe,dataY:ve}),f&&t.length>0){let D=Ne(t,oe,Se,E,N);if(D&&D.distance<50){let vt=t.findIndex(sr=>sr.id===D.spectrumId);xe({px:E(D.x),py:N(D.y),dataX:D.x,dataY:D.y,color:t[vt]?.color??A(vt)}),re.current?.(D.x,D.y)}else xe(null),re.current?.(oe,ve)}else re.current?.(oe,ve)},[E,N,b.showCrosshair,f,t]),yt=Nt(()=>{ht(null),xe(null)},[]),or=We({onZoomIn:dt,onZoomOut:bt,onReset:ft}),ir=$(()=>je(t.length,T.xLabel,T.yLabel),[t.length,T.xLabel,T.yLabel]),ar=b.displayMode==="stacked";if(t.length===0)return R("div",{ref:b.responsive?u:void 0,style:{width:b.responsive?"100%":x,height:S,display:"flex",alignItems:"center",justifyContent:"center",border:`1px dashed ${M.gridColor}`,borderRadius:8,color:M.tickColor,fontFamily:"system-ui, sans-serif",fontSize:14},className:e.className,children:"No spectra loaded"});let ye=b.showToolbar?37:0;return J("div",{ref:b.responsive?u:void 0,style:{width:b.responsive?"100%":x,background:M.background,borderRadius:4,overflow:"hidden"},className:e.className,role:"img","aria-label":ir,tabIndex:0,onKeyDown:or,children:[b.showToolbar&&R(ze,{onZoomIn:dt,onZoomOut:bt,onReset:ft,isZoomed:qt.isZoomed,theme:k}),b.showLegend&&b.legendPosition==="top"&&R(le,{spectra:t,theme:k,position:"top",onToggleVisibility:c,onHighlight:gt,highlightedId:he}),R(Be,{enabled:b.enableDragDrop,theme:k,width:x,height:S-ye,onDrop:l,children:ar?R("svg",{width:x,height:S-ye,style:{position:"absolute",top:0,left:0},children:J("g",{transform:`translate(${v.left}, ${v.top})`,children:[R(He,{spectra:t,xScale:E,plotWidth:C,plotHeight:w,margin:v,theme:k,showGrid:b.showGrid,xLabel:T.xLabel,yLabel:T.yLabel}),R("rect",{ref:pt,x:0,y:0,width:C,height:w,fill:"transparent",style:{cursor:"grab"},onMouseMove:xt,onMouseLeave:yt})]})}):J(Jr,{children:[R("div",{style:{position:"absolute",top:v.top,left:v.left,width:C,height:w,overflow:"hidden"},children:R(Y,{ref:p,spectra:t,xScale:E,yScale:N,width:C,height:w,highlightedId:he??void 0})}),R("svg",{width:x,height:S-ye,style:{position:"absolute",top:0,left:0},children:J("g",{transform:`translate(${v.left}, ${v.top})`,children:[R(G,{xScale:E,yScale:N,width:C,height:w,xLabel:T.xLabel,yLabel:T.yLabel,showGrid:b.showGrid,colors:M}),R("defs",{children:R("clipPath",{id:g,children:R("rect",{x:0,y:0,width:C,height:w})})}),J("g",{clipPath:`url(#${g})`,children:[o.length>0&&R(Fe,{regions:o,xScale:E,height:w,colors:M}),r.length>0&&R(Le,{peaks:r,xScale:E,yScale:N,colors:M,onPeakClick:a})]}),n.length>0&&R($e,{annotations:n,xScale:E,yScale:N,colors:M}),b.showCrosshair&&R(Ue,{position:rr,width:C,height:w,colors:M,snapPoint:nr}),te&&R("rect",{x:E(te.xStart),y:0,width:Math.abs(E(te.xEnd)-E(te.xStart)),height:w,fill:M.regionFill,stroke:M.regionStroke,strokeWidth:1,pointerEvents:"none"}),R("rect",{ref:pt,x:0,y:0,width:C,height:w,fill:"transparent",style:{cursor:b.showCrosshair?"crosshair":"grab"},onMouseDown:Qt,onMouseMove:I=>{xt(I),er(I)},onMouseUp:tr,onMouseLeave:yt})]})})]})}),b.showLegend&&b.legendPosition==="bottom"&&R(le,{spectra:t,theme:k,position:"bottom",onToggleVisibility:c,onHighlight:gt,highlightedId:he})]})}import{memo as qr,useEffect as Qr,useRef as en,useMemo as Ge}from"react";import{scaleLinear as Ot}from"d3-scale";import{jsx as ue,jsxs as zt}from"react/jsx-runtime";var tn=qr(function({spectra:t,xExtent:r,yExtent:o,visibleXDomain:n,width:a=200,height:s=50,theme:i="light",isZoomed:c=!1}){let l=en(null),m=Ge(()=>U(i),[i]),p=Ge(()=>Ot().domain(r).range([0,a]),[r,a]),f=Ge(()=>Ot().domain(o).range([s-2,2]),[o,s]);Qr(()=>{let g=l.current?.getContext("2d");if(g){g.clearRect(0,0,a,s);for(let b=0;b<t.length;b++){let x=t[b];if(x.visible===!1)continue;let S=Math.min(x.x.length,x.y.length);if(S<2)continue;let v=x.color??A(b);g.beginPath(),g.strokeStyle=v,g.lineWidth=1,g.globalAlpha=.7;let y=Math.max(1,Math.floor(S/a)),k=!1;for(let C=0;C<S;C+=y){let w=p(x.x[C]),M=f(x.y[C]);k?g.lineTo(w,M):(g.moveTo(w,M),k=!0)}g.stroke()}}},[t,p,f,a,s]);let u=p(Math.min(n[0],n[1])),d=p(Math.max(n[0],n[1])),h=Math.max(d-u,2);return zt("div",{className:"spectraview-minimap",style:{position:"relative",width:a,height:s,border:`1px solid ${m.gridColor}`,borderRadius:3,overflow:"hidden",background:m.background},children:[ue("canvas",{ref:l,width:a,height:s,style:{position:"absolute",top:0,left:0}}),c&&zt("svg",{width:a,height:s,style:{position:"absolute",top:0,left:0},children:[ue("rect",{x:0,y:0,width:u,height:s,fill:m.background,opacity:.6}),ue("rect",{x:u+h,y:0,width:a-u-h,height:s,fill:m.background,opacity:.6}),ue("rect",{x:u,y:0,width:h,height:s,fill:"none",stroke:i==="dark"?"#60a5fa":"#3b82f6",strokeWidth:1.5,rx:1})]})]})});import{memo as rn,useMemo as Vt}from"react";import{jsx as Bt,jsxs as q}from"react/jsx-runtime";function Ke(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 nn=rn(function({data:t,spectra:r,peaks:o=[],plotWidth:n,plotHeight:a,colors:s,numberFormat:i="auto"}){if(!t)return null;let c=Vt(()=>t?r.filter(b=>b.visible!==!1).map((b,x)=>{let S=Math.min(b.x.length,b.y.length);if(S<1)return null;let v=se(b.x,t.dataX,S);return v<0?null:{label:b.label,color:b.color??A(x),value:b.y[v],x:b.x[v]}}).filter(Boolean):[],[t?.dataX,r]),l=Vt(()=>{if(!t||o.length===0)return null;let b=null,x=1/0;for(let S of o){let v=Math.abs(S.x-t.dataX);v<x&&(x=v,b=S)}return b},[t?.dataX,o]),m=16,p=18,f=l?m:0,u=p+c.length*m+f+8,d=160,h=t.px+15,g=t.py-u/2;return h+d>n&&(h=t.px-d-15),g<0&&(g=4),g+u>a&&(g=a-u-4),q("g",{className:"spectraview-tooltip",transform:`translate(${h}, ${g})`,pointerEvents:"none",children:[Bt("rect",{x:0,y:0,width:d,height:u,rx:4,fill:s.tooltipBg,stroke:s.tooltipBorder,strokeWidth:.5,opacity:.95}),q("text",{x:8,y:14,fill:s.tooltipText,fontSize:10,fontFamily:"monospace",fontWeight:600,children:["x = ",Ke(t.dataX,i)]}),c.map((b,x)=>q("g",{transform:`translate(0, ${p+x*m})`,children:[Bt("circle",{cx:12,cy:8,r:3,fill:b.color}),q("text",{x:20,y:11,fill:s.tooltipText,fontSize:9,fontFamily:"monospace",children:[b.label.slice(0,10),": ",Ke(b.value,i)]})]},b.label)),l&&q("text",{x:8,y:p+c.length*m+12,fill:s.labelColor,fontSize:9,fontFamily:"monospace",fontStyle:"italic",children:["Peak: ",l.label??Ke(l.x,i)]})]})});import{memo as on,useMemo as Ht,useState as an}from"react";import{jsx as B,jsxs as _}from"react/jsx-runtime";var sn=on(function({spectrum:t,theme:r="light",maxRows:o=200,height:n=300,highlightRange:a}){let s=Ht(()=>U(r),[r]),[i,c]=an(!1),l=Math.min(t.x.length,t.y.length),m=Ht(()=>{let g=Array.from({length:l},(b,x)=>x);return i&&g.reverse(),g.slice(0,o)},[l,i,o]),p=g=>{if(!a)return!1;let b=Math.min(a[0],a[1]),x=Math.max(a[0],a[1]);return g>=b&&g<=x},f=g=>Math.abs(g)>=100?g.toFixed(2):Math.abs(g)>=.01?g.toFixed(4):g.toExponential(3),u=r==="dark"?"#1f2937":"#f3f4f6",d=r==="dark"?"#374151":"#e5e7eb",h=r==="dark"?"rgba(59,130,246,0.15)":"rgba(59,130,246,0.08)";return _("div",{className:"spectraview-datatable",style:{height:n,overflow:"auto",border:`1px solid ${d}`,borderRadius:4,fontFamily:"monospace",fontSize:12,color:s.tickColor,background:s.background},children:[_("table",{style:{width:"100%",borderCollapse:"collapse"},children:[B("thead",{children:_("tr",{style:{position:"sticky",top:0,background:u,borderBottom:`1px solid ${d}`},children:[B("th",{style:{padding:"6px 8px",textAlign:"right"},children:"#"}),_("th",{style:{padding:"6px 8px",textAlign:"right",cursor:"pointer",userSelect:"none"},onClick:()=>c(g=>!g),title:"Click to reverse sort",children:[t.xUnit??"x"," ",i?"\u2191":"\u2193"]}),B("th",{style:{padding:"6px 8px",textAlign:"right"},children:t.yUnit??"y"})]})}),B("tbody",{children:m.map(g=>{let b=t.x[g],x=t.y[g];return _("tr",{style:{background:p(b)?h:"transparent",borderBottom:`1px solid ${d}`},children:[B("td",{style:{padding:"3px 8px",textAlign:"right",opacity:.5},children:g}),B("td",{style:{padding:"3px 8px",textAlign:"right"},children:f(b)}),B("td",{style:{padding:"3px 8px",textAlign:"right"},children:f(x)})]},g)})})]}),l>o&&_("div",{style:{padding:"6px 8px",textAlign:"center",fontSize:11,opacity:.6,borderTop:`1px solid ${d}`},children:["Showing ",o," of ",l," points"]})]})});import{useMemo as mn}from"react";function Je(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 c=i-s;if(c===0)return[];let l=o*c,m=[];for(let u=1;u<t.length-1;u++)if(t[u]>t[u-1]&&t[u]>t[u+1]){let d=ln(t,u),h=cn(t,u),g=t[u]-Math.max(d,h);g>=l&&m.push({index:u,prom:g})}m.sort((u,d)=>d.prom-u.prom);let p=[];for(let u of m)p.some(h=>Math.abs(h.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:un(e[u.index])})).sort((u,d)=>u.x-d.x)}function ln(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 cn(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 un(e){return Math.round(e).toString()}function pn(e,t={}){let{enabled:r=!0,spectrumIds:o,prominence:n,minDistance:a,maxPeaks:s}=t;return mn(()=>{if(!r)return[];let i=o?e.filter(l=>o.includes(l.id)):e,c=[];for(let l of i){if(l.visible===!1)continue;let m=Je(l.x,l.y,{prominence:n,minDistance:a,maxPeaks:s});for(let p of m)c.push({...p,spectrumId:l.id})}return c},[e,r,o,n,a,s])}import{useCallback as X,useState as rt}from"react";var Zt=0,fn=[" ",",",";"," "];function _t(e){let t=e.trim().split(/\r?\n/).slice(0,5),r=",",o=0;for(let n of fn){let a=t.map(i=>i.split(n).length-1),s=Math.min(...a);s>0&&s>=o&&(a.every(c=>c===a[0])||s>o)&&(o=s,r=n)}return r}function qe(e,t={}){let{xColumn:r=0,yColumn:o=1,hasHeader:n=!0,label:a="CSV Spectrum"}=t,s=t.delimiter??_t(e),i=e.trim().split(/\r?\n/);if(i.length<2)throw new Error("CSV file must contain at least 2 lines");let c=a,l=0;if(n){let f=i[0].split(s).map(u=>u.trim());!t.label&&f[o]&&(c=f[o]),l=1}let m=[],p=[];for(let f=l;f<i.length;f++){let u=i[f].trim();if(u===""||u.startsWith("#"))continue;let d=u.split(s),h=parseFloat(d[r]),g=parseFloat(d[o]);!isNaN(h)&&!isNaN(g)&&(m.push(h),p.push(g))}if(m.length===0)throw new Error("No valid numeric data found in CSV");return{id:`csv-${++Zt}`,label:c,x:new Float64Array(m),y:new Float64Array(p)}}function dn(e,t={}){let{hasHeader:r=!0,label:o}=t,n=t.delimiter??_t(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 c,l=0;r&&(c=a[0].split(n).map(u=>u.trim()),l=1);let m=[],p=Array.from({length:i-1},()=>[]);for(let u=l;u<a.length;u++){let d=a[u].trim();if(d===""||d.startsWith("#"))continue;let h=d.split(n),g=parseFloat(h[0]);if(!isNaN(g)){m.push(g);for(let b=1;b<i;b++){let x=parseFloat(h[b]);p[b-1].push(isNaN(x)?0:x)}}}let f=new Float64Array(m);return p.map((u,d)=>({id:`csv-${++Zt}`,label:o??c?.[d+1]??`Spectrum ${d+1}`,x:f,y:new Float64Array(u)}))}var bn=0;function et(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)=>Qe(r,o));if(typeof t=="object"&&t!==null){let r=t;return Array.isArray(r.spectra)?r.spectra.map((o,n)=>Qe(o,n)):[Qe(t,0)]}throw new Error("Invalid JSON structure: expected an object or array")}function Qe(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-${++bn}`,label:n,x:new Float64Array(r),y:new Float64Array(o),xUnit:e.xUnit,yUnit:e.yUnit,type:e.type,meta:e.meta}}var Wt=0,me=null,Xt=!1;async function gn(){if(Xt)return me;Xt=!0;try{me=await import("jcampconverter")}catch{me=null}return me}function jt(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 tt(e){let t=await gn();return t?hn(e,t):[xn(e)]}function hn(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-${++Wt}`,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:jt(o.info),meta:o.info}})}function xn(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 c=i.match(/^##(.+?)=\s*(.*)$/);if(c){let l=c[1].trim().toUpperCase(),m=c[2].trim();if(l==="XYDATA"||l==="XYPOINTS"){a=!0;continue}if(l==="END"){a=!1;continue}r[l]=m}continue}if(a&&i!==""){let c=i.split(/[\s,]+/).map(Number);if(c.length>=2&&!c.some(isNaN)){let l=c[0],m=parseFloat(r.FIRSTX??"0"),p=parseFloat(r.LASTX??"0"),f=parseInt(r.NPOINTS??"0",10);if(f>0&&c.length===2)o.push(c[0]),n.push(c[1]);else if(c.length>1){let u=f>1?(p-m)/(f-1):0;for(let d=1;d<c.length;d++)o.push(l+(d-1)*u),n.push(c[d])}}}}if(o.length===0)throw new Error("Failed to parse JCAMP-DX: no data found. Install jcampconverter for full format support.");return{id:`jcamp-${++Wt}`,label:r.TITLE??"JCAMP Spectrum",x:new Float64Array(o),y:new Float64Array(n),xUnit:r.XUNITS??"cm\u207B\xB9",yUnit:r.YUNITS??"Absorbance",type:jt(r),meta:r}}function yn(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 Sn(e=[]){let[t,r]=rt(e),[o,n]=rt(!1),[a,s]=rt(null),i=X(async(u,d)=>{n(!0),s(null);try{let h;switch(d){case"jcamp":h=await tt(u);break;case"csv":h=[qe(u)];break;case"json":h=et(u);break}r(g=>[...g,...h])}catch(h){let g=h instanceof Error?h.message:"Failed to parse file";s(g)}finally{n(!1)}},[]),c=X(async u=>{let d=yn(u.name);if(!d){s(`Unsupported file format: ${u.name}`);return}let h=await u.text();await i(h,d)},[i]),l=X(u=>{r(d=>[...d,u])},[]),m=X(u=>{r(d=>d.filter(h=>h.id!==u))},[]),p=X(u=>{r(d=>d.map(h=>h.id===u?{...h,visible:h.visible===!1}:h))},[]),f=X(()=>{r([]),s(null)},[]);return{spectra:t,loading:o,error:a,loadFile:c,loadText:i,addSpectrum:l,removeSpectrum:m,toggleVisibility:p,clear:f}}import{useCallback as pe}from"react";var Yt={solid:"",dashed:"8 4",dotted:"2 2","dash-dot":"8 4 2 4"};function nt(e,t,r,o){let{width:n,height:a,background:s="#ffffff",title:i}=o,c=e.filter(l=>l.visible!==!1).map((l,m)=>{let p=l.color??A(m),f=l.lineStyle??"solid",u=l.lineWidth??1.5,d=Yt[f]??"",h=Math.min(l.x.length,l.y.length);if(h<2)return"";let g=[];for(let b=0;b<h;b++){let x=t(l.x[b]).toFixed(2),S=r(l.y[b]).toFixed(2);g.push(`${b===0?"M":"L"}${x},${S}`)}return`<path d="${g.join(" ")}" fill="none" stroke="${p}" stroke-width="${u}"${d?` stroke-dasharray="${d}"`:""}/>
3
+ <!-- ${l.label} -->`}).filter(Boolean).join(`
4
4
  `);return`<?xml version="1.0" encoding="UTF-8"?>
5
5
  <svg xmlns="http://www.w3.org/2000/svg" width="${n}" height="${a}" viewBox="0 0 ${n} ${a}">
6
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
- ${l}
9
+ ${c}
10
10
  </g>
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
- `+c.join(`
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};
11
+ </svg>`}function ot(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 fe(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 vn(){let e=pe((n,a="spectrum.png")=>{n.toBlob(s=>{s&&fe(s,a)},"image/png")},[]),t=pe((n,a="spectra.csv")=>{let s=n.filter(i=>i.visible!==!1);if(s.length!==0)if(s.length===1){let i=s[0],c=`${i.xUnit??"x"},${i.yUnit??"y"}
12
+ `,l=Array.from(i.x).map((p,f)=>`${p},${i.y[f]}`),m=c+l.join(`
13
+ `);fe(new Blob([m],{type:"text/csv"}),a)}else{let i=Math.max(...s.map(p=>p.x.length)),c=s.map(p=>`${p.label}_x,${p.label}_y`).join(","),l=[];for(let p=0;p<i;p++){let f=s.map(u=>p<u.x.length?`${u.x[p]},${u.y[p]}`:",");l.push(f.join(","))}let m=c+`
14
+ `+l.join(`
15
+ `);fe(new Blob([m],{type:"text/csv"}),a)}},[]),r=pe((n,a="spectra.json")=>{let i=n.filter(l=>l.visible!==!1).map(l=>({label:l.label,x:Array.from(l.x),y:Array.from(l.y),xUnit:l.xUnit,yUnit:l.yUnit,type:l.type})),c=JSON.stringify(i,null,2);fe(new Blob([c],{type:"application/json"}),a)},[]),o=pe((n,a,s,i,c,l="spectrum.svg")=>{let m=nt(n,a,s,{width:i,height:c});ot(m,l)},[]);return{exportPng:e,exportSvg:o,exportCsv:t,exportJson:r}}import{useMemo as wn}from"react";function it(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,c=r[i-1],l=r[i];if((s-c)*(e[l]-e[c])-(l-c)*(e[s]-e[c])>=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],c=r[n+1],l=(s-i)/(c-i);o[s]=e[i]*(1-l)+e[c]*l}}let a=new Float64Array(t);for(let s=0;s<t;s++)a[s]=e[s]-o[s];return a}function at(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 st(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 lt(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 c=e[i]-o;n+=c*c}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 ct(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=kn(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 c=0;for(let l=-n;l<=n;l++)c+=a[l+n]*e[i+l];s[i]=c}return s}function kn(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 ut(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 Cn(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 Mn={none:"Raw","min-max":"Min-Max",area:"Area Normalized",snv:"SNV",baseline:"Baseline Corrected",smooth:"Smoothed",derivative:"1st Derivative"};function Rn(e,t,r){if(t==="none")return e;let o;switch(t){case"min-max":o=at(e.y);break;case"area":o=st(e.x,e.y);break;case"snv":o=lt(e.y);break;case"baseline":o=it(e.y);break;case"smooth":o=ct(e.y,r);break;case"derivative":o=ut(e.x,e.y);break;default:return e}return{...e,y:o}}function Tn({spectra:e,mode:t,smoothWindow:r=7}){return{spectra:wn(()=>e.map(n=>Rn(n,t,r)),[e,t,r]),modeLabel:Mn[t]}}import{useCallback as de,useRef as Gt,useState as mt}from"react";function An({initialState:e,maxDepth:t=50}){let[r,o]=mt(e),n=Gt([]),a=Gt([]),[s,i]=mt(0),[c,l]=mt(0),m=de(d=>{o(h=>(n.current.push(h),n.current.length>t&&n.current.shift(),i(n.current.length),a.current=[],l(0),d))},[t]),p=de(()=>{let d=n.current.pop();return d===void 0?!1:(o(h=>(a.current.push(h),l(a.current.length),i(n.current.length),d)),!0)},[]),f=de(()=>{let d=a.current.pop();return d===void 0?!1:(o(h=>(n.current.push(h),i(n.current.length),l(a.current.length),d)),!0)},[]),u=de(()=>{o(e),n.current=[],a.current=[],i(0),l(0)},[e]);return{state:r,push:m,undo:p,redo:f,reset:u,canUndo:s>0,canRedo:c>0,undoCount:s,redoCount:c}}import{useCallback as En,useState as Ln}from"react";import{jsx as Q,jsxs as Kt}from"react/jsx-runtime";var Pn=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"}),Fn=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"}),be=e=>({display:"block",width:"100%",padding:"6px 12px",border:"none",background:"transparent",color:e==="dark"?"#d1d5db":"#374151",fontSize:12,textAlign:"left",cursor:"pointer"});function Dn({theme:e,onExportPng:t,onExportSvg:r,onExportCsv:o,onExportJson:n}){let[a,s]=Ln(!1),i=En(c=>{s(!1),c?.()},[]);return Kt("div",{style:{position:"relative",display:"inline-block"},children:[Q("button",{type:"button",style:Pn(e),onClick:()=>s(!a),"aria-label":"Export","aria-expanded":a,"aria-haspopup":"true",children:"Export"}),a&&Kt("div",{style:Fn(e),role:"menu",children:[t&&Q("button",{type:"button",role:"menuitem",style:be(e),onClick:()=>i(t),children:"PNG Image"}),r&&Q("button",{type:"button",role:"menuitem",style:be(e),onClick:()=>i(r),children:"SVG Vector"}),o&&Q("button",{type:"button",role:"menuitem",style:be(e),onClick:()=>i(o),children:"CSV Data"}),n&&Q("button",{type:"button",role:"menuitem",style:be(e),onClick:()=>i(n),children:"JSON Data"})]})]})}function Un(e,t={}){let{delimiter:r=",",includeHeader:o=!0,xRange:n,precision:a=6}=t,s=Math.min(e.x.length,e.y.length),i=[];if(o){let c=e.xUnit??"x",l=e.yUnit??"y";i.push(`${c}${r}${l}`)}for(let c=0;c<s;c++){let l=e.x[c];if(n){let p=Math.min(n[0],n[1]),f=Math.max(n[0],n[1]);if(l<p||l>f)continue}let m=e.y[c];i.push(`${l.toFixed(a)}${r}${m.toFixed(a)}`)}return i.join(`
16
+ `)}function $n(e,t={}){let{delimiter:r=",",includeHeader:o=!0,xRange:n,precision:a=6}=t;if(e.length===0)return"";let s=e[0],i=Math.min(...e.map(l=>Math.min(l.x.length,l.y.length))),c=[];if(o){let l=[s.xUnit??"x",...e.map(m=>m.label)];c.push(l.join(r))}for(let l=0;l<i;l++){let m=s.x[l];if(n){let f=Math.min(n[0],n[1]),u=Math.max(n[0],n[1]);if(m<f||m>u)continue}let p=[m.toFixed(a),...e.map(f=>f.y[l].toFixed(a))];c.push(p.join(r))}return c.join(`
17
+ `)}function Nn(e,t={}){let{xRange:r,precision:o=6}=t,n=Math.min(e.x.length,e.y.length),a=[],s=[];for(let i=0;i<n;i++){let c=e.x[i];if(r){let l=Math.min(r[0],r[1]),m=Math.max(r[0],r[1]);if(c<l||c>m)continue}a.push(parseFloat(c.toFixed(o))),s.push(parseFloat(e.y[i].toFixed(o)))}return JSON.stringify({id:e.id,label:e.label,xUnit:e.xUnit,yUnit:e.yUnit,type:e.type,x:a,y:s},null,2)}function In(e,t,r="text/plain"){let o=new Blob([e],{type:r}),n=URL.createObjectURL(o),a=document.createElement("a");a.href=n,a.download=t,a.click(),URL.revokeObjectURL(n)}var ee=0;function On(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-${++ee}`,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 zn(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-${++ee}`,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 Vn(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-${++ee}`,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 Bn(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,c=0,l=0;for(let p=0;p<r;p++){let f=e.y[p]-a,u=t.y[p]-s;i+=f*u,c+=f*f,l+=u*u}let m=Math.sqrt(c*l);return m===0?0:i/m}function Hn(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-${++ee}`,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 Zn(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],c=0,l=r-1;for(;c<l-1;){let d=c+l>>>1;a?e.x[d]<=i?c=d:l=d:e.x[d]>=i?c=d:l=d}let m=e.x[c],p=e.x[l],f=e.y[c],u=e.y[l];if(m===p)n[s]=f;else{let d=(i-m)/(p-m);n[s]=f+d*(u-f)}}return{...e,id:`interp-${++ee}`,x:new Float64Array(t),y:n}}var _n=0,Xn=1,Wn=4,jn=128,Yn={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"},Gn={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 Kn(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 Jn(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),c=t.getFloat64(8,!0),l=t.getFloat64(16,!0),m=t.getUint32(24,!0),p=Yn[a]??"Arbitrary",f=Gn[s]??"Arbitrary",u=new Uint8Array(e,30,130),d=qn(u),h=(o&Wn)!==0,g=(o&jn)!==0,b=(o&Xn)!==0,x=Kn(a,s),S=null;if(!g&&i>0){S=new Float64Array(i);let w=i>1?(l-c)/(i-1):0;for(let M=0;M<i;M++)S[M]=c+M*w}let v=[],y=512,k=null;if(g&&!h){k=new Float64Array(i);for(let w=0;w<i;w++)k[w]=t.getFloat32(y,!0),y+=4}let C=h?m:1;for(let w=0;w<C;w++){let M,T,L=i;if(h){if(y+32>e.byteLength)break;let P=t.getFloat32(y+4,!0),ge=t.getFloat32(y+8,!0);if(L=t.getUint32(y+12,!0)||i,y+=32,g){M=new Float64Array(L);for(let O=0;O<L&&!(y+4>e.byteLength);O++)M[O]=t.getFloat32(y,!0),y+=4}else if(S)M=S;else{M=new Float64Array(L);let O=L>1?(ge-P)/(L-1):0;for(let z=0;z<L;z++)M[z]=P+z*O}}else M=k??S??new Float64Array(0);if(T=new Float64Array(L),b)for(let P=0;P<L&&!(y+2>e.byteLength);P++)T[P]=t.getInt16(y,!0),y+=2;else for(let P=0;P<L&&!(y+4>e.byteLength);P++)T[P]=t.getFloat32(y,!0),y+=4;v.push({id:`spc-${++_n}`,label:d||`SPC Spectrum ${w+1}`,x:M,y:T,xUnit:p,yUnit:f,type:x,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 qn(e){let t=e.indexOf(0),r=t>=0?e.slice(0,t):e;return new TextDecoder("ascii").decode(r).trim()}export{$e as AnnotationLayer,G as AxisLayer,Ue as Crosshair,Mt as DARK_THEME,sn as DataTable,Be as DropZone,Dn as ExportMenu,Zr as KEYBOARD_SHORTCUTS,wt as LIGHT_THEME,Yt as LINE_DASH_PATTERNS,le as Legend,tn as Minimap,Le as PeakMarkers,Fe as RegionSelector,we as SPECTRUM_COLORS,Kr as SpectraView,Y as SpectrumCanvas,He as StackedView,ze as Toolbar,nn as Tooltip,zn as addSpectra,it as baselineRubberBand,se as binarySearchClosest,ke as computeXExtent,W as computeYExtent,Bn as correlationCoefficient,Ce as createXScale,j as createYScale,ut as derivative1st,Cn as derivative2nd,Je as detectPeaks,On as differenceSpectrum,In as downloadString,ot as downloadSvg,je as generateChartDescription,nt as generateSvg,A as getSpectrumColor,U as getThemeColors,Zn as interpolateToGrid,Ae as lttbDownsample,$n as multiSpectraToCsv,st as normalizeArea,at as normalizeMinMax,lt as normalizeSNV,qe as parseCsv,dn as parseCsvMulti,tt as parseJcamp,et as parseJson,Jn as parseSpc,Hr as prefersReducedMotion,Hn as residualSpectrum,Vn as scaleSpectrum,ct as smoothSavitzkyGolay,Ne as snapToNearestSpectrum,Un as spectrumToCsv,Nn as spectrumToJson,vn as useExport,An as useHistory,We as useKeyboardNavigation,Tn as useNormalization,pn as usePeakPicking,_e as useRegionSelect,Xe as useResizeObserver,Sn as useSpectrumData,Te as useZoomPan};
16
18
  //# sourceMappingURL=index.js.map