@wafertools/wafermap 0.24.2 → 0.25.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +5 -0
  3. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  4. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +5 -0
  5. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
  6. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +5 -0
  7. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/capability.d.ts +5 -0
  9. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +24 -5
  11. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/correlation.d.ts +5 -0
  13. package/dist/packages/canvas-adapter/charts/correlation.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/histogram.d.ts +5 -0
  15. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  16. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +5 -0
  17. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  18. package/dist/packages/canvas-adapter/charts/scatter.d.ts +5 -0
  19. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  20. package/dist/packages/canvas-adapter/dieList.d.ts +9 -0
  21. package/dist/packages/canvas-adapter/dieList.js +6 -6
  22. package/dist/packages/canvas-adapter/insightsTab.d.ts +5 -0
  23. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  24. package/dist/packages/canvas-adapter/metadataBadge.d.ts +4 -0
  25. package/dist/packages/canvas-adapter/metadataBadge.js +1 -1
  26. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +14 -1
  27. package/dist/packages/canvas-adapter/renderWaferGallery.js +7 -7
  28. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +22 -0
  29. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  30. package/dist/packages/canvas-adapter/summaryPanel.d.ts +1 -1
  31. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  32. package/dist/packages/canvas-adapter/toCanvas.d.ts +11 -0
  33. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  34. package/dist/packages/canvas-adapter/toolbar.d.ts +57 -8
  35. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  36. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  37. package/dist/packages/canvas-adapter/userGuideHtml.js +1 -1
  38. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  39. package/dist/packages/canvas-adapter/version.js +1 -1
  40. package/dist/packages/canvas-adapter/warnings.d.ts +1 -1
  41. package/dist/packages/canvas-adapter/warnings.js +1 -1
  42. package/dist/packages/renderer/buildView.d.ts +22 -0
  43. package/dist/packages/renderer/buildView.js +1 -1
  44. package/dist/packages/stats/renderSummaryReport.js +1 -1
  45. package/package.json +5 -5
