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