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