@@ -1 +1 @@
1
- import{buildTestHistogramData as Ht,buildTestHistogramSeries as At}from"../../stats/histogram.js";import{CLR as lt}from"../toolbar.js";import{fmt as st}from"../../renderer/fmt.js";import{QUANTITY as Wt,categorical as Ct}from"./palette.js";import{cardShell as Et,observeResize as xt,makeTooltip as Pt,positionChartTooltip as bt,makeTestSelect as $t,makeWaferSelect as Bt,makeToggle as Rt,renderEmptyState as pt,chartFillHeight as yt,applyCanvasFlow as Mt,resolveChartCanvasColors as Tt,makeAxisFormat as vt,PADDING as tt}from"./chartShell.js";const mt=230,St=36,at=18;function kt(s,R,N,z,rt,I,b,u=4){const j=Math.max(1,I/u),F=Math.pow(10,Math.floor(Math.log10(j))),f=j/F,G=(f<=1?1:f<=2?2:f<=5?5:10)*F,$=z-N;s.save(),s.font="10px system-ui, sans-serif",s.textAlign="right",s.textBaseline="middle";for(let K=0;K<=I+1e-9;K+=G){const _=z-K/(I||1)*$;s.strokeStyle=b.grid,s.lineWidth=1,s.beginPath(),s.moveTo(R,_),s.lineTo(R+rt,_),s.stroke(),s.fillStyle=b.text,s.fillText(String(Math.round(K)),R-4,_)}s.strokeStyle=b.axis,s.lineWidth=1,s.beginPath(),s.moveTo(R,N),s.lineTo(R,z),s.stroke(),s.restore()}export function renderHistogramPanel(s){const{title:R="Test value distribution",items:N,testDefs:z,onSaveImage:rt,groups:I}=s,{card:b,body:u,controlsRow:j}=Et(R,rt),F=z.filter(a=>a.testNumber!==void 0);let f=s.selectedTestNumber??F[0]?.testNumber??null,G=null,$=!1;const K=$t(F,f,a=>{f=a,U()},{maxWidth:"200px"});j.appendChild(K);const _=Bt(N,G,a=>{G=a,U()});j.appendChild(_),j.appendChild(Rt("Axis includes limits",$,a=>{$=a,U()}));const A=Pt(b);let J=null,y=null;function et(a){const w=z.find(O=>O.testNumber===a);return{unit:w?.unit,limitLow:w?.limitLow,limitHigh:w?.limitHigh}}function U(){if(J?.disconnect(),J=null,u.innerHTML="",f===null){pt(u,"No parametric test data available for a histogram.");return}const a=I&&I.length>0?At(I,f,16,$?et(f).limitLow:void 0,$?et(f).limitHigh:void 0):null;if(_.style.display=a?"none":"",a){if(a.series.length===0){pt(u,"No parametric test data available for a histogram.");return}Lt(a);return}const w=G!==null?[N[G]]:N,{unit:O,limitLow:nt,limitHigh:x}=et(f),i=Ht(w,f,16,$?nt:void 0,$?x:void 0);if(i.length===0){pt(u,"No parametric test data available for a histogram.");return}const D=Math.max(...i.map(p=>p.count),1),P=document.createElement("div");Object.assign(P.style,{fontSize:"12px",color:lt.label,marginBottom:"2px"}),P.textContent=`max ${D} dies/bucket`,u.appendChild(P);const h=document.createElement("canvas");h.style.display="block",h.style.cursor="default",u.appendChild(h);let v=-1;const g=window.devicePixelRatio||1,B=i[0].rangeLow,X=i[i.length-1].rangeHigh,ot=X-B||1,Z=vt(Math.max(Math.abs(B),Math.abs(X)),O);function Y(p){const m=tt+36,e=h.clientWidth-m-tt,n=p-St-at;return{plotX:m,plotMaxWidth:Math.max(10,e),plotMaxHeight:n,plotTop:at}}function ct(p){const{plotX:m,plotMaxWidth:e}=Y(mt);if(p<m||p>m+e)return-1;const n=Math.floor((p-m)/e*i.length);return n>=0&&n<i.length?n:-1}function H(){Mt(h,P.offsetHeight);const p=u.clientWidth,m=yt(b,u,h,mt);h.width=Math.max(1,Math.floor(p*g)),h.height=Math.max(1,Math.floor(m*g)),h.style.width=`${p}px`,h.style.height=`${m}px`;const e=h.getContext("2d");e.setTransform(g,0,0,g,0,0),e.clearRect(0,0,p,m),e.font="11px system-ui, sans-serif";const n=Tt(b),{plotX:c,plotMaxWidth:t,plotMaxHeight:d,plotTop:o}=Y(m),l=o+d,S=t/i.length;kt(e,c,o,l,t,D,{text:n.textMuted,axis:n.border,grid:n.border}),i.forEach((M,C)=>{const k=M.count/D*d,r=c+C*S,L=l-k;e.fillStyle=C===v?n.text:Wt,e.globalAlpha=C===v?1:.7,e.fillRect(r+1,L,Math.max(1,S-2),k),e.globalAlpha=1,C===v&&(e.fillStyle=n.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${M.count}`,r+S/2,L-2))});const V=M=>c+(M-B)/ot*t;e.font="10px system-ui, sans-serif";for(const[M,C]of[[nt,"LSL"],[x,"USL"]]){if(M===void 0)continue;const k=V(M);e.strokeStyle=n.warnBorder,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(k,o),e.lineTo(k,l),e.stroke(),e.setLineDash([]),e.fillStyle=n.warnBorder,e.textAlign="center",e.textBaseline="bottom",e.fillText(C,k,o-2)}e.font="11px system-ui, sans-serif",e.strokeStyle=n.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,l),e.lineTo(c+t,l),e.stroke(),e.fillStyle=n.textMuted,e.textAlign="center",e.textBaseline="top";const W=Math.max(2,Math.floor(t/44)),dt=Math.ceil(i.length/W),ht=Math.max(1,dt);for(let M=0;M<=i.length;M+=ht){const C=M<i.length?i[M].rangeLow:i[i.length-1].rangeHigh,k=c+M*S;e.beginPath(),e.moveTo(k,l),e.lineTo(k,l+4),e.stroke(),e.fillText(Z.tick(C),k,l+6)}Z.unitLabel&&(e.save(),e.font="10px system-ui, sans-serif",e.fillStyle=n.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${Z.unitLabel})`,c+t,l+20),e.restore())}h.addEventListener("mousemove",p=>{const m=h.getBoundingClientRect(),e=ct(p.clientX-m.left);if(e!==v&&(v=e,H()),e>=0){const n=i[e];A.innerHTML=`<strong>${st(n.rangeLow,O,"engineering")} \u2013 ${st(n.rangeHigh,O,"engineering")}</strong><br>${n.count} dies`,A.style.display="block",bt(A,b,p.clientX,p.clientY)}else A.style.display="none"}),h.addEventListener("mouseleave",()=>{v!==-1&&(v=-1,H()),A.style.display="none"}),J=xt(b,()=>H()),H()}function Lt(a){const{unit:w,limitLow:O,limitHigh:nt}=et(f),x=a.ranges,i=a.series,D=Ct;y&&!i.some(n=>n.groupKey===y)&&(y=null);const P=Math.max(1,...i.flatMap(n=>n.counts)),h=document.createElement("div");Object.assign(h.style,{fontSize:"12px",color:lt.label,marginBottom:"2px"}),h.textContent=`${i.length} groups \xB7 max ${P} dies/bucket`,u.appendChild(h);const v=document.createElement("div");Object.assign(v.style,{display:"flex",flexWrap:"wrap",gap:"4px 12px",marginBottom:"4px"}),i.forEach((n,c)=>{const t=document.createElement("button");t.type="button",t.title=`${n.groupKey} \u2014 click to emphasize (dim the rest)`,Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:"5px",fontSize:"11px",padding:"1px 4px",border:"none",background:"none",cursor:"pointer",color:lt.text,borderRadius:"3px"});const d=document.createElement("span");Object.assign(d.style,{width:"10px",height:"10px",borderRadius:"2px",background:D(c),flex:"0 0 auto"});const o=document.createElement("span");o.textContent=n.groupKey,t.append(d,o);const l=y!==null&&y!==n.groupKey;t.style.opacity=l?"0.45":"1",y===n.groupKey&&(t.style.background=lt.bgHover),t.setAttribute("aria-pressed",y===n.groupKey?"true":"false"),t.addEventListener("click",()=>{y=y===n.groupKey?null:n.groupKey,U()}),v.appendChild(t)}),u.appendChild(v);const g=document.createElement("canvas");g.style.display="block",g.style.cursor="default",u.appendChild(g);const B=window.devicePixelRatio||1,X=x[0].rangeLow,ot=x[x.length-1].rangeHigh,Z=ot-X||1,Y=vt(Math.max(Math.abs(X),Math.abs(ot)),w),ct=()=>h.offsetHeight+v.offsetHeight;let H=-1,p={plotX:tt+36,plotMaxWidth:10,barWidth:10};function m(){Mt(g,ct());const n=u.clientWidth,c=yt(b,u,g,mt);g.width=Math.max(1,Math.floor(n*B)),g.height=Math.max(1,Math.floor(c*B)),g.style.width=`${n}px`,g.style.height=`${c}px`;const t=g.getContext("2d");t.setTransform(B,0,0,B,0,0),t.clearRect(0,0,n,c),t.font="11px system-ui, sans-serif";const d=Tt(b),o=tt+36,l=Math.max(10,n-o-tt),S=at,V=c-St-at,T=S+V,W=l/x.length;p={plotX:o,plotMaxWidth:l,barWidth:W},kt(t,o,S,T,l,P,{text:d.textMuted,axis:d.border,grid:d.border}),H>=0&&(t.fillStyle=d.bgHover,t.globalAlpha=.5,t.fillRect(o+H*W,S,W,V),t.globalAlpha=1);const dt=i.map((r,L)=>({s:r,i:L})).sort((r,L)=>(r.s.groupKey===y?1:0)-(L.s.groupKey===y?1:0));for(const{s:r,i:L}of dt){const E=y===r.groupKey,ut=y!==null&&!E,ft=D(L);t.strokeStyle=ft,t.fillStyle=ft,t.lineWidth=E?2:1,t.globalAlpha=ut?.12:y?.32:.22,t.beginPath(),t.moveTo(o,T),r.counts.forEach((gt,it)=>{const Q=o+it*W,q=T-gt/P*V;t.lineTo(Q,q),t.lineTo(Q+W,q)}),t.lineTo(o+l,T),t.closePath(),t.fill(),t.globalAlpha=ut?.3:1,t.beginPath(),r.counts.forEach((gt,it)=>{const Q=o+it*W,q=T-gt/P*V;it===0?t.moveTo(Q,q):t.lineTo(Q,q),t.lineTo(Q+W,q)}),t.stroke()}t.globalAlpha=1;const ht=r=>o+(r-X)/Z*l;t.font="10px system-ui, sans-serif";for(const[r,L]of[[O,"LSL"],[nt,"USL"]]){if(r===void 0)continue;const E=ht(r);t.strokeStyle=d.warnBorder,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(E,S),t.lineTo(E,T),t.stroke(),t.setLineDash([]),t.fillStyle=d.warnBorder,t.textAlign="center",t.textBaseline="bottom",t.fillText(L,E,S-2)}t.font="11px system-ui, sans-serif",t.strokeStyle=d.border,t.lineWidth=1,t.beginPath(),t.moveTo(o,T),t.lineTo(o+l,T),t.stroke(),t.fillStyle=d.textMuted,t.textAlign="center",t.textBaseline="top";const C=Math.max(2,Math.floor(l/44)),k=Math.max(1,Math.ceil(x.length/C));for(let r=0;r<=x.length;r+=k){const L=r<x.length?x[r].rangeLow:x[x.length-1].rangeHigh,E=o+r*W;t.beginPath(),t.moveTo(E,T),t.lineTo(E,T+4),t.stroke(),t.fillText(Y.tick(L),E,T+6)}Y.unitLabel&&(t.save(),t.font="10px system-ui, sans-serif",t.fillStyle=d.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${Y.unitLabel})`,o+l,T+20),t.restore())}function e(n){const{plotX:c,plotMaxWidth:t,barWidth:d}=p;if(n<c||n>c+t)return-1;const o=Math.floor((n-c)/d);return o>=0&&o<x.length?o:-1}g.addEventListener("mousemove",n=>{const c=g.getBoundingClientRect(),t=e(n.clientX-c.left);if(t!==H&&(H=t,m()),t>=0){const d=x[t],o=i.map((l,S)=>`<span style="display:inline-block;width:9px;height:9px;border-radius:2px;background:${D(S)};margin-right:5px;"></span>${l.groupKey}: ${l.counts[t]}`).join("<br>");A.innerHTML=`<strong>${st(d.rangeLow,w,"engineering")} \u2013 ${st(d.rangeHigh,w,"engineering")}</strong><br>${o}`,A.style.display="block",bt(A,b,n.clientX,n.clientY)}else A.style.display="none"}),g.addEventListener("mouseleave",()=>{H!==-1&&(H=-1,m()),A.style.display="none"}),J=xt(b,()=>m()),m()}U();function wt(a){!F.some(w=>w.testNumber===a)||a===f||(f=a,K.value=String(a),U())}return{card:b,setTest:wt,destroy:()=>J?.disconnect()}}
1
+ import{buildTestHistogramData as Ht,buildTestHistogramSeries as At}from"../../stats/histogram.js";import{CLR as lt}from"../toolbar.js";import{fmt as st}from"../../renderer/fmt.js";import{QUANTITY as Ct,categorical as Wt}from"./palette.js";import{cardShell as Dt,observeResize as xt,makeTooltip as Et,attachChartTip as Pt,positionChartTooltip as bt,makeTestSelect as $t,makeWaferSelect as Bt,makeToggle as Rt,renderEmptyState as pt,chartFillHeight as yt,applyCanvasFlow as Mt,resolveChartCanvasColors as Tt,makeAxisFormat as wt,PADDING as tt}from"./chartShell.js";const mt=230,vt=36,at=18;function St(l,B,N,z,rt,R,i,f=4){const j=Math.max(1,R/f),F=Math.pow(10,Math.floor(Math.log10(j))),x=j/F,G=(x<=1?1:x<=2?2:x<=5?5:10)*F,P=z-N;l.save(),l.font="10px system-ui, sans-serif",l.textAlign="right",l.textBaseline="middle";for(let I=0;I<=R+1e-9;I+=G){const _=z-I/(R||1)*P;l.strokeStyle=i.grid,l.lineWidth=1,l.beginPath(),l.moveTo(B,_),l.lineTo(B+rt,_),l.stroke(),l.fillStyle=i.text,l.fillText(String(Math.round(I)),B-4,_)}l.strokeStyle=i.axis,l.lineWidth=1,l.beginPath(),l.moveTo(B,N),l.lineTo(B,z),l.stroke(),l.restore()}export function renderHistogramPanel(l){const{title:B="Test value distribution",items:N,testDefs:z,onSaveImage:rt,groups:R}=l,{card:i,body:f,controlsRow:j}=Dt(B,rt,l.ownerDocument),F=z.filter(r=>r.testNumber!==void 0);let x=l.selectedTestNumber??F[0]?.testNumber??null,G=null,P=!1;const I=$t(F,x,r=>{x=r,U()},{maxWidth:"200px",ownerDocument:i.ownerDocument});j.appendChild(I);const _=Bt(N,G,r=>{G=r,U()},{ownerDocument:i.ownerDocument});j.appendChild(_),j.appendChild(Rt("Axis includes limits",P,r=>{P=r,U()},i.ownerDocument));const L=Et(i);let J=null,y=null;function et(r){const H=z.find(K=>K.testNumber===r);return{unit:H?.unit,limitLow:H?.limitLow,limitHigh:H?.limitHigh}}function U(){if(J?.disconnect(),J=null,f.innerHTML="",x===null){pt(f,"No parametric test data available for a histogram.");return}const r=R&&R.length>0?At(R,x,16,P?et(x).limitLow:void 0,P?et(x).limitHigh:void 0):null;if(_.style.display=r?"none":"",r){if(r.series.length===0){pt(f,"No parametric test data available for a histogram.");return}kt(r);return}const H=G!==null?[N[G]]:N,{unit:K,limitLow:nt,limitHigh:b}=et(x),s=Ht(H,x,16,P?nt:void 0,P?b:void 0);if(s.length===0){pt(f,"No parametric test data available for a histogram.");return}const O=Math.max(...s.map(m=>m.count),1),E=i.ownerDocument.createElement("div");Object.assign(E.style,{fontSize:"12px",color:lt.label,marginBottom:"2px"}),E.textContent=`max ${O} dies/bucket`,f.appendChild(E);const g=i.ownerDocument.createElement("canvas");g.style.display="block",g.style.cursor="default",f.appendChild(g);let w=-1;const p=window.devicePixelRatio||1,$=s[0].rangeLow,X=s[s.length-1].rangeHigh,ot=X-$||1,Z=wt(Math.max(Math.abs($),Math.abs(X)),K);function Y(m){const u=tt+36,e=g.clientWidth-u-tt,n=m-vt-at;return{plotX:u,plotMaxWidth:Math.max(10,e),plotMaxHeight:n,plotTop:at}}function ct(m){const{plotX:u,plotMaxWidth:e}=Y(mt);if(m<u||m>u+e)return-1;const n=Math.floor((m-u)/e*s.length);return n>=0&&n<s.length?n:-1}function A(){Mt(g,E.offsetHeight);const m=f.clientWidth,u=yt(i,f,g,mt);g.width=Math.max(1,Math.floor(m*p)),g.height=Math.max(1,Math.floor(u*p)),g.style.width=`${m}px`,g.style.height=`${u}px`;const e=g.getContext("2d");e.setTransform(p,0,0,p,0,0),e.clearRect(0,0,m,u),e.font="11px system-ui, sans-serif";const n=Tt(i),{plotX:d,plotMaxWidth:t,plotMaxHeight:h,plotTop:o}=Y(u),a=o+h,v=t/s.length;St(e,d,o,a,t,O,{text:n.textMuted,axis:n.border,grid:n.border}),s.forEach((M,W)=>{const S=M.count/O*h,c=d+W*v,k=a-S;e.fillStyle=W===w?n.text:Ct,e.globalAlpha=W===w?1:.7,e.fillRect(c+1,k,Math.max(1,v-2),S),e.globalAlpha=1,W===w&&(e.fillStyle=n.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${M.count}`,c+v/2,k-2))});const V=M=>d+(M-$)/ot*t;e.font="10px system-ui, sans-serif";for(const[M,W]of[[nt,"LSL"],[b,"USL"]]){if(M===void 0)continue;const S=V(M);e.strokeStyle=n.warnBorder,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(S,o),e.lineTo(S,a),e.stroke(),e.setLineDash([]),e.fillStyle=n.warnBorder,e.textAlign="center",e.textBaseline="bottom",e.fillText(W,S,o-2)}e.font="11px system-ui, sans-serif",e.strokeStyle=n.border,e.lineWidth=1,e.beginPath(),e.moveTo(d,a),e.lineTo(d+t,a),e.stroke(),e.fillStyle=n.textMuted,e.textAlign="center",e.textBaseline="top";const C=Math.max(2,Math.floor(t/44)),dt=Math.ceil(s.length/C),ht=Math.max(1,dt);for(let M=0;M<=s.length;M+=ht){const W=M<s.length?s[M].rangeLow:s[s.length-1].rangeHigh,S=d+M*v;e.beginPath(),e.moveTo(S,a),e.lineTo(S,a+4),e.stroke(),e.fillText(Z.tick(W),S,a+6)}Z.unitLabel&&(e.save(),e.font="10px system-ui, sans-serif",e.fillStyle=n.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${Z.unitLabel})`,d+t,a+20),e.restore())}g.addEventListener("mousemove",m=>{const u=g.getBoundingClientRect(),e=ct(m.clientX-u.left);if(e!==w&&(w=e,A()),e>=0){const n=s[e];L.innerHTML=`<strong>${st(n.rangeLow,K,"engineering")} \u2013 ${st(n.rangeHigh,K,"engineering")}</strong><br>${n.count} dies`,L.style.display="block",bt(L,i,m.clientX,m.clientY)}else L.style.display="none"}),g.addEventListener("mouseleave",()=>{w!==-1&&(w=-1,A()),L.style.display="none"}),J=xt(i,()=>A()),A()}function kt(r){const{unit:H,limitLow:K,limitHigh:nt}=et(x),b=r.ranges,s=r.series,O=Wt;y&&!s.some(n=>n.groupKey===y)&&(y=null);const E=Math.max(1,...s.flatMap(n=>n.counts)),g=i.ownerDocument.createElement("div");Object.assign(g.style,{fontSize:"12px",color:lt.label,marginBottom:"2px"}),g.textContent=`${s.length} groups \xB7 max ${E} dies/bucket`,f.appendChild(g);const w=i.ownerDocument.createElement("div");Object.assign(w.style,{display:"flex",flexWrap:"wrap",gap:"4px 12px",marginBottom:"4px"}),s.forEach((n,d)=>{const t=i.ownerDocument.createElement("button");t.type="button",Pt(t,i,L,`${n.groupKey} \u2014 click to emphasize (dim the rest)`),Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:"5px",fontSize:"11px",padding:"1px 4px",border:"none",background:"none",cursor:"pointer",color:lt.text,borderRadius:"3px"});const h=i.ownerDocument.createElement("span");Object.assign(h.style,{width:"10px",height:"10px",borderRadius:"2px",background:O(d),flex:"0 0 auto"});const o=i.ownerDocument.createElement("span");o.textContent=n.groupKey,t.append(h,o);const a=y!==null&&y!==n.groupKey;t.style.opacity=a?"0.45":"1",y===n.groupKey&&(t.style.background=lt.bgHover),t.setAttribute("aria-pressed",y===n.groupKey?"true":"false"),t.addEventListener("click",()=>{y=y===n.groupKey?null:n.groupKey,U()}),w.appendChild(t)}),f.appendChild(w);const p=i.ownerDocument.createElement("canvas");p.style.display="block",p.style.cursor="default",f.appendChild(p);const $=window.devicePixelRatio||1,X=b[0].rangeLow,ot=b[b.length-1].rangeHigh,Z=ot-X||1,Y=wt(Math.max(Math.abs(X),Math.abs(ot)),H),ct=()=>g.offsetHeight+w.offsetHeight;let A=-1,m={plotX:tt+36,plotMaxWidth:10,barWidth:10};function u(){Mt(p,ct());const n=f.clientWidth,d=yt(i,f,p,mt);p.width=Math.max(1,Math.floor(n*$)),p.height=Math.max(1,Math.floor(d*$)),p.style.width=`${n}px`,p.style.height=`${d}px`;const t=p.getContext("2d");t.setTransform($,0,0,$,0,0),t.clearRect(0,0,n,d),t.font="11px system-ui, sans-serif";const h=Tt(i),o=tt+36,a=Math.max(10,n-o-tt),v=at,V=d-vt-at,T=v+V,C=a/b.length;m={plotX:o,plotMaxWidth:a,barWidth:C},St(t,o,v,T,a,E,{text:h.textMuted,axis:h.border,grid:h.border}),A>=0&&(t.fillStyle=h.bgHover,t.globalAlpha=.5,t.fillRect(o+A*C,v,C,V),t.globalAlpha=1);const dt=s.map((c,k)=>({s:c,i:k})).sort((c,k)=>(c.s.groupKey===y?1:0)-(k.s.groupKey===y?1:0));for(const{s:c,i:k}of dt){const D=y===c.groupKey,ut=y!==null&&!D,ft=O(k);t.strokeStyle=ft,t.fillStyle=ft,t.lineWidth=D?2:1,t.globalAlpha=ut?.12:y?.32:.22,t.beginPath(),t.moveTo(o,T),c.counts.forEach((gt,it)=>{const Q=o+it*C,q=T-gt/E*V;t.lineTo(Q,q),t.lineTo(Q+C,q)}),t.lineTo(o+a,T),t.closePath(),t.fill(),t.globalAlpha=ut?.3:1,t.beginPath(),c.counts.forEach((gt,it)=>{const Q=o+it*C,q=T-gt/E*V;it===0?t.moveTo(Q,q):t.lineTo(Q,q),t.lineTo(Q+C,q)}),t.stroke()}t.globalAlpha=1;const ht=c=>o+(c-X)/Z*a;t.font="10px system-ui, sans-serif";for(const[c,k]of[[K,"LSL"],[nt,"USL"]]){if(c===void 0)continue;const D=ht(c);t.strokeStyle=h.warnBorder,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(D,v),t.lineTo(D,T),t.stroke(),t.setLineDash([]),t.fillStyle=h.warnBorder,t.textAlign="center",t.textBaseline="bottom",t.fillText(k,D,v-2)}t.font="11px system-ui, sans-serif",t.strokeStyle=h.border,t.lineWidth=1,t.beginPath(),t.moveTo(o,T),t.lineTo(o+a,T),t.stroke(),t.fillStyle=h.textMuted,t.textAlign="center",t.textBaseline="top";const W=Math.max(2,Math.floor(a/44)),S=Math.max(1,Math.ceil(b.length/W));for(let c=0;c<=b.length;c+=S){const k=c<b.length?b[c].rangeLow:b[b.length-1].rangeHigh,D=o+c*C;t.beginPath(),t.moveTo(D,T),t.lineTo(D,T+4),t.stroke(),t.fillText(Y.tick(k),D,T+6)}Y.unitLabel&&(t.save(),t.font="10px system-ui, sans-serif",t.fillStyle=h.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${Y.unitLabel})`,o+a,T+20),t.restore())}function e(n){const{plotX:d,plotMaxWidth:t,barWidth:h}=m;if(n<d||n>d+t)return-1;const o=Math.floor((n-d)/h);return o>=0&&o<b.length?o:-1}p.addEventListener("mousemove",n=>{const d=p.getBoundingClientRect(),t=e(n.clientX-d.left);if(t!==A&&(A=t,u()),t>=0){const h=b[t],o=s.map((a,v)=>`<span style="display:inline-block;width:9px;height:9px;border-radius:2px;background:${O(v)};margin-right:5px;"></span>${a.groupKey}: ${a.counts[t]}`).join("<br>");L.innerHTML=`<strong>${st(h.rangeLow,H,"engineering")} \u2013 ${st(h.rangeHigh,H,"engineering")}</strong><br>${o}`,L.style.display="block",bt(L,i,n.clientX,n.clientY)}else L.style.display="none"}),p.addEventListener("mouseleave",()=>{A!==-1&&(A=-1,u()),L.style.display="none"}),J=xt(i,()=>u()),u()}U();function Lt(r){!F.some(H=>H.testNumber===r)||r===x||(x=r,I.value=String(r),U())}return{card:i,setTest:Lt,destroy:()=>J?.disconnect()}}
@@ -8,6 +8,11 @@ export interface RegionYieldDiagramOptions {
8
8
  rows: RegionYieldDatum[];
9
9
  colorScheme?: string;
10
10
  onSaveImage?: SaveImageHandler;
11
+ /** Document to build this panel's DOM into. Default `document` — pass the
12
+ * host's own `ownerDocument` when the container might live in a
13
+ * different document (e.g. a gallery card detached into its own popup
14
+ * window). */
15
+ ownerDocument?: Document;
11
16
  }
12
17
  export interface RegionYieldDiagramHandle {
13
18
  card: HTMLElement;
@@ -1 +1 @@
1
- import{parseRegionKey as W}from"../../stats/regions.js";import{yieldFill as E}from"./palette.js";import{cardShell as A,resolveChartCanvasColors as N,observeResize as F,growCardToFitContent as D,renderEmptyState as $}from"./chartShell.js";const k=320,j=220,z={SE:[0,Math.PI/2],SW:[Math.PI/2,Math.PI],NW:[Math.PI,1.5*Math.PI],NE:[1.5*Math.PI,2*Math.PI]};function O(t,o,n,d,c,a){t.beginPath(),t.moveTo(o+a,n),t.arcTo(o+d,n,o+d,n+c,a),t.arcTo(o+d,n+c,o,n+c,a),t.arcTo(o,n+c,o,n,a),t.arcTo(o,n,o+d,n,a),t.closePath()}function w(t,o,n,d,c,a){t.font="10px system-ui, sans-serif";const g=t.measureText(d).width;t.font="700 11px system-ui, sans-serif";const b=t.measureText(c).width,l=Math.max(g,b)+12,M=30;t.fillStyle=a.bg,O(t,o-l/2,n-M/2,l,M,4),t.fill(),t.strokeStyle=a.border,t.lineWidth=1,t.stroke(),t.textAlign="center",t.textBaseline="middle",t.fillStyle=a.textMuted,t.font="10px system-ui, sans-serif",t.fillText(d,o,n-7),t.fillStyle=a.text,t.font="700 11px system-ui, sans-serif",t.fillText(c,o,n+7)}export function renderRegionYieldDiagram(t){const{mode:o,rows:n,onSaveImage:d}=t,c=t.title??(o==="ring"?"Ring yield":"Quadrant yield"),{card:a,body:g}=A(c,d),b=n.length>0;if(!b)return $(g,"No bin data available for regional yield."),{card:a,hasData:b,destroy:()=>{}};Object.assign(g.style,{display:"flex",justifyContent:"center",alignItems:"center"});const l=document.createElement("canvas");Object.assign(l.style,{display:"block",flex:"none"}),g.appendChild(l);const M=window.devicePixelRatio||1;function p(){const r=Math.max(j,Math.min(g.clientWidth||k,k));D(a,g,r),l.width=Math.max(1,Math.floor(r*M)),l.height=Math.max(1,Math.floor(r*M)),l.style.width=`${r}px`,l.style.height=`${r}px`;const e=l.getContext("2d");e.setTransform(M,0,0,M,0,0),e.clearRect(0,0,r,r);const u=N(a),T=E,h=r/2,f=r/2,m=r/2-34;if(o==="ring"){const i=n.length;for(let s=0;s<i;s++){const y=s/i*m,P=(s+1)/i*m;e.beginPath(),e.arc(h,f,P,0,2*Math.PI),e.arc(h,f,y,0,2*Math.PI,!0),e.closePath(),e.fillStyle=T(n[s].yieldPercent),e.fill(),e.strokeStyle=u.border,e.lineWidth=1,e.stroke()}e.beginPath(),e.arc(h,f,m,0,2*Math.PI),e.strokeStyle=u.text,e.lineWidth=1.5,e.stroke();for(let s=0;s<i;s++){const y=(s+.5)/i*m,P=-Math.PI/2+s/i*2*Math.PI,I=h+Math.cos(P)*y,S=f+Math.sin(P)*y;w(e,I,S,n[s].label,`${n[s].yieldPercent.toFixed(1)}%`,u)}}else{for(const i of n){const s=W(i.key).quadrant,y=s?z[s]:void 0;if(!y)continue;const[P,I]=y;e.beginPath(),e.moveTo(h,f),e.arc(h,f,m,P,I),e.closePath(),e.fillStyle=T(i.yieldPercent),e.fill(),e.strokeStyle=u.border,e.lineWidth=1,e.stroke();const S=(P+I)/2,R=h+Math.cos(S)*m*.6,C=f+Math.sin(S)*m*.6;w(e,R,C,i.label,`${i.yieldPercent.toFixed(1)}%`,u)}e.beginPath(),e.arc(h,f,m,0,2*Math.PI),e.strokeStyle=u.text,e.lineWidth=1.5,e.stroke()}}const v=F(a,()=>p());return p(),{card:a,hasData:b,destroy:()=>v.disconnect()}}
1
+ import{parseRegionKey as W}from"../../stats/regions.js";import{yieldFill as E}from"./palette.js";import{cardShell as A,resolveChartCanvasColors as D,observeResize as N,growCardToFitContent as F,renderEmptyState as $}from"./chartShell.js";const T=320,j=220,z={SE:[0,Math.PI/2],SW:[Math.PI/2,Math.PI],NW:[Math.PI,1.5*Math.PI],NE:[1.5*Math.PI,2*Math.PI]};function O(t,a,n,c,d,o){t.beginPath(),t.moveTo(a+o,n),t.arcTo(a+c,n,a+c,n+d,o),t.arcTo(a+c,n+d,a,n+d,o),t.arcTo(a,n+d,a,n,o),t.arcTo(a,n,a+c,n,o),t.closePath()}function k(t,a,n,c,d,o){t.font="10px system-ui, sans-serif";const g=t.measureText(c).width;t.font="700 11px system-ui, sans-serif";const b=t.measureText(d).width,l=Math.max(g,b)+12,M=30;t.fillStyle=o.bg,O(t,a-l/2,n-M/2,l,M,4),t.fill(),t.strokeStyle=o.border,t.lineWidth=1,t.stroke(),t.textAlign="center",t.textBaseline="middle",t.fillStyle=o.textMuted,t.font="10px system-ui, sans-serif",t.fillText(c,a,n-7),t.fillStyle=o.text,t.font="700 11px system-ui, sans-serif",t.fillText(d,a,n+7)}export function renderRegionYieldDiagram(t){const{mode:a,rows:n,onSaveImage:c}=t,d=t.title??(a==="ring"?"Ring yield":"Quadrant yield"),{card:o,body:g}=A(d,c,t.ownerDocument),b=n.length>0;if(!b)return $(g,"No bin data available for regional yield."),{card:o,hasData:b,destroy:()=>{}};Object.assign(g.style,{display:"flex",justifyContent:"center",alignItems:"center"});const l=o.ownerDocument.createElement("canvas");Object.assign(l.style,{display:"block",flex:"none"}),g.appendChild(l);const M=window.devicePixelRatio||1;function p(){const r=Math.max(j,Math.min(g.clientWidth||T,T));F(o,g,r),l.width=Math.max(1,Math.floor(r*M)),l.height=Math.max(1,Math.floor(r*M)),l.style.width=`${r}px`,l.style.height=`${r}px`;const e=l.getContext("2d");e.setTransform(M,0,0,M,0,0),e.clearRect(0,0,r,r);const u=D(o),w=E,h=r/2,f=r/2,m=r/2-34;if(a==="ring"){const i=n.length;for(let s=0;s<i;s++){const y=s/i*m,P=(s+1)/i*m;e.beginPath(),e.arc(h,f,P,0,2*Math.PI),e.arc(h,f,y,0,2*Math.PI,!0),e.closePath(),e.fillStyle=w(n[s].yieldPercent),e.fill(),e.strokeStyle=u.border,e.lineWidth=1,e.stroke()}e.beginPath(),e.arc(h,f,m,0,2*Math.PI),e.strokeStyle=u.text,e.lineWidth=1.5,e.stroke();for(let s=0;s<i;s++){const y=(s+.5)/i*m,P=-Math.PI/2+s/i*2*Math.PI,I=h+Math.cos(P)*y,S=f+Math.sin(P)*y;k(e,I,S,n[s].label,`${n[s].yieldPercent.toFixed(1)}%`,u)}}else{for(const i of n){const s=W(i.key).quadrant,y=s?z[s]:void 0;if(!y)continue;const[P,I]=y;e.beginPath(),e.moveTo(h,f),e.arc(h,f,m,P,I),e.closePath(),e.fillStyle=w(i.yieldPercent),e.fill(),e.strokeStyle=u.border,e.lineWidth=1,e.stroke();const S=(P+I)/2,R=h+Math.cos(S)*m*.6,C=f+Math.sin(S)*m*.6;k(e,R,C,i.label,`${i.yieldPercent.toFixed(1)}%`,u)}e.beginPath(),e.arc(h,f,m,0,2*Math.PI),e.strokeStyle=u.text,e.lineWidth=1.5,e.stroke()}}const v=N(o,()=>p());return p(),{card:o,hasData:b,destroy:()=>v.disconnect()}}
@@ -25,6 +25,11 @@ export interface ScatterPanelOptions {
25
25
  key: string;
26
26
  items: ScatterPanelItem[];
27
27
  }[];
28
+ /** Document to build this panel's DOM into. Default `document` — pass the
29
+ * host's own `ownerDocument` when the container might live in a
30
+ * different document (e.g. a gallery card detached into its own popup
31
+ * window). */
32
+ ownerDocument?: Document;
28
33
  }
