@wafertools/wafermap 0.30.2 → 0.30.3

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 (82) hide show
  1. package/AGENTS.md +16 -5
  2. package/CHANGELOG.md +384 -0
  3. package/README.md +6 -3
  4. package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
  5. package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
  6. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
  7. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -1
  9. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +81 -7
  12. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  13. package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
  14. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
  15. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  16. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  17. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  18. package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
  19. package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
  21. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  22. package/dist/packages/canvas-adapter/dieList.js +2 -2
  23. package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
  24. package/dist/packages/canvas-adapter/drilldown.js +1 -0
  25. package/dist/packages/canvas-adapter/icons.js +1 -1
  26. package/dist/packages/canvas-adapter/insightsTab.d.ts +55 -3
  27. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  28. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  29. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  30. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  31. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  32. package/dist/packages/canvas-adapter/toolbar.d.ts +30 -2
  33. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  34. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  35. package/dist/packages/canvas-adapter/userGuideHtml.js +126 -3
  36. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  37. package/dist/packages/canvas-adapter/version.js +1 -1
  38. package/dist/packages/canvas-adapter/warnings.js +1 -1
  39. package/dist/packages/renderer/axisTicks.d.ts +32 -0
  40. package/dist/packages/renderer/axisTicks.js +1 -0
  41. package/dist/packages/renderer/buildView.d.ts +11 -37
  42. package/dist/packages/renderer/buildView.js +1 -1
  43. package/dist/packages/renderer/buildWaferMap.d.ts +58 -1
  44. package/dist/packages/renderer/buildWaferMap.js +1 -1
  45. package/dist/packages/renderer/deprecate.d.ts +13 -5
  46. package/dist/packages/renderer/deprecate.js +1 -1
  47. package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
  48. package/dist/packages/renderer/derivedTests/apply.js +1 -0
  49. package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
  50. package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
  51. package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
  52. package/dist/packages/renderer/derivedTests/parser.js +2 -0
  53. package/dist/packages/renderer/fmt.d.ts +24 -2
  54. package/dist/packages/renderer/fmt.js +1 -1
  55. package/dist/packages/renderer/index.d.ts +1 -0
  56. package/dist/packages/renderer/index.js +1 -1
  57. package/dist/packages/renderer/spec.d.ts +49 -0
  58. package/dist/packages/renderer/spec.js +1 -0
  59. package/dist/packages/renderer/testLabel.d.ts +154 -0
  60. package/dist/packages/renderer/testLabel.js +1 -0
  61. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  62. package/dist/packages/stats/capability.d.ts +4 -0
  63. package/dist/packages/stats/capability.js +1 -1
  64. package/dist/packages/stats/correlation.d.ts +3 -0
  65. package/dist/packages/stats/correlation.js +1 -1
  66. package/dist/packages/stats/deprecated.d.ts +3 -0
  67. package/dist/packages/stats/deprecated.js +1 -1
  68. package/dist/packages/stats/index.d.ts +2 -2
  69. package/dist/packages/stats/index.js +1 -1
  70. package/dist/packages/stats/mergeTestDefs.js +1 -1
  71. package/dist/packages/stats/renderFindingsReport.js +8 -28
  72. package/dist/packages/stats/renderSummaryReport.js +21 -30
  73. package/dist/packages/stats/reportHtml.d.ts +27 -0
  74. package/dist/packages/stats/reportHtml.js +35 -14
  75. package/dist/packages/stats/sweep.d.ts +178 -0
  76. package/dist/packages/stats/sweep.js +1 -0
  77. package/dist/packages/stats/sweepXFromName.d.ts +27 -0
  78. package/dist/packages/stats/sweepXFromName.js +1 -0
  79. package/dist/packages/stats/testPassRate.js +1 -1
  80. package/dist/packages/stats/types.d.ts +28 -1
  81. package/llms.txt +3 -2
  82. package/package.json +1 -1
