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