29
34
  export interface ScatterPanelHandle {
30
35
  card: HTMLElement;
@@ -1 +1 @@
1
- import{getColorScheme as ht}from"../../renderer/colorSchemes.js";import{categorical as gt}from"./palette.js";import{buildScatterData as bt,buildScatterDataGrouped as yt}from"../../stats/scatter.js";import{buildFacetTable as St}from"../../stats/facets.js";import{CLR as M}from"../toolbar.js";import{cardShell as Mt,observeResize as Tt,makeTooltip as wt,makeTestSelect as kt,makeWaferSelect as vt,chartFillHeight as Lt,applyCanvasFlow as Ct,drawAxisUnit as At,resolveChartCanvasColors as Bt,makeAxisFormat as nt}from"./chartShell.js";const r=52,Ot=16,l=16,Et=44;export function renderScatterPanel(j){const{title:ot="Test scatter",items:B,testDefs:_,colorScheme:it="default",onSaveImage:lt,groups:P}=j,{card:T,body:C,controlsRow:X}=Mt(ot,lt),R=_.filter(e=>e.testNumber!==void 0),b=!!(P&&P.length>0),Y=(P??[]).map(e=>e.key),at=new Map(Y.map((e,n)=>[e,n]));let m=j.xTestNumber??R[0]?.testNumber??null,f=j.yTestNumber??R[1]?.testNumber??m,O=null;const q=()=>O!==null?B[O]?[B[O]]:[]:B;function K(e,n,o){const i=document.createElement("label");Object.assign(i.style,{display:"inline-flex",alignItems:"center",gap:"4px",fontSize:"11px",color:M.label});const d=document.createElement("span");d.textContent=e;const p=kt(R,n,o,{maxWidth:"180px",emptyText:"No tests"});return i.append(d,p),{wrap:i,select:p}}const{wrap:st,select:rt}=K("X:",m,e=>{m=e,H()}),{wrap:ct,select:dt}=K("Y:",f,e=>{f=e,H()});X.append(st,ct),!b&&B.length>1&&X.appendChild(vt(B,O,e=>{O=e,H()}));const E=document.createElement("div");Object.assign(E.style,{color:M.warnText,background:M.warnBg,border:`1px solid ${M.warnBorder}`,borderRadius:"4px",padding:"4px 8px",fontSize:"11px",marginBottom:"4px",display:"none"}),T.insertBefore(E,C);function J(){const e=b?[]:St(q(),{facetableOnly:!0}).filter(n=>n.splittable).map(n=>n.label);e.length>0?(E.textContent=`\u26A0 Mixed ${e.join(", ")} across these points \u2014 trends here may be confounded (Simpson's paradox). Use Group by, or the Wafer picker, to narrow to a like-for-like set.`,E.style.display=""):E.style.display="none"}J();const D=document.createElement("div");D.textContent=b?"One point per die \xB7 coloured by group \xB7 click legend to filter":"One point per die across all wafers \xB7 coloured by hard bin \xB7 click legend to filter",Object.assign(D.style,{color:M.label,fontSize:"11px",marginBottom:"4px"}),T.insertBefore(D,C);const pt=ht(it),{forBin:mt}=pt,G=e=>b?e.group??"\u2014":String(e.hbin??0),Q=e=>b?gt(at.get(e)??0):mt(Number(e)),V=e=>b?e:e==="0"?"No bin data":`Bin ${e}`,g=new Set,A=document.createElement("div");Object.assign(A.style,{display:"flex",flexWrap:"wrap",gap:"4px",marginBottom:"6px"}),C.appendChild(A);const u=document.createElement("canvas");u.style.display="block",u.style.cursor="crosshair",C.appendChild(u);const F=window.devicePixelRatio||1,Ht=wt(T);let h=[],w=0,I=1,k=0,N=1;function $(e){const n=_.find(o=>o.testNumber===e);return{unit:n?.unit,limitLow:n?.limitLow,limitHigh:n?.limitHigh}}function xt(){const e=C.clientWidth,n=Math.max(200,Math.min(400,e*.65)),o=Lt(T,C,u,n);return{w:e,h:o,plotW:Math.max(10,e-r-Ot),plotH:Math.max(10,o-l-Et)}}function Z(){for(const e of A.querySelectorAll("[data-cat]")){const n=e.dataset.cat,o=g.size===0||g.has(n);e.style.opacity=o?"1":"0.35";const i=g.has(n);e.style.boxShadow=i?`0 0 0 2px ${M.text}`:"none",e.setAttribute("aria-pressed",i?"true":"false")}}function tt(e){A.innerHTML="",g.clear();for(const n of e){const o=document.createElement("button");o.type="button",o.dataset.cat=n,o.title=`${V(n)} \u2014 click to filter`;const i=Q(n);Object.assign(o.style,{display:"inline-flex",alignItems:"center",gap:"4px",padding:"2px 7px",borderRadius:"10px",border:`1px solid ${M.menuBorder}`,background:"none",cursor:"pointer",fontSize:"11px",color:M.text,whiteSpace:"nowrap"});const d=document.createElement("span");Object.assign(d.style,{display:"inline-block",width:"9px",height:"9px",borderRadius:"50%",background:i,flexShrink:"0"}),o.append(d,document.createTextNode(V(n))),o.addEventListener("click",()=>{g.has(n)?g.delete(n):g.add(n),Z(),z()}),A.appendChild(o)}Z()}function z(){Ct(u,A.offsetHeight);const e=Bt(T),n=I-w,o=N-k,{w:i,h:d,plotW:p,plotH:c}=xt();u.width=Math.max(1,Math.floor(i*F)),u.height=Math.max(1,Math.floor(d*F)),u.style.width=`${i}px`,u.style.height=`${d}px`;const t=u.getContext("2d");if(t.setTransform(F,0,0,F,0,0),t.clearRect(0,0,i,d),h.length===0){t.font="12px system-ui, sans-serif",t.fillStyle=e.textMuted,t.textAlign="center",t.textBaseline="middle",t.fillText("No data \u2014 select two parametric tests with values.",i/2,d/2);return}const y=4,v=nt(Math.max(Math.abs(w),Math.abs(I)),m!==null?$(m).unit:void 0),U=nt(Math.max(Math.abs(k),Math.abs(N)),f!==null?$(f).unit:void 0);t.font="10px system-ui, sans-serif",t.strokeStyle=e.border,t.lineWidth=.5,t.fillStyle=e.textMuted;for(let a=0;a<=y;a++){const S=w+n*a/y,s=r+a/y*p;t.beginPath(),t.moveTo(s,l),t.lineTo(s,l+c),t.stroke(),t.textAlign="center",t.textBaseline="top",t.fillText(v.tick(S),s,l+c+4);const L=k+o*a/y,x=l+(1-a/y)*c;t.beginPath(),t.moveTo(r,x),t.lineTo(r+p,x),t.stroke(),t.textAlign="right",t.textBaseline="middle",t.fillText(U.tick(L),r-4,x)}v.unitLabel&&At(t,v.unitLabel,r+p/2,l+c+24,e.textMuted),U.unitLabel&&(t.save(),t.font="10px system-ui, sans-serif",t.fillStyle=e.textMuted,t.textAlign="center",t.textBaseline="middle",t.translate(6,l+c/2),t.rotate(-Math.PI/2),t.fillText(`(${U.unitLabel})`,0,0),t.restore());const W=g.size===0?h:h.filter(a=>g.has(G(a))),et=W.length>5e3?Math.ceil(W.length/5e3):1;t.globalAlpha=Math.max(.15,Math.min(.7,200/(W.length/et)));for(let a=0;a<W.length;a+=et){const S=W[a],s=r+(S.x-w)/n*p,L=l+(1-(S.y-k)/o)*c;t.beginPath(),t.arc(s,L,2.5,0,Math.PI*2),t.fillStyle=Q(G(S)),t.fill()}if(t.globalAlpha=1,m!==null&&f!==null){const a=$(m),S=$(f);t.strokeStyle=e.textMuted,t.fillStyle=e.textMuted,t.lineWidth=1,t.setLineDash([4,3]),t.font="9px system-ui, sans-serif",t.textBaseline="top";for(const[s,L]of[[a.limitLow,"LSL"],[a.limitHigh,"USL"]]){if(s===void 0||s<w||s>I)continue;const x=r+(s-w)/n*p;t.beginPath(),t.moveTo(x,l),t.lineTo(x,l+c),t.stroke(),t.textAlign="left",t.fillText(L,x+2,l+2)}for(const[s,L]of[[S.limitLow,"LSL"],[S.limitHigh,"USL"]]){if(s===void 0||s<k||s>N)continue;const x=l+(1-(s-k)/o)*c;t.beginPath(),t.moveTo(r,x),t.lineTo(r+p,x),t.stroke(),t.textAlign="left",t.fillText(L,r+3,x+2)}t.setLineDash([])}t.strokeStyle=e.border,t.lineWidth=1,t.beginPath(),t.moveTo(r,l),t.lineTo(r,l+c),t.moveTo(r,l+c),t.lineTo(r+p,l+c),t.stroke()}function H(){if(J(),R.length<2||m===null||f===null){h=[],tt([]),z();return}h=b?yt(P,m,f):bt(q(),m,f);let e;if(b){const n=new Set(h.map(G));e=Y.filter(o=>n.has(o))}else e=Array.from(new Set(h.map(n=>n.hbin??0))).sort((n,o)=>n-o).map(String);if(tt(e),h.length>0){const n=h.map(v=>v.x),o=h.map(v=>v.y),i=Math.min(...n),d=Math.max(...n),p=Math.min(...o),c=Math.max(...o),t=(d-i)*.05||1,y=(c-p)*.05||1;w=i-t,I=d+t,k=p-y,N=c+y}z()}const ft=Tt(T,()=>z());H();function ut(e,n){m=e,f=n,rt.value=String(e),dt.value=String(n),H()}return{card:T,setXY:ut,destroy:()=>ft.disconnect()}}
1
+ import{getColorScheme as ge}from"../../renderer/colorSchemes.js";import{categorical as be}from"./palette.js";import{buildScatterData as ye,buildScatterDataGrouped as we}from"../../stats/scatter.js";import{buildFacetTable as Se}from"../../stats/facets.js";import{CLR as T}from"../toolbar.js";import{cardShell as Te,observeResize as Me,makeTooltip as ke,attachChartTip as ve,makeTestSelect as Le,makeWaferSelect as Ce,chartFillHeight as Ae,applyCanvasFlow as De,drawAxisUnit as Be,resolveChartCanvasColors as Oe,makeAxisFormat as ne}from"./chartShell.js";const c=52,Ee=16,a=16,He=44;export function renderScatterPanel(W){const{title:oe="Test scatter",items:D,testDefs:_,colorScheme:ie="default",onSaveImage:le,groups:P}=W,{card:i,body:C,controlsRow:X}=Te(oe,le,W.ownerDocument),R=_.filter(t=>t.testNumber!==void 0),y=!!(P&&P.length>0),Y=(P??[]).map(t=>t.key),ae=new Map(Y.map((t,n)=>[t,n]));let x=W.xTestNumber??R[0]?.testNumber??null,f=W.yTestNumber??R[1]?.testNumber??x,B=null;const q=()=>B!==null?D[B]?[D[B]]:[]:D;function K(t,n,o){const l=i.ownerDocument.createElement("label");Object.assign(l.style,{display:"inline-flex",alignItems:"center",gap:"4px",fontSize:"11px",color:T.label});const p=i.ownerDocument.createElement("span");p.textContent=t;const m=Le(R,n,o,{maxWidth:"180px",emptyText:"No tests",ownerDocument:i.ownerDocument});return l.append(p,m),{wrap:l,select:m}}const{wrap:se,select:re}=K("X:",x,t=>{x=t,E()}),{wrap:ce,select:de}=K("Y:",f,t=>{f=t,E()});X.append(se,ce),!y&&D.length>1&&X.appendChild(Ce(D,B,t=>{B=t,E()},{ownerDocument:i.ownerDocument}));const O=i.ownerDocument.createElement("div");Object.assign(O.style,{color:T.warnText,background:T.warnBg,border:`1px solid ${T.warnBorder}`,borderRadius:"4px",padding:"4px 8px",fontSize:"11px",marginBottom:"4px",display:"none"}),i.insertBefore(O,C);function J(){const t=y?[]:Se(q(),{facetableOnly:!0}).filter(n=>n.splittable).map(n=>n.label);t.length>0?(O.textContent=`\u26A0 Mixed ${t.join(", ")} across these points \u2014 trends here may be confounded (Simpson's paradox). Use Group by, or the Wafer picker, to narrow to a like-for-like set.`,O.style.display=""):O.style.display="none"}J();const j=i.ownerDocument.createElement("div");j.textContent=y?"One point per die \xB7 coloured by group \xB7 click legend to filter":"One point per die across all wafers \xB7 coloured by hard bin \xB7 click legend to filter",Object.assign(j.style,{color:T.label,fontSize:"11px",marginBottom:"4px"}),i.insertBefore(j,C);const pe=ge(ie),{forBin:me}=pe,G=t=>y?t.group??"\u2014":String(t.hbin??0),Q=t=>y?be(ae.get(t)??0):me(Number(t)),V=t=>y?t:t==="0"?"No bin data":`Bin ${t}`,b=new Set,A=i.ownerDocument.createElement("div");Object.assign(A.style,{display:"flex",flexWrap:"wrap",gap:"4px",marginBottom:"6px"}),C.appendChild(A);const h=i.ownerDocument.createElement("canvas");h.style.display="block",h.style.cursor="crosshair",C.appendChild(h);const F=window.devicePixelRatio||1,xe=ke(i);let g=[],M=0,I=1,k=0,N=1;function $(t){const n=_.find(o=>o.testNumber===t);return{unit:n?.unit,limitLow:n?.limitLow,limitHigh:n?.limitHigh}}function ue(){const t=C.clientWidth,n=Math.max(200,Math.min(400,t*.65)),o=Ae(i,C,h,n);return{w:t,h:o,plotW:Math.max(10,t-c-Ee),plotH:Math.max(10,o-a-He)}}function Z(){for(const t of A.querySelectorAll("[data-cat]")){const n=t.dataset.cat,o=b.size===0||b.has(n);t.style.opacity=o?"1":"0.35";const l=b.has(n);t.style.boxShadow=l?`0 0 0 2px ${T.text}`:"none",t.setAttribute("aria-pressed",l?"true":"false")}}function ee(t){A.innerHTML="",b.clear();for(const n of t){const o=i.ownerDocument.createElement("button");o.type="button",o.dataset.cat=n,ve(o,i,xe,`${V(n)} \u2014 click to filter`);const l=Q(n);Object.assign(o.style,{display:"inline-flex",alignItems:"center",gap:"4px",padding:"2px 7px",borderRadius:"10px",border:`1px solid ${T.menuBorder}`,background:"none",cursor:"pointer",fontSize:"11px",color:T.text,whiteSpace:"nowrap"});const p=i.ownerDocument.createElement("span");Object.assign(p.style,{display:"inline-block",width:"9px",height:"9px",borderRadius:"50%",background:l,flexShrink:"0"}),o.append(p,i.ownerDocument.createTextNode(V(n))),o.addEventListener("click",()=>{b.has(n)?b.delete(n):b.add(n),Z(),z()}),A.appendChild(o)}Z()}function z(){De(h,A.offsetHeight);const t=Oe(i),n=I-M,o=N-k,{w:l,h:p,plotW:m,plotH:d}=ue();h.width=Math.max(1,Math.floor(l*F)),h.height=Math.max(1,Math.floor(p*F)),h.style.width=`${l}px`,h.style.height=`${p}px`;const e=h.getContext("2d");if(e.setTransform(F,0,0,F,0,0),e.clearRect(0,0,l,p),g.length===0){e.font="12px system-ui, sans-serif",e.fillStyle=t.textMuted,e.textAlign="center",e.textBaseline="middle",e.fillText("No data \u2014 select two parametric tests with values.",l/2,p/2);return}const w=4,v=ne(Math.max(Math.abs(M),Math.abs(I)),x!==null?$(x).unit:void 0),U=ne(Math.max(Math.abs(k),Math.abs(N)),f!==null?$(f).unit:void 0);e.font="10px system-ui, sans-serif",e.strokeStyle=t.border,e.lineWidth=.5,e.fillStyle=t.textMuted;for(let s=0;s<=w;s++){const S=M+n*s/w,r=c+s/w*m;e.beginPath(),e.moveTo(r,a),e.lineTo(r,a+d),e.stroke(),e.textAlign="center",e.textBaseline="top",e.fillText(v.tick(S),r,a+d+4);const L=k+o*s/w,u=a+(1-s/w)*d;e.beginPath(),e.moveTo(c,u),e.lineTo(c+m,u),e.stroke(),e.textAlign="right",e.textBaseline="middle",e.fillText(U.tick(L),c-4,u)}v.unitLabel&&Be(e,v.unitLabel,c+m/2,a+d+24,t.textMuted),U.unitLabel&&(e.save(),e.font="10px system-ui, sans-serif",e.fillStyle=t.textMuted,e.textAlign="center",e.textBaseline="middle",e.translate(6,a+d/2),e.rotate(-Math.PI/2),e.fillText(`(${U.unitLabel})`,0,0),e.restore());const H=b.size===0?g:g.filter(s=>b.has(G(s))),te=H.length>5e3?Math.ceil(H.length/5e3):1;e.globalAlpha=Math.max(.15,Math.min(.7,200/(H.length/te)));for(let s=0;s<H.length;s+=te){const S=H[s],r=c+(S.x-M)/n*m,L=a+(1-(S.y-k)/o)*d;e.beginPath(),e.arc(r,L,2.5,0,Math.PI*2),e.fillStyle=Q(G(S)),e.fill()}if(e.globalAlpha=1,x!==null&&f!==null){const s=$(x),S=$(f);e.strokeStyle=t.textMuted,e.fillStyle=t.textMuted,e.lineWidth=1,e.setLineDash([4,3]),e.font="9px system-ui, sans-serif",e.textBaseline="top";for(const[r,L]of[[s.limitLow,"LSL"],[s.limitHigh,"USL"]]){if(r===void 0||r<M||r>I)continue;const u=c+(r-M)/n*m;e.beginPath(),e.moveTo(u,a),e.lineTo(u,a+d),e.stroke(),e.textAlign="left",e.fillText(L,u+2,a+2)}for(const[r,L]of[[S.limitLow,"LSL"],[S.limitHigh,"USL"]]){if(r===void 0||r<k||r>N)continue;const u=a+(1-(r-k)/o)*d;e.beginPath(),e.moveTo(c,u),e.lineTo(c+m,u),e.stroke(),e.textAlign="left",e.fillText(L,c+3,u+2)}e.setLineDash([])}e.strokeStyle=t.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,a),e.lineTo(c,a+d),e.moveTo(c,a+d),e.lineTo(c+m,a+d),e.stroke()}function E(){if(J(),R.length<2||x===null||f===null){g=[],ee([]),z();return}g=y?we(P,x,f):ye(q(),x,f);let t;if(y){const n=new Set(g.map(G));t=Y.filter(o=>n.has(o))}else t=Array.from(new Set(g.map(n=>n.hbin??0))).sort((n,o)=>n-o).map(String);if(ee(t),g.length>0){const n=g.map(v=>v.x),o=g.map(v=>v.y),l=Math.min(...n),p=Math.max(...n),m=Math.min(...o),d=Math.max(...o),e=(p-l)*.05||1,w=(d-m)*.05||1;M=l-e,I=p+e,k=m-w,N=d+w}z()}const fe=Me(i,()=>z());E();function he(t,n){x=t,f=n,re.value=String(t),de.value=String(n),E()}return{card:i,setXY:he,destroy:()=>fe.disconnect()}}
@@ -98,6 +98,15 @@ export interface DieListOptions extends DieListDisplayOptions {
98
98
  waferMetadata?: WaferMetadata;
99
99
  /** Label/order hints for die metadata columns, e.g. `WaferMapResult.metadataFields`. */
100
100
  metadataFields?: MetadataFieldDef[];
101
+ /**
102
+ * Document to build elements and inject the shared `<style>` block into.
103
+ * Default `document`. Pass the anchor's `ownerDocument` when the table may
104
+ * be mounted into a different document than the bare global — e.g. a
105
+ * gallery card detached into its own popup window — otherwise the table's
106
+ * cells build in the wrong document and the injected font-size rule never
107
+ * reaches the document the table actually renders in.
108
+ */
109
+ ownerDocument?: Document;
101
110
  }