@@ -1 +1 @@
1
- import{NO_DATA_FILL as ae}from"../../renderer/colorMap.js";import{maxOf as re,minOf as ce}from"../../core/utils.js";import{categorical as Me}from"./palette.js";import{buildScatterData as ve,buildScatterDataGrouped as Le}from"../../stats/scatter.js";import{pearsonOfPairs as ke}from"../../stats/correlation.js";import{buildFacetTable as Ce}from"../../stats/facets.js";import{SPACE as T,RADIUS as Ae,fontPx as j,FONT as q,CLR as H}from"../toolbar.js";import{cardShell as De,observeResize as Oe,makeTooltip as $e,attachChartTip as Be,makeTestSelect as Pe,makeWaferSelect as He,chartFillHeight as We,applyCanvasFlow as Ee,drawAxisUnit as Fe,resolveChartCanvasColors as Ie,makeAxisFormat as de,makeSeriesLegendItem as Ne,prepareCanvas as Re}from"./chartShell.js";const c=52,ze=16,s=16,_e=44;export function renderScatterPanel(W){const{title:me="Test scatter",items:C,testDefs:J,binColors:fe,onSaveImage:pe,groups:E}=W,{card:i,body:L,controlsRow:Q}=De(me,pe,W.ownerDocument),F=J.filter(t=>t.testNumber!==void 0),y=!!(E&&E.length>0),V=(E??[]).map(t=>t.key),ue=new Map(V.map((t,n)=>[t,n]));let p=W.xTestNumber??F[0]?.testNumber??null,x=W.yTestNumber??F[1]?.testNumber??p,A=null;const Z=()=>A!==null?C[A]?[C[A]]:[]:C;function ee(t,n,o){const l=i.ownerDocument.createElement("label");Object.assign(l.style,{display:"inline-flex",alignItems:"center",gap:T.xs,fontSize:q.body,color:H.label});const g=i.ownerDocument.createElement("span");g.textContent=t;const m=Pe(F,n,o,{maxWidth:"180px",emptyText:"No tests",ownerDocument:i.ownerDocument});return l.append(g,m),{wrap:l,select:m}}const{wrap:xe,select:he}=ee("X:",p,t=>{p=t,$()}),{wrap:ge,select:be}=ee("Y:",x,t=>{x=t,$()});Q.append(xe,ge),!y&&C.length>1&&Q.appendChild(He(C,A,t=>{A=t,$()},{ownerDocument:i.ownerDocument}));const D=i.ownerDocument.createElement("div");Object.assign(D.style,{color:H.warnText,background:H.warnBg,border:`1px solid ${H.warnBorder}`,borderRadius:Ae.control,padding:`${T.xs} ${T.md}`,fontSize:q.body,marginBottom:T.xs,display:"none"}),i.insertBefore(D,L);function te(){const t=y?[]:Ce(Z(),{facetableOnly:!0}).filter(n=>n.splittable).map(n=>n.label);t.length>0?(D.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.`,D.style.display=""):D.style.display="none"}te();const U=i.ownerDocument.createElement("div");Object.assign(U.style,{color:H.label,fontSize:q.body,marginBottom:T.xs}),i.insertBefore(U,L);function G(){const t=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",n=h.size===0?f:f.filter(g=>h.has(I(g))),{r:o,n:l}=ke(n);U.textContent=o===null?`${t} \xB7 n = ${l.toLocaleString()}`:`${t} \xB7 r = ${o.toFixed(3)} \xB7 n = ${l.toLocaleString()}`}const I=t=>y?t.group??"\u2014":String(t.hbin??0),ne=t=>y?Me(ue.get(t)??0):t==="0"?ae:fe?.get(Number(t))??ae,oe=t=>y?t:t==="0"?"No bin data":`Bin ${t}`,h=new Set,O=i.ownerDocument.createElement("div");Object.assign(O.style,{display:"flex",flexWrap:"wrap",gap:T.sm,marginTop:T.md,marginBottom:T.xl}),L.appendChild(O);const k=i.ownerDocument.createElement("canvas");k.style.display="block",k.style.cursor="crosshair",L.appendChild(k);const ye=$e(i);let f=[],w=0,N=1,M=0,R=1;function z(t){const n=J.find(o=>o.testNumber===t);return{unit:n?.unit,limitLow:n?.limitLow,limitHigh:n?.limitHigh}}function Se(){const t=L.clientWidth,n=Math.max(200,Math.min(400,t*.65)),o=We(i,L,k,n);return{w:t,h:o,plotW:Math.max(10,t-c-ze),plotH:Math.max(10,o-s-_e)}}const X=new Map;function ie(){for(const[t,n]of X){const o=h.has(t);n.setState({selected:o,dimmed:h.size>0&&!o})}}function le(t){O.innerHTML="",X.clear(),h.clear();for(const n of t){const o=Ne(oe(n),ne(n),i.ownerDocument),l=o.el;l.dataset.cat=n,X.set(n,o),Be(l,i,ye,`${oe(n)} \u2014 click to filter`),l.addEventListener("click",()=>{h.has(n)?h.delete(n):h.add(n),ie(),G(),_()}),O.appendChild(l)}ie()}function _(){Ee(k,O);const t=Ie(i),n=N-w,o=R-M,{w:l,h:g,plotW:m,plotH:d}=Se(),B=Re(k,i,l,g);if(!B)return;const{ctx:e}=B;if(f.length===0){e.font=`${j()}px 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,g/2);return}const b=4,Y=de(Math.max(Math.abs(w),Math.abs(N)),p!==null?z(p).unit:void 0),K=de(Math.max(Math.abs(M),Math.abs(R)),x!==null?z(x).unit:void 0);e.font=`${j(-1)}px system-ui, sans-serif`,e.strokeStyle=t.border,e.lineWidth=.5,e.fillStyle=t.textMuted;for(let a=0;a<=b;a++){const S=w+n*a/b,r=c+a/b*m;e.beginPath(),e.moveTo(r,s),e.lineTo(r,s+d),e.stroke(),e.textAlign="center",e.textBaseline="top",e.fillText(Y.tick(S),r,s+d+4);const v=M+o*a/b,u=s+(1-a/b)*d;e.beginPath(),e.moveTo(c,u),e.lineTo(c+m,u),e.stroke(),e.textAlign="right",e.textBaseline="middle",e.fillText(K.tick(v),c-4,u)}Y.unitLabel&&Fe(e,Y.unitLabel,c+m/2,s+d+24,t.textMuted),K.unitLabel&&(e.save(),e.font=`${j(-1)}px system-ui, sans-serif`,e.fillStyle=t.textMuted,e.textAlign="center",e.textBaseline="middle",e.translate(6,s+d/2),e.rotate(-Math.PI/2),e.fillText(`(${K.unitLabel})`,0,0),e.restore());const P=h.size===0?f:f.filter(a=>h.has(I(a))),se=P.length>5e3?Math.ceil(P.length/5e3):1;e.globalAlpha=Math.max(.15,Math.min(.7,200/(P.length/se)));for(let a=0;a<P.length;a+=se){const S=P[a],r=c+(S.x-w)/n*m,v=s+(1-(S.y-M)/o)*d;e.beginPath(),e.arc(r,v,2.5,0,Math.PI*2),e.fillStyle=ne(I(S)),e.fill()}if(e.globalAlpha=1,p!==null&&x!==null){const a=z(p),S=z(x);e.strokeStyle=t.textMuted,e.fillStyle=t.textMuted,e.lineWidth=1,e.setLineDash([4,3]),e.font=`${j(-1)}px system-ui, sans-serif`,e.textBaseline="top";for(const[r,v]of[[a.limitLow,"LSL"],[a.limitHigh,"USL"]]){if(r===void 0||r<w||r>N)continue;const u=c+(r-w)/n*m;e.beginPath(),e.moveTo(u,s),e.lineTo(u,s+d),e.stroke(),e.textAlign="left",e.fillText(v,u+2,s+2)}for(const[r,v]of[[S.limitLow,"LSL"],[S.limitHigh,"USL"]]){if(r===void 0||r<M||r>R)continue;const u=s+(1-(r-M)/o)*d;e.beginPath(),e.moveTo(c,u),e.lineTo(c+m,u),e.stroke(),e.textAlign="left",e.fillText(v,c+3,u+2)}e.setLineDash([])}e.strokeStyle=t.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,s),e.lineTo(c,s+d),e.moveTo(c,s+d),e.lineTo(c+m,s+d),e.stroke()}function $(){if(te(),F.length<2||p===null||x===null){f=[],le([]),G(),_();return}f=y?Le(E,p,x):ve(Z(),p,x);let t;if(y){const n=new Set(f.map(I));t=V.filter(o=>n.has(o))}else t=Array.from(new Set(f.map(n=>n.hbin??0))).sort((n,o)=>n-o).map(String);if(le(t),G(),f.length>0){const n=f.map(b=>b.x),o=f.map(b=>b.y),l=ce(n),g=re(n),m=ce(o),d=re(o),B=(g-l)*.05||1,e=(d-m)*.05||1;w=l-B,N=g+B,M=m-e,R=d+e}_()}const Te=Oe(i,()=>_());$();function we(t,n){p=t,x=n,he.value=String(t),be.value=String(n),$()}return{card:i,setXY:we,destroy:()=>Te.disconnect()}}
1
+ import{NO_DATA_FILL as ce}from"../../renderer/colorMap.js";import{maxOf as de,minOf as me}from"../../core/utils.js";import{categorical as ve}from"./palette.js";import{buildScatterData as Ce,buildScatterDataGrouped as Ae}from"../../stats/scatter.js";import{pearsonOfPairs as De}from"../../stats/correlation.js";import{buildFacetTable as Oe}from"../../stats/facets.js";import{SPACE as S,RADIUS as Pe,fontPx as W,FONT as V,CLR as F}from"../toolbar.js";import{fitTicks as fe}from"../../renderer/axisTicks.js";import{cardShell as $e,observeResize as Be,makeTooltip as Ee,attachChartTip as He,makeTestSelect as Ie,makeWaferSelect as We,chartFillHeight as Fe,applyCanvasFlow as Ne,drawAxisUnit as Re,resolveChartCanvasColors as ze,makeAxisFormat as pe,horizontalTickSpacing as _e,VERTICAL_TICK_SPACING_PX as Ue,makeSeriesLegendItem as je,prepareCanvas as Ge}from"./chartShell.js";const r=52,Xe=16,l=16,Ye=44;export function renderScatterPanel(N){const{title:ue="Test scatter",items:v,testDefs:q,binColors:xe,onSaveImage:he,groups:R}=N,{card:i,body:M,controlsRow:J}=$e(ue,he,N.ownerDocument),z=q.filter(t=>t.testNumber!==void 0),y=!!(R&&R.length>0),Q=(R??[]).map(t=>t.key),ge=new Map(Q.map((t,n)=>[t,n]));let f=N.xTestNumber??z[0]?.testNumber??null,x=N.yTestNumber??z[1]?.testNumber??f,C=null;const Z=()=>C!==null?v[C]?[v[C]]:[]:v;function ee(t,n,o){const s=i.ownerDocument.createElement("label");Object.assign(s.style,{display:"inline-flex",alignItems:"center",gap:S.xs,fontSize:V.body,color:F.label});const g=i.ownerDocument.createElement("span");g.textContent=t;const c=Ie(z,n,o,{maxWidth:"180px",emptyText:"No tests",ownerDocument:i.ownerDocument});return s.append(g,c),{wrap:s,select:c}}const{wrap:be,select:ye}=ee("X:",f,t=>{f=t,$()}),{wrap:Te,select:Se}=ee("Y:",x,t=>{x=t,$()});J.append(be,Te),!y&&v.length>1&&J.appendChild(We(v,C,t=>{C=t,$()},{ownerDocument:i.ownerDocument}));const A=i.ownerDocument.createElement("div");Object.assign(A.style,{color:F.warnText,background:F.warnBg,border:`1px solid ${F.warnBorder}`,borderRadius:Pe.control,padding:`${S.xs} ${S.md}`,fontSize:V.body,marginBottom:S.xs,display:"none"}),i.insertBefore(A,M);function te(){const t=y?[]:Oe(Z(),{facetableOnly:!0}).filter(n=>n.splittable).map(n=>n.label);t.length>0?(A.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.`,A.style.display=""):A.style.display="none"}te();const j=i.ownerDocument.createElement("div");Object.assign(j.style,{color:F.label,fontSize:V.body,marginBottom:S.xs}),i.insertBefore(j,M);function G(){const t=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",n=h.size===0?p:p.filter(g=>h.has(_(g))),{r:o,n:s}=De(n);j.textContent=o===null?`${t} \xB7 n = ${s.toLocaleString()}`:`${t} \xB7 r = ${o.toFixed(3)} \xB7 n = ${s.toLocaleString()}`}const _=t=>y?t.group??"\u2014":String(t.hbin??0),ne=t=>y?ve(ge.get(t)??0):t==="0"?ce:xe?.get(Number(t))??ce,oe=t=>y?t:t==="0"?"No bin data":`Bin ${t}`,h=new Set,D=i.ownerDocument.createElement("div");Object.assign(D.style,{display:"flex",flexWrap:"wrap",gap:S.sm,marginTop:S.md,marginBottom:S.xl}),M.appendChild(D);const k=i.ownerDocument.createElement("canvas");k.style.display="block",k.style.cursor="crosshair",M.appendChild(k);const we=Ee(i);let p=[],b=0,L=1,T=0,O=1;function P(t){const n=q.find(o=>o.testNumber===t);return{unit:n?.unit,limitLow:n?.limitLow,limitHigh:n?.limitHigh}}function Me(){const t=M.clientWidth,n=Math.max(200,Math.min(400,t*.65)),o=Fe(i,M,k,n);return{w:t,h:o,plotW:Math.max(10,t-r-Xe),plotH:Math.max(10,o-l-Ye)}}const X=new Map;function ie(){for(const[t,n]of X){const o=h.has(t);n.setState({selected:o,dimmed:h.size>0&&!o})}}function se(t){D.innerHTML="",X.clear(),h.clear();for(const n of t){const o=je(oe(n),ne(n),i.ownerDocument),s=o.el;s.dataset.cat=n,X.set(n,o),He(s,i,we,`${oe(n)} \u2014 click to filter`),s.addEventListener("click",()=>{h.has(n)?h.delete(n):h.add(n),ie(),G(),U()}),D.appendChild(s)}ie()}function U(){Ne(k,D);const t=ze(i),n=L-b,o=O-T,{w:s,h:g,plotW:c,plotH:a}=Me(),B=Ge(k,i,s,g);if(!B)return;const{ctx:e}=B;if(p.length===0){e.font=`${W()}px 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.",s/2,g/2);return}e.font=`${W(-1)}px system-ui, sans-serif`;const E=f!==null?P(f).unit:void 0,le=fe(b,L,c,_e(e,Math.max(Math.abs(b),Math.abs(L)),E)),ae=fe(T,O,a,()=>Ue),Y=pe(Math.max(Math.abs(b),Math.abs(L)),f!==null?P(f).unit:void 0,le.step||void 0),K=pe(Math.max(Math.abs(T),Math.abs(O)),x!==null?P(x).unit:void 0,ae.step||void 0);e.font=`${W(-1)}px system-ui, sans-serif`,e.strokeStyle=t.border,e.lineWidth=.5,e.fillStyle=t.textMuted;for(const d of le.ticks){const m=r+(d-b)/n*c;e.beginPath(),e.moveTo(m,l),e.lineTo(m,l+a),e.stroke(),e.textAlign="center",e.textBaseline="top",e.fillText(Y.tick(d),m,l+a+4)}for(const d of ae.ticks){const m=l+(1-(d-T)/o)*a;e.beginPath(),e.moveTo(r,m),e.lineTo(r+c,m),e.stroke(),e.textAlign="right",e.textBaseline="middle",e.fillText(K.tick(d),r-4,m)}Y.unitLabel&&Re(e,Y.unitLabel,r+c/2,l+a+24,t.textMuted),K.unitLabel&&(e.save(),e.font=`${W(-1)}px system-ui, sans-serif`,e.fillStyle=t.textMuted,e.textAlign="center",e.textBaseline="middle",e.translate(6,l+a/2),e.rotate(-Math.PI/2),e.fillText(`(${K.unitLabel})`,0,0),e.restore());const H=h.size===0?p:p.filter(d=>h.has(_(d))),re=H.length>5e3?Math.ceil(H.length/5e3):1;e.globalAlpha=Math.max(.15,Math.min(.7,200/(H.length/re)));for(let d=0;d<H.length;d+=re){const m=H[d],u=r+(m.x-b)/n*c,I=l+(1-(m.y-T)/o)*a;e.beginPath(),e.arc(u,I,2.5,0,Math.PI*2),e.fillStyle=ne(_(m)),e.fill()}if(e.globalAlpha=1,f!==null&&x!==null){const d=P(f),m=P(x);e.strokeStyle=t.textMuted,e.fillStyle=t.textMuted,e.lineWidth=1,e.setLineDash([4,3]),e.font=`${W(-1)}px system-ui, sans-serif`,e.textBaseline="top";for(const[u,I]of[[d.limitLow,"LSL"],[d.limitHigh,"USL"]]){if(u===void 0||u<b||u>L)continue;const w=r+(u-b)/n*c;e.beginPath(),e.moveTo(w,l),e.lineTo(w,l+a),e.stroke(),e.textAlign="left",e.fillText(I,w+2,l+2)}for(const[u,I]of[[m.limitLow,"LSL"],[m.limitHigh,"USL"]]){if(u===void 0||u<T||u>O)continue;const w=l+(1-(u-T)/o)*a;e.beginPath(),e.moveTo(r,w),e.lineTo(r+c,w),e.stroke(),e.textAlign="left",e.fillText(I,r+3,w+2)}e.setLineDash([])}e.strokeStyle=t.border,e.lineWidth=1,e.beginPath(),e.moveTo(r,l),e.lineTo(r,l+a),e.moveTo(r,l+a),e.lineTo(r+c,l+a),e.stroke()}function $(){if(te(),z.length<2||f===null||x===null){p=[],se([]),G(),U();return}p=y?Ae(R,f,x):Ce(Z(),f,x);let t;if(y){const n=new Set(p.map(_));t=Q.filter(o=>n.has(o))}else t=Array.from(new Set(p.map(n=>n.hbin??0))).sort((n,o)=>n-o).map(String);if(se(t),G(),p.length>0){const n=p.map(E=>E.x),o=p.map(E=>E.y),s=me(n),g=de(n),c=me(o),a=de(o),B=(g-s)*.05||1,e=(a-c)*.05||1;b=s-B,L=g+B,T=c-e,O=a+e}U()}const ke=Be(i,()=>U());$();function Le(t,n){f=t,x=n,ye.value=String(t),Se.value=String(n),$()}return{card:i,setXY:Le,destroy:()=>ke.disconnect()}}
@@ -0,0 +1,31 @@
1
+ import { type SweepSpec } from '../../stats/sweep.js';
2
+ import type { Die } from '../../core/dies.js';
3
+ import type { TestDef } from '../../renderer/buildWaferMap.js';
4
+ import { type SaveImageHandler } from './chartShell.js';
5
+ export interface SweepPanelOptions {
6
+ /** The curve definition. Title comes from here, not from the panel. */
7
+ spec: SweepSpec;
8
+ /** Population to aggregate over — already scoped by the caller. Scope is not
9
+ * a property of the sweep: the same definition is valid for any population,
10
+ * which is what keeps a saved spec portable. */
11
+ dies: Die[];
12
+ /**
13
+ * Who `dies` are, read straight after the count — `"selected on W03"` gives
14
+ * "median across 12 dies selected on W03". Omit when the dies are simply the
15
+ * view's own population (the Insights tab, which states its scope itself).
16
+ * A sweep of a hand-picked subset looks exactly like a whole-wafer one, so
17
+ * a drilldown must always pass this.
18
+ */
19
+ population?: string;
20
+ testDefs: TestDef[] | undefined;
21
+ onSaveImage?: SaveImageHandler;
22
+ ownerDocument?: Document;
23
+ }
24
+ export interface SweepPanelHandle {
25
+ card: HTMLElement;
26
+ destroy: () => void;
27
+ /** Re-aggregate over a new population, keeping the same curve definition. */
28
+ setDies: (dies: Die[]) => void;
29
+ }
30
+ export declare function renderSweepPanel(options: SweepPanelOptions): SweepPanelHandle;
31
+ //# sourceMappingURL=sweep.d.ts.map
@@ -0,0 +1 @@
1
+ import{buildSweepData as ce}from"../../stats/sweep.js";import{fmt as C}from"../../renderer/fmt.js";import{SPACE as ne,fontPx as fe,FONT as oe,CLR as G,ALPHA as pe}from"../toolbar.js";import{categorical as de}from"./palette.js";import{cardShell as he,observeResize as ue,makeTooltip as me,positionChartTooltip as xe,renderEmptyState as se,chartFillHeight as ge,populationPhrase as be,PADDING as Y,prepareCanvas as ye,makeAxisFormat as $e,VERTICAL_TICK_SPACING_PX as Te}from"./chartShell.js";import{fitTicks as ve}from"../../renderer/axisTicks.js";import{escHtml as u}from"../../core/utils.js";import{unmarkedLabel as Se,derivedTestNote as Me,DERIVED_MARK as J,DERIVED_KEY as Le}from"../../renderer/testLabel.js";function _(m,a){return a?C(m,a).replace(/(\.\d*?)0+(?=\D|$)/,"$1").replace(/\.(?=\D|$)/,""):Number.isInteger(m)?String(m):String(Number(m.toPrecision(3)))}function Pe(m){const a=Se(m.label),S=a.length>9?`${a.slice(0,8)}\u2026`:a;return m.derived?`${J} ${S}`:S}const ie=260,we=62,ke=56,Ae=3;function Ee(m,a){const S=m.series[0]?.points[0]?.testNumber;return S===void 0?void 0:a?.find(j=>j.testNumber===S)?.unit}export function renderSweepPanel(m){const{spec:a,testDefs:S,onSaveImage:j,population:re}=m;let z=m.dies;const{card:y,body:M}=he(a.title,j,m.ownerDocument);y.style.alignSelf="start";const V=y.ownerDocument,N=V.createElement("div");Object.assign(N.style,{color:G.label,fontSize:oe.body,marginBottom:ne.sm}),y.insertBefore(N,M);const W=V.createElement("div");Object.assign(W.style,{color:G.label,fontSize:oe.body,marginTop:ne.sm}),y.appendChild(W);const L=me(y);let X=null,E=null,$;const H=n=>a.series[n]?.color??de(n);function Q(){if(M.innerHTML="",W.innerHTML="",X?.disconnect(),X=null,a.series.length===0){N.textContent="",se(M,"This sweep defines no series.");return}if(E=ce(z,S,a),$=Ee(E,S),E.series.filter(i=>i.points.some(T=>T.count>0)).length===0){N.textContent="",se(M,"No values recorded for the tests in this sweep."),Z();return}N.textContent=`Line = median across ${be(E.dieCount,z.length,re)}, band = p10\u2013p90`+(a.series.length>2?" \xB7 crossing and separation measured between the first two series":"");const c=V.createElement("canvas");c.style.display="block",M.appendChild(c);let o=null;const P=()=>{const i=E;if(i===null)return;const T=Math.max(1,M.clientWidth),B=Math.max(ie,ge(y,M,c,ie)),F=ye(c,y,T,B);if(!F)return;const{ctx:e,theme:x}=F,g=t=>i.xScale==="log"?Math.log10(t):t;let f=1/0,h=-1/0,b=1/0,v=-1/0;for(const t of i.series)for(const s of t.points)s.count!==0&&(f=Math.min(f,s.p10),h=Math.max(h,s.p90),b=Math.min(b,g(s.x)),v=Math.max(v,g(s.x)));for(const t of i.separations)f=Math.min(f,t.y),h=Math.max(h,t.y);if(!Number.isFinite(f)||!Number.isFinite(h))return;f===h&&(f-=1,h+=1);const l=(h-f)*.08;if(f-=l,h+=l,b===v&&(b-=.5,v+=.5),i.xScale==="log"){const t=(v-b)*.03;b-=t,v+=t}e.font=`${fe(-1)}px system-ui, sans-serif`;const r=Y+we,p=T-Y-8,R=Y,w=B-ke,k=t=>w-(t-f)/(h-f)*(w-R),D=t=>r+(g(t)-b)/(v-b)*(p-r);o={left:r,right:p,xLo:b,xHi:v,yOf:k,ax:g},e.strokeStyle=x.border,e.lineWidth=1,e.beginPath(),e.moveTo(r,R),e.lineTo(r,w),e.lineTo(p,w),e.stroke(),e.fillStyle=x.text,e.textAlign="right",e.textBaseline="middle";const q=ve(f,h,w-R,()=>Te),le=$e(Math.max(Math.abs(f),Math.abs(h)),$,q.step||void 0);for(const t of q.ticks)e.fillText(le.tickWithUnit(t),r-6,k(t));for(const t of i.separations){const s=k(t.y);e.save(),e.strokeStyle=x.limitLine,e.globalAlpha=.55,e.setLineDash([4,3]),e.beginPath(),e.moveTo(r,s),e.lineTo(p,s),e.stroke(),e.restore()}if(i.series.forEach((t,s)=>{const A=t.points.filter(d=>d.count>0);if(!(A.length<2)){e.save(),e.fillStyle=H(s),e.globalAlpha=.14,e.beginPath(),A.forEach((d,I)=>{const O=D(d.x),te=k(d.p90);I===0?e.moveTo(O,te):e.lineTo(O,te)});for(let d=A.length-1;d>=0;d--){const I=A[d];e.lineTo(D(I.x),k(I.p10))}e.closePath(),e.fill(),e.restore()}}),i.series.forEach((t,s)=>{e.strokeStyle=H(s),e.lineWidth=1.75,e.beginPath();let A=!1;for(const d of t.points){if(d.count===0){A=!1;continue}const I=D(d.x),O=k(d.median);A?e.lineTo(I,O):e.moveTo(I,O),A=!0}e.stroke(),e.fillStyle=H(s);for(const d of t.points)d.count!==0&&(e.beginPath(),e.arc(D(d.x),k(d.median),Ae,0,Math.PI*2),e.fill())}),i.crossing){const t=D(i.crossing.x),s=k(i.crossing.y);e.save(),e.strokeStyle=x.text,e.globalAlpha=.45,e.setLineDash([3,3]),e.beginPath(),e.moveTo(t,s),e.lineTo(t,w),e.stroke(),e.restore(),e.save(),e.strokeStyle=x.text,e.lineWidth=2,e.beginPath(),e.arc(t,s,5.5,0,Math.PI*2),e.stroke(),e.restore()}e.fillStyle=x.text,e.textBaseline="top",e.textAlign="center";const K=i.xIsPhysical?[...new Set(i.series.flatMap(t=>t.points.map(s=>s.x)))].sort((t,s)=>t-s).map(t=>({x:t,label:_(t,i.xUnit)})):(i.series[0]?.points??[]).map(t=>({x:t.x,label:Pe(t)})),ae=Math.max(1,Math.ceil(K.length*46/Math.max(1,p-r)));K.forEach((t,s)=>{s%ae!==0&&s!==K.length-1||e.fillText(t.label,D(t.x),w+6)}),e.save(),e.fillStyle=x.text,e.globalAlpha=.75,e.textAlign="center",e.textBaseline="bottom",e.fillText(i.xLabel,(r+p)/2,B-4),e.restore(),e.save(),e.fillStyle=x.text,e.globalAlpha=.75,e.translate(12,(R+w)/2),e.rotate(-Math.PI/2),e.textBaseline="top",e.fillText(i.yLabel+($?` (${$})`:""),0,0),e.restore(),e.textAlign="left",e.textBaseline="middle";let U=r+4;const ee=R+8;i.series.forEach((t,s)=>{e.fillStyle=H(s),e.fillRect(U,ee-4,10,3),U+=14,e.fillStyle=x.text,e.fillText(t.label,U,ee),U+=e.measureText(t.label).width+16})};P(),X=ue(M,P),c.addEventListener("mousemove",i=>{const T=E;if(T===null||o===null){L.style.display="none";return}const B=c.getBoundingClientRect(),F=i.clientX-B.left,e=o.xLo+(F-o.left)/Math.max(1,o.right-o.left)*(o.xHi-o.xLo),x=o.ax;let g=null;for(const l of T.series)for(const r of l.points){if(r.count===0)continue;const p=Math.abs(x(r.x)-e);(g===null||p<g.dist)&&(g={x:r.x,dist:p})}if(g===null){L.style.display="none";return}const f=T.series.map(l=>l.points.find(r=>r.x===g.x&&r.count>0)),h=T.series.map((l,r)=>{const p=f[r];return p===void 0?"":`<span style="color:${H(r)}">\u25A0</span> ${p.derived?`${J} `:""}${u(l.label)}: <strong>${u(C(p.median,$))}</strong><br><span style="opacity:${pe.muted}">p10 ${u(C(p.p10,$))} \u2013 p90 ${u(C(p.p90,$))} \xB7 n=${p.count.toLocaleString()}</span>`}).filter(Boolean);if(h.length===0){L.style.display="none";return}const b=T.series.flatMap(l=>l.points).find(l=>l.x===g.x&&l.count>0),v=[...new Set(f.map(l=>Me(l)).filter(l=>l!==void 0))];L.innerHTML=`<strong>${u(b?.label??"")}</strong><br>`+h.join("<br>")+v.map(l=>`<br><em>${u(l)}</em>`).join(""),L.style.display="block",xe(L,y,i.clientX,i.clientY)}),c.addEventListener("mouseleave",()=>{L.style.display="none"}),Z()}function Z(){const n=E;if(n===null)return;const c=[];n.notMeasured!==void 0?c.push(`<strong>Crossing and widths:</strong> not measured \u2014 ${u(n.notMeasured)}`):n.crossing?c.push(`<strong>Crossing:</strong> ${u(`${n.xLabel} = ${_(n.crossing.x,n.xUnit)}`)} \xB7 ${u(`${n.yLabel} ${C(n.crossing.y,$)}`)}`+(n.crossing.multiple?" <em>(first of several \u2014 the curves cross more than once)</em>":"")):(a.crossing??!0)&&a.series.length>=2&&c.push("<strong>Crossing:</strong> the first two series do not cross over the swept range");for(const o of n.separations)if(o.distance!==null){const P=o.ratio!==void 0&&o.from!==void 0&&o.to!==void 0?`\xD7${Number(o.ratio.toPrecision(3))} on the ${n.xLabel} axis (${_(o.from,n.xUnit)} \u2192 ${_(o.to,n.xUnit)})`:`${_(o.distance,n.xUnit)} on the ${n.xLabel} axis`;c.push(`<strong>Width at ${u(C(o.y,$))}:</strong> ${u(P)}`)}else{const P=o.reason==="second-series-never-reaches"?n.series[1]?.label:n.series[0]?.label;c.push(`<strong>Width at ${u(C(o.y,$))}:</strong> not measurable \u2014 ${u(P??"a series")} never reaches this level`)}n.series.some(o=>o.points.some(P=>P.derived))&&c.push(`${J} ${u(Le)} \u2014 hover a point for the expression`);for(const o of n.warnings)c.push(`<span style="color:${G.errText}">${u(o)}</span>`);W.innerHTML=c.join("<br>")}return Q(),{card:y,destroy:()=>{X?.disconnect(),L.remove(),y.remove()},setDies:n=>{z=n,Q()}}}
@@ -1,6 +1,6 @@
1
1
  import { type TrendItem } from '../../stats/trend.js';
2
2
  import { type TestDef } from '../../renderer/buildWaferMap.js';
3
- import { type AxisPrefs, type SaveImageHandler } from './chartShell.js';
3
+ import { type AxisPrefs, type SaveImageHandler, type WaferContextMenuHandler } from './chartShell.js';
4
4
  export interface TrendPanelOptions {
5
5
  title?: string;
6
6
  items: TrendItem[];
@@ -9,6 +9,8 @@ export interface TrendPanelOptions {
9
9
  onSaveImage?: SaveImageHandler;
10
10
  /** Click a point to open that wafer's map on this test. */
11
11
  onOpen?: (key: number, testNumber: number) => void;
12
+ /** Right-click on a wafer's point — see `WaferContextMenuHandler`. */
13
+ onWaferContextMenu?: WaferContextMenuHandler;
12
14
  /**
13
15
  * What the dashed reference line is, as a noun phrase — "lot mean" when the
14
16
  * items are one lot, else "mean of all wafers" (see stats/population.ts).
@@ -1 +1 @@
1
- import{buildTestTrendData as fe,trendCentre as ue}from"../../stats/trend.js";import{isParametricTest as he}from"../../renderer/buildWaferMap.js";import{fmt as D}from"../../renderer/fmt.js";import{SPACE as oe,fontPx as pe,FONT as ge,CLR as xe}from"../toolbar.js";import{QUANTITY as _}from"./palette.js";import{cardShell as be,observeResize as ye,makeTooltip as ve,positionChartTooltip as Te,makeLinkedTestSelect as Le,makeLinkedAxisPrefs as we,renderEmptyState as z,growCardToFitContent as ke,chartFillHeight as Ce,PADDING as X,resolveAxisRange as Se,shouldIncludeLimitsByDefault as Ae,drawOffAxisLimits as Pe,prepareCanvas as Ne}from"./chartShell.js";import{escHtml as j}from"../../core/utils.js";const le=220,De=56,He=34,Me=3.5;export function renderTrendPanel(g){const{items:G,testDefs:se,onSaveImage:re,onOpen:C,centreLabel:U="mean of all wafers"}=g,ae=g.title??"Wafer-to-wafer trend",{card:l,body:f,controlsRow:Y}=be(ae,re,g.ownerDocument);l.style.alignSelf="start";const H=se.filter(o=>o.testNumber!==void 0&&he(o));let h=g.selectedTestNumber??H[0]?.testNumber??null,I=0;const Q=Le(H,h,o=>{h=o,S(),g.onTestChange?.(o)},{maxWidth:"240px",emptyText:"No parametric tests",ownerDocument:l.ownerDocument});Y.appendChild(Q.el);const O=l.ownerDocument.createElement("span");Object.assign(O.style,{display:"inline-flex",gap:oe.lg,alignItems:"center"}),Y.appendChild(O);const $=we(O,g.axisPrefs,o=>{g.onAxisPrefsChange?.(o),S()},l.ownerDocument),T=l.ownerDocument.createElement("div");Object.assign(T.style,{color:xe.label,fontSize:ge.body,marginBottom:oe.sm}),l.insertBefore(T,f);const L=ve(l);let M=null,n=[];function ce(o){return H.find(B=>B.testNumber===o)}function S(){const{includeLimits:o,clipOutliers:B}=$.get();if(f.innerHTML="",M?.disconnect(),M=null,H.length===0||h===null){T.textContent="",z(f,"No parametric test data available for a trend.");return}if(G.length<2){T.textContent="",z(f,"A wafer-to-wafer trend needs at least two wafers.");return}if(n=fe(G,h),!n.some(r=>r.count>0)){T.textContent="",z(f,"No values recorded for this test.");return}const s=ce(h),x=ue(n),q=()=>{T.textContent=`Point = wafer mean, whisker = \xB11\u03C3, dashed = ${U} \xB7 wafers in slot order`+(C?" \xB7 click a point to open that wafer":"")+(I?` \xB7 axis clipped, ${I} outside`:"")};q();const u=l.ownerDocument.createElement("canvas");u.style.display="block",f.appendChild(u);let A=null;const J=()=>{const r=Math.max(1,f.clientWidth),d=Math.max(le,Ce(l,f,u,le)),i=Ne(u,l,r,d);if(!i)return;const{ctx:e,theme:b}=i;let P=1/0,N=-1/0;const V=[];for(const t of n)!t.count||!Number.isFinite(t.mean)||(P=Math.min(P,t.mean-t.stddev),N=Math.max(N,t.mean+t.stddev),V.push(t.mean-t.stddev,t.mean+t.stddev));if(!Number.isFinite(P)||!Number.isFinite(N))return;const Z=o??Ae(P,N,s?.limitLow,s?.limitHigh);$.sync(Z,s?.limitLow!==void 0||s?.limitHigh!==void 0);const E=Se({dataMin:P,dataMax:N,limitLow:s?.limitLow,limitHigh:s?.limitHigh,includeLimits:Z,clipOutliers:B,values:V});I=E.clippedCount,q();let p=E.lo,w=E.hi;const ee=(w-p)*.08;p-=ee,w+=ee;const a=X+De;e.font=`${pe(-1)}px system-ui, sans-serif`;const te=t=>t.length>8?`${t.slice(0,7)}\u2026`:t,me=n.length?Math.ceil(Math.max(e.measureText(te(n[0].label)).width,e.measureText(te(n[n.length-1].label)).width)/2):0,y=r-X-me,F=X,k=d-He,v=t=>k-(t-p)/(w-p)*(k-F),ne=n.length>1?(y-a)/(n.length-1):0;A={left:a,step:ne};const R=t=>n.length>1?a+t*ne:(a+y)/2;e.strokeStyle=b.border,e.lineWidth=1,e.beginPath(),e.moveTo(a,F),e.lineTo(a,k),e.lineTo(y,k),e.stroke(),e.fillStyle=b.text,e.textAlign="right",e.textBaseline="middle";for(const t of[0,.5,1]){const c=p+(w-p)*t;e.fillText(D(c,s?.unit),a-6,v(c))}Pe(e,E.offAxis,{left:a,right:y,top:F,bottom:k},"vertical",b.limitLine,t=>D(t,s?.unit));for(const[t,c]of[[s?.limitLow,"LSL"],[s?.limitHigh,"USL"]]){if(t===void 0||t<p||t>w)continue;const m=v(t);e.save(),e.strokeStyle=b.limitLine,e.globalAlpha=.7,e.setLineDash([4,3]),e.beginPath(),e.moveTo(a,m),e.lineTo(y,m),e.stroke(),e.restore(),e.save(),e.fillStyle=b.limitLine,e.textAlign="left",e.fillText(c,a+3,m-6),e.restore()}if(x!==null&&x>=p&&x<=w){const t=v(x);e.save(),e.strokeStyle=b.text,e.globalAlpha=.5,e.setLineDash([3,3]),e.beginPath(),e.moveTo(a,t),e.lineTo(y,t),e.stroke(),e.restore()}e.strokeStyle=_,e.lineWidth=1.5,e.beginPath();let W=!1;n.forEach((t,c)=>{if(!t.count||!Number.isFinite(t.mean)){W=!1;return}const m=R(c),ie=v(t.mean);W?e.lineTo(m,ie):e.moveTo(m,ie),W=!0}),e.stroke(),n.forEach((t,c)=>{if(!t.count||!Number.isFinite(t.mean))return;const m=R(c);t.stddev>0&&(e.save(),e.strokeStyle=_,e.globalAlpha=.55,e.lineWidth=1,e.beginPath(),e.moveTo(m,v(t.mean-t.stddev)),e.lineTo(m,v(t.mean+t.stddev)),e.stroke(),e.restore()),e.fillStyle=_,e.beginPath(),e.arc(m,v(t.mean),Me,0,Math.PI*2),e.fill()}),e.fillStyle=b.text,e.textBaseline="top",e.textAlign="center";const de=Math.max(1,Math.ceil(n.length*42/Math.max(1,y-a)));n.forEach((t,c)=>{if(c%de!==0&&c!==n.length-1)return;const m=t.label.length>8?`${t.label.slice(0,7)}\u2026`:t.label;e.fillText(m,R(c),k+6)}),ke(l,f,d)},K=r=>{if(!A||A.step<=0)return-1;const d=u.getBoundingClientRect(),i=Math.round((r.clientX-d.left-A.left)/A.step);return i>=0&&i<n.length&&n[i].count>0?i:-1};u.addEventListener("mousemove",r=>{const d=K(r);if(d<0){L.style.display="none",u.style.cursor="default";return}const i=n[d];u.style.cursor=C&&i.key!==void 0?"pointer":"default",L.innerHTML=`<strong>${j(i.label)}</strong><br>`+j(`mean ${D(i.mean,s?.unit)} \xB7 \u03C3 ${D(i.stddev,s?.unit)}`)+`<br>n = ${i.count.toLocaleString()}`+(x!==null?"<br>"+j(`\u0394 vs ${U} ${i.mean-x>=0?"+":""}${D(i.mean-x,s?.unit)}`):"")+(C&&i.key!==void 0?"<br><em>click to open this wafer</em>":""),L.style.display="block",Te(L,l,r.clientX,r.clientY)}),u.addEventListener("mouseleave",()=>{L.style.display="none"}),u.addEventListener("click",r=>{const d=K(r);if(d<0||!C)return;const i=n[d];i.key!==void 0&&h!==null&&C(i.key,h)}),J(),M=ye(f,J)}return S(),{card:l,destroy:()=>{M?.disconnect(),L.remove(),l.remove()},setTest:o=>{Q.set(o)&&(h=o,S())},setAxisPrefs:o=>{$.set(o)&&S()}}}
1
+ import{buildTestTrendData as he,trendCentre as pe}from"../../stats/trend.js";import{isParametricTest as xe}from"../../renderer/buildWaferMap.js";import{fmt as D}from"../../renderer/fmt.js";import{SPACE as le,fontPx as ge,FONT as ye,CLR as be}from"../toolbar.js";import{QUANTITY as U}from"./palette.js";import{cardShell as Te,observeResize as ve,makeTooltip as Le,positionChartTooltip as ke,makeLinkedTestSelect as we,makeLinkedAxisPrefs as Ce,renderEmptyState as X,chartFillHeight as Ae,PADDING as z,resolveAxisRange as Me,shouldIncludeLimitsByDefault as Se,drawOffAxisLimits as Pe,makeAxisFormat as Ne,VERTICAL_TICK_SPACING_PX as Ee,WAFER_MENU_HINT as He,prepareCanvas as Ie}from"./chartShell.js";import{fitTicks as De}from"../../renderer/axisTicks.js";import{escHtml as G}from"../../core/utils.js";const se=220,Oe=56,We=34,$e=3.5;export function renderTrendPanel(f){const{items:j,testDefs:re,onSaveImage:ae,onOpen:C,centreLabel:Y="mean of all wafers"}=f,ce=f.title??"Wafer-to-wafer trend",{card:s,body:u,controlsRow:K}=Te(ce,ae,f.ownerDocument);s.style.alignSelf="start";const N=re.filter(o=>o.testNumber!==void 0&&xe(o));let h=f.selectedTestNumber??N[0]?.testNumber??null,O=0;const Q=we(N,h,o=>{h=o,A(),f.onTestChange?.(o)},{maxWidth:"240px",emptyText:"No parametric tests",ownerDocument:s.ownerDocument});K.appendChild(Q.el);const W=s.ownerDocument.createElement("span");Object.assign(W.style,{display:"inline-flex",gap:le.lg,alignItems:"center"}),K.appendChild(W);const $=Ce(W,f.axisPrefs,o=>{f.onAxisPrefsChange?.(o),A()},s.ownerDocument),w=s.ownerDocument.createElement("div");Object.assign(w.style,{color:be.label,fontSize:ye.body,marginBottom:le.sm}),s.insertBefore(w,u);const g=Le(s);let E=null,i=[];function me(o){return N.find(R=>R.testNumber===o)}function A(){const{includeLimits:o,clipOutliers:R}=$.get();if(u.innerHTML="",E?.disconnect(),E=null,N.length===0||h===null){w.textContent="",X(u,"No parametric test data available for a trend.");return}if(j.length<2){w.textContent="",X(u,"A wafer-to-wafer trend needs at least two wafers.");return}if(i=he(j,h),!i.some(l=>l.count>0)){w.textContent="",X(u,"No values recorded for this test.");return}const r=me(h),y=pe(i),V=()=>{w.textContent=`Point = wafer mean, whisker = \xB11\u03C3, dashed = ${Y} \xB7 wafers in slot order`+(C?" \xB7 click a point to open that wafer":"")+(O?` \xB7 axis clipped, ${O} outside`:"")};V();const d=s.ownerDocument.createElement("canvas");d.style.display="block",u.appendChild(d);let M=null;const q=()=>{const l=Math.max(1,u.clientWidth),a=Math.max(se,Ae(s,u,d,se)),n=Ie(d,s,l,a);if(!n)return;const{ctx:e,theme:b}=n;let S=1/0,P=-1/0;const J=[];for(const t of i)!t.count||!Number.isFinite(t.mean)||(S=Math.min(S,t.mean-t.stddev),P=Math.max(P,t.mean+t.stddev),J.push(t.mean-t.stddev,t.mean+t.stddev));if(!Number.isFinite(S)||!Number.isFinite(P))return;const Z=o??Se(S,P,r?.limitLow,r?.limitHigh);$.sync(Z,r?.limitLow!==void 0||r?.limitHigh!==void 0);const H=Me({dataMin:S,dataMax:P,limitLow:r?.limitLow,limitHigh:r?.limitHigh,includeLimits:Z,clipOutliers:R,values:J});O=H.clippedCount,V();let x=H.lo,T=H.hi;const ee=(T-x)*.08;x-=ee,T+=ee;const c=z+Oe;e.font=`${ge(-1)}px system-ui, sans-serif`;const te=t=>t.length>8?`${t.slice(0,7)}\u2026`:t,de=i.length?Math.ceil(Math.max(e.measureText(te(i[0].label)).width,e.measureText(te(i[i.length-1].label)).width)/2):0,v=l-z-de,I=z,L=a-We,k=t=>L-(t-x)/(T-x)*(L-I),ne=i.length>1?(v-c)/(i.length-1):0;M={left:c,step:ne};const _=t=>i.length>1?c+t*ne:(c+v)/2;e.strokeStyle=b.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,I),e.lineTo(c,L),e.lineTo(v,L),e.stroke(),e.fillStyle=b.text,e.textAlign="right",e.textBaseline="middle";const ie=De(x,T,L-I,()=>Ee),fe=Ne(Math.max(Math.abs(x),Math.abs(T)),r?.unit,ie.step||void 0);for(const t of ie.ticks)e.fillText(fe.tickWithUnit(t),c-6,k(t));Pe(e,H.offAxis,{left:c,right:v,top:I,bottom:L},"vertical",b.limitLine,t=>D(t,r?.unit));for(const[t,p]of[[r?.limitLow,"LSL"],[r?.limitHigh,"USL"]]){if(t===void 0||t<x||t>T)continue;const m=k(t);e.save(),e.strokeStyle=b.limitLine,e.globalAlpha=.7,e.setLineDash([4,3]),e.beginPath(),e.moveTo(c,m),e.lineTo(v,m),e.stroke(),e.restore(),e.save(),e.fillStyle=b.limitLine,e.textAlign="left",e.fillText(p,c+3,m-6),e.restore()}if(y!==null&&y>=x&&y<=T){const t=k(y);e.save(),e.strokeStyle=b.text,e.globalAlpha=.5,e.setLineDash([3,3]),e.beginPath(),e.moveTo(c,t),e.lineTo(v,t),e.stroke(),e.restore()}e.strokeStyle=U,e.lineWidth=1.5,e.beginPath();let B=!1;i.forEach((t,p)=>{if(!t.count||!Number.isFinite(t.mean)){B=!1;return}const m=_(p),oe=k(t.mean);B?e.lineTo(m,oe):e.moveTo(m,oe),B=!0}),e.stroke(),i.forEach((t,p)=>{if(!t.count||!Number.isFinite(t.mean))return;const m=_(p);t.stddev>0&&(e.save(),e.strokeStyle=U,e.globalAlpha=.55,e.lineWidth=1,e.beginPath(),e.moveTo(m,k(t.mean-t.stddev)),e.lineTo(m,k(t.mean+t.stddev)),e.stroke(),e.restore()),e.fillStyle=U,e.beginPath(),e.arc(m,k(t.mean),$e,0,Math.PI*2),e.fill()}),e.fillStyle=b.text,e.textBaseline="top",e.textAlign="center";const ue=Math.max(1,Math.ceil(i.length*42/Math.max(1,v-c)));i.forEach((t,p)=>{if(p%ue!==0&&p!==i.length-1)return;const m=t.label.length>8?`${t.label.slice(0,7)}\u2026`:t.label;e.fillText(m,_(p),L+6)})},F=l=>{if(!M||M.step<=0)return-1;const a=d.getBoundingClientRect(),n=Math.round((l.clientX-a.left-M.left)/M.step);return n>=0&&n<i.length&&i[n].count>0?n:-1};d.addEventListener("mousemove",l=>{const a=F(l);if(a<0){g.style.display="none",d.style.cursor="default";return}const n=i[a];d.style.cursor=C&&n.key!==void 0?"pointer":"default",g.innerHTML=`<strong>${G(n.label)}</strong><br>`+G(`mean ${D(n.mean,r?.unit)} \xB7 \u03C3 ${D(n.stddev,r?.unit)}`)+`<br>n = ${n.count.toLocaleString()}`+(y!==null?"<br>"+G(`\u0394 vs ${Y} ${n.mean-y>=0?"+":""}${D(n.mean-y,r?.unit)}`):"")+(C&&n.key!==void 0?"<br><em>click to open this wafer</em>":"")+(f.onWaferContextMenu&&n.key!==void 0?He:""),g.style.display="block",ke(g,s,l.clientX,l.clientY)}),d.addEventListener("mouseleave",()=>{g.style.display="none"}),d.addEventListener("click",l=>{const a=F(l);if(a<0||!C)return;const n=i[a];n.key!==void 0&&h!==null&&C(n.key,h)}),d.addEventListener("contextmenu",l=>{const a=F(l),n=a<0?void 0:i[a].key;n===void 0||!f.onWaferContextMenu||(g.style.display="none",f.onWaferContextMenu(n,h??void 0,l))}),q(),E=ve(u,q)}return A(),{card:s,destroy:()=>{E?.disconnect(),g.remove(),s.remove()},setTest:o=>{Q.set(o)&&(h=o,A())},setAxisPrefs:o=>{$.set(o)&&A()}}}
@@ -1,4 +1,4 @@
1
- import{hasPosition as R,isPositionedDie as O}from"../core/dies.js";import{classifyDie as z}from"../core/classify.js";import{isParametricTest as B,getTestPassStatus as x}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as V}from"../stats/metadataColumns.js";import{LEADING as D,TRACKING as W,wireControlHover as G,controlStyle as K,SPACE as X,RADIUS as I,FONT as C,CLR as c,saveTextFile as Y}from"./toolbar.js";import{csvField as T}from"./summaryPanel.js";import{fmt as k}from"../renderer/fmt.js";const _=5e4,F=new WeakSet;function Q(t){if(F.has(t))return;F.add(t);const o=t.createElement("style");o.textContent=`
1
+ import{hasPosition as I,isPositionedDie as B}from"../core/dies.js";import{classifyDie as W}from"../core/classify.js";import{isParametricTest as G,getTestPassStatus as S}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as Y}from"../stats/metadataColumns.js";import{LEADING as N,TRACKING as _,wireControlHover as U,controlStyle as X,SPACE as Q,RADIUS as F,FONT as g,CLR as c,saveTextFile as q}from"./toolbar.js";import{csvField as H}from"./summaryPanel.js";import{fmt as P}from"../renderer/fmt.js";import{testLabel as E,markedTestLabel as j,derivedFields as J,derivedKeyText as Z,derivedTestSource as ee,isDerivedTest as te,DERIVED_KEY as ne}from"../renderer/testLabel.js";const L=(t,r)=>r?`${t} (${r})`:t,ie=5e4,M=new WeakSet;function le(t){if(M.has(t))return;M.add(t);const r=t.createElement("style");r.textContent=`
2
2
  .wmap-dielist-table { width: 100%; border-collapse: collapse; font-size: 12px; font-variant-numeric: tabular-nums; }
3
3
  .wmap-dielist-th {
4
4
  text-align: left; padding: 4px 8px; position: sticky; top: 0;
@@ -9,5 +9,5 @@ import{hasPosition as R,isPositionedDie as O}from"../core/dies.js";import{classi
9
9
  padding: 4px 8px; color: ${c.text};
10
10
  border-bottom: 1px solid ${c.menuBorder}; white-space: nowrap;
11
11
  }
12
- `,t.head.appendChild(o)}function d(t,o,n,a){const i=t.createElement(o);return n&&Object.assign(i.style,n),a!==void 0&&(i.textContent=a),i}function U(t){return R(t)?String(t.x):""}function q(t){return R(t)?String(t.y):""}function J(t,o){if(o!==void 0)return o.filter(a=>a.testNumber!==void 0);const n=new Map;for(const a of t)for(const i of Object.keys(a.testValues??{})){const s=Number(i);n.has(s)||n.set(s,{testNumber:s,name:`Test ${s}`})}return[...n.values()].sort((a,i)=>a.testNumber-i.testNumber)}function Z(t,o,n){if(!o)return;const a=new Map;for(const i of t){if(!O(i))continue;const s=o(i);s&&a.set(i,z(i,s,{ringCount:n}))}return a.size>0?a:void 0}export function buildDieListSection(t,o,n={}){if(!t.length)return null;const a=n.ownerDocument??document;Q(a);const i=J(t,o),s=n.maxRows??_,S=Math.max(0,t.length-s),H=S>0?t.slice(0,s):t,f=d(a,"div",{display:"flex",flexDirection:"column",gap:X.md,flex:"1",minHeight:"0",minWidth:"0"}),w=d(a,"div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});w.appendChild(d(a,"div",{fontSize:C.meta,fontWeight:"700",letterSpacing:W,textTransform:"uppercase",color:c.label},n.title??`Die list (${t.length})`));const g=d(a,"button",{fontSize:C.body,padding:"3px 8px",borderRadius:I.control,...K("outlined"),background:c.menuBg},"Export CSV");g.type="button",G(g),w.appendChild(g),f.appendChild(w),n.note&&f.appendChild(d(a,"div",{fontSize:C.body,color:c.label,lineHeight:D.base,flexShrink:"0"},n.note));const v=Z(t,n.getWafer,n.ringCount??4),P=t.some(e=>e.edgeExcluded===!0),j=[...n.extraColumn?[n.extraColumn.label]:[],"X","Y","Ring","Quadrant","Edge excluded","Site","Hard bin","Soft bin",...i.map(e=>e.unit?`${e.name} (${e.unit})`:e.name)],{columns:M,truncatedKeys:h}=V({dies:t,waferMetadata:n.waferMetadata,metadataFields:n.metadataFields,dieKeys:n.metadataColumns,waferKeys:n.waferMetadataColumns==="none"?"none":void 0,waferPlacement:n.waferMetadataColumns??"csv",reservedLabels:j}),y=[...n.extraColumn?[{label:n.extraColumn.label,get:e=>n.extraColumn.get(e)??""}]:[],{label:"X",get:U},{label:"Y",get:q},...v?[{label:"Ring",get:e=>{const l=v.get(e);return l?String(l.ring):""}},{label:"Quadrant",get:e=>v.get(e)?.quadrant??""}]:[],...P?[{label:"Edge excluded",get:e=>e.edgeExcluded?"Yes":"No"}]:[],{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=>{const l=!B(e);return{label:e.unit?`${e.name} (${e.unit})`:e.name,get:r=>{if(l){const p=x(r,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const u=r.testValues?.[e.testNumber];if(u!==void 0)return k(u,e.unit);const b=x(r,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"},csvGet:r=>{if(l){const p=x(r,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const u=r.testValues?.[e.testNumber];if(u!==void 0)return k(u,void 0,"engineering");const b=x(r,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"}}}),...M.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],N=y.filter(e=>!e.csvOnly),$=d(a,"div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${c.menuBorder}`,borderRadius:I.control,...n.maxHeight?{maxHeight:n.maxHeight}:{}}),m=a.createElement("table");m.className="wmap-dielist-table",m.setAttribute("aria-label",n.title??`Die list (${t.length} dies)`);const E=d(a,"thead"),A=d(a,"tr");for(const e of N){const l=a.createElement("th");l.className="wmap-dielist-th",l.scope="col",l.textContent=e.label,A.appendChild(l)}E.appendChild(A),m.appendChild(E);const L=d(a,"tbody");for(const e of H){const l=d(a,"tr");for(const r of N){const u=a.createElement("td");u.className="wmap-dielist-td",u.textContent=r.get(e),l.appendChild(u)}L.appendChild(l)}if(m.appendChild(L),$.appendChild(m),f.appendChild($),S>0||h.length>0){const e=[];S>0&&e.push(`Showing the first ${s.toLocaleString()} of ${t.length.toLocaleString()} dies. The CSV export contains all ${t.length.toLocaleString()}.`),h.length>0&&e.push(`${h.length} further metadata field${h.length===1?"":"s"} not shown.`),f.appendChild(d(a,"div",{fontSize:C.body,color:c.label,lineHeight:D.base,flexShrink:"0"},e.join(" ")))}return g.addEventListener("click",()=>{const e=[y.map(l=>T(l.label)).join(",")];for(const l of t)e.push(y.map(r=>T((r.csvGet??r.get)(l))).join(","));Y(e.join(`
12
+ `,t.head.appendChild(r)}function s(t,r,n,i){const a=t.createElement(r);return n&&Object.assign(a.style,n),i!==void 0&&(a.textContent=i),a}function ae(t){return I(t)?String(t.x):""}function re(t){return I(t)?String(t.y):""}function oe(t,r){if(r!==void 0)return r.filter(i=>i.testNumber!==void 0);const n=new Map;for(const i of t)for(const a of Object.keys(i.testValues??{})){const d=Number(a);n.has(d)||n.set(d,{testNumber:d,name:E(void 0,d)})}return[...n.values()].sort((i,a)=>i.testNumber-a.testNumber)}function se(t,r,n){if(!r)return;const i=new Map;for(const a of t){if(!B(a))continue;const d=r(a);d&&i.set(a,W(a,d,{ringCount:n}))}return i.size>0?i:void 0}export function buildDieListSection(t,r,n={}){if(!t.length)return null;const i=n.ownerDocument??document;le(i);const a=oe(t,r),d=n.maxRows??ie,C=Math.max(0,t.length-d),z=C>0?t.slice(0,d):t,f=s(i,"div",{display:"flex",flexDirection:"column",gap:Q.md,flex:"1",minHeight:"0",minWidth:"0"}),v=s(i,"div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});v.appendChild(s(i,"div",{fontSize:g.meta,fontWeight:"700",letterSpacing:_,textTransform:"uppercase",color:c.label},n.title??`Die list (${t.length})`));const h=s(i,"button",{fontSize:g.body,padding:"3px 8px",borderRadius:F.control,...X("outlined"),background:c.menuBg},"Export CSV");h.type="button",U(h),v.appendChild(h),f.appendChild(v),n.note&&f.appendChild(s(i,"div",{fontSize:g.body,color:c.label,lineHeight:N.base,flexShrink:"0"},n.note));const w=se(t,n.getWafer,n.ringCount??4),K=t.some(e=>e.edgeExcluded===!0),O=[...n.extraColumn?[n.extraColumn.label]:[],"X","Y","Ring","Quadrant","Edge excluded","Site","Hard bin","Soft bin",...a.map(e=>L(E(e,e.testNumber),e.unit))],{columns:V,truncatedKeys:x}=Y({dies:t,waferMetadata:n.waferMetadata,metadataFields:n.metadataFields,dieKeys:n.metadataColumns,waferKeys:n.waferMetadataColumns==="none"?"none":void 0,waferPlacement:n.waferMetadataColumns??"csv",reservedLabels:O}),y=[...n.extraColumn?[{label:n.extraColumn.label,get:e=>n.extraColumn.get(e)??""}]:[],{label:"X",get:ae},{label:"Y",get:re},...w?[{label:"Ring",get:e=>{const l=w.get(e);return l?String(l.ring):""}},{label:"Quadrant",get:e=>w.get(e)?.quadrant??""}]:[],...K?[{label:"Edge excluded",get:e=>e.edgeExcluded?"Yes":"No"}]:[],{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):""},...a.map(e=>{const l=!G(e);return{label:L(j(e,e.testNumber),e.unit),...te(e)?{csvLabel:`${L(E(e,e.testNumber),e.unit)} [derived from ${ee(e)??ne}]`}:{},get:o=>{if(l){const p=S(o,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const u=o.testValues?.[e.testNumber];if(u!==void 0)return P(u,e.unit);const b=S(o,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"},csvGet:o=>{if(l){const p=S(o,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const u=o.testValues?.[e.testNumber];if(u!==void 0)return P(u,void 0,"engineering");const b=S(o,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"}}}),...V.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],$=y.filter(e=>!e.csvOnly),A=Z(a.map(e=>({label:j(e,e.testNumber),...J(e)})));A&&f.appendChild(s(i,"div",{fontSize:g.body,color:c.label,lineHeight:N.base,flexShrink:"0"},A));const D=s(i,"div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${c.menuBorder}`,borderRadius:F.control,...n.maxHeight?{maxHeight:n.maxHeight}:{}}),m=i.createElement("table");m.className="wmap-dielist-table",m.setAttribute("aria-label",n.title??`Die list (${t.length} dies)`);const R=s(i,"thead"),T=s(i,"tr");for(const e of $){const l=i.createElement("th");l.className="wmap-dielist-th",l.scope="col",l.textContent=e.label,T.appendChild(l)}R.appendChild(T),m.appendChild(R);const k=s(i,"tbody");for(const e of z){const l=s(i,"tr");for(const o of $){const u=i.createElement("td");u.className="wmap-dielist-td",u.textContent=o.get(e),l.appendChild(u)}k.appendChild(l)}if(m.appendChild(k),D.appendChild(m),f.appendChild(D),C>0||x.length>0){const e=[];C>0&&e.push(`Showing the first ${d.toLocaleString()} of ${t.length.toLocaleString()} dies. The CSV export contains all ${t.length.toLocaleString()}.`),x.length>0&&e.push(`${x.length} further metadata field${x.length===1?"":"s"} not shown.`),f.appendChild(s(i,"div",{fontSize:g.body,color:c.label,lineHeight:N.base,flexShrink:"0"},e.join(" ")))}return h.addEventListener("click",()=>{const e=[y.map(l=>H(l.csvLabel??l.label)).join(",")];for(const l of t)e.push(y.map(o=>H((o.csvGet??o.get)(l))).join(","));q(e.join(`
13
13
  `),"die-list.csv","text/csv",n.onSaveText)}),f}
@@ -0,0 +1,23 @@
1
+ import { type SweepSpec } from '../stats/sweep.js';
2
+ import { type SaveImageHandler } from './toolbar.js';
3
+ import type { DrilldownSource, DrilldownItem } from './chartPopulation.js';
4
+ export type { DrilldownSource, DrilldownItem };
5
+ /** What the targets can draw on — the host's chart definitions and hooks. */
6
+ export interface DrilldownContext {
7
+ sweeps?: SweepSpec[];
8
+ onSaveImage?: SaveImageHandler;
9
+ }
10
+ /**
11
+ * Open the drilldown menu for `source` at viewport point `at`.
12
+ *
13
+ * `anchor` is an on-page element of the triggering render (the map canvas, or
14
+ * the toolbar button) — it resolves the menu layer and the chart modal's root,
15
+ * and gets focus back when the menu is dismissed with Escape. Returns a close
16
+ * function; the menu also closes on an outside press, Escape, Tab, or when a
17
+ * chart is picked.
18
+ */
19
+ export declare function openDrilldownMenu(at: {
20
+ x: number;
21
+ y: number;
22
+ }, anchor: HTMLElement, source: DrilldownSource, ctx: DrilldownContext): () => void;
23
+ //# sourceMappingURL=drilldown.d.ts.map
@@ -0,0 +1 @@
1
+ import{isYieldEligibleDie as b}from"../core/dies.js";import{isParametricTest as y}from"../renderer/buildWaferMap.js";import{buildSweepData as T}from"../stats/sweep.js";import{renderSweepPanel as C}from"./charts/sweep.js";import{renderHistogramPanel as E}from"./charts/histogram.js";import{renderCapabilityPanel as $}from"./charts/capability.js";import{openChartExpandModal as M,populationPhrase as w}from"./charts/chartShell.js";import{buildCheckMenuEl as I,createToolbarHelpers as x,getTooltip as N,menuLayerFor as R,wireMenuA11y as L,CLR as V,FONT as k,SPACE as F}from"./toolbar.js";const P=30,v=e=>e.items.flatMap(t=>t.dies);function O(e){return e.items.map(t=>({...t,dies:t.dies.filter(a=>b(a))}))}function A(e,t,a){return(e.testDefs??[]).filter(y).map(s=>s.testNumber).filter(s=>{let o=0;for(const i of t){const n=i.testValues?.[s];if(n!==void 0&&Number.isFinite(n)&&++o>=a)return!0}return!1})}function S(e,t,a,s,o){const n=e.ownerDocument.createElement("div");n.textContent=a,n.dataset.wmapPopulation="1",Object.assign(n.style,{color:V.label,fontSize:k.body,marginBottom:F.sm});const r=e.querySelector("[data-wmap-chart-expand]")?.parentElement;r?.parentElement===e?r.after(n):e.prepend(n),M(e,t,{anchor:s,onClosed:o})}function H(e,t,a,s){const o=v(t);let i=t.notMeasuredReason??null;return!i&&!o.some(n=>b(n))&&(i="These dies are all partial or edge-excluded, which charts leave out"),i||T(o,t.testDefs,e).series.some(r=>r.points.some(d=>d.count>0))||(i="These dies have no values for this sweep\u2019s tests"),{section:"Sweep",label:e.title,unavailable:i,open:()=>{const n=C({spec:e,dies:o,population:t.population,testDefs:t.testDefs,onSaveImage:a.onSaveImage,ownerDocument:s.ownerDocument}),r=o.filter(d=>b(d)).length;M(n.card,`${e.title} \u2014 ${w(r,o.length,t.population)}`,{anchor:s,onClosed:()=>n.destroy()})}}}function W(e,t,a){const s=O(e),o=v(e).length,i=s.flatMap(l=>l.dies),n=w(i.length,o,e.population),r=(e.testDefs??[]).filter(y),d=a.ownerDocument,f=(l,m)=>e.notMeasuredReason?e.notMeasuredReason:i.length===0?"These dies are all partial or edge-excluded, which charts leave out":A(e,i,l).length>0?null:m,u={section:"Distributions",label:"Value histogram",unavailable:f(1,"These dies have no parametric test values"),open:()=>{const l=E({title:"Value histogram",items:s,testDefs:r,selectedTestNumber:e.activeTest,onSaveImage:t.onSaveImage,ownerDocument:d});S(l.card,`Value histogram \u2014 ${n}`,`Population: ${n}`,a,()=>l.destroy())}},h={section:"Distributions",label:"Process capability",unavailable:f(2,"Needs at least 2 of these dies with values for one test"),open:()=>{const l=$({title:"Process capability",items:s,testDefs:r,selectedTestNumber:e.activeTest,onSaveImage:t.onSaveImage,ownerDocument:d}),m=i.length<P?` \xB7 fewer than ${P} dies, so each Ppk is a rough estimate`:"";S(l.card,`Process capability \u2014 ${n}`,`Population: ${n}${m}`,a,()=>l.destroy())}};return[u,h]}function B(e,t,a){return[...W(e,t,a),...(t.sweeps??[]).map(s=>H(s,e,t,a))]}const g=new WeakMap;export function openDrilldownMenu(e,t,a,s){const o=t.ownerDocument,i=o.defaultView??window;g.get(o)?.();const n=B(a,s,t),r=[];let d;for(const p of n)p.section!==d&&(r.push({section:p.section}),d=p.section),r.push({label:p.label,active:!1,action:!0,enabled:p.unavailable===null,disabledHint:p.unavailable??void 0,onClick:()=>{c(),p.open()}});const{makeMenuSection:f}=x(N(o)),u=I(new DOMRect(e.x,e.y-4,0,0),r,{makeMenuSection:f},i),h=v(a).length;u.setAttribute("role","menu"),u.setAttribute("aria-label",`Open a chart of ${w(h,h,a.population)}`),u.dataset.wmapDrilldownMenu="1",R(t).appendChild(u);const l=u.getBoundingClientRect();l.bottom>i.innerHeight-4&&(u.style.top=`${Math.max(4,e.y-l.height)}px`),l.right>i.innerWidth-4&&(u.style.left=`${Math.max(4,i.innerWidth-l.width-4)}px`);let m=!1;function c(){m||(m=!0,u.remove(),o.removeEventListener("pointerdown",D,!0),i.removeEventListener("blur",c),g.get(o)===c&&g.delete(o))}function D(p){u.contains(p.target)||c()}return o.addEventListener("pointerdown",D,!0),i.addEventListener("blur",c),L(u,t,c),g.set(o,c),c}
@@ -2,7 +2,7 @@ export const ICONS={aggr:'<svg viewBox="0 0 24 24" width="16" height="16" fill="
2
2
  <rect x="3" y="3" width="4" height="18" rx="1"/>
3
3
  <rect x="10" y="3" width="4" height="18" rx="1"/>
4
4
  <rect x="17" y="3" width="4" height="18" rx="1"/>
5
- </svg>`,download:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"/><circle cx="12" cy="13" r="3"/></svg>',downloadAll:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"/><circle cx="12" cy="13" r="3"/><circle cx="5.5" cy="4.5" r="0.8" fill="currentColor" stroke="none"/><circle cx="8" cy="4.5" r="0.8" fill="currentColor" stroke="none"/><circle cx="5.5" cy="7" r="0.8" fill="currentColor" stroke="none"/><circle cx="8" cy="7" r="0.8" fill="currentColor" stroke="none"/></svg>',expand:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>',findings:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4"/><path d="M12 2v4"/><path d="M16 2v4"/><rect width="16" height="18" x="4" y="4" rx="2"/><path d="M8 10h6"/><path d="M8 14h8"/><path d="M8 18h5"/></svg>',flipH:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m3 7 5 5-5 5V7"/><path d="m21 7-5 5 5 5V7"/><path d="M12 20v2"/><path d="M12 14v2"/><path d="M12 8v2"/><path d="M12 2v2"/></svg>',flipV:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m17 3-5 5-5-5h10"/><path d="m17 21-5-5-5 5h10"/><path d="M4 12H2"/><path d="M10 12H8"/><path d="M16 12h-2"/><path d="M22 12h-2"/></svg>',help:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>',labels:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v16"/><path d="M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2"/><path d="M9 20h6"/></svg>',legend:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/><path d="M14 4h7"/><path d="M14 9h7"/><path d="M14 15h7"/><path d="M14 20h7"/></svg>',logScale:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 21 L5 3"/><path d="M3.5 5 L5 3 L6.5 5"/><path d="M5 21 L21 21"/><path d="M19 19.5 L21 21 L19 22.5"/><path d="M6 20 C 6 12, 10 7, 20 6"/></svg>',maximize:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg>',minimize:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3"/><path d="M21 8h-3a2 2 0 0 1-2-2V3"/><path d="M3 16h3a2 2 0 0 1 2 2v3"/><path d="M16 21v-3a2 2 0 0 1 2-2h3"/></svg>',mode:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
5
+ </svg>`,download:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"/><circle cx="12" cy="13" r="3"/></svg>',downloadAll:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"/><circle cx="12" cy="13" r="3"/><circle cx="5.5" cy="4.5" r="0.8" fill="currentColor" stroke="none"/><circle cx="8" cy="4.5" r="0.8" fill="currentColor" stroke="none"/><circle cx="5.5" cy="7" r="0.8" fill="currentColor" stroke="none"/><circle cx="8" cy="7" r="0.8" fill="currentColor" stroke="none"/></svg>',drilldown:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M7 16c3-1 4-9 7-9s3 5 6 5"/></svg>',expand:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>',findings:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4"/><path d="M12 2v4"/><path d="M16 2v4"/><rect width="16" height="18" x="4" y="4" rx="2"/><path d="M8 10h6"/><path d="M8 14h8"/><path d="M8 18h5"/></svg>',flipH:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m3 7 5 5-5 5V7"/><path d="m21 7-5 5 5 5V7"/><path d="M12 20v2"/><path d="M12 14v2"/><path d="M12 8v2"/><path d="M12 2v2"/></svg>',flipV:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m17 3-5 5-5-5h10"/><path d="m17 21-5-5-5 5h10"/><path d="M4 12H2"/><path d="M10 12H8"/><path d="M16 12h-2"/><path d="M22 12h-2"/></svg>',help:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>',labels:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 4v16"/><path d="M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2"/><path d="M9 20h6"/></svg>',legend:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/><path d="M14 4h7"/><path d="M14 9h7"/><path d="M14 15h7"/><path d="M14 20h7"/></svg>',logScale:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 21 L5 3"/><path d="M3.5 5 L5 3 L6.5 5"/><path d="M5 21 L21 21"/><path d="M19 19.5 L21 21 L19 22.5"/><path d="M6 20 C 6 12, 10 7, 20 6"/></svg>',maximize:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg>',minimize:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3"/><path d="M21 8h-3a2 2 0 0 1-2-2V3"/><path d="M3 16h3a2 2 0 0 1 2 2v3"/><path d="M16 21v-3a2 2 0 0 1 2-2h3"/></svg>',mode:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
6
6
  <ellipse cx="12" cy="7" rx="9" ry="4" />
7
7
 
8
8
  <g stroke="currentColor" stroke-width="1.4" opacity="0.85">
@@ -2,7 +2,8 @@ import type { LotStatsSummary } from '../stats/types.js';
2
2
  import type { WaferMapDisplayItem } from './renderWaferGallery.js';
3
3
  import type { BinColors } from '../renderer/binColors.js';
4
4
  import { type SaveImageHandler, type SaveTextHandler } from './toolbar.js';
5
- export type InsightsView = 'overview' | 'distributions' | 'correlation';
5
+ import type { SweepSpec } from '../stats/sweep.js';
6
+ export type InsightsView = 'overview' | 'distributions' | 'correlation' | 'sweeps';
6
7
  /** Public option shape for `RenderOptions.insights`/`GalleryOptions.insights`. */
7
8
  export interface InsightsOptions {
8
9
  /**
@@ -10,11 +11,60 @@ export interface InsightsOptions {
10
11
  * grid with wmap's own chart suite across three sub-tabs — Overview
11
12
  * (yield, bins, ring/quadrant yield, test values), Distributions
12
13
  * (process capability, boxplot, histogram), and Correlation (matrix +
13
- * scatter). Default false.
14
+ * scatter) — plus a fourth, Sweeps, when `sweeps` defines any. Default false.
14
15
  */
15
16
  enabled?: boolean;
16
- /** Which sub-tab is shown first. Default 'overview'. */
17
+ /** Which sub-tab is shown first. Default 'overview'. `'sweeps'` with no
18
+ * `sweeps` defined falls back to 'overview' — there is no such tab to open. */
17
19
  defaultView?: InsightsView;
20
+ /**
21
+ * Parametric sweeps, one card each in their own Sweeps sub-tab — which appears
22
+ * only when this is non-empty.
23
+ *
24
+ * A sweep reads an ordered run of tests as a response curve rather than as
25
+ * independent tests, and measures the PAIR: where the first two series cross,
26
+ * and how far apart they are at given levels. The case it exists for is the
27
+ * same quantity measured at a series of power levels, recorded as a block of
28
+ * consecutive test numbers, swept up in one block and down in another.
29
+ *
30
+ * ```ts
31
+ * insights: { enabled: true, sweeps: [{
32
+ * id: 'power', title: 'Power Sweep',
33
+ * series: [
34
+ * { label: 'Rising', tests: [1010, 1011, 1012], xValues: [0, 5, 10] },
35
+ * { label: 'Falling', tests: [1020, 1021, 1022], xValues: [0, 5, 10] },
36
+ * ],
37
+ * separationAt: [1.2, 2.5],
38
+ * }] }
39
+ * ```
40
+ *
41
+ * Deliberately carries no population scope of its own: a sweep definition
42
+ * says which tests form the curve, and is therefore valid for any population
43
+ * and portable between hosts. The dies it aggregates are whatever the
44
+ * Insights view is currently scoped to.
45
+ *
46
+ * The same definitions drive drilldown: a user can select dies on a map and
47
+ * right-click (or use the toolbar's "Chart the selection") to sweep just
48
+ * those dies. A single map offers this even when `enabled` is off.
49
+ *
50
+ * **Provisional.** This arrived as one site's request. The mechanism — an
51
+ * ordered run of tests read as a curve — is a recurring semiconductor shape
52
+ * (shmoo, VDD/temperature sweeps, retention, endurance, IV), which is the case
53
+ * for it being library-level; against it is that only one host has asked. It
54
+ * is shipping so tsmap can put it in front of that user, and the answer
55
+ * decides it: a second sweep-shaped use means it is general and stays, while
56
+ * "also measure X, and split by Y" means it is a bespoke chart and belongs
57
+ * behind a host-contributed-panel extension point instead. `separationAt` is
58
+ * the narrowest part of the surface and the first thing to drop if it is not
59
+ * used. Do not widen this shape before that question is settled.
60
+ *
61
+ * Range strings in `tests` (`"1010..1030"`) are NOT a widening in that sense:
62
+ * they are input syntax for the same list of tests, and add nothing to what a
63
+ * sweep measures. They were added because ranges are the first thing anyone
64
+ * writing a sweep by hand asks for, and they share the derived-test parser so
65
+ * the syntax is one syntax library-wide.
66
+ */
67
+ sweeps?: SweepSpec[];
18
68
  /**
19
69
  * Open the Insights view on mount instead of starting on the map/grid.
20
70
  * Default false — the tab is offered, the map is what you see first.
@@ -71,6 +121,8 @@ export interface InsightsTabDeps {
71
121
  focusTest?: (testNumber: number) => void;
72
122
  /** Default sub-tab shown on first render. Default 'overview'. */
73
123
  defaultView?: InsightsView;
124
+ /** Sweep definitions to render in the Distributions view — see `InsightsOptions.sweeps`. */
125
+ sweeps?: SweepSpec[];
74
126
  /**
75
127
  * When provided, the tab bar gets a leading "‹ Map"/"‹ Gallery" tab that
76
128
  * exits Insights back to the host's normal view — one visible navigation
@@ -1 +1 @@
1
- import{buildFacetTable as $e,facetValueOf as Le,FACET_NONE_VALUE as Ee}from"../stats/facets.js";import{mergeTestDefs as Ie}from"../stats/mergeTestDefs.js";import{isParametricTest as Oe}from"../renderer/buildWaferMap.js";import{waferDisplayLabel as Pe}from"../core/waferLabel.js";import{INPUT_DEFAULT_PASS_BINS as R,itemPassBins as Ae,passBinsLabel as ae}from"../core/passBins.js";import{NO_DATA_FILL as oe}from"../renderer/colorMap.js";import{describeWaferPopulation as Re,populationStat as We}from"../stats/population.js";import{LEADING as je,ALPHA as Ge,SPACE as w,EDGE_GUTTER as X,FONT as G,CLR as B,RADIUS as Ye,SHADOW as He,controlStyle as Fe,wireControlHover as re,wireTooltip as Me}from"./toolbar.js";import{ICONS as Ne}from"./icons.js";import{renderCapabilityPanel as Ve}from"./charts/capability.js";import{renderBoxplotPanel as _e}from"./charts/boxplot.js";import{renderTrendPanel as Ue}from"./charts/trend.js";import{renderHistogramPanel as ze}from"./charts/histogram.js";import{renderCorrelationPanel as qe}from"./charts/correlation.js";import{renderScatterPanel as Qe}from"./charts/scatter.js";import{renderBarPanel as se}from"./charts/barPanel.js";import{renderBinClusterPanel as Ke}from"./charts/binCluster.js";import{renderTestPassRatePanel as Xe}from"./charts/testPassRate.js";import{QUANTITY as Je}from"./charts/palette.js";import{cardFrameStyle as Ze,makeChartGridWrap as J,makeLabeledSelect as et,makeLinkedGroupSelect as tt}from"./charts/chartShell.js";import{buildYieldData as V,buildYieldDataCombined as ie}from"../stats/yield.js";import{buildBinParetoData as nt}from"../stats/binPareto.js";import{buildLotTestSection as at,buildLotFunctionalSection as ot,buildMetadataStripBox as rt}from"./summaryPanel.js";import{buildRegionYieldData as le,buildRingRegions as st,buildQuadrantRegions as it}from"../stats/regions.js";import{renderRegionYieldDiagram as de}from"./charts/regionYieldDiagram.js";const lt=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}];export function createInsightsTab(D){const{getItems:ce,getLotStats:pe,getBinColors:Z,getRingCount:ue,onSaveImage:T,onSaveText:_,openWafer:Y,focusTest:ee}=D,fe=D.showMetadataStrip??!0,W=D.contentInset??X,l=D.ownerDocument??document,O=l.createElement("div");Object.assign(O.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const me=X,P=l.createElement("div");Object.assign(P.style,{paddingLeft:me});const k=l.createElement("div");Object.assign(k.style,{display:"flex",gap:w.xs,alignItems:"center",background:B.menuBg,border:`1px solid ${B.menuBorder}`,borderRadius:Ye.container,boxShadow:He.panel,padding:`0 ${w.lg}`,marginBottom:w.lg,marginLeft:W,marginRight:W}),k.setAttribute("role","tablist"),k.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from(k.querySelectorAll('[role="tab"]')),r=t.indexOf(l.activeElement);if(r===-1)return;e.preventDefault();const d=t[(r+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];d.focus(),d.click()});const j=l.createElement("div");Object.assign(j.style,{display:"flex",flexDirection:"column",gap:w.lg,marginTop:"0",paddingLeft:W,paddingRight:W}),O.appendChild(P),O.appendChild(k),O.appendChild(j);let I=D.defaultView??"overview",A,L=null,$=null,H={includeLimits:void 0,clipOutliers:!1};const U=e=>{$!==e&&($=e,M())};let F=new Map;function te(){for(const e of F.values())e.destroy();F=new Map}function z(e,t,r){Y?.(e,t,r)}function be(e){return Y?{open:(t,r)=>z(t,`Wafer ${e.find(d=>d.waferIndex===t)?.label??t}`,r)}:ee&&e.length===1?{open:(t,r)=>ee(r),label:"show this test on the map"}:null}function ge(){return ce().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:Pe(e,t),waferIndex:t,passBins:Ae(e),statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function ne(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${B.iconActive}`:"2px solid transparent",color:t?B.iconActive:B.label,fontWeight:t?"700":"500",fontSize:G.body,padding:`${w.sm} ${X} ${w.md}`,cursor:"pointer",marginBottom:"-1px"})}function ye(e,t){const r=l.createElement("button");r.type="button",r.textContent=t;const d=e===I;return ne(r,d),r.dataset.on=d?"true":"false",re(r,"bare"),r.dataset.wmapInsightsTab=e,r.setAttribute("role","tab"),r.setAttribute("aria-selected",d?"true":"false"),r.tabIndex=d?0:-1,r.addEventListener("click",()=>{I!==e&&(I=e,M({keepSections:!0}))}),r}function he(e){const t=l.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,ne(t,!1),t.dataset.wmapInsightsBack="1",t.addEventListener("click",e.onBack),t}function Ce(e,t,r,d=!0){const c=J(l),p=Z();let m="label";const b=r??"group",u=ae(e.map(i=>i.passBins)),s=pe?.(),g=new Map(s?.lotYieldSeries.map(i=>[i.waferIndex,i.yieldPercent])),S=i=>({...i,label:i.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:g.get(i.waferIndex),key:i.waferIndex}),f=e.map(S),y=t?.map(i=>({key:i.key,items:i.items.map(S)})),x=()=>y?ie(y,R,m):V(f,R,m),C={title:t?`Yield by ${b} (pass: ${u})`:`Yield by wafer (pass: ${u})`,data:x(),selfControl:{current:m,options:[["yield","Sort: yield"],["label",t?`Sort: ${b}`:"Sort: wafer ID"]],onChange:i=>(m=i,{data:x()})},barColor:()=>Je,valueLabel:i=>`${i.percent.toFixed(1)}%`,reference:i=>{if(i.length<3)return null;const v=i.map(K=>K.percent).sort((K,De)=>K-De),E=v.length%2?v[(v.length-1)/2]:(v[v.length/2-1]+v[v.length/2])/2;return{value:E,label:`median ${E.toFixed(1)}%`}},drill:y?{onOpenGroup:i=>{const v=y.find(E=>E.key===i.label)?.items??[];return{data:V(v,R,m),title:`Yield by wafer \u2014 ${b}: ${i.label} (pass: ${u})`}},onBack:()=>({data:x(),title:`Yield by ${b} (pass: ${u})`}),groupLabelText:b}:void 0,onOpen:Y?i=>{typeof i.key=="number"&&z(i.key,`Wafer ${i.label}`)}:void 0,ownerDocument:l};let n=null;if(d&&(n=se(C,T),n.card.style.minHeight="360px",c.appendChild(n.card)),t){const i=Ke({title:"Hard bin pareto",groups:t.map(v=>({key:v.key,items:v.items.map(E=>({dies:E.dies,hardBinCounts:E.statsSummary?.stats.hardBinCounts,softBinCounts:E.statsSummary?.stats.softBinCounts}))})),onSaveImage:T,ownerDocument:l});return i.card.style.minHeight="360px",c.appendChild(i.card),{card:c,destroy:()=>{n?.destroy(),i.destroy()}}}let a="hbin";const o=i=>i===void 0?oe:(a==="hbin"?p.hard:p.soft).get(i)??oe,h=e.map(i=>({dies:i.dies,hardBinCounts:i.statsSummary?.stats.hardBinCounts,softBinCounts:i.statsSummary?.stats.softBinCounts})),N=()=>nt(h,a),Be={title:"Hard bin pareto",data:N(),selfControl:{current:a,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:i=>(a=i,{data:N(),title:`${a==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:i=>o(i.binCode),ownerDocument:l},Q=se(Be,T);return Q.card.style.minHeight="360px",c.appendChild(Q.card),{card:c,destroy:()=>{n?.destroy(),Q.destroy()}}}function q(){const e=l.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...Ze(),display:"flex",flexDirection:"column",gap:w.lg,minWidth:"0"}),e}function Se(e){if(P.innerHTML="",P.style.marginBottom="0",!fe)return;const t=rt(e.map(r=>({metadata:r.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",P.appendChild(t),P.style.marginBottom=w.xs)}function xe(e,t,r,d){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const c=e.map(o=>o.wafer),p=e.map(o=>o.dies),m=e.flatMap(o=>o.dies),b=e.map(o=>o.statsSummary),u=b.every(o=>o!==void 0)?b:void 0,s=ue?.()??4,g=[],S=[],y=Xe({groups:(d??[{key:"",items:e}]).map(o=>({key:o.key,items:o.items.map(h=>({dies:h.dies,testSpecYield:h.statsSummary?.stats.testSpecYield,functionalYield:h.statsSummary?.stats.functionalYield}))})),testDefs:r,onSaveImage:T,ownerDocument:l});y.card.style.minHeight="300px",g.push(y.card),S.push(y.destroy);const x=le(p,c,s,o=>e[o].passBins,st);if(x.length){const o=de({title:"Ring yield",mode:"ring",rows:x,onSaveImage:T,ownerDocument:l});g.push(o.card),S.push(o.destroy)}const C=le(p,c,s,o=>e[o].passBins,it);if(C.length){const o=de({title:"Quadrant yield",mode:"quadrant",rows:C,onSaveImage:T,ownerDocument:l});g.push(o.card),S.push(o.destroy)}let n=null;if(t.length){const o=at(m,t,void 0,u,_,p.map(h=>({dies:h})),void 0,"full");if(o){const h=q();h.appendChild(o),n=h}}let a=null;if(r.length){const o=ot(m,r,u,_);if(o){const h=q();h.appendChild(o),a=h}}return{elements:g,testValuesCard:n,functionalCard:a,destroy:()=>{for(const o of S)o()}}}function we(e){const t=ae(e.map(s=>s.passBins)),r=e.length===1,d=e[0],c=q();Object.assign(c.style,{flexDirection:"row",flexWrap:"wrap",gap:w.md});const p=s=>{const g=s.lastElementChild;return g&&Object.assign(g.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),s};function m(s,g,S){const f=l.createElement("div");Object.assign(f.style,{display:"flex",alignItems:"baseline",gap:w.sm,paddingRight:w.xxl,marginRight:w.md,borderRight:`1px solid ${B.menuBorder}`});const y=l.createElement("div");y.textContent=s,Object.assign(y.style,{fontSize:G.heading,fontWeight:"700",color:B.value,lineHeight:je.tight});const x=l.createElement("div");if(x.textContent=g,Object.assign(x.style,{fontSize:G.body,color:B.label}),f.append(y,x),S){const C=l.createElement("div");C.textContent=S,Object.assign(C.style,{fontSize:G.meta,color:B.label,opacity:Ge.muted}),f.appendChild(C)}return f}if(r){const s=V([{...d,key:d.waferIndex}],R)[0]?.percent;return s!==void 0&&c.appendChild(m(`${s.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),c.appendChild(m(String(d.dies.length),"Total dies")),p(c)}c.appendChild(m(String(e.length),"Wafers"));const b=V(e.map(s=>({...s,key:s.waferIndex})),R).map(s=>s.percent).filter(s=>Number.isFinite(s));if(b.length){const s=b.reduce((f,y)=>f+y,0)/b.length,g=ie([{key:"all",items:e.map(f=>({...f,key:f.waferIndex}))}],R)[0]?.percent,S=g!==void 0&&g.toFixed(1)!==s.toFixed(1);c.appendChild(m(`${s.toFixed(1)}%`,`Mean per-wafer yield \xB7 pass: ${t}`,S?`${g.toFixed(1)}% by dies`:void 0))}const u=e.map(s=>s.statsSummary);if(u.every(s=>s!==void 0)){const s=u.reduce((S,f)=>S+f.stats.analyzedDies,0),g=u.reduce((S,f)=>S+f.stats.excludedDies,0);c.appendChild(m(s.toLocaleString(),"Dies analysed",g?`${g.toLocaleString()} excluded`:void 0))}else c.appendChild(m(e.reduce((s,g)=>s+g.dies.length,0).toLocaleString(),"Total dies"));return p(c)}function Te(e,t,r,d,c){const p=l.createElement("div");Object.assign(p.style,{display:"flex",flexDirection:"column",gap:w.lg});const m=e.length===1&&!d;e.length&&!d&&p.appendChild(we(e));const b=Ce(e,d,c,!m);p.appendChild(b.card);const u=xe(e,t,r,d);for(const s of u.elements)b.card.appendChild(s);return u.testValuesCard&&p.appendChild(u.testValuesCard),u.functionalCard&&p.appendChild(u.functionalCard),{card:p,destroy:()=>{b.destroy(),u.destroy()}}}function ve(e,t,r,d){const c=J(l),p=be(e),m=a=>{H=a;for(const o of[f,y,x])o!==b&&o?.setAxisPrefs(a)};let b=null;const u=a=>o=>{b=a(),m(o),b=null},s=a=>({...a,testStats:a.statsSummary?.stats.perTestStats}),g=e.map(s),S=r?.map(a=>({key:a.key,items:a.items.map(s)})),f=_e({title:"Test value distribution",axisPrefs:H,onAxisPrefsChange:u(()=>f),onTestChange:a=>C(a),selectedTestNumber:L??void 0,items:g,testDefs:t,groups:S,groupLabelText:d,onSaveImage:T,onGroupChange:a=>U(a),onOpen:p?.open,openActionLabel:p?.label,ownerDocument:l}),y=ze({title:"Value histogram",axisPrefs:H,onAxisPrefsChange:u(()=>y),onTestChange:a=>C(a),selectedTestNumber:L??void 0,items:e,testDefs:t,groups:r,onGroupChange:a=>U(a),onSaveImage:T,ownerDocument:l}),x=Ue({title:"Wafer-to-wafer trend",axisPrefs:H,onAxisPrefsChange:u(()=>x),onTestChange:a=>C(a),selectedTestNumber:L??void 0,items:e.map(a=>({label:a.label,key:a.waferIndex,dies:a.dies,testStats:a.statsSummary?.stats.perTestStats})),testDefs:t,onSaveImage:T,centreLabel:We(Re(e.map(a=>a.wafer.metadata)),"mean"),onOpen:Y?(a,o)=>z(a,`Wafer ${e.find(h=>h.waferIndex===a)?.label??a}`,o):void 0,ownerDocument:l}),C=a=>{L=a,f.setTest(a),y.setTest(a),x.setTest(a),n.setTest(a)},n=Ve({title:"Process capability",items:e,testDefs:t,onSaveImage:T,selectedTestNumber:L??void 0,onSelectTest:a=>C(a),ownerDocument:l});return c.append(n.card,f.card,y.card,x.card),{card:c,destroy:()=>{n.destroy(),f.destroy(),y.destroy(),x.destroy()}}}function ke(e,t,r){const d=J(l),c=Qe({title:"Test scatter",items:e,testDefs:t,groups:r,binColors:Z().hard,onSaveImage:T,ownerDocument:l}),p=qe({title:"Test correlation matrix",items:e,testDefs:t,onSaveImage:T,onSaveText:_,onSelectPair:(m,b)=>c.setXY(m,b),ownerDocument:l});return d.append(p.card,c.card),{card:d,destroy:()=>{p.destroy(),c.destroy()}}}function M(e){e?.keepSections||te(),j.innerHTML="",k.innerHTML="",D.backTab&&k.appendChild(he(D.backTab));for(const n of lt)k.appendChild(ye(n.key,n.label));const t=ge();Se(t);const r=$e(t,{facetableOnly:!0}).filter(n=>n.splittable),d=l.createElement("div");Object.assign(d.style,{display:"flex",gap:w.md,alignItems:"center",marginLeft:w.xxl,paddingBottom:w.xs});let c;r.length>0?(d.appendChild(et("Group by:",[{value:"",label:"None"},...r.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],A??"",n=>{A=n||void 0,$=null,M()},{hook:"group-by",ownerDocument:l})),c=r.find(n=>n.key===A)?.label):A=void 0;let p;if(A){const n=new Map,a=[];for(const o of t){const h=Le(o.metadata,A)??Ee;n.has(h)||(n.set(h,[]),a.push(h)),n.get(h).push(o)}p=a.map(o=>({key:o,items:n.get(o)}))}$!==null&&!p?.some(n=>n.key===$)&&($=null),p&&p.length>0&&d.appendChild(tt(p.map(n=>n.key),"Show:",$,n=>U(n),{ownerDocument:l}).el);let m=null;if(D.onOpenGuide){const n=l.createElement("button");n.type="button",n.innerHTML=Ne.help,n.setAttribute("aria-label","User guide"),Object.assign(n.style,{...Fe("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),re(n,"bare"),Me(n,"User guide"),n.addEventListener("click",()=>D.onOpenGuide()),Object.assign(n.style,{marginLeft:"auto"}),m=n}k.appendChild(d),m&&k.appendChild(m);const b=$!==null?p?.find(n=>n.key===$):void 0,u=b?.items??t,s=b?void 0:p,g=Ie(u),S=g.defs,f=S.filter(Oe);L!==null&&!f.some(n=>n.testNumber===L)&&(L=null);const y=e?.keepSections?F.get(I):void 0,x=y??(I==="overview"?Te(u,f,S,s,c):I==="distributions"?ve(u,f,s,c):ke(u,f,s));y||F.set(I,x);const C=g.conflicts.filter(n=>n.excluded);if(C.length>0){const n=l.createElement("div");Object.assign(n.style,{color:B.label,fontSize:G.body,padding:`0 ${W} ${w.sm}`});const a=C.slice(0,6).map(N=>N.testNumber).join(", "),o=C.length>6?`, and ${C.length-6} more`:"",h=C.length===1?"it":"them";n.textContent=`${C.length} test ${C.length===1?"number is":"numbers are"} hidden (${a}${o}) \u2014 the wafers in view describe ${h} differently, so their values cannot be pooled. `+(p&&p.length>0?`Pick one ${c??"group"} under "Show:" to analyse ${h}.`:"Load one test program at a time to analyse them."),n.dataset.wmapWithheldTests=String(C.length),j.appendChild(n)}j.appendChild(x.card)}return{el:O,render:M,destroy:()=>{te(),O.remove()}}}
1
+ import{buildFacetTable as Re,facetValueOf as We,FACET_NONE_VALUE as Me}from"../stats/facets.js";import{mergeTestDefs as re}from"../stats/mergeTestDefs.js";import{isParametricTest as Ye}from"../renderer/buildWaferMap.js";import{waferDisplayLabel as je,waferIdentityLabel as Ge}from"../core/waferLabel.js";import{hasDrilldownTargets as se,waferPopulation as He}from"./chartPopulation.js";import{openDrilldownMenu as Fe}from"./drilldown.js";import{INPUT_DEFAULT_PASS_BINS as R,itemPassBins as Ne,passBinsLabel as ie}from"../core/passBins.js";import{NO_DATA_FILL as le}from"../renderer/colorMap.js";import{describeWaferPopulation as Ve,populationStat as _e}from"../stats/population.js";import{LEADING as Ue,ALPHA as ze,SPACE as x,EDGE_GUTTER as Z,FONT as Y,CLR as B,RADIUS as qe,SHADOW as Qe,controlStyle as Ke,wireControlHover as de,wireTooltip as Xe}from"./toolbar.js";import{ICONS as Je}from"./icons.js";import{renderCapabilityPanel as Ze}from"./charts/capability.js";import{renderBoxplotPanel as et}from"./charts/boxplot.js";import{renderTrendPanel as tt}from"./charts/trend.js";import{renderSweepPanel as nt}from"./charts/sweep.js";import{renderHistogramPanel as at}from"./charts/histogram.js";import{renderCorrelationPanel as ot}from"./charts/correlation.js";import{renderScatterPanel as rt}from"./charts/scatter.js";import{renderBarPanel as ce}from"./charts/barPanel.js";import{renderBinClusterPanel as st}from"./charts/binCluster.js";import{renderTestPassRatePanel as it}from"./charts/testPassRate.js";import{QUANTITY as lt}from"./charts/palette.js";import{cardFrameStyle as dt,makeChartGridWrap as V,makeLabeledSelect as ct,makeLinkedGroupSelect as pt}from"./charts/chartShell.js";import{buildYieldData as _,buildYieldDataCombined as pe}from"../stats/yield.js";import{buildBinParetoData as ut}from"../stats/binPareto.js";import{buildLotTestSection as ft,buildLotFunctionalSection as mt,buildMetadataStripBox as bt}from"./summaryPanel.js";import{buildRegionYieldData as ue,buildRingRegions as gt,buildQuadrantRegions as yt}from"../stats/regions.js";import{renderRegionYieldDiagram as fe}from"./charts/regionYieldDiagram.js";const me=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}],ht={key:"sweeps",label:"Sweeps"};export function createInsightsTab(T){const{getItems:be,getLotStats:ge,getBinColors:ee,getRingCount:ye,onSaveImage:v,onSaveText:U,openWafer:j,focusTest:te}=T,he=T.showMetadataStrip??!0,W=T.contentInset??Z,p=T.ownerDocument??document,P=p.createElement("div");Object.assign(P.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const we=Z,O=p.createElement("div");Object.assign(O.style,{paddingLeft:we});const D=p.createElement("div");Object.assign(D.style,{display:"flex",gap:x.xs,alignItems:"center",background:B.menuBg,border:`1px solid ${B.menuBorder}`,borderRadius:qe.container,boxShadow:Qe.panel,padding:`0 ${x.lg}`,marginBottom:x.lg,marginLeft:W,marginRight:W}),D.setAttribute("role","tablist"),D.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from(D.querySelectorAll('[role="tab"]')),r=t.indexOf(p.activeElement);if(r===-1)return;e.preventDefault();const c=t[(r+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];c.focus(),c.click()});const M=p.createElement("div");Object.assign(M.style,{display:"flex",flexDirection:"column",gap:x.lg,marginTop:"0",paddingLeft:W,paddingRight:W}),P.appendChild(O),P.appendChild(D),P.appendChild(M);const ne=(T.sweeps?.length??0)>0,Ce=ne?[...me,ht]:me;let E=T.defaultView==="sweeps"&&!ne?"overview":T.defaultView??"overview",A,L=null,$=null,G={includeLimits:void 0,clipOutliers:!1};const z=e=>{$!==e&&($=e,F())};let H=new Map;function ae(){for(const e of H.values())e.destroy();H=new Map}function q(e,t,r){j?.(e,t,r)}function Q(e){if(e.some(t=>se(t.testDefs,T.sweeps)))return(t,r,c)=>{const i=e.find(f=>f.waferIndex===t);if(!i||!se(i.testDefs,T.sweeps))return;c.preventDefault();const s=He(i.dies,{waferLabel:i.identity,testDefs:i.testDefs,activeTest:r??L??void 0,waferIndex:t});Fe({x:c.clientX,y:c.clientY},c.target,s,{sweeps:T.sweeps,onSaveImage:v})}}function Se(e){return j?{open:(t,r)=>q(t,`Wafer ${e.find(c=>c.waferIndex===t)?.label??t}`,r)}:te&&e.length===1?{open:(t,r)=>te(r),label:"show this test on the map"}:null}function xe(){return be().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:je(e,t),identity:Ge(e),waferIndex:t,passBins:Ne(e),statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function oe(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${B.iconActive}`:"2px solid transparent",color:t?B.iconActive:B.label,fontWeight:t?"700":"500",fontSize:Y.body,padding:`${x.sm} ${Z} ${x.md}`,cursor:"pointer",marginBottom:"-1px"})}function Te(e,t){const r=p.createElement("button");r.type="button",r.textContent=t;const c=e===E;return oe(r,c),r.dataset.on=c?"true":"false",de(r,"bare"),r.dataset.wmapInsightsTab=e,r.setAttribute("role","tab"),r.setAttribute("aria-selected",c?"true":"false"),r.tabIndex=c?0:-1,r.addEventListener("click",()=>{E!==e&&(E=e,F({keepSections:!0}))}),r}function ve(e){const t=p.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,oe(t,!1),t.dataset.wmapInsightsBack="1",t.addEventListener("click",e.onBack),t}function ke(e,t,r,c=!0){const i=V(p),s=ee();let f="label";const b=r??"group",u=ie(e.map(d=>d.passBins)),l=ge?.(),g=new Map(l?.lotYieldSeries.map(d=>[d.waferIndex,d.yieldPercent])),C=d=>({...d,label:d.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:g.get(d.waferIndex),key:d.waferIndex}),m=e.map(C),y=t?.map(d=>({key:d.key,items:d.items.map(C)})),S=()=>y?pe(y,R,f):_(m,R,f),w={title:t?`Yield by ${b} (pass: ${u})`:`Yield by wafer (pass: ${u})`,data:S(),selfControl:{current:f,options:[["yield","Sort: yield"],["label",t?`Sort: ${b}`:"Sort: wafer ID"]],onChange:d=>(f=d,{data:S()})},barColor:()=>lt,valueLabel:d=>`${d.percent.toFixed(1)}%`,reference:d=>{if(d.length<3)return null;const k=d.map(J=>J.percent).sort((J,Ae)=>J-Ae),I=k.length%2?k[(k.length-1)/2]:(k[k.length/2-1]+k[k.length/2])/2;return{value:I,label:`median ${I.toFixed(1)}%`}},drill:y?{onOpenGroup:d=>{const k=y.find(I=>I.key===d.label)?.items??[];return{data:_(k,R,f),title:`Yield by wafer \u2014 ${b}: ${d.label} (pass: ${u})`}},onBack:()=>({data:S(),title:`Yield by ${b} (pass: ${u})`}),groupLabelText:b}:void 0,onOpen:j?d=>{typeof d.key=="number"&&q(d.key,`Wafer ${d.label}`)}:void 0,onWaferContextMenu:Q(e),ownerDocument:p};let n=null;if(c&&(n=ce(w,v),n.card.style.minHeight="360px",i.appendChild(n.card)),t){const d=st({title:"Hard bin pareto",groups:t.map(k=>({key:k.key,items:k.items.map(I=>({dies:I.dies,hardBinCounts:I.statsSummary?.stats.hardBinCounts,softBinCounts:I.statsSummary?.stats.softBinCounts}))})),onSaveImage:v,ownerDocument:p});return d.card.style.minHeight="360px",i.appendChild(d.card),{card:i,destroy:()=>{n?.destroy(),d.destroy()}}}let a="hbin";const o=d=>d===void 0?le:(a==="hbin"?s.hard:s.soft).get(d)??le,h=e.map(d=>({dies:d.dies,hardBinCounts:d.statsSummary?.stats.hardBinCounts,softBinCounts:d.statsSummary?.stats.softBinCounts})),N=()=>ut(h,a),Oe={title:"Hard bin pareto",data:N(),selfControl:{current:a,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:d=>(a=d,{data:N(),title:`${a==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:d=>o(d.binCode),ownerDocument:p},X=ce(Oe,v);return X.card.style.minHeight="360px",i.appendChild(X.card),{card:i,destroy:()=>{n?.destroy(),X.destroy()}}}function K(){const e=p.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...dt(),display:"flex",flexDirection:"column",gap:x.lg,minWidth:"0"}),e}function De(e){if(O.innerHTML="",O.style.marginBottom="0",!he)return;const t=bt(e.map(r=>({metadata:r.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",O.appendChild(t),O.style.marginBottom=x.xs)}function Be(e,t,r,c){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const i=e.map(o=>o.wafer),s=e.map(o=>o.dies),f=e.flatMap(o=>o.dies),b=e.map(o=>o.statsSummary),u=b.every(o=>o!==void 0)?b:void 0,l=ye?.()??4,g=[],C=[],y=it({groups:(c??[{key:"",items:e}]).map(o=>({key:o.key,items:o.items.map(h=>({dies:h.dies,testSpecYield:h.statsSummary?.stats.testSpecYield,functionalYield:h.statsSummary?.stats.functionalYield}))})),testDefs:r,onSaveImage:v,ownerDocument:p});y.card.style.minHeight="300px",g.push(y.card),C.push(y.destroy);const S=ue(s,i,l,o=>e[o].passBins,gt);if(S.length){const o=fe({title:"Ring yield",mode:"ring",rows:S,onSaveImage:v,ownerDocument:p});g.push(o.card),C.push(o.destroy)}const w=ue(s,i,l,o=>e[o].passBins,yt);if(w.length){const o=fe({title:"Quadrant yield",mode:"quadrant",rows:w,onSaveImage:v,ownerDocument:p});g.push(o.card),C.push(o.destroy)}let n=null;if(t.length){const o=ft(f,t,void 0,u,U,s.map(h=>({dies:h})),void 0,"full");if(o){const h=K();h.appendChild(o),n=h}}let a=null;if(r.length){const o=mt(f,r,u,U);if(o){const h=K();h.appendChild(o),a=h}}return{elements:g,testValuesCard:n,functionalCard:a,destroy:()=>{for(const o of C)o()}}}function Le(e){const t=ie(e.map(l=>l.passBins)),r=e.length===1,c=e[0],i=K();Object.assign(i.style,{flexDirection:"row",flexWrap:"wrap",gap:x.md});const s=l=>{const g=l.lastElementChild;return g&&Object.assign(g.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),l};function f(l,g,C){const m=p.createElement("div");Object.assign(m.style,{display:"flex",alignItems:"baseline",gap:x.sm,paddingRight:x.xxl,marginRight:x.md,borderRight:`1px solid ${B.menuBorder}`});const y=p.createElement("div");y.textContent=l,Object.assign(y.style,{fontSize:Y.heading,fontWeight:"700",color:B.value,lineHeight:Ue.tight});const S=p.createElement("div");if(S.textContent=g,Object.assign(S.style,{fontSize:Y.body,color:B.label}),m.append(y,S),C){const w=p.createElement("div");w.textContent=C,Object.assign(w.style,{fontSize:Y.meta,color:B.label,opacity:ze.muted}),m.appendChild(w)}return m}if(r){const l=_([{...c,key:c.waferIndex}],R)[0]?.percent;return l!==void 0&&i.appendChild(f(`${l.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),i.appendChild(f(String(c.dies.length),"Total dies")),s(i)}i.appendChild(f(String(e.length),"Wafers"));const b=_(e.map(l=>({...l,key:l.waferIndex})),R).map(l=>l.percent).filter(l=>Number.isFinite(l));if(b.length){const l=b.reduce((m,y)=>m+y,0)/b.length,g=pe([{key:"all",items:e.map(m=>({...m,key:m.waferIndex}))}],R)[0]?.percent,C=g!==void 0&&g.toFixed(1)!==l.toFixed(1);i.appendChild(f(`${l.toFixed(1)}%`,`Mean per-wafer yield \xB7 pass: ${t}`,C?`${g.toFixed(1)}% by dies`:void 0))}const u=e.map(l=>l.statsSummary);if(u.every(l=>l!==void 0)){const l=u.reduce((C,m)=>C+m.stats.analyzedDies,0),g=u.reduce((C,m)=>C+m.stats.excludedDies,0);i.appendChild(f(l.toLocaleString(),"Dies analysed",g?`${g.toLocaleString()} excluded`:void 0))}else i.appendChild(f(e.reduce((l,g)=>l+g.dies.length,0).toLocaleString(),"Total dies"));return s(i)}function $e(e,t,r,c,i){const s=p.createElement("div");Object.assign(s.style,{display:"flex",flexDirection:"column",gap:x.lg});const f=e.length===1&&!c;e.length&&!c&&s.appendChild(Le(e));const b=ke(e,c,i,!f);s.appendChild(b.card);const u=Be(e,t,r,c);for(const l of u.elements)b.card.appendChild(l);return u.testValuesCard&&s.appendChild(u.testValuesCard),u.functionalCard&&s.appendChild(u.functionalCard),{card:s,destroy:()=>{b.destroy(),u.destroy()}}}function Ee(e,t,r,c){const i=V(p),s=Se(e),f=a=>{G=a;for(const o of[m,y,S])o!==b&&o?.setAxisPrefs(a)};let b=null;const u=a=>o=>{b=a(),f(o),b=null},l=a=>({...a,testStats:a.statsSummary?.stats.perTestStats}),g=e.map(l),C=r?.map(a=>({key:a.key,items:a.items.map(l)})),m=et({title:"Test value distribution",axisPrefs:G,onAxisPrefsChange:u(()=>m),onTestChange:a=>w(a),selectedTestNumber:L??void 0,items:g,testDefs:t,groups:C,groupLabelText:c,onSaveImage:v,onGroupChange:a=>z(a),onOpen:s?.open,openActionLabel:s?.label,onWaferContextMenu:Q(e),ownerDocument:p}),y=at({title:"Value histogram",axisPrefs:G,onAxisPrefsChange:u(()=>y),onTestChange:a=>w(a),selectedTestNumber:L??void 0,items:e,testDefs:t,groups:r,onGroupChange:a=>z(a),onSaveImage:v,ownerDocument:p}),S=tt({title:"Wafer-to-wafer trend",axisPrefs:G,onAxisPrefsChange:u(()=>S),onTestChange:a=>w(a),selectedTestNumber:L??void 0,items:e.map(a=>({label:a.label,key:a.waferIndex,dies:a.dies,testStats:a.statsSummary?.stats.perTestStats})),testDefs:t,onSaveImage:v,centreLabel:_e(Ve(e.map(a=>a.wafer.metadata)),"mean"),onOpen:j?(a,o)=>q(a,`Wafer ${e.find(h=>h.waferIndex===a)?.label??a}`,o):void 0,onWaferContextMenu:Q(e),ownerDocument:p}),w=a=>{L=a,m.setTest(a),y.setTest(a),S.setTest(a),n.setTest(a)},n=Ze({title:"Process capability",items:e,testDefs:t,onSaveImage:v,selectedTestNumber:L??void 0,onSelectTest:a=>w(a),ownerDocument:p});return i.append(n.card,m.card,y.card,S.card),{card:i,destroy:()=>{n.destroy(),m.destroy(),y.destroy(),S.destroy()}}}function Ie(e){const t=V(p),r=re(e).defs,c=e.flatMap(s=>s.dies),i=(T.sweeps??[]).map(s=>nt({spec:s,dies:c,testDefs:r,onSaveImage:v,ownerDocument:p}));for(const s of i)t.appendChild(s.card);return{card:t,destroy:()=>{for(const s of i)s.destroy()}}}function Pe(e,t,r){const c=V(p),i=rt({title:"Test scatter",items:e,testDefs:t,groups:r,binColors:ee().hard,onSaveImage:v,ownerDocument:p}),s=ot({title:"Test correlation matrix",items:e,testDefs:t,onSaveImage:v,onSaveText:U,onSelectPair:(f,b)=>i.setXY(f,b),ownerDocument:p});return c.append(s.card,i.card),{card:c,destroy:()=>{s.destroy(),i.destroy()}}}function F(e){e?.keepSections||ae(),M.innerHTML="",D.innerHTML="",T.backTab&&D.appendChild(ve(T.backTab));for(const n of Ce)D.appendChild(Te(n.key,n.label));const t=xe();De(t);const r=Re(t,{facetableOnly:!0}).filter(n=>n.splittable),c=p.createElement("div");Object.assign(c.style,{display:"flex",gap:x.md,alignItems:"center",marginLeft:x.xxl,paddingBottom:x.xs});let i;r.length>0?(c.appendChild(ct("Group by:",[{value:"",label:"None"},...r.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],A??"",n=>{A=n||void 0,$=null,F()},{hook:"group-by",ownerDocument:p})),i=r.find(n=>n.key===A)?.label):A=void 0;let s;if(A){const n=new Map,a=[];for(const o of t){const h=We(o.metadata,A)??Me;n.has(h)||(n.set(h,[]),a.push(h)),n.get(h).push(o)}s=a.map(o=>({key:o,items:n.get(o)}))}$!==null&&!s?.some(n=>n.key===$)&&($=null),s&&s.length>0&&c.appendChild(pt(s.map(n=>n.key),"Show:",$,n=>z(n),{ownerDocument:p}).el);let f=null;if(T.onOpenGuide){const n=p.createElement("button");n.type="button",n.innerHTML=Je.help,n.setAttribute("aria-label","User guide"),Object.assign(n.style,{...Ke("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),de(n,"bare"),Xe(n,"User guide"),n.addEventListener("click",()=>T.onOpenGuide()),Object.assign(n.style,{marginLeft:"auto"}),f=n}D.appendChild(c),f&&D.appendChild(f);const b=$!==null?s?.find(n=>n.key===$):void 0,u=b?.items??t,l=b?void 0:s,g=re(u),C=g.defs,m=C.filter(Ye);L!==null&&!m.some(n=>n.testNumber===L)&&(L=null);const y=e?.keepSections?H.get(E):void 0,S=y??(E==="overview"?$e(u,m,C,l,i):E==="distributions"?Ee(u,m,l,i):E==="sweeps"?Ie(u):Pe(u,m,l));y||H.set(E,S);const w=g.conflicts.filter(n=>n.excluded);if(w.length>0){const n=p.createElement("div");Object.assign(n.style,{color:B.label,fontSize:Y.body,padding:`0 ${W} ${x.sm}`});const a=w.slice(0,6).map(N=>N.testNumber).join(", "),o=w.length>6?`, and ${w.length-6} more`:"",h=w.length===1?"it":"them";n.textContent=`${w.length} test ${w.length===1?"number is":"numbers are"} hidden (${a}${o}) \u2014 the wafers in view describe ${h} differently, so their values cannot be pooled. `+(s&&s.length>0?`Pick one ${i??"group"} under "Show:" to analyse ${h}.`:"Load one test program at a time to analyse them."),n.dataset.wmapWithheldTests=String(w.length),M.appendChild(n)}M.appendChild(S.card)}return{el:P,render:F,destroy:()=>{ae(),P.remove()}}}