102
111
  /**
103
112
  * Build a scrollable die-list table + "Export CSV" button. Returns `null`
@@ -1,13 +1,13 @@
1
- import{hasPosition as k}from"../core/dies.js";import{isParametricTest as H}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as R}from"../stats/metadataColumns.js";import{CLR as a,saveTextFile as E}from"./toolbar.js";import{csvField as y}from"./summaryPanel.js";import{fmt as M}from"../renderer/fmt.js";const T=5e4;let $=!1;function B(){if($)return;$=!0;const n=document.createElement("style");n.textContent=`
1
+ import{hasPosition as H}from"../core/dies.js";import{isParametricTest as R}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as E}from"../stats/metadataColumns.js";import{CLR as o,saveTextFile as M}from"./toolbar.js";import{csvField as $}from"./summaryPanel.js";import{fmt as T}from"../renderer/fmt.js";const B=5e4,N=new WeakSet;function F(n){if(N.has(n))return;N.add(n);const s=n.createElement("style");s.textContent=`
2
2
  .wmap-dielist-table { width: 100%; border-collapse: collapse; font-size: 11px; }
3
3
  .wmap-dielist-th {
4
4
  text-align: left; padding: 4px 8px; position: sticky; top: 0;
5
- background: ${a.panelBg}; color: ${a.label}; font-weight: 600;
6
- border-bottom: 1px solid ${a.menuBorder}; white-space: nowrap;
5
+ background: ${o.panelBg}; color: ${o.label}; font-weight: 600;
6
+ border-bottom: 1px solid ${o.menuBorder}; white-space: nowrap;
7
7
  }
8
8
  .wmap-dielist-td {
9
- padding: 3px 8px; color: ${a.text};
10
- border-bottom: 1px solid ${a.menuBorder}; white-space: nowrap;
9
+ padding: 3px 8px; color: ${o.text};
10
+ border-bottom: 1px solid ${o.menuBorder}; white-space: nowrap;
11
11
  }
12
- `,document.head.appendChild(n)}function o(n,s,t){const i=document.createElement(n);return s&&Object.assign(i.style,s),t!==void 0&&(i.textContent=t),i}function F(n){return k(n)?`(${n.x}, ${n.y})`:"\u2014"}function P(n,s){if(s?.length)return s.filter(H);const t=new Map;for(const i of n)for(const d of Object.keys(i.testValues??{})){const r=Number(d);t.has(r)||t.set(r,{testNumber:r,name:`Test ${r}`})}return[...t.values()].sort((i,d)=>i.testNumber-d.testNumber)}export function buildDieListSection(n,s,t={}){if(!n.length)return null;B();const i=P(n,s),d=t.maxRows??T,r=Math.max(0,n.length-d),N=r>0?n.slice(0,d):n,u=o("div",{display:"flex",flexDirection:"column",gap:"8px",flex:"1",minHeight:"0",minWidth:"0"}),h=o("div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});h.appendChild(o("div",{fontSize:"10px",fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:a.label},t.title??`Die list (${n.length})`));const b=o("button",{fontSize:"11px",padding:"3px 8px",borderRadius:"4px",border:`1px solid ${a.menuBorder}`,background:a.menuBg,color:a.text,cursor:"pointer"},"Export CSV");b.type="button",h.appendChild(b),u.appendChild(h),t.note&&u.appendChild(o("div",{fontSize:"11px",color:a.label,lineHeight:"1.4",flexShrink:"0"},t.note));const L=[...t.extraColumn?[t.extraColumn.label]:[],"Position","Site","Hard bin","Soft bin",...i.map(e=>e.name)],{columns:j,truncatedKeys:f}=R({dies:n,waferMetadata:t.waferMetadata,metadataFields:t.metadataFields,dieKeys:t.metadataColumns,waferKeys:t.waferMetadataColumns==="none"?"none":void 0,waferPlacement:t.waferMetadataColumns??"csv",reservedLabels:L}),x=[...t.extraColumn?[{label:t.extraColumn.label,get:e=>t.extraColumn.get(e)??""}]:[],{label:"Position",get:F},{label:"Site",get:e=>e.siteNum!==void 0?String(e.siteNum):""},{label:"Hard bin",get:e=>e.hbin!==void 0?String(e.hbin):""},{label:"Soft bin",get:e=>e.sbin!==void 0?String(e.sbin):""},...i.map(e=>({label:e.name,get:l=>{const c=l.testValues?.[e.testNumber];if(c!==void 0)return M(c,e.unit);const m=l.testPass?.[e.testNumber];return m===void 0?"":m?"PASS":"FAIL"}})),...j.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],g=x.filter(e=>!e.csvOnly),C=o("div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${a.menuBorder}`,borderRadius:"4px",...t.maxHeight?{maxHeight:t.maxHeight}:{}}),p=document.createElement("table");p.className="wmap-dielist-table",p.setAttribute("aria-label",t.title??`Die list (${n.length} dies)`);const w=o("thead"),S=o("tr");for(const e of g){const l=document.createElement("th");l.className="wmap-dielist-th",l.scope="col",l.textContent=e.label,S.appendChild(l)}w.appendChild(S),p.appendChild(w);const v=o("tbody");for(const e of N){const l=o("tr");for(const c of g){const m=document.createElement("td");m.className="wmap-dielist-td",m.textContent=c.get(e),l.appendChild(m)}v.appendChild(l)}if(p.appendChild(v),C.appendChild(p),u.appendChild(C),r>0||f.length>0){const e=[];r>0&&e.push(`Showing the first ${d.toLocaleString()} of ${n.length.toLocaleString()} dies. The CSV export contains all ${n.length.toLocaleString()}.`),f.length>0&&e.push(`${f.length} further metadata field${f.length===1?"":"s"} not shown.`),u.appendChild(o("div",{fontSize:"11px",color:a.label,lineHeight:"1.4",flexShrink:"0"},e.join(" ")))}return b.addEventListener("click",()=>{const e=[x.map(l=>y(l.label)).join(",")];for(const l of n)e.push(x.map(c=>y(c.get(l))).join(","));E(e.join(`
12
+ `,n.head.appendChild(s)}function i(n,s,t,l){const r=n.createElement(s);return t&&Object.assign(r.style,t),l!==void 0&&(r.textContent=l),r}function P(n){return H(n)?`(${n.x}, ${n.y})`:"\u2014"}function D(n,s){if(s?.length)return s.filter(R);const t=new Map;for(const l of n)for(const r of Object.keys(l.testValues??{})){const d=Number(r);t.has(d)||t.set(d,{testNumber:d,name:`Test ${d}`})}return[...t.values()].sort((l,r)=>l.testNumber-r.testNumber)}export function buildDieListSection(n,s,t={}){if(!n.length)return null;const l=t.ownerDocument??document;F(l);const r=D(n,s),d=t.maxRows??B,h=Math.max(0,n.length-d),k=h>0?n.slice(0,d):n,u=i(l,"div",{display:"flex",flexDirection:"column",gap:"8px",flex:"1",minHeight:"0",minWidth:"0"}),b=i(l,"div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});b.appendChild(i(l,"div",{fontSize:"10px",fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:o.label},t.title??`Die list (${n.length})`));const x=i(l,"button",{fontSize:"11px",padding:"3px 8px",borderRadius:"4px",border:`1px solid ${o.menuBorder}`,background:o.menuBg,color:o.text,cursor:"pointer"},"Export CSV");x.type="button",b.appendChild(x),u.appendChild(b),t.note&&u.appendChild(i(l,"div",{fontSize:"11px",color:o.label,lineHeight:"1.4",flexShrink:"0"},t.note));const L=[...t.extraColumn?[t.extraColumn.label]:[],"Position","Site","Hard bin","Soft bin",...r.map(e=>e.name)],{columns:j,truncatedKeys:f}=E({dies:n,waferMetadata:t.waferMetadata,metadataFields:t.metadataFields,dieKeys:t.metadataColumns,waferKeys:t.waferMetadataColumns==="none"?"none":void 0,waferPlacement:t.waferMetadataColumns??"csv",reservedLabels:L}),g=[...t.extraColumn?[{label:t.extraColumn.label,get:e=>t.extraColumn.get(e)??""}]:[],{label:"Position",get:P},{label:"Site",get:e=>e.siteNum!==void 0?String(e.siteNum):""},{label:"Hard bin",get:e=>e.hbin!==void 0?String(e.hbin):""},{label:"Soft bin",get:e=>e.sbin!==void 0?String(e.sbin):""},...r.map(e=>({label:e.name,get:a=>{const c=a.testValues?.[e.testNumber];if(c!==void 0)return T(c,e.unit);const m=a.testPass?.[e.testNumber];return m===void 0?"":m?"PASS":"FAIL"}})),...j.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],C=g.filter(e=>!e.csvOnly),w=i(l,"div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${o.menuBorder}`,borderRadius:"4px",...t.maxHeight?{maxHeight:t.maxHeight}:{}}),p=l.createElement("table");p.className="wmap-dielist-table",p.setAttribute("aria-label",t.title??`Die list (${n.length} dies)`);const S=i(l,"thead"),v=i(l,"tr");for(const e of C){const a=l.createElement("th");a.className="wmap-dielist-th",a.scope="col",a.textContent=e.label,v.appendChild(a)}S.appendChild(v),p.appendChild(S);const y=i(l,"tbody");for(const e of k){const a=i(l,"tr");for(const c of C){const m=l.createElement("td");m.className="wmap-dielist-td",m.textContent=c.get(e),a.appendChild(m)}y.appendChild(a)}if(p.appendChild(y),w.appendChild(p),u.appendChild(w),h>0||f.length>0){const e=[];h>0&&e.push(`Showing the first ${d.toLocaleString()} of ${n.length.toLocaleString()} dies. The CSV export contains all ${n.length.toLocaleString()}.`),f.length>0&&e.push(`${f.length} further metadata field${f.length===1?"":"s"} not shown.`),u.appendChild(i(l,"div",{fontSize:"11px",color:o.label,lineHeight:"1.4",flexShrink:"0"},e.join(" ")))}return x.addEventListener("click",()=>{const e=[g.map(a=>$(a.label)).join(",")];for(const a of n)e.push(g.map(c=>$(c.get(a))).join(","));M(e.join(`
13
13
  `),t.csvFilename??"dies.csv","text/csv",t.onSaveText)}),u}
@@ -63,6 +63,11 @@ export interface InsightsTabDeps {
63
63
  * renderWaferGallery.ts's `rebuildLegend`/`setInsightsOpen`.
64
64
  */
65
65
  showMetadataStrip?: boolean;
66
+ /** Document to build this tab's DOM into. Default `document` — pass the
67
+ * render's own `ownerDocument` when the container might live in a
68
+ * different document (e.g. a gallery card detached into its own popup
69
+ * window). */
70
+ ownerDocument?: Document;
66
71
  }
67
72
  export interface InsightsTabHandle {
68
73
  /** Append this wherever the tab's content should live; starts hidden (`display: none`). */
@@ -1 +1 @@
1
- import{buildFacetTable as de,facetValueOf as ce,FACET_NONE_VALUE as pe}from"../stats/facets.js";import{isParametricTest as ue}from"../renderer/buildWaferMap.js";import{getColorScheme as me}from"../renderer/colorSchemes.js";import{CLR as C}from"./toolbar.js";import{renderCapabilityPanel as fe}from"./charts/capability.js";import{renderBoxplotPanel as be}from"./charts/boxplot.js";import{renderHistogramPanel as ye}from"./charts/histogram.js";import{renderCorrelationPanel as he}from"./charts/correlation.js";import{renderScatterPanel as ge}from"./charts/scatter.js";import{renderBarPanel as q}from"./charts/barPanel.js";import{renderBinClusterPanel as Ce}from"./charts/binCluster.js";import{QUANTITY as xe}from"./charts/palette.js";import{makeChartGridWrap as j,makeLabeledSelect as Se}from"./charts/chartShell.js";import{buildYieldData as A,buildYieldDataCombined as we}from"../stats/yield.js";import{buildBinParetoData as Be}from"../stats/binPareto.js";import{buildLotTestSection as ke,buildLotFunctionalSection as ve,buildMetadataStripBox as Te}from"./summaryPanel.js";import{buildRegionYieldData as N,buildRingRegions as $e,buildQuadrantRegions as Ee}from"../stats/regions.js";import{renderRegionYieldDiagram as z}from"./charts/regionYieldDiagram.js";const Oe=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}];export function createInsightsTab(v){const{getItems:H,getLotStats:Q,getColorSchemeName:h,passBins:f,getRingCount:K,onSaveImage:y,onSaveText:M,openWafer:D}=v,U=v.showMetadataStrip??!0,S=document.createElement("div");Object.assign(S.style,{display:"none",flexDirection:"column",gap:"10px",width:"100%"});const L=document.createElement("div"),x=document.createElement("div");Object.assign(x.style,{display:"flex",gap:"4px",borderBottom:`1px solid ${C.menuBorder}`,marginBottom:"2px"}),x.setAttribute("role","tablist"),x.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from(x.querySelectorAll('[role="tab"]')),a=t.indexOf(document.activeElement);if(a===-1)return;e.preventDefault();const r=t[(a+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];r.focus(),r.click()});const T=document.createElement("div");Object.assign(T.style,{display:"flex",flexDirection:"column",gap:"10px"}),S.appendChild(L),S.appendChild(x),S.appendChild(T);let $=v.defaultView??"overview",w,O=[];function V(e,t,a){D?.(e,t,a)}function _(){return H().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:e.label??String(e.wafer.metadata?.waferId??""),waferIndex:t,statsSummary:e.statsSummary}).filter(e=>e!=null)}function F(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${C.iconActive}`:"2px solid transparent",color:t?C.iconActive:C.label,fontWeight:t?"700":"500",fontSize:"12px",padding:"6px 10px 8px",cursor:"pointer",marginBottom:"-1px"})}function X(e,t){const a=document.createElement("button");a.type="button",a.textContent=t;const r=e===$;return F(a,r),a.dataset.wmapInsightsTab=e,a.setAttribute("role","tab"),a.setAttribute("aria-selected",r?"true":"false"),a.tabIndex=r?0:-1,a.addEventListener("click",()=>{$!==e&&($=e,R())}),a}function J(e){const t=document.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,F(t,!1),t.dataset.wmapInsightsBack="1",t.addEventListener("click",e.onBack),t}function Z(e,t,a,r=!0){const l=j(),s=me(h());let p="label";const d=a??"group",n=f.length===1?`bin ${f[0]}`:`bins ${f.join(", ")}`,u=Q?.(),m=new Map(u?.lotYieldSeries.map(o=>[o.waferIndex,o.yieldPercent])),c=o=>({...o,label:o.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:m.get(o.waferIndex),key:o.waferIndex}),B=e.map(c),g=t?.map(o=>({key:o.key,items:o.items.map(c)})),k=()=>g?we(g,f,p):A(B,f,p),i={title:t?`Yield by ${d} (pass: ${n})`:`Yield by wafer (pass: ${n})`,data:k(),selfControl:{current:p,options:[["yield","Sort: yield"],["label",t?`Sort: ${d}`:"Sort: wafer ID"]],onChange:o=>(p=o,{data:k()})},barColor:()=>xe,valueLabel:o=>`${o.percent.toFixed(1)}%`,drill:g?{onOpenGroup:o=>{const P=g.find(E=>E.key===o.label)?.items??[];return{data:A(P,f,p),title:`Yield by wafer \u2014 ${d}: ${o.label} (pass: ${n})`}},onBack:()=>({data:k(),title:`Yield by ${d} (pass: ${n})`}),groupLabelText:d}:void 0,onOpen:D?o=>{typeof o.key=="number"&&V(o.key,`Wafer ${o.label}`)}:void 0};let b=null;if(r&&(b=q(i,y),b.card.style.minHeight="360px",l.appendChild(b.card)),t){const o=Ce({title:"Hard bin pareto",groups:t.map(P=>({key:P.key,items:P.items.map(E=>({dies:E.dies,hardBinCounts:E.statsSummary?.stats.hardBinCounts,softBinCounts:E.statsSummary?.stats.softBinCounts}))})),colorScheme:h(),onSaveImage:y});return o.card.style.minHeight="360px",l.appendChild(o.card),{card:l,destroy:()=>{b?.destroy(),o.destroy()}}}let I="hbin";const ie=s.forBin,le=e.map(o=>({dies:o.dies,hardBinCounts:o.statsSummary?.stats.hardBinCounts,softBinCounts:o.statsSummary?.stats.softBinCounts})),G=()=>Be(le,I),se={title:"Hard bin pareto",data:G(),selfControl:{current:I,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:o=>(I=o,{data:G(),title:`${I==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:o=>ie(o.binCode??0)},Y=q(se,y);return Y.card.style.minHeight="360px",l.appendChild(Y.card),{card:l,destroy:()=>{b?.destroy(),Y.destroy()}}}function W(){const e=document.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{display:"flex",flexDirection:"column",gap:"10px",minWidth:"0",background:C.menuBg,border:`1px solid ${C.menuBorder}`,borderRadius:"6px",padding:"12px"}),e}function ee(e){if(L.innerHTML="",!U)return;const t=Te(e.map(a=>({metadata:a.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",L.appendChild(t))}function te(e,t,a){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const r=e.map(i=>i.wafer),l=e.map(i=>i.dies),s=e.flatMap(i=>i.dies),p=e.map(i=>i.statsSummary),d=p.every(i=>i!==void 0)?p:void 0,n=K?.()??4,u=[],m=[],c=N(l,r,n,f,$e);if(c.length){const i=z({title:"Ring yield",mode:"ring",rows:c,colorScheme:h(),onSaveImage:y});u.push(i.card),m.push(i.destroy)}const B=N(l,r,n,f,Ee);if(B.length){const i=z({title:"Quadrant yield",mode:"quadrant",rows:B,colorScheme:h(),onSaveImage:y});u.push(i.card),m.push(i.destroy)}let g=null;if(t.length){const i=ke(s,t,void 0,d,M);if(i){const b=W();b.appendChild(i),g=b}}let k=null;if(a.length){const i=ve(s,a,d,M);if(i){const b=W();b.appendChild(i),k=b}}return{elements:u,testValuesCard:g,functionalCard:k,destroy:()=>{for(const i of m)i()}}}function ne(e){const t=f.length===1?`bin ${f[0]}`:`bins ${f.join(", ")}`,a=A([{...e,key:e.waferIndex}],f)[0]?.percent,r=W();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:"8px"});function l(s,p){const d=document.createElement("div");Object.assign(d.style,{border:`1px solid ${C.menuBorder}`,borderRadius:"6px",padding:"8px 16px",textAlign:"center",minWidth:"110px"});const n=document.createElement("div");n.textContent=s,Object.assign(n.style,{fontSize:"20px",fontWeight:"700",color:C.value,lineHeight:"1.2"});const u=document.createElement("div");return u.textContent=p,Object.assign(u.style,{fontSize:"10px",color:C.label,marginTop:"2px"}),d.append(n,u),d}return a!==void 0&&r.appendChild(l(`${a.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),r.appendChild(l(String(e.dies.length),"Total dies")),r}function ae(e,t,a,r,l){const s=document.createElement("div");Object.assign(s.style,{display:"flex",flexDirection:"column",gap:"10px"});const p=e.length===1&&!r;p&&s.appendChild(ne(e[0]));const d=Z(e,r,l,!p);s.appendChild(d.card);const n=te(e,t,a);for(const u of n.elements)d.card.appendChild(u);return n.testValuesCard&&s.appendChild(n.testValuesCard),n.functionalCard&&s.appendChild(n.functionalCard),{card:s,destroy:()=>{d.destroy(),n.destroy()}}}function oe(e,t,a,r){const l=j(),s=c=>({...c,testStats:c.statsSummary?.stats.perTestStats}),p=e.map(s),d=a?.map(c=>({key:c.key,items:c.items.map(s)})),n=be({title:"Test value distribution",items:p,testDefs:t,groups:d,groupLabelText:r,colorScheme:h(),onSaveImage:y,onOpen:D?(c,B)=>V(c,`Wafer ${e.find(g=>g.waferIndex===c)?.label??c}`,B):void 0}),u=ye({title:"Value histogram",items:e,testDefs:t,groups:a,colorScheme:h(),onSaveImage:y}),m=fe({title:"Process capability",items:e,testDefs:t,groups:a,colorScheme:h(),onSaveImage:y,onSelectTest:c=>{n.setTest(c),u.setTest(c)}});return l.append(m.card,n.card,u.card),{card:l,destroy:()=>{m.destroy(),n.destroy(),u.destroy()}}}function re(e,t,a){const r=j(),l=ge({title:"Test scatter",items:e,testDefs:t,groups:a,colorScheme:h(),onSaveImage:y}),s=he({title:"Test correlation matrix",items:e,testDefs:t,groups:a,colorScheme:h(),onSaveImage:y,onSelectPair:(p,d)=>l.setXY(p,d)});return r.append(s.card,l.card),{card:r,destroy:()=>{s.destroy(),l.destroy()}}}function R(){for(const n of O)n.destroy();O=[],T.innerHTML="",x.innerHTML="",v.backTab&&x.appendChild(J(v.backTab));for(const n of Oe)x.appendChild(X(n.key,n.label));const e=_();ee(e);const t=de(e,{facetableOnly:!0}).filter(n=>n.splittable),a=document.createElement("div");Object.assign(a.style,{display:"flex",gap:"8px",alignItems:"center"});let r;t.length>0?(a.appendChild(Se("Group by:",[{value:"",label:"None"},...t.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],w??"",n=>{w=n||void 0,R()},{hook:"group-by"})),r=t.find(n=>n.key===w)?.label):w=void 0,T.appendChild(a);const l=H().find(n=>n?.testDefs?.length)?.testDefs??[],s=l.filter(ue);let p;if(w){const n=new Map,u=[];for(const m of e){const c=ce(m.metadata,w)??pe;n.has(c)||(n.set(c,[]),u.push(c)),n.get(c).push(m)}p=u.map(m=>({key:m,items:n.get(m)}))}const d=$==="overview"?ae(e,s,l,p,r):$==="distributions"?oe(e,s,p,r):re(e,s,p);O.push(d),T.appendChild(d.card)}return{el:S,render:R,destroy:()=>{for(const e of O)e.destroy();S.remove()}}}
1
+ import{buildFacetTable as ce,facetValueOf as pe,FACET_NONE_VALUE as ue}from"../stats/facets.js";import{isParametricTest as me}from"../renderer/buildWaferMap.js";import{getColorScheme as fe}from"../renderer/colorSchemes.js";import{CLR as x}from"./toolbar.js";import{renderCapabilityPanel as be}from"./charts/capability.js";import{renderBoxplotPanel as ye}from"./charts/boxplot.js";import{renderHistogramPanel as he}from"./charts/histogram.js";import{renderCorrelationPanel as ge}from"./charts/correlation.js";import{renderScatterPanel as Ce}from"./charts/scatter.js";import{renderBarPanel as N}from"./charts/barPanel.js";import{renderBinClusterPanel as xe}from"./charts/binCluster.js";import{QUANTITY as Se}from"./charts/palette.js";import{makeChartGridWrap as A,makeLabeledSelect as we}from"./charts/chartShell.js";import{buildYieldData as H,buildYieldDataCombined as Be}from"../stats/yield.js";import{buildBinParetoData as ke}from"../stats/binPareto.js";import{buildLotTestSection as ve,buildLotFunctionalSection as Te,buildMetadataStripBox as De}from"./summaryPanel.js";import{buildRegionYieldData as z,buildRingRegions as $e,buildQuadrantRegions as Ee}from"../stats/regions.js";import{renderRegionYieldDiagram as Q}from"./charts/regionYieldDiagram.js";const Oe=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}];export function createInsightsTab(w){const{getItems:M,getLotStats:K,getColorSchemeName:g,passBins:b,getRingCount:U,onSaveImage:h,onSaveText:V,openWafer:L}=w,_=w.showMetadataStrip??!0,i=w.ownerDocument??document,B=i.createElement("div");Object.assign(B.style,{display:"none",flexDirection:"column",gap:"10px",width:"100%"});const W=i.createElement("div"),S=i.createElement("div");Object.assign(S.style,{display:"flex",gap:"4px",borderBottom:`1px solid ${x.menuBorder}`,marginBottom:"2px"}),S.setAttribute("role","tablist"),S.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from(S.querySelectorAll('[role="tab"]')),o=t.indexOf(i.activeElement);if(o===-1)return;e.preventDefault();const r=t[(o+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];r.focus(),r.click()});const D=i.createElement("div");Object.assign(D.style,{display:"flex",flexDirection:"column",gap:"10px"}),B.appendChild(W),B.appendChild(S),B.appendChild(D);let $=w.defaultView??"overview",k,O=[];function F(e,t,o){L?.(e,t,o)}function X(){return M().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:e.label??String(e.wafer.metadata?.waferId??""),waferIndex:t,statsSummary:e.statsSummary}).filter(e=>e!=null)}function G(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${x.iconActive}`:"2px solid transparent",color:t?x.iconActive:x.label,fontWeight:t?"700":"500",fontSize:"12px",padding:"6px 10px 8px",cursor:"pointer",marginBottom:"-1px"})}function J(e,t){const o=i.createElement("button");o.type="button",o.textContent=t;const r=e===$;return G(o,r),o.dataset.wmapInsightsTab=e,o.setAttribute("role","tab"),o.setAttribute("aria-selected",r?"true":"false"),o.tabIndex=r?0:-1,o.addEventListener("click",()=>{$!==e&&($=e,Y())}),o}function Z(e){const t=i.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,G(t,!1),t.dataset.wmapInsightsBack="1",t.addEventListener("click",e.onBack),t}function ee(e,t,o,r=!0){const s=A(i),d=fe(g());let u="label";const c=o??"group",n=b.length===1?`bin ${b[0]}`:`bins ${b.join(", ")}`,m=K?.(),f=new Map(m?.lotYieldSeries.map(a=>[a.waferIndex,a.yieldPercent])),p=a=>({...a,label:a.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:f.get(a.waferIndex),key:a.waferIndex}),v=e.map(p),C=t?.map(a=>({key:a.key,items:a.items.map(p)})),T=()=>C?Be(C,b,u):H(v,b,u),l={title:t?`Yield by ${c} (pass: ${n})`:`Yield by wafer (pass: ${n})`,data:T(),selfControl:{current:u,options:[["yield","Sort: yield"],["label",t?`Sort: ${c}`:"Sort: wafer ID"]],onChange:a=>(u=a,{data:T()})},barColor:()=>Se,valueLabel:a=>`${a.percent.toFixed(1)}%`,drill:C?{onOpenGroup:a=>{const P=C.find(E=>E.key===a.label)?.items??[];return{data:H(P,b,u),title:`Yield by wafer \u2014 ${c}: ${a.label} (pass: ${n})`}},onBack:()=>({data:T(),title:`Yield by ${c} (pass: ${n})`}),groupLabelText:c}:void 0,onOpen:L?a=>{typeof a.key=="number"&&F(a.key,`Wafer ${a.label}`)}:void 0,ownerDocument:i};let y=null;if(r&&(y=N(l,h),y.card.style.minHeight="360px",s.appendChild(y.card)),t){const a=xe({title:"Hard bin pareto",groups:t.map(P=>({key:P.key,items:P.items.map(E=>({dies:E.dies,hardBinCounts:E.statsSummary?.stats.hardBinCounts,softBinCounts:E.statsSummary?.stats.softBinCounts}))})),colorScheme:g(),onSaveImage:h,ownerDocument:i});return a.card.style.minHeight="360px",s.appendChild(a.card),{card:s,destroy:()=>{y?.destroy(),a.destroy()}}}let I="hbin";const le=d.forBin,se=e.map(a=>({dies:a.dies,hardBinCounts:a.statsSummary?.stats.hardBinCounts,softBinCounts:a.statsSummary?.stats.softBinCounts})),q=()=>ke(se,I),de={title:"Hard bin pareto",data:q(),selfControl:{current:I,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:a=>(I=a,{data:q(),title:`${I==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:a=>le(a.binCode??0),ownerDocument:i},j=N(de,h);return j.card.style.minHeight="360px",s.appendChild(j.card),{card:s,destroy:()=>{y?.destroy(),j.destroy()}}}function R(){const e=i.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{display:"flex",flexDirection:"column",gap:"10px",minWidth:"0",background:x.menuBg,border:`1px solid ${x.menuBorder}`,borderRadius:"6px",padding:"12px"}),e}function te(e){if(W.innerHTML="",!_)return;const t=De(e.map(o=>({metadata:o.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",W.appendChild(t))}function ne(e,t,o){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const r=e.map(l=>l.wafer),s=e.map(l=>l.dies),d=e.flatMap(l=>l.dies),u=e.map(l=>l.statsSummary),c=u.every(l=>l!==void 0)?u:void 0,n=U?.()??4,m=[],f=[],p=z(s,r,n,b,$e);if(p.length){const l=Q({title:"Ring yield",mode:"ring",rows:p,colorScheme:g(),onSaveImage:h,ownerDocument:i});m.push(l.card),f.push(l.destroy)}const v=z(s,r,n,b,Ee);if(v.length){const l=Q({title:"Quadrant yield",mode:"quadrant",rows:v,colorScheme:g(),onSaveImage:h,ownerDocument:i});m.push(l.card),f.push(l.destroy)}let C=null;if(t.length){const l=ve(d,t,void 0,c,V);if(l){const y=R();y.appendChild(l),C=y}}let T=null;if(o.length){const l=Te(d,o,c,V);if(l){const y=R();y.appendChild(l),T=y}}return{elements:m,testValuesCard:C,functionalCard:T,destroy:()=>{for(const l of f)l()}}}function oe(e){const t=b.length===1?`bin ${b[0]}`:`bins ${b.join(", ")}`,o=H([{...e,key:e.waferIndex}],b)[0]?.percent,r=R();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:"8px"});function s(d,u){const c=i.createElement("div");Object.assign(c.style,{border:`1px solid ${x.menuBorder}`,borderRadius:"6px",padding:"8px 16px",textAlign:"center",minWidth:"110px"});const n=i.createElement("div");n.textContent=d,Object.assign(n.style,{fontSize:"20px",fontWeight:"700",color:x.value,lineHeight:"1.2"});const m=i.createElement("div");return m.textContent=u,Object.assign(m.style,{fontSize:"10px",color:x.label,marginTop:"2px"}),c.append(n,m),c}return o!==void 0&&r.appendChild(s(`${o.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),r.appendChild(s(String(e.dies.length),"Total dies")),r}function ae(e,t,o,r,s){const d=i.createElement("div");Object.assign(d.style,{display:"flex",flexDirection:"column",gap:"10px"});const u=e.length===1&&!r;u&&d.appendChild(oe(e[0]));const c=ee(e,r,s,!u);d.appendChild(c.card);const n=ne(e,t,o);for(const m of n.elements)c.card.appendChild(m);return n.testValuesCard&&d.appendChild(n.testValuesCard),n.functionalCard&&d.appendChild(n.functionalCard),{card:d,destroy:()=>{c.destroy(),n.destroy()}}}function re(e,t,o,r){const s=A(i),d=p=>({...p,testStats:p.statsSummary?.stats.perTestStats}),u=e.map(d),c=o?.map(p=>({key:p.key,items:p.items.map(d)})),n=ye({title:"Test value distribution",items:u,testDefs:t,groups:c,groupLabelText:r,colorScheme:g(),onSaveImage:h,onOpen:L?(p,v)=>F(p,`Wafer ${e.find(C=>C.waferIndex===p)?.label??p}`,v):void 0,ownerDocument:i}),m=he({title:"Value histogram",items:e,testDefs:t,groups:o,colorScheme:g(),onSaveImage:h,ownerDocument:i}),f=be({title:"Process capability",items:e,testDefs:t,groups:o,colorScheme:g(),onSaveImage:h,onSelectTest:p=>{n.setTest(p),m.setTest(p)},ownerDocument:i});return s.append(f.card,n.card,m.card),{card:s,destroy:()=>{f.destroy(),n.destroy(),m.destroy()}}}function ie(e,t,o){const r=A(i),s=Ce({title:"Test scatter",items:e,testDefs:t,groups:o,colorScheme:g(),onSaveImage:h,ownerDocument:i}),d=ge({title:"Test correlation matrix",items:e,testDefs:t,groups:o,colorScheme:g(),onSaveImage:h,onSelectPair:(u,c)=>s.setXY(u,c),ownerDocument:i});return r.append(d.card,s.card),{card:r,destroy:()=>{d.destroy(),s.destroy()}}}function Y(){for(const n of O)n.destroy();O=[],D.innerHTML="",S.innerHTML="",w.backTab&&S.appendChild(Z(w.backTab));for(const n of Oe)S.appendChild(J(n.key,n.label));const e=X();te(e);const t=ce(e,{facetableOnly:!0}).filter(n=>n.splittable),o=i.createElement("div");Object.assign(o.style,{display:"flex",gap:"8px",alignItems:"center"});let r;t.length>0?(o.appendChild(we("Group by:",[{value:"",label:"None"},...t.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],k??"",n=>{k=n||void 0,Y()},{hook:"group-by",ownerDocument:i})),r=t.find(n=>n.key===k)?.label):k=void 0,D.appendChild(o);const s=M().find(n=>n?.testDefs?.length)?.testDefs??[],d=s.filter(me);let u;if(k){const n=new Map,m=[];for(const f of e){const p=pe(f.metadata,k)??ue;n.has(p)||(n.set(p,[]),m.push(p)),n.get(p).push(f)}u=m.map(f=>({key:f,items:n.get(f)}))}const c=$==="overview"?ae(e,d,s,u,r):$==="distributions"?re(e,d,u,r):ie(e,d,u);O.push(c),D.appendChild(c.card)}return{el:B,render:Y,destroy:()=>{for(const e of O)e.destroy();B.remove()}}}
@@ -9,6 +9,10 @@ export interface MetadataBadgeOptions {
9
9
  * method instead of a (nonexistent) single waferId, per this project's
10
10
  * requirement that stacked maps always self-identify as such. */
11
11
  lotStack?: MetadataBadgeLotStack;
12
+ /** Document to build the badge into. Default `document` — pass the render's
13
+ * own `ownerDocument` when the container might live in a different
14
+ * document (e.g. a gallery card detached into its own popup window). */
15
+ ownerDocument?: Document;
12
16
  }
13
17
  export interface MetadataBadgeController {
14
18
  el: HTMLDivElement;
@@ -1 +1 @@
1
- import{metadataEntries as m,buildCompactMetadataRows as b}from"./summaryPanel.js";import{CLR as c,Z_BASE as h,wireExpandToggle as w}from"./toolbar.js";function y(n,r){if(r)return r.aggrMethod?`${r.lotSize} wafers \xB7 ${r.aggrMethod}`:`${r.lotSize} wafers`;if(n.lot&&n.waferId!==void 0&&n.waferId!==""){const t=String(n.lot),e=String(n.waferId);return e.includes(t)?e:`${t} \xB7 ${e}`}for(const t of["lot","waferId","product","testProgram"]){const e=n[t];if(e!=null&&e!=="")return String(e)}}export function createMetadataBadge(n,r={}){let t=n??{},e=r.lotStack,a=!1;const o=document.createElement("div");Object.assign(o.style,{position:"absolute",bottom:"4px",left:"4px",zIndex:h,background:c.menuBg,border:`1px solid ${c.menuBorder}`,borderRadius:"4px",boxShadow:"0 1px 4px rgba(0,0,0,0.12)",padding:"3px 8px",fontSize:"11px",color:c.value,cursor:"pointer",maxWidth:"260px",pointerEvents:"auto"});const l=document.createElement("div");Object.assign(l.style,{display:"flex",alignItems:"center",gap:"6px",whiteSpace:"nowrap"});const s=document.createElement("span");s.style.overflow="hidden",s.style.textOverflow="ellipsis";const f=document.createElement("span");Object.assign(f.style,{fontSize:"12px",lineHeight:"1",color:c.label,flexShrink:"0"}),l.appendChild(s),l.appendChild(f);const i=document.createElement("div");Object.assign(i.style,{marginTop:"5px",display:"none"}),o.appendChild(l),o.appendChild(i);function u(){const d=y(t,e)??"Wafer info";if(s.textContent=d,f.textContent=a?"\u25B4":"\u25BE",o.setAttribute("aria-expanded",String(a)),o.setAttribute("aria-label",`Wafer info: ${d}. ${a?"Click to collapse.":"Click to expand."}`),i.innerHTML="",i.style.display=a?"block":"none",!a)return;if(e){const g=document.createElement("div");Object.assign(g.style,{fontWeight:"600",marginBottom:"3px"}),g.textContent=e.aggrMethod?`${e.lotSize} wafers stacked \xB7 ${e.aggrMethod}`:`${e.lotSize} wafers stacked`,i.appendChild(g)}const p=b(t);p&&i.appendChild(p)}const x=w(o,d=>{a=d,u()});return u(),{el:o,update(d,p){t=d??{},e=p,u()},isEmpty(){return!e&&m(t).length===0},destroy(){x.destroy(),o.remove()}}}
1
+ import{metadataEntries as b,buildCompactMetadataRows as h}from"./summaryPanel.js";import{CLR as f,Z_BASE as w,wireExpandToggle as y}from"./toolbar.js";function C(n,o){if(o)return o.aggrMethod?`${o.lotSize} wafers \xB7 ${o.aggrMethod}`:`${o.lotSize} wafers`;if(n.lot&&n.waferId!==void 0&&n.waferId!==""){const t=String(n.lot),e=String(n.waferId);return e.includes(t)?e:`${t} \xB7 ${e}`}for(const t of["lot","waferId","product","testProgram"]){const e=n[t];if(e!=null&&e!=="")return String(e)}}export function createMetadataBadge(n,o={}){let t=n??{},e=o.lotStack,a=!1;const i=o.ownerDocument??document,r=i.createElement("div");Object.assign(r.style,{position:"absolute",bottom:"4px",left:"4px",zIndex:w,background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:"4px",boxShadow:"0 1px 4px rgba(0,0,0,0.12)",padding:"3px 8px",fontSize:"11px",color:f.value,cursor:"pointer",maxWidth:"260px",pointerEvents:"auto"});const s=i.createElement("div");Object.assign(s.style,{display:"flex",alignItems:"center",gap:"6px",whiteSpace:"nowrap"});const p=i.createElement("span");p.style.overflow="hidden",p.style.textOverflow="ellipsis";const g=i.createElement("span");Object.assign(g.style,{fontSize:"12px",lineHeight:"1",color:f.label,flexShrink:"0"}),s.appendChild(p),s.appendChild(g);const d=i.createElement("div");Object.assign(d.style,{marginTop:"5px",display:"none"}),r.appendChild(s),r.appendChild(d);function u(){const l=C(t,e)??"Wafer info";if(p.textContent=l,g.textContent=a?"\u25B4":"\u25BE",r.setAttribute("aria-expanded",String(a)),r.setAttribute("aria-label",`Wafer info: ${l}. ${a?"Click to collapse.":"Click to expand."}`),d.innerHTML="",d.style.display=a?"block":"none",!a)return;if(e){const x=i.createElement("div");Object.assign(x.style,{fontWeight:"600",marginBottom:"3px"}),x.textContent=e.aggrMethod?`${e.lotSize} wafers stacked \xB7 ${e.aggrMethod}`:`${e.lotSize} wafers stacked`,d.appendChild(x)}const c=h(t);c&&d.appendChild(c)}const m=y(r,l=>{a=l,u()});return u(),{el:r,update(l,c){t=l??{},e=c,u()},isEmpty(){return!e&&b(t).length===0},destroy(){m.destroy(),r.remove()}}}
@@ -42,6 +42,19 @@ export interface GalleryOptions {
42
42
  onViewOptionsChange?: (opts: WaferViewOptions, changed: (keyof WaferViewOptions)[], category: 'preference' | 'state' | 'mixed') => void;
43
43
  /** Legend position for bin modes. Default 'default'. */
44
44
  legendPosition?: 'default' | 'compact' | 'bottom' | 'top' | 'left' | 'floating';
45
+ /**
46
+ * Draw a legend on every card as well as the lot-level one. Default false.
47
+ *
48
+ * The lot legend already names every bin and is interactive, so per-card
49
+ * legends are off by default and the cards spend that width on the wafer.
50
+ * Turn this on when per-card bin highlighting matters more than card size.
51
+ *
52
+ * Ignored — cards always keep their own — when the plot mode is `value` with
53
+ * `colorbarRangeMode: 'data'`, where each card is scaled to its own range and
54
+ * a shared legend could not describe it. The toolbar shows the toggle
55
+ * disabled with that reason rather than appearing to do nothing.
56
+ */
57
+ perCardLegend?: boolean;
45
58
  /** Padding inside each card canvas in CSS pixels. Default 6. */
46
59
  cardPadding?: number;
47
60
  /** Filename stem for the composite gallery PNG. Default 'wafer-gallery'. */
@@ -81,7 +94,7 @@ export interface GalleryOptions {
81
94
  * Options for the always-available Summary panel alongside the gallery
82
95
  * grid — a "Lot" tab with full lot-level stats (metadata, yield, bin
83
96
  * breakdown, ring/quadrant yield, test values, findings) and a combined
84
- * Report button, plus a "Wafers" tab listing every wafer that has its own
97
+ * Report button, plus a "Findings" tab listing every wafer that has its own
85
98
  * per-wafer findings (click a row to open that wafer). Independent of
86
99
  * `insights` below: this always shows/hides its own toolbar button
87
100
  * regardless of whether Insights is open, since Insights has no per-wafer