@wafertools/wafermap 0.21.1

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 (138) hide show
  1. package/CHANGELOG.md +751 -0
  2. package/LICENSE +21 -0
  3. package/README.md +79 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +1 -0
  6. package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
  7. package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
  8. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
  9. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
  10. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
  11. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
  12. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
  13. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
  14. package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
  15. package/dist/packages/canvas-adapter/charts/capability.js +1 -0
  16. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
  17. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
  18. package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
  19. package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
  21. package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
  22. package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
  23. package/dist/packages/canvas-adapter/charts/index.js +1 -0
  24. package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
  25. package/dist/packages/canvas-adapter/charts/palette.js +1 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
  27. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
  29. package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
  30. package/dist/packages/canvas-adapter/icons.d.ts +2 -0
  31. package/dist/packages/canvas-adapter/icons.js +42 -0
  32. package/dist/packages/canvas-adapter/index.d.ts +12 -0
  33. package/dist/packages/canvas-adapter/index.js +1 -0
  34. package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
  35. package/dist/packages/canvas-adapter/insightsTab.js +1 -0
  36. package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
  37. package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
  38. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
  39. package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
  40. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
  41. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
  42. package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
  43. package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
  44. package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
  45. package/dist/packages/canvas-adapter/toCanvas.js +1 -0
  46. package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
  47. package/dist/packages/canvas-adapter/toolbar.js +9 -0
  48. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
  49. package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
  50. package/dist/packages/canvas-adapter/version.d.ts +3 -0
  51. package/dist/packages/canvas-adapter/version.js +1 -0
  52. package/dist/packages/core/aggregates.d.ts +54 -0
  53. package/dist/packages/core/aggregates.js +1 -0
  54. package/dist/packages/core/classify.d.ts +24 -0
  55. package/dist/packages/core/classify.js +1 -0
  56. package/dist/packages/core/dies.d.ts +92 -0
  57. package/dist/packages/core/dies.js +1 -0
  58. package/dist/packages/core/index.d.ts +13 -0
  59. package/dist/packages/core/index.js +1 -0
  60. package/dist/packages/core/inference/grid.d.ts +35 -0
  61. package/dist/packages/core/inference/grid.js +1 -0
  62. package/dist/packages/core/inference/index.d.ts +7 -0
  63. package/dist/packages/core/inference/index.js +1 -0
  64. package/dist/packages/core/inference/pitch.d.ts +26 -0
  65. package/dist/packages/core/inference/pitch.js +1 -0
  66. package/dist/packages/core/inference/wafer.d.ts +30 -0
  67. package/dist/packages/core/inference/wafer.js +1 -0
  68. package/dist/packages/core/metadata.d.ts +31 -0
  69. package/dist/packages/core/metadata.js +1 -0
  70. package/dist/packages/core/probe.d.ts +16 -0
  71. package/dist/packages/core/probe.js +1 -0
  72. package/dist/packages/core/reticle.d.ts +68 -0
  73. package/dist/packages/core/reticle.js +1 -0
  74. package/dist/packages/core/transforms.d.ts +142 -0
  75. package/dist/packages/core/transforms.js +1 -0
  76. package/dist/packages/core/utils.d.ts +60 -0
  77. package/dist/packages/core/utils.js +1 -0
  78. package/dist/packages/core/wafer.d.ts +45 -0
  79. package/dist/packages/core/wafer.js +1 -0
  80. package/dist/packages/renderer/buildView.d.ts +420 -0
  81. package/dist/packages/renderer/buildView.js +1 -0
  82. package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
  83. package/dist/packages/renderer/buildWaferMap.js +1 -0
  84. package/dist/packages/renderer/colorMap.d.ts +46 -0
  85. package/dist/packages/renderer/colorMap.js +1 -0
  86. package/dist/packages/renderer/colorSchemes.d.ts +40 -0
  87. package/dist/packages/renderer/colorSchemes.js +1 -0
  88. package/dist/packages/renderer/fmt.d.ts +30 -0
  89. package/dist/packages/renderer/fmt.js +1 -0
  90. package/dist/packages/renderer/index.d.ts +7 -0
  91. package/dist/packages/renderer/index.js +1 -0
  92. package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
  93. package/dist/packages/stats/analyzeWaferLot.js +1 -0
  94. package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
  95. package/dist/packages/stats/analyzeWaferMap.js +1 -0
  96. package/dist/packages/stats/binPareto.d.ts +49 -0
  97. package/dist/packages/stats/binPareto.js +1 -0
  98. package/dist/packages/stats/boxplot.d.ts +39 -0
  99. package/dist/packages/stats/boxplot.js +1 -0
  100. package/dist/packages/stats/capability.d.ts +55 -0
  101. package/dist/packages/stats/capability.js +1 -0
  102. package/dist/packages/stats/clusterDetection.d.ts +18 -0
  103. package/dist/packages/stats/clusterDetection.js +1 -0
  104. package/dist/packages/stats/correlation.d.ts +55 -0
  105. package/dist/packages/stats/correlation.js +1 -0
  106. package/dist/packages/stats/facets.d.ts +74 -0
  107. package/dist/packages/stats/facets.js +1 -0
  108. package/dist/packages/stats/filterFindings.d.ts +9 -0
  109. package/dist/packages/stats/filterFindings.js +1 -0
  110. package/dist/packages/stats/findingsNarrative.d.ts +3 -0
  111. package/dist/packages/stats/findingsNarrative.js +1 -0
  112. package/dist/packages/stats/histogram.d.ts +42 -0
  113. package/dist/packages/stats/histogram.js +1 -0
  114. package/dist/packages/stats/index.d.ts +27 -0
  115. package/dist/packages/stats/index.js +1 -0
  116. package/dist/packages/stats/math.d.ts +10 -0
  117. package/dist/packages/stats/math.js +1 -0
  118. package/dist/packages/stats/patternClassification.d.ts +107 -0
  119. package/dist/packages/stats/patternClassification.js +1 -0
  120. package/dist/packages/stats/regions.d.ts +57 -0
  121. package/dist/packages/stats/regions.js +1 -0
  122. package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
  123. package/dist/packages/stats/renderFindingsReport.js +41 -0
  124. package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
  125. package/dist/packages/stats/renderSummaryReport.js +68 -0
  126. package/dist/packages/stats/reportHtml.d.ts +42 -0
  127. package/dist/packages/stats/reportHtml.js +301 -0
  128. package/dist/packages/stats/scatter.d.ts +24 -0
  129. package/dist/packages/stats/scatter.js +1 -0
  130. package/dist/packages/stats/types.d.ts +269 -0
  131. package/dist/packages/stats/types.js +1 -0
  132. package/dist/packages/stats/yield.d.ts +56 -0
  133. package/dist/packages/stats/yield.js +1 -0
  134. package/dist/packages/worker/index.d.ts +44 -0
  135. package/dist/packages/worker/index.js +1 -0
  136. package/dist/packages/worker/wafermap.worker.d.ts +44 -0
  137. package/dist/packages/worker/wafermap.worker.js +1 -0
  138. package/package.json +122 -0
@@ -0,0 +1,30 @@
1
+ import { type HistogramItem } from '../../stats/histogram.js';
2
+ import type { TestDef } from '../../renderer/buildWaferMap.js';
3
+ import { type SaveImageHandler } from './chartShell.js';
4
+ export interface HistogramPanelOptions {
5
+ title?: string;
6
+ items: HistogramItem[];
7
+ testDefs: TestDef[];
8
+ selectedTestNumber?: number;
9
+ colorScheme?: string;
10
+ onSaveImage?: SaveImageHandler;
11
+ /**
12
+ * When the Analysis tab's "Group by" is active, this panel switches to an
13
+ * overlaid multi-series view (one coloured series per group over shared
14
+ * buckets, with a click-to-emphasize legend) instead of the single-item
15
+ * view. `items` above is ignored when `groups` is provided. Absent ⇒
16
+ * today's plain per-item view with a wafer selector.
17
+ */
18
+ groups?: {
19
+ key: string;
20
+ items: HistogramItem[];
21
+ }[];
22
+ }
23
+ export interface HistogramPanelHandle {
24
+ card: HTMLElement;
25
+ /** Cross-panel link (e.g. from the capability panel): switch to `testNumber` in place. */
26
+ setTest: (testNumber: number) => void;
27
+ destroy: () => void;
28
+ }
29
+ export declare function renderHistogramPanel(options: HistogramPanelOptions): HistogramPanelHandle;
30
+ //# sourceMappingURL=histogram.d.ts.map
@@ -0,0 +1 @@
1
+ import{buildTestHistogramData as Ht,buildTestHistogramSeries as At}from"../../stats/histogram.js";import{CLR as lt}from"../toolbar.js";import{fmt as st}from"../../renderer/fmt.js";import{QUANTITY as Wt,categorical as Ct}from"./palette.js";import{cardShell as Et,observeResize as xt,makeTooltip as Pt,positionChartTooltip as bt,makeTestSelect as $t,makeWaferSelect as Bt,makeToggle as Rt,renderEmptyState as pt,chartFillHeight as yt,applyCanvasFlow as Mt,resolveChartCanvasColors as Tt,makeAxisFormat as vt,PADDING as tt}from"./chartShell.js";const mt=230,St=36,at=18;function kt(s,R,N,z,rt,I,b,u=4){const j=Math.max(1,I/u),F=Math.pow(10,Math.floor(Math.log10(j))),f=j/F,G=(f<=1?1:f<=2?2:f<=5?5:10)*F,$=z-N;s.save(),s.font="10px system-ui, sans-serif",s.textAlign="right",s.textBaseline="middle";for(let K=0;K<=I+1e-9;K+=G){const _=z-K/(I||1)*$;s.strokeStyle=b.grid,s.lineWidth=1,s.beginPath(),s.moveTo(R,_),s.lineTo(R+rt,_),s.stroke(),s.fillStyle=b.text,s.fillText(String(Math.round(K)),R-4,_)}s.strokeStyle=b.axis,s.lineWidth=1,s.beginPath(),s.moveTo(R,N),s.lineTo(R,z),s.stroke(),s.restore()}export function renderHistogramPanel(s){const{title:R="Test value distribution",items:N,testDefs:z,onSaveImage:rt,groups:I}=s,{card:b,body:u,controlsRow:j}=Et(R,rt),F=z.filter(a=>a.testNumber!==void 0);let f=s.selectedTestNumber??F[0]?.testNumber??null,G=null,$=!1;const K=$t(F,f,a=>{f=a,U()},{maxWidth:"200px"});j.appendChild(K);const _=Bt(N,G,a=>{G=a,U()});j.appendChild(_),j.appendChild(Rt("Axis includes limits",$,a=>{$=a,U()}));const A=Pt(b);let J=null,M=null;function et(a){const w=z.find(O=>O.testNumber===a);return{unit:w?.unit,limitLow:w?.limitLow,limitHigh:w?.limitHigh}}function U(){if(J?.disconnect(),J=null,u.innerHTML="",f===null){pt(u,"No parametric test data available for a histogram.");return}const a=I&&I.length>0?At(I,f,16,$?et(f).limitLow:void 0,$?et(f).limitHigh:void 0):null;if(_.style.display=a?"none":"",a){if(a.series.length===0){pt(u,"No parametric test data available for a histogram.");return}Lt(a);return}const w=G!==null?[N[G]]:N,{unit:O,limitLow:nt,limitHigh:x}=et(f),i=Ht(w,f,16,$?nt:void 0,$?x:void 0);if(i.length===0){pt(u,"No parametric test data available for a histogram.");return}const D=Math.max(...i.map(p=>p.count),1),P=document.createElement("div");Object.assign(P.style,{fontSize:"12px",color:lt.label,marginBottom:"2px"}),P.textContent=`max ${D} dies/bucket`,u.appendChild(P);const h=document.createElement("canvas");h.style.display="block",h.style.cursor="default",u.appendChild(h);let v=-1;const g=window.devicePixelRatio||1,B=i[0].rangeLow,X=i[i.length-1].rangeHigh,ot=X-B||1,Z=vt(Math.max(Math.abs(B),Math.abs(X)),O);function Y(p){const m=tt+36,e=h.clientWidth-m-tt,n=p-St-at;return{plotX:m,plotMaxWidth:Math.max(10,e),plotMaxHeight:n,plotTop:at}}function ct(p){const{plotX:m,plotMaxWidth:e}=Y(mt);if(p<m||p>m+e)return-1;const n=Math.floor((p-m)/e*i.length);return n>=0&&n<i.length?n:-1}function H(){Mt(h,P.offsetHeight);const p=u.clientWidth,m=yt(b,u,h,mt);h.width=Math.max(1,Math.floor(p*g)),h.height=Math.max(1,Math.floor(m*g)),h.style.width=`${p}px`,h.style.height=`${m}px`;const e=h.getContext("2d");e.setTransform(g,0,0,g,0,0),e.clearRect(0,0,p,m),e.font="11px system-ui, sans-serif";const n=Tt(b),{plotX:c,plotMaxWidth:t,plotMaxHeight:d,plotTop:o}=Y(m),l=o+d,S=t/i.length;kt(e,c,o,l,t,D,{text:n.textMuted,axis:n.border,grid:n.border}),i.forEach((y,C)=>{const k=y.count/D*d,r=c+C*S,L=l-k;e.fillStyle=C===v?n.text:Wt,e.globalAlpha=C===v?1:.7,e.fillRect(r+1,L,Math.max(1,S-2),k),e.globalAlpha=1,C===v&&(e.fillStyle=n.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${y.count}`,r+S/2,L-2))});const V=y=>c+(y-B)/ot*t;e.font="10px system-ui, sans-serif";for(const[y,C]of[[nt,"LSL"],[x,"USL"]]){if(y===void 0)continue;const k=V(y);e.strokeStyle=n.warnBorder,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(k,o),e.lineTo(k,l),e.stroke(),e.setLineDash([]),e.fillStyle=n.warnBorder,e.textAlign="center",e.textBaseline="bottom",e.fillText(C,k,o-2)}e.font="11px system-ui, sans-serif",e.strokeStyle=n.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,l),e.lineTo(c+t,l),e.stroke(),e.fillStyle=n.textMuted,e.textAlign="center",e.textBaseline="top";const W=Math.max(2,Math.floor(t/44)),dt=Math.ceil(i.length/W),ht=Math.max(1,dt);for(let y=0;y<=i.length;y+=ht){const C=y<i.length?i[y].rangeLow:i[i.length-1].rangeHigh,k=c+y*S;e.beginPath(),e.moveTo(k,l),e.lineTo(k,l+4),e.stroke(),e.fillText(Z.tick(C),k,l+6)}Z.unitLabel&&(e.save(),e.font="10px system-ui, sans-serif",e.fillStyle=n.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${Z.unitLabel})`,c+t,l+20),e.restore())}h.addEventListener("mousemove",p=>{const m=h.getBoundingClientRect(),e=ct(p.clientX-m.left);if(e!==v&&(v=e,H()),e>=0){const n=i[e];A.innerHTML=`<strong>${st(n.rangeLow,O,"engineering")} \u2013 ${st(n.rangeHigh,O,"engineering")}</strong><br>${n.count} dies`,A.style.display="block",bt(A,b,p.clientX,p.clientY)}else A.style.display="none"}),h.addEventListener("mouseleave",()=>{v!==-1&&(v=-1,H()),A.style.display="none"}),J=xt(b,()=>H()),H()}function Lt(a){const{unit:w,limitLow:O,limitHigh:nt}=et(f),x=a.ranges,i=a.series,D=Ct;M&&!i.some(n=>n.groupKey===M)&&(M=null);const P=Math.max(1,...i.flatMap(n=>n.counts)),h=document.createElement("div");Object.assign(h.style,{fontSize:"12px",color:lt.label,marginBottom:"2px"}),h.textContent=`${i.length} groups \xB7 max ${P} dies/bucket`,u.appendChild(h);const v=document.createElement("div");Object.assign(v.style,{display:"flex",flexWrap:"wrap",gap:"4px 12px",marginBottom:"4px"}),i.forEach((n,c)=>{const t=document.createElement("button");t.type="button",t.title=`${n.groupKey} \u2014 click to emphasize (dim the rest)`,Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:"5px",fontSize:"11px",padding:"1px 4px",border:"none",background:"none",cursor:"pointer",color:lt.text,borderRadius:"3px"});const d=document.createElement("span");Object.assign(d.style,{width:"10px",height:"10px",borderRadius:"2px",background:D(c),flex:"0 0 auto"});const o=document.createElement("span");o.textContent=n.groupKey,t.append(d,o);const l=M!==null&&M!==n.groupKey;t.style.opacity=l?"0.45":"1",M===n.groupKey&&(t.style.background=lt.bgHover),t.addEventListener("click",()=>{M=M===n.groupKey?null:n.groupKey,U()}),v.appendChild(t)}),u.appendChild(v);const g=document.createElement("canvas");g.style.display="block",g.style.cursor="default",u.appendChild(g);const B=window.devicePixelRatio||1,X=x[0].rangeLow,ot=x[x.length-1].rangeHigh,Z=ot-X||1,Y=vt(Math.max(Math.abs(X),Math.abs(ot)),w),ct=()=>h.offsetHeight+v.offsetHeight;let H=-1,p={plotX:tt+36,plotMaxWidth:10,barWidth:10};function m(){Mt(g,ct());const n=u.clientWidth,c=yt(b,u,g,mt);g.width=Math.max(1,Math.floor(n*B)),g.height=Math.max(1,Math.floor(c*B)),g.style.width=`${n}px`,g.style.height=`${c}px`;const t=g.getContext("2d");t.setTransform(B,0,0,B,0,0),t.clearRect(0,0,n,c),t.font="11px system-ui, sans-serif";const d=Tt(b),o=tt+36,l=Math.max(10,n-o-tt),S=at,V=c-St-at,T=S+V,W=l/x.length;p={plotX:o,plotMaxWidth:l,barWidth:W},kt(t,o,S,T,l,P,{text:d.textMuted,axis:d.border,grid:d.border}),H>=0&&(t.fillStyle=d.bgHover,t.globalAlpha=.5,t.fillRect(o+H*W,S,W,V),t.globalAlpha=1);const dt=i.map((r,L)=>({s:r,i:L})).sort((r,L)=>(r.s.groupKey===M?1:0)-(L.s.groupKey===M?1:0));for(const{s:r,i:L}of dt){const E=M===r.groupKey,ut=M!==null&&!E,ft=D(L);t.strokeStyle=ft,t.fillStyle=ft,t.lineWidth=E?2:1,t.globalAlpha=ut?.12:M?.32:.22,t.beginPath(),t.moveTo(o,T),r.counts.forEach((gt,it)=>{const Q=o+it*W,q=T-gt/P*V;t.lineTo(Q,q),t.lineTo(Q+W,q)}),t.lineTo(o+l,T),t.closePath(),t.fill(),t.globalAlpha=ut?.3:1,t.beginPath(),r.counts.forEach((gt,it)=>{const Q=o+it*W,q=T-gt/P*V;it===0?t.moveTo(Q,q):t.lineTo(Q,q),t.lineTo(Q+W,q)}),t.stroke()}t.globalAlpha=1;const ht=r=>o+(r-X)/Z*l;t.font="10px system-ui, sans-serif";for(const[r,L]of[[O,"LSL"],[nt,"USL"]]){if(r===void 0)continue;const E=ht(r);t.strokeStyle=d.warnBorder,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(E,S),t.lineTo(E,T),t.stroke(),t.setLineDash([]),t.fillStyle=d.warnBorder,t.textAlign="center",t.textBaseline="bottom",t.fillText(L,E,S-2)}t.font="11px system-ui, sans-serif",t.strokeStyle=d.border,t.lineWidth=1,t.beginPath(),t.moveTo(o,T),t.lineTo(o+l,T),t.stroke(),t.fillStyle=d.textMuted,t.textAlign="center",t.textBaseline="top";const C=Math.max(2,Math.floor(l/44)),k=Math.max(1,Math.ceil(x.length/C));for(let r=0;r<=x.length;r+=k){const L=r<x.length?x[r].rangeLow:x[x.length-1].rangeHigh,E=o+r*W;t.beginPath(),t.moveTo(E,T),t.lineTo(E,T+4),t.stroke(),t.fillText(Y.tick(L),E,T+6)}Y.unitLabel&&(t.save(),t.font="10px system-ui, sans-serif",t.fillStyle=d.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${Y.unitLabel})`,o+l,T+20),t.restore())}function e(n){const{plotX:c,plotMaxWidth:t,barWidth:d}=p;if(n<c||n>c+t)return-1;const o=Math.floor((n-c)/d);return o>=0&&o<x.length?o:-1}g.addEventListener("mousemove",n=>{const c=g.getBoundingClientRect(),t=e(n.clientX-c.left);if(t!==H&&(H=t,m()),t>=0){const d=x[t],o=i.map((l,S)=>`<span style="display:inline-block;width:9px;height:9px;border-radius:2px;background:${D(S)};margin-right:5px;"></span>${l.groupKey}: ${l.counts[t]}`).join("<br>");A.innerHTML=`<strong>${st(d.rangeLow,w,"engineering")} \u2013 ${st(d.rangeHigh,w,"engineering")}</strong><br>${o}`,A.style.display="block",bt(A,b,n.clientX,n.clientY)}else A.style.display="none"}),g.addEventListener("mouseleave",()=>{H!==-1&&(H=-1,m()),A.style.display="none"}),J=xt(b,()=>m()),m()}U();function wt(a){!F.some(w=>w.testNumber===a)||a===f||(f=a,K.value=String(a),U())}return{card:b,setTest:wt,destroy:()=>J?.disconnect()}}
@@ -0,0 +1,16 @@
1
+ export { renderCapabilityPanel } from './capability.js';
2
+ export type { CapabilityPanelOptions, CapabilityPanelHandle } from './capability.js';
3
+ export { renderBoxplotPanel } from './boxplot.js';
4
+ export type { BoxplotPanelOptions, BoxplotPanelHandle } from './boxplot.js';
5
+ export { renderHistogramPanel } from './histogram.js';
6
+ export type { HistogramPanelOptions, HistogramPanelHandle } from './histogram.js';
7
+ export { renderCorrelationPanel } from './correlation.js';
8
+ export type { CorrelationPanelOptions, CorrelationPanelHandle } from './correlation.js';
9
+ export { renderScatterPanel } from './scatter.js';
10
+ export type { ScatterPanelOptions, ScatterPanelHandle } from './scatter.js';
11
+ export { renderBarPanel } from './barPanel.js';
12
+ export type { ChartPanel, BarPanelHandle } from './barPanel.js';
13
+ export { renderBinClusterPanel } from './binCluster.js';
14
+ export type { BinClusterPanelOptions, BinClusterPanelHandle } from './binCluster.js';
15
+ export * from './chartShell.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ export{renderCapabilityPanel}from"./capability.js";export{renderBoxplotPanel}from"./boxplot.js";export{renderHistogramPanel}from"./histogram.js";export{renderCorrelationPanel}from"./correlation.js";export{renderScatterPanel}from"./scatter.js";export{renderBarPanel}from"./barPanel.js";export{renderBinClusterPanel}from"./binCluster.js";export*from"./chartShell.js";
@@ -0,0 +1,26 @@
1
+ /** Neutral single fill for "how much" encodings (bars, boxes, histogram). */
2
+ export declare const QUANTITY = "#4e79a7";
3
+ export declare function categorical(i: number): string;
4
+ /**
5
+ * Sequential fill for a yield percentage on a **fixed** domain — light at
6
+ * ≤50%, deepest at 100% — so the same yield is always the same colour
7
+ * across wafers, lots, and renders (a data-range-normalized ramp lets a
8
+ * better wafer render more alarming than a worse one). Single-hue blue:
9
+ * "more saturated = higher yield", monotonic in lightness, CVD-safe, and
10
+ * never reads as an alarm. The visible label chips remain the primary
11
+ * carrier of the number; this fill is reinforcement only.
12
+ */
13
+ export declare function yieldFill(yieldPercent: number): string;
14
+ /**
15
+ * Semantic capability colour from Ppk against the conventional 1.33
16
+ * threshold: capable (≥1.33), marginal (≥1.0), poor (<1.0). Okabe-Ito
17
+ * hues (bluish green / orange / vermillion) rather than pure green/red so
18
+ * the distinction survives the common colour-vision deficiencies; the
19
+ * per-column Ppk value in the tooltip and the worst-first sort carry the
20
+ * same information positionally.
21
+ */
22
+ export declare function capabilityColor(ppk: number | null): string;
23
+ /** Correlation sign hues (blue = positive, vermillion = negative); |r| sets intensity via background blend. */
24
+ export declare const CORRELATION_POSITIVE = "#0072B2";
25
+ export declare const CORRELATION_NEGATIVE = "#D55E00";
26
+ //# sourceMappingURL=palette.d.ts.map
@@ -0,0 +1 @@
1
+ import{clamp01 as p}from"../../core/utils.js";export const QUANTITY="#4e79a7";const n=["#0072B2","#E69F00","#009E73","#CC79A7","#56B4E9","#D55E00","#8C6D31","#999999"];export function categorical(t){return n[(t%n.length+n.length)%n.length]}function E(t,e,c){const s=[parseInt(t.slice(1,3),16),parseInt(t.slice(3,5),16),parseInt(t.slice(5,7),16)],l=[parseInt(e.slice(1,3),16),parseInt(e.slice(3,5),16),parseInt(e.slice(5,7),16)],r=s.map((o,i)=>Math.round(o+(l[i]-o)*c));return`rgb(${r[0]},${r[1]},${r[2]})`}export function yieldFill(t){const e=p((t-50)/50);return E("#e7eef6","#2f6395",e)}export function capabilityColor(t){return t===null?"#999999":t>=1.33?"#009E73":t>=1?"#E69F00":"#D55E00"}export const CORRELATION_POSITIVE="#0072B2",CORRELATION_NEGATIVE="#D55E00";
@@ -0,0 +1,18 @@
1
+ import type { RegionYieldDatum } from '../../stats/regions.js';
2
+ import { type SaveImageHandler } from './chartShell.js';
3
+ export type RegionYieldMode = 'ring' | 'quadrant';
4
+ export interface RegionYieldDiagramOptions {
5
+ title?: string;
6
+ mode: RegionYieldMode;
7
+ /** Ring mode: ordered ring 1 (core) → ring N (edge), matching `buildRegionYieldData`'s output for `buildRingRegions`. Quadrant mode: any order — each row's quadrant is read from its `key` (`quadrant:NE` etc.), not position. */
8
+ rows: RegionYieldDatum[];
9
+ colorScheme?: string;
10
+ onSaveImage?: SaveImageHandler;
11
+ }
12
+ export interface RegionYieldDiagramHandle {
13
+ card: HTMLElement;
14
+ hasData: boolean;
15
+ destroy: () => void;
16
+ }
17
+ export declare function renderRegionYieldDiagram(options: RegionYieldDiagramOptions): RegionYieldDiagramHandle;
18
+ //# sourceMappingURL=regionYieldDiagram.d.ts.map
@@ -0,0 +1 @@
1
+ import{parseRegionKey as W}from"../../stats/regions.js";import{yieldFill as E}from"./palette.js";import{cardShell as A,resolveChartCanvasColors as N,observeResize as F,growCardToFitContent as D,renderEmptyState as $}from"./chartShell.js";const k=320,j=220,z={SE:[0,Math.PI/2],SW:[Math.PI/2,Math.PI],NW:[Math.PI,1.5*Math.PI],NE:[1.5*Math.PI,2*Math.PI]};function O(t,o,n,d,c,a){t.beginPath(),t.moveTo(o+a,n),t.arcTo(o+d,n,o+d,n+c,a),t.arcTo(o+d,n+c,o,n+c,a),t.arcTo(o,n+c,o,n,a),t.arcTo(o,n,o+d,n,a),t.closePath()}function w(t,o,n,d,c,a){t.font="10px system-ui, sans-serif";const g=t.measureText(d).width;t.font="700 11px system-ui, sans-serif";const b=t.measureText(c).width,l=Math.max(g,b)+12,M=30;t.fillStyle=a.bg,O(t,o-l/2,n-M/2,l,M,4),t.fill(),t.strokeStyle=a.border,t.lineWidth=1,t.stroke(),t.textAlign="center",t.textBaseline="middle",t.fillStyle=a.textMuted,t.font="10px system-ui, sans-serif",t.fillText(d,o,n-7),t.fillStyle=a.text,t.font="700 11px system-ui, sans-serif",t.fillText(c,o,n+7)}export function renderRegionYieldDiagram(t){const{mode:o,rows:n,onSaveImage:d}=t,c=t.title??(o==="ring"?"Ring yield":"Quadrant yield"),{card:a,body:g}=A(c,d),b=n.length>0;if(!b)return $(g,"No bin data available for regional yield."),{card:a,hasData:b,destroy:()=>{}};Object.assign(g.style,{display:"flex",justifyContent:"center",alignItems:"center"});const l=document.createElement("canvas");Object.assign(l.style,{display:"block",flex:"none"}),g.appendChild(l);const M=window.devicePixelRatio||1;function p(){const r=Math.max(j,Math.min(g.clientWidth||k,k));D(a,g,r),l.width=Math.max(1,Math.floor(r*M)),l.height=Math.max(1,Math.floor(r*M)),l.style.width=`${r}px`,l.style.height=`${r}px`;const e=l.getContext("2d");e.setTransform(M,0,0,M,0,0),e.clearRect(0,0,r,r);const u=N(a),T=E,h=r/2,f=r/2,m=r/2-34;if(o==="ring"){const i=n.length;for(let s=0;s<i;s++){const y=s/i*m,P=(s+1)/i*m;e.beginPath(),e.arc(h,f,P,0,2*Math.PI),e.arc(h,f,y,0,2*Math.PI,!0),e.closePath(),e.fillStyle=T(n[s].yieldPercent),e.fill(),e.strokeStyle=u.border,e.lineWidth=1,e.stroke()}e.beginPath(),e.arc(h,f,m,0,2*Math.PI),e.strokeStyle=u.text,e.lineWidth=1.5,e.stroke();for(let s=0;s<i;s++){const y=(s+.5)/i*m,P=-Math.PI/2+s/i*2*Math.PI,I=h+Math.cos(P)*y,S=f+Math.sin(P)*y;w(e,I,S,n[s].label,`${n[s].yieldPercent.toFixed(1)}%`,u)}}else{for(const i of n){const s=W(i.key).quadrant,y=s?z[s]:void 0;if(!y)continue;const[P,I]=y;e.beginPath(),e.moveTo(h,f),e.arc(h,f,m,P,I),e.closePath(),e.fillStyle=T(i.yieldPercent),e.fill(),e.strokeStyle=u.border,e.lineWidth=1,e.stroke();const S=(P+I)/2,R=h+Math.cos(S)*m*.6,C=f+Math.sin(S)*m*.6;w(e,R,C,i.label,`${i.yieldPercent.toFixed(1)}%`,u)}e.beginPath(),e.arc(h,f,m,0,2*Math.PI),e.strokeStyle=u.text,e.lineWidth=1.5,e.stroke()}}const v=F(a,()=>p());return p(),{card:a,hasData:b,destroy:()=>v.disconnect()}}
@@ -0,0 +1,37 @@
1
+ import { type ScatterItem } from '../../stats/scatter.js';
2
+ import { type FacetItem } from '../../stats/facets.js';
3
+ import type { TestDef } from '../../renderer/buildWaferMap.js';
4
+ import { type SaveImageHandler } from './chartShell.js';
5
+ type ScatterPanelItem = FacetItem & ScatterItem & {
6
+ label?: string;
7
+ };
8
+ export interface ScatterPanelOptions {
9
+ title?: string;
10
+ items: ScatterPanelItem[];
11
+ testDefs: TestDef[];
12
+ xTestNumber?: number;
13
+ yTestNumber?: number;
14
+ colorScheme?: string;
15
+ onSaveImage?: SaveImageHandler;
16
+ /**
17
+ * When the Analysis tab's "Group by" is active, every group's points are
18
+ * plotted together (never restricted to one group, unlike capability/
19
+ * correlation) but coloured by group instead of hard bin, with a
20
+ * click-to-filter legend keyed on group instead of bin. `items` above is
21
+ * used to compute the Simpson's-paradox warning when `groups` is absent;
22
+ * ignored for point data when `groups` is provided.
23
+ */
24
+ groups?: {
25
+ key: string;
26
+ items: ScatterPanelItem[];
27
+ }[];
28
+ }
29
+ export interface ScatterPanelHandle {
30
+ card: HTMLElement;
31
+ /** Cross-panel link (e.g. from the correlation matrix): switch X/Y in place. */
32
+ setXY: (xTestNumber: number, yTestNumber: number) => void;
33
+ destroy: () => void;
34
+ }
35
+ export declare function renderScatterPanel(options: ScatterPanelOptions): ScatterPanelHandle;
36
+ export {};
37
+ //# sourceMappingURL=scatter.d.ts.map
@@ -0,0 +1 @@
1
+ import{getColorScheme as ht}from"../../renderer/colorSchemes.js";import{categorical as gt}from"./palette.js";import{buildScatterData as bt,buildScatterDataGrouped as yt}from"../../stats/scatter.js";import{buildFacetTable as St}from"../../stats/facets.js";import{CLR as M}from"../toolbar.js";import{cardShell as Mt,observeResize as Tt,makeTooltip as wt,makeTestSelect as kt,makeWaferSelect as vt,chartFillHeight as Lt,applyCanvasFlow as Ct,drawAxisUnit as At,resolveChartCanvasColors as Bt,makeAxisFormat as nt}from"./chartShell.js";const s=52,Ot=16,i=16,Et=44;export function renderScatterPanel(j){const{title:ot="Test scatter",items:B,testDefs:_,colorScheme:it="default",onSaveImage:lt,groups:P}=j,{card:T,body:C,controlsRow:X}=Mt(ot,lt),R=_.filter(e=>e.testNumber!==void 0),b=!!(P&&P.length>0),Y=(P??[]).map(e=>e.key),at=new Map(Y.map((e,n)=>[e,n]));let m=j.xTestNumber??R[0]?.testNumber??null,f=j.yTestNumber??R[1]?.testNumber??m,O=null;const q=()=>O!==null?B[O]?[B[O]]:[]:B;function K(e,n,o){const c=document.createElement("label");Object.assign(c.style,{display:"inline-flex",alignItems:"center",gap:"4px",fontSize:"11px",color:M.label});const d=document.createElement("span");d.textContent=e;const p=kt(R,n,o,{maxWidth:"180px",emptyText:"No tests"});return c.append(d,p),{wrap:c,select:p}}const{wrap:st,select:rt}=K("X:",m,e=>{m=e,H()}),{wrap:ct,select:dt}=K("Y:",f,e=>{f=e,H()});X.append(st,ct),!b&&B.length>1&&X.appendChild(vt(B,O,e=>{O=e,H()}));const E=document.createElement("div");Object.assign(E.style,{color:M.warnText,background:M.warnBg,border:`1px solid ${M.warnBorder}`,borderRadius:"4px",padding:"4px 8px",fontSize:"11px",marginBottom:"4px",display:"none"}),T.insertBefore(E,C);function J(){const e=b?[]:St(q(),{facetableOnly:!0}).filter(n=>n.splittable).map(n=>n.label);e.length>0?(E.textContent=`\u26A0 Mixed ${e.join(", ")} across these points \u2014 trends here may be confounded (Simpson's paradox). Use Group by, or the Wafer picker, to narrow to a like-for-like set.`,E.style.display=""):E.style.display="none"}J();const D=document.createElement("div");D.textContent=b?"One point per die \xB7 coloured by group \xB7 click legend to filter":"One point per die across all wafers \xB7 coloured by hard bin \xB7 click legend to filter",Object.assign(D.style,{color:M.label,fontSize:"11px",marginBottom:"4px"}),T.insertBefore(D,C);const pt=ht(it),{forBin:mt}=pt,G=e=>b?e.group??"\u2014":String(e.hbin??0),Q=e=>b?gt(at.get(e)??0):mt(Number(e)),V=e=>b?e:e==="0"?"No bin data":`Bin ${e}`,g=new Set,A=document.createElement("div");Object.assign(A.style,{display:"flex",flexWrap:"wrap",gap:"4px",marginBottom:"6px"}),C.appendChild(A);const u=document.createElement("canvas");u.style.display="block",u.style.cursor="crosshair",C.appendChild(u);const F=window.devicePixelRatio||1,Ht=wt(T);let h=[],w=0,I=1,k=0,N=1;function $(e){const n=_.find(o=>o.testNumber===e);return{unit:n?.unit,limitLow:n?.limitLow,limitHigh:n?.limitHigh}}function xt(){const e=C.clientWidth,n=Math.max(200,Math.min(400,e*.65)),o=Lt(T,C,u,n);return{w:e,h:o,plotW:Math.max(10,e-s-Ot),plotH:Math.max(10,o-i-Et)}}function Z(){for(const e of A.querySelectorAll("[data-cat]")){const n=e.dataset.cat,o=g.size===0||g.has(n);e.style.opacity=o?"1":"0.35",e.style.boxShadow=g.has(n)?`0 0 0 2px ${M.text}`:"none"}}function tt(e){A.innerHTML="",g.clear();for(const n of e){const o=document.createElement("button");o.type="button",o.dataset.cat=n,o.title=`${V(n)} \u2014 click to filter`;const c=Q(n);Object.assign(o.style,{display:"inline-flex",alignItems:"center",gap:"4px",padding:"2px 7px",borderRadius:"10px",border:`1px solid ${M.menuBorder}`,background:"none",cursor:"pointer",fontSize:"11px",color:M.text,whiteSpace:"nowrap"});const d=document.createElement("span");Object.assign(d.style,{display:"inline-block",width:"9px",height:"9px",borderRadius:"50%",background:c,flexShrink:"0"}),o.append(d,document.createTextNode(V(n))),o.addEventListener("click",()=>{g.has(n)?g.delete(n):g.add(n),Z(),z()}),A.appendChild(o)}Z()}function z(){Ct(u,A.offsetHeight);const e=Bt(T),n=I-w,o=N-k,{w:c,h:d,plotW:p,plotH:r}=xt();u.width=Math.max(1,Math.floor(c*F)),u.height=Math.max(1,Math.floor(d*F)),u.style.width=`${c}px`,u.style.height=`${d}px`;const t=u.getContext("2d");if(t.setTransform(F,0,0,F,0,0),t.clearRect(0,0,c,d),h.length===0){t.font="12px system-ui, sans-serif",t.fillStyle=e.textMuted,t.textAlign="center",t.textBaseline="middle",t.fillText("No data \u2014 select two parametric tests with values.",c/2,d/2);return}const y=4,v=nt(Math.max(Math.abs(w),Math.abs(I)),m!==null?$(m).unit:void 0),U=nt(Math.max(Math.abs(k),Math.abs(N)),f!==null?$(f).unit:void 0);t.font="10px system-ui, sans-serif",t.strokeStyle=e.border,t.lineWidth=.5,t.fillStyle=e.textMuted;for(let l=0;l<=y;l++){const S=w+n*l/y,a=s+l/y*p;t.beginPath(),t.moveTo(a,i),t.lineTo(a,i+r),t.stroke(),t.textAlign="center",t.textBaseline="top",t.fillText(v.tick(S),a,i+r+4);const L=k+o*l/y,x=i+(1-l/y)*r;t.beginPath(),t.moveTo(s,x),t.lineTo(s+p,x),t.stroke(),t.textAlign="right",t.textBaseline="middle",t.fillText(U.tick(L),s-4,x)}v.unitLabel&&At(t,v.unitLabel,s+p/2,i+r+24,e.textMuted),U.unitLabel&&(t.save(),t.font="10px system-ui, sans-serif",t.fillStyle=e.textMuted,t.textAlign="center",t.textBaseline="middle",t.translate(6,i+r/2),t.rotate(-Math.PI/2),t.fillText(`(${U.unitLabel})`,0,0),t.restore());const W=g.size===0?h:h.filter(l=>g.has(G(l))),et=W.length>5e3?Math.ceil(W.length/5e3):1;t.globalAlpha=Math.max(.15,Math.min(.7,200/(W.length/et)));for(let l=0;l<W.length;l+=et){const S=W[l],a=s+(S.x-w)/n*p,L=i+(1-(S.y-k)/o)*r;t.beginPath(),t.arc(a,L,2.5,0,Math.PI*2),t.fillStyle=Q(G(S)),t.fill()}if(t.globalAlpha=1,m!==null&&f!==null){const l=$(m),S=$(f);t.strokeStyle=e.textMuted,t.fillStyle=e.textMuted,t.lineWidth=1,t.setLineDash([4,3]),t.font="9px system-ui, sans-serif",t.textBaseline="top";for(const[a,L]of[[l.limitLow,"LSL"],[l.limitHigh,"USL"]]){if(a===void 0||a<w||a>I)continue;const x=s+(a-w)/n*p;t.beginPath(),t.moveTo(x,i),t.lineTo(x,i+r),t.stroke(),t.textAlign="left",t.fillText(L,x+2,i+2)}for(const[a,L]of[[S.limitLow,"LSL"],[S.limitHigh,"USL"]]){if(a===void 0||a<k||a>N)continue;const x=i+(1-(a-k)/o)*r;t.beginPath(),t.moveTo(s,x),t.lineTo(s+p,x),t.stroke(),t.textAlign="left",t.fillText(L,s+3,x+2)}t.setLineDash([])}t.strokeStyle=e.border,t.lineWidth=1,t.beginPath(),t.moveTo(s,i),t.lineTo(s,i+r),t.moveTo(s,i+r),t.lineTo(s+p,i+r),t.stroke()}function H(){if(J(),R.length<2||m===null||f===null){h=[],tt([]),z();return}h=b?yt(P,m,f):bt(q(),m,f);let e;if(b){const n=new Set(h.map(G));e=Y.filter(o=>n.has(o))}else e=Array.from(new Set(h.map(n=>n.hbin??0))).sort((n,o)=>n-o).map(String);if(tt(e),h.length>0){const n=h.map(v=>v.x),o=h.map(v=>v.y),c=Math.min(...n),d=Math.max(...n),p=Math.min(...o),r=Math.max(...o),t=(d-c)*.05||1,y=(r-p)*.05||1;w=c-t,I=d+t,k=p-y,N=r+y}z()}const ft=Tt(T,()=>z());H();function ut(e,n){m=e,f=n,rt.value=String(e),dt.value=String(n),H()}return{card:T,setXY:ut,destroy:()=>ft.disconnect()}}
@@ -0,0 +1,2 @@
1
+ export declare const ICONS: Record<string, string>;
2
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1,42 @@
1
+ export const ICONS={aggr:'<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="M18 4H6l6 8-6 8h12"/></svg>',analysis:'<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="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/></svg>',boxSelect:'<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 3a2 2 0 0 0-2 2"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M21 19a2 2 0 0 1-2 2"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M9 3h1"/><path d="M9 21h1"/><path d="M14 3h1"/><path d="M14 21h1"/><path d="M3 9v1"/><path d="M21 9v1"/><path d="M3 14v1"/><path d="M21 14v1"/></svg>',close:'<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="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',columns:`<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="3" y="3" width="4" height="18" rx="1"/>
3
+ <rect x="10" y="3" width="4" height="18" rx="1"/>
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">
6
+ <ellipse cx="12" cy="7" rx="9" ry="4" />
7
+
8
+ <g stroke="currentColor" stroke-width="1.4" opacity="0.85">
9
+ <line x1="4" y1="7" x2="20" y2="7" />
10
+ <line x1="12" y1="3.5" x2="12" y2="10.5" />
11
+ </g>
12
+
13
+ <path d="M3 12 c0 2.5 4 4 9 4 s9-1.5 9-4" />
14
+
15
+ <path d="M3 17 c0 2.5 4 4 9 4 s9-1.5 9-4" />
16
+ </svg>`,orient:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76" fill="currentColor" stroke="none"/></svg>',overlays:'<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="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>',palette:'<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="m14.622 17.897-10.68-2.913"/><path d="M18.376 2.622a1 1 0 1 1 3.002 3.002L17.36 9.643a.5.5 0 0 0 0 .707l.944.944a2.41 2.41 0 0 1 0 3.408l-.944.944a.5.5 0 0 1-.707 0L8.354 7.348a.5.5 0 0 1 0-.707l.944-.944a2.41 2.41 0 0 1 3.408 0l.944.944a.5.5 0 0 0 .707 0z"/><path d="M9 8c-1.804 2.71-3.97 3.46-6.583 3.948a.507.507 0 0 0-.302.819l7.32 8.883a1 1 0 0 0 1.185.204C12.735 20.405 16 16.792 16 15"/></svg>',pan:'<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 2v20"/><path d="m15 19-3 3-3-3"/><path d="m19 9 3 3-3 3"/><path d="M2 12h20"/><path d="m5 9-3 3 3 3"/><path d="m9 5 3-3 3 3"/></svg>',quadrants:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><line x1="12" y1="3" x2="12" y2="21"/><line x1="3" y1="12" x2="21" y2="12"/></svg>',reset:'<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 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>',reticle:'<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 x="3" y="3" width="8" height="6" rx="0.5"/><rect x="13" y="3" width="8" height="6" rx="0.5"/><rect x="3" y="11" width="8" height="6" rx="0.5"/><rect x="13" y="11" width="8" height="6" rx="0.5"/><rect x="3" y="19" width="8" height="2" rx="0.5" stroke-dasharray="2 1"/><rect x="13" y="19" width="8" height="2" rx="0.5" stroke-dasharray="2 1"/></svg>',rings:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5.5"/><circle cx="12" cy="12" r="2" fill="currentColor" stroke="none"/></svg>',rotateCW:'<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="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg>',specRange:'<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 4 L5 20"/><path d="M5 4 L8 4"/><path d="M5 20 L8 20"/><path d="M19 4 L19 20"/><path d="M19 4 L16 4"/><path d="M19 20 L16 20"/><path d="M5 12 L19 12" stroke-dasharray="3 2"/></svg>',wafer:`<svg
17
+ viewBox="0 0 24 24"
18
+ width="16"
19
+ height="16"
20
+ fill="none"
21
+ stroke="currentColor"
22
+ stroke-width="1.8"
23
+ stroke-linecap="round"
24
+ stroke-linejoin="round">
25
+
26
+ <!-- Wafer -->
27
+ <circle cx="12" cy="11.3" r="9"/>
28
+
29
+ <!-- Orientation flat -->
30
+ <path d="M8.5 19.6h7"/>
31
+
32
+ <!-- Vertical grid -->
33
+ <path d="M8 4.8v13"/>
34
+ <path d="M12 2.8v16"/>
35
+ <path d="M16 4.8v13"/>
36
+
37
+ <!-- Horizontal grid -->
38
+ <path d="M5.8 8h12.4"/>
39
+ <path d="M4.2 12h15.6"/>
40
+ <path d="M5.8 16h12.4"/>
41
+
42
+ </svg>`,windowMinimize:'<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 19h14"/></svg>',windowRestore:'<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 x="4" y="5" width="16" height="14" rx="1.5"/></svg>',xyIndicator:'<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 19 L5 7"/><path d="M5 7 L3 10"/><path d="M5 7 L7 10"/><path d="M5 19 L17 19"/><path d="M17 19 L14 17"/><path d="M17 19 L14 21"/><text x="18" y="8" font-size="6" fill="currentColor" stroke="none">X</text><text x="2" y="6" font-size="6" fill="currentColor" stroke="none">Y</text></svg>',zoomIn:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="11" x2="11" y1="8" y2="14"/><line x1="8" x2="14" y1="11" y2="11"/></svg>',zoomMode:'<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 13.5 2-2.5-2-2.5"/><path d="m21 21-4.3-4.3"/><path d="M9 8.5 7 11l2 2.5"/><circle cx="11" cy="11" r="8"/></svg>',zoomOut:'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="8" x2="14" y1="11" y2="11"/></svg>'};
@@ -0,0 +1,12 @@
1
+ export { toCanvas } from './toCanvas.js';
2
+ export type { ToCanvasOptions, ToCanvasResult, HitTarget, ViewportTransform } from './toCanvas.js';
3
+ export { renderWaferMap } from './renderWaferMap.js';
4
+ export type { RenderOptions, WaferMapController, WaferViewOptions, WaferPreferences, WaferDisplayState } from './renderWaferMap.js';
5
+ export { renderWaferGallery } from './renderWaferGallery.js';
6
+ export type { WaferMapDisplayItem, WaferMapDisplayItemFactory, GalleryOptions, GalleryController } from './renderWaferGallery.js';
7
+ export type { SummaryPanelOptions } from './summaryPanel.js';
8
+ export type { InsightsOptions, InsightsView } from './insightsTab.js';
9
+ export { setDetachWindowOpener } from './toolbar.js';
10
+ export type { DetachWindowOpener } from './toolbar.js';
11
+ export type { UserGuideExtension } from './toolbar.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ export{toCanvas}from"./toCanvas.js";export{renderWaferMap}from"./renderWaferMap.js";export{renderWaferGallery}from"./renderWaferGallery.js";export{setDetachWindowOpener}from"./toolbar.js";
@@ -0,0 +1,76 @@
1
+ import type { LotStatsSummary } from '../stats/types.js';
2
+ import type { WaferMapDisplayItem } from './renderWaferGallery.js';
3
+ import { type SaveImageHandler, type SaveTextHandler } from './toolbar.js';
4
+ export type InsightsView = 'overview' | 'distributions' | 'correlation';
5
+ /** Public option shape for `RenderOptions.insights`/`GalleryOptions.insights`. */
6
+ export interface InsightsOptions {
7
+ /**
8
+ * Show an "Insights" tab in the toolbar. Selecting it replaces the canvas/
9
+ * grid with wmap's own chart suite across three sub-tabs — Overview
10
+ * (yield, bins, ring/quadrant yield, test values), Distributions
11
+ * (process capability, boxplot, histogram), and Correlation (matrix +
12
+ * scatter). Default false.
13
+ */
14
+ enabled?: boolean;
15
+ /** Which sub-tab is shown first. Default 'overview'. */
16
+ defaultView?: InsightsView;
17
+ }
18
+ export interface InsightsTabDeps {
19
+ /** Current gallery/single-wafer items — read fresh each render (a gallery's list can still be building). `null` entries (not-yet-built cards) are skipped. */
20
+ getItems: () => Array<WaferMapDisplayItem | null>;
21
+ /** Precomputed lot-level yield, when the host has one — reused directly instead of recomputing (see stats/yield.ts). Omit when there is no lot (e.g. a single wafer). */
22
+ getLotStats?: () => LotStatsSummary | undefined;
23
+ /** Read fresh each render so a live colour-scheme change is picked up. */
24
+ getColorSchemeName: () => string;
25
+ passBins: number[];
26
+ /** Read fresh each render — used by the Overview tab's ring/quadrant regional yield cards. Default 4. */
27
+ getRingCount?: () => number;
28
+ onSaveImage?: SaveImageHandler;
29
+ /** Optional host hook for the Overview tab's test-values "Export CSV" button — see `saveTextFile` (toolbar.ts). */
30
+ onSaveText?: SaveTextHandler;
31
+ /**
32
+ * Opens one wafer's detail view — omit to disable click-to-open (e.g. a
33
+ * single-wafer host, where the only wafer is already the one on screen).
34
+ * `testNumber`, when given (boxplot leaf-row clicks), asks the host to
35
+ * open the map in test-value mode on that test rather than its default
36
+ * plot mode — so opening from a specific test's boxplot row lands on that
37
+ * same test instead of hard-bin mode.
38
+ */
39
+ openWafer?: (waferIndex: number, label: string, testNumber?: number) => void;
40
+ /** Default sub-tab shown on first render. Default 'overview'. */
41
+ defaultView?: InsightsView;
42
+ /**
43
+ * When provided, the tab bar gets a leading "‹ Map"/"‹ Gallery" tab that
44
+ * exits Insights back to the host's normal view — one visible navigation
45
+ * model (a tab row) instead of relying on the host toolbar's icon-swap
46
+ * toggle alone, whose "way back" is discoverable only via tooltip.
47
+ */
48
+ backTab?: {
49
+ label: string;
50
+ onBack: () => void;
51
+ };
52
+ /**
53
+ * Show this tab's own identity strip (lot/wafer/product/etc.), mounted
54
+ * above the Overview/Distributions/Correlation tab bar so it stays in the
55
+ * same place across every sub-tab. Default true — needed by
56
+ * `renderWaferMap.ts`, whose single-wafer `metadataBadge` overlay sits
57
+ * under this tab's opaque inset:0 root and is fully covered while Insights
58
+ * is open, so this strip is its only identity display there.
59
+ * `renderWaferGallery.ts` sets this false: its own legend strip (built from
60
+ * the same wafer/lot metadata, via the same `buildMetadataStripRow`) stays
61
+ * mounted above the grid/Insights body in both views, so this strip would
62
+ * just be a second, independent copy of the same content — see
63
+ * renderWaferGallery.ts's `rebuildLegend`/`setInsightsOpen`.
64
+ */
65
+ showMetadataStrip?: boolean;
66
+ }
67
+ export interface InsightsTabHandle {
68
+ /** Append this wherever the tab's content should live; starts hidden (`display: none`). */
69
+ el: HTMLElement;
70
+ /** (Re)builds the tab's content from the current items — call whenever the tab is opened. */
71
+ render: () => void;
72
+ /** Tears down every live panel's observers and removes `el`. */
73
+ destroy: () => void;
74
+ }
75
+ export declare function createInsightsTab(deps: InsightsTabDeps): InsightsTabHandle;
76
+ //# sourceMappingURL=insightsTab.d.ts.map
@@ -0,0 +1 @@
1
+ import{buildFacetTable as de,facetValueOf as ce,FACET_NONE_VALUE as pe}from"../stats/facets.js";import{isParametricTest as ue}from"../renderer/buildWaferMap.js";import{getColorScheme as me}from"../renderer/colorSchemes.js";import{CLR as x}from"./toolbar.js";import{renderCapabilityPanel as fe}from"./charts/capability.js";import{renderBoxplotPanel as be}from"./charts/boxplot.js";import{renderHistogramPanel as ye}from"./charts/histogram.js";import{renderCorrelationPanel as he}from"./charts/correlation.js";import{renderScatterPanel as ge}from"./charts/scatter.js";import{renderBarPanel as q}from"./charts/barPanel.js";import{renderBinClusterPanel as xe}from"./charts/binCluster.js";import{QUANTITY as Ce}from"./charts/palette.js";import{makeChartGridWrap as j,makeLabeledSelect as Se}from"./charts/chartShell.js";import{buildYieldData as A,buildYieldDataCombined as ve}from"../stats/yield.js";import{buildBinParetoData as we}from"../stats/binPareto.js";import{buildLotTestSection as Be,buildLotFunctionalSection as ke,buildMetadataStripBox as Te}from"./summaryPanel.js";import{buildRegionYieldData as N,buildRingRegions as $e,buildQuadrantRegions as Ee}from"../stats/regions.js";import{renderRegionYieldDiagram as z}from"./charts/regionYieldDiagram.js";const Oe=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}];export function createInsightsTab(k){const{getItems:H,getLotStats:Q,getColorSchemeName:h,passBins:f,getRingCount:K,onSaveImage:y,onSaveText:M,openWafer:D}=k,U=k.showMetadataStrip??!0,S=document.createElement("div");Object.assign(S.style,{display:"none",flexDirection:"column",gap:"10px",width:"100%"});const L=document.createElement("div"),C=document.createElement("div");Object.assign(C.style,{display:"flex",gap:"4px",borderBottom:`1px solid ${x.menuBorder}`,marginBottom:"2px"}),C.setAttribute("role","tablist"),C.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from(C.querySelectorAll('[role="tab"]')),o=t.indexOf(document.activeElement);if(o===-1)return;e.preventDefault();const r=t[(o+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];r.focus(),r.click()});const T=document.createElement("div");Object.assign(T.style,{display:"flex",flexDirection:"column",gap:"10px"}),S.appendChild(L),S.appendChild(C),S.appendChild(T);let $=k.defaultView??"overview",v,O=[];function V(e,t,o){D?.(e,t,o)}function _(){return H().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:e.label??String(e.wafer.metadata?.waferId??""),waferIndex:t,statsSummary:e.statsSummary}).filter(e=>e!=null)}function F(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${x.iconActive}`:"2px solid transparent",color:t?x.iconActive:x.label,fontWeight:t?"700":"500",fontSize:"12px",padding:"6px 10px 8px",cursor:"pointer",marginBottom:"-1px"})}function X(e,t){const o=document.createElement("button");o.type="button",o.textContent=t;const r=e===$;return F(o,r),o.setAttribute("role","tab"),o.setAttribute("aria-selected",r?"true":"false"),o.tabIndex=r?0:-1,o.addEventListener("click",()=>{$!==e&&($=e,R())}),o}function J(e){const t=document.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,F(t,!1),t.addEventListener("click",e.onBack),t}function Z(e,t,o,r=!0){const l=j(),s=me(h());let p="label";const d=o??"group",n=f.length===1?`bin ${f[0]}`:`bins ${f.join(", ")}`,u=Q?.(),m=new Map(u?.lotYieldSeries.map(a=>[a.waferIndex,a.yieldPercent])),c=a=>({...a,label:a.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:m.get(a.waferIndex),key:a.waferIndex}),w=e.map(c),g=t?.map(a=>({key:a.key,items:a.items.map(c)})),B=()=>g?ve(g,f,p):A(w,f,p),i={title:t?`Yield by ${d} (pass: ${n})`:`Yield by wafer (pass: ${n})`,data:B(),selfControl:{current:p,options:[["yield","Sort: yield"],["label",t?`Sort: ${d}`:"Sort: wafer ID"]],onChange:a=>(p=a,{data:B()})},barColor:()=>Ce,valueLabel:a=>`${a.percent.toFixed(1)}%`,drill:g?{onOpenGroup:a=>{const P=g.find(E=>E.key===a.label)?.items??[];return{data:A(P,f,p),title:`Yield by wafer \u2014 ${d}: ${a.label} (pass: ${n})`}},onBack:()=>({data:B(),title:`Yield by ${d} (pass: ${n})`}),groupLabelText:d}:void 0,onOpen:D?a=>{typeof a.key=="number"&&V(a.key,`Wafer ${a.label}`)}:void 0};let b=null;if(r&&(b=q(i,y),b.card.style.minHeight="360px",l.appendChild(b.card)),t){const a=xe({title:"Hard bin pareto",groups:t.map(P=>({key:P.key,items:P.items.map(E=>({dies:E.dies,hardBinCounts:E.statsSummary?.stats.hardBinCounts,softBinCounts:E.statsSummary?.stats.softBinCounts}))})),colorScheme:h(),onSaveImage:y});return a.card.style.minHeight="360px",l.appendChild(a.card),{card:l,destroy:()=>{b?.destroy(),a.destroy()}}}let I="hbin";const ie=s.forBin,le=e.map(a=>({dies:a.dies,hardBinCounts:a.statsSummary?.stats.hardBinCounts,softBinCounts:a.statsSummary?.stats.softBinCounts})),G=()=>we(le,I),se={title:"Hard bin pareto",data:G(),selfControl:{current:I,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:a=>(I=a,{data:G(),title:`${I==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:a=>ie(a.binCode??0)},Y=q(se,y);return Y.card.style.minHeight="360px",l.appendChild(Y.card),{card:l,destroy:()=>{b?.destroy(),Y.destroy()}}}function W(){const e=document.createElement("div");return Object.assign(e.style,{display:"flex",flexDirection:"column",gap:"10px",minWidth:"0",background:x.menuBg,border:`1px solid ${x.menuBorder}`,borderRadius:"6px",padding:"12px"}),e}function ee(e){if(L.innerHTML="",!U)return;const t=Te(e.map(o=>({metadata:o.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",L.appendChild(t))}function te(e,t,o){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const r=e.map(i=>i.wafer),l=e.map(i=>i.dies),s=e.flatMap(i=>i.dies),p=e.map(i=>i.statsSummary),d=p.every(i=>i!==void 0)?p:void 0,n=K?.()??4,u=[],m=[],c=N(l,r,n,f,$e);if(c.length){const i=z({title:"Ring yield",mode:"ring",rows:c,colorScheme:h(),onSaveImage:y});u.push(i.card),m.push(i.destroy)}const w=N(l,r,n,f,Ee);if(w.length){const i=z({title:"Quadrant yield",mode:"quadrant",rows:w,colorScheme:h(),onSaveImage:y});u.push(i.card),m.push(i.destroy)}let g=null;if(t.length){const i=Be(s,t,void 0,d,M);if(i){const b=W();b.appendChild(i),g=b}}let B=null;if(o.length){const i=ke(s,o,d,M);if(i){const b=W();b.appendChild(i),B=b}}return{elements:u,testValuesCard:g,functionalCard:B,destroy:()=>{for(const i of m)i()}}}function ne(e){const t=f.length===1?`bin ${f[0]}`:`bins ${f.join(", ")}`,o=A([{...e,key:e.waferIndex}],f)[0]?.percent,r=W();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:"8px"});function l(s,p){const d=document.createElement("div");Object.assign(d.style,{border:`1px solid ${x.menuBorder}`,borderRadius:"6px",padding:"8px 16px",textAlign:"center",minWidth:"110px"});const n=document.createElement("div");n.textContent=s,Object.assign(n.style,{fontSize:"20px",fontWeight:"700",color:x.value,lineHeight:"1.2"});const u=document.createElement("div");return u.textContent=p,Object.assign(u.style,{fontSize:"10px",color:x.label,marginTop:"2px"}),d.append(n,u),d}return o!==void 0&&r.appendChild(l(`${o.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),r.appendChild(l(String(e.dies.length),"Total dies")),r}function oe(e,t,o,r,l){const s=document.createElement("div");Object.assign(s.style,{display:"flex",flexDirection:"column",gap:"10px"});const p=e.length===1&&!r;p&&s.appendChild(ne(e[0]));const d=Z(e,r,l,!p);s.appendChild(d.card);const n=te(e,t,o);for(const u of n.elements)d.card.appendChild(u);return n.testValuesCard&&s.appendChild(n.testValuesCard),n.functionalCard&&s.appendChild(n.functionalCard),{card:s,destroy:()=>{d.destroy(),n.destroy()}}}function ae(e,t,o,r){const l=j(),s=c=>({...c,testStats:c.statsSummary?.stats.perTestStats}),p=e.map(s),d=o?.map(c=>({key:c.key,items:c.items.map(s)})),n=be({title:"Test value distribution",items:p,testDefs:t,groups:d,groupLabelText:r,colorScheme:h(),onSaveImage:y,onOpen:D?(c,w)=>V(c,`Wafer ${e.find(g=>g.waferIndex===c)?.label??c}`,w):void 0}),u=ye({title:"Value histogram",items:e,testDefs:t,groups:o,colorScheme:h(),onSaveImage:y}),m=fe({title:"Process capability",items:e,testDefs:t,groups:o,colorScheme:h(),onSaveImage:y,onSelectTest:c=>{n.setTest(c),u.setTest(c)}});return l.append(m.card,n.card,u.card),{card:l,destroy:()=>{m.destroy(),n.destroy(),u.destroy()}}}function re(e,t,o){const r=j(),l=ge({title:"Test scatter",items:e,testDefs:t,groups:o,colorScheme:h(),onSaveImage:y}),s=he({title:"Test correlation matrix",items:e,testDefs:t,groups:o,colorScheme:h(),onSaveImage:y,onSelectPair:(p,d)=>l.setXY(p,d)});return r.append(s.card,l.card),{card:r,destroy:()=>{s.destroy(),l.destroy()}}}function R(){for(const n of O)n.destroy();O=[],T.innerHTML="",C.innerHTML="",k.backTab&&C.appendChild(J(k.backTab));for(const n of Oe)C.appendChild(X(n.key,n.label));const e=_();ee(e);const t=de(e,{facetableOnly:!0}).filter(n=>n.splittable),o=document.createElement("div");Object.assign(o.style,{display:"flex",gap:"8px",alignItems:"center"});let r;t.length>0?(o.appendChild(Se("Group by:",[{value:"",label:"None"},...t.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],v??"",n=>{v=n||void 0,R()})),r=t.find(n=>n.key===v)?.label):v=void 0,T.appendChild(o);const l=H().find(n=>n?.testDefs?.length)?.testDefs??[],s=l.filter(ue);let p;if(v){const n=new Map,u=[];for(const m of e){const c=ce(m.metadata,v)??pe;n.has(c)||(n.set(c,[]),u.push(c)),n.get(c).push(m)}p=u.map(m=>({key:m,items:n.get(m)}))}const d=$==="overview"?oe(e,s,l,p,r):$==="distributions"?ae(e,s,p,r):re(e,s,p);O.push(d),T.appendChild(d.card)}return{el:S,render:R,destroy:()=>{for(const e of O)e.destroy();S.remove()}}}
@@ -0,0 +1,26 @@
1
+ import type { WaferMetadata } from '../core/metadata.js';
2
+ export interface MetadataBadgeLotStack {
3
+ lotSize: number;
4
+ aggrMethod?: string;
5
+ }
6
+ export interface MetadataBadgeOptions {
7
+ /** Present when the host result is a lot-stack aggregation rather than a
8
+ * single wafer — the badge then leads with wafer count + aggregation
9
+ * method instead of a (nonexistent) single waferId, per this project's
10
+ * requirement that stacked maps always self-identify as such. */
11
+ lotStack?: MetadataBadgeLotStack;
12
+ }
13
+ export interface MetadataBadgeController {
14
+ el: HTMLDivElement;
15
+ /** Re-render with new metadata / lot-stack context — call from `setResult()`
16
+ * whenever `wafer`/`isLotStack`/`aggrMethod`/`lotSize` change, so the badge
17
+ * never goes stale. */
18
+ update(metadata: WaferMetadata | null | undefined, lotStack?: MetadataBadgeLotStack): void;
19
+ /** True when there is nothing to show (no lot-stack context and no metadata
20
+ * fields at all) — the caller should not mount `el` in that case, rather
21
+ * than show empty chrome. */
22
+ isEmpty(): boolean;
23
+ destroy(): void;
24
+ }
25
+ export declare function createMetadataBadge(metadata: WaferMetadata | null | undefined, opts?: MetadataBadgeOptions): MetadataBadgeController;
26
+ //# sourceMappingURL=metadataBadge.d.ts.map
@@ -0,0 +1 @@
1
+ import{metadataEntries as m,buildCompactMetadataRows as b}from"./summaryPanel.js";import{CLR as c,Z_BASE as h,wireExpandToggle as w}from"./toolbar.js";function y(n,r){if(r)return r.aggrMethod?`${r.lotSize} wafers \xB7 ${r.aggrMethod}`:`${r.lotSize} wafers`;if(n.lot&&n.waferId!==void 0&&n.waferId!==""){const t=String(n.lot),e=String(n.waferId);return e.includes(t)?e:`${t} \xB7 ${e}`}for(const t of["lot","waferId","product","testProgram"]){const e=n[t];if(e!=null&&e!=="")return String(e)}}export function createMetadataBadge(n,r={}){let t=n??{},e=r.lotStack,a=!1;const o=document.createElement("div");Object.assign(o.style,{position:"absolute",bottom:"4px",left:"4px",zIndex:h,background:c.menuBg,border:`1px solid ${c.menuBorder}`,borderRadius:"4px",boxShadow:"0 1px 4px rgba(0,0,0,0.12)",padding:"3px 8px",fontSize:"11px",color:c.value,cursor:"pointer",maxWidth:"260px",pointerEvents:"auto"});const l=document.createElement("div");Object.assign(l.style,{display:"flex",alignItems:"center",gap:"6px",whiteSpace:"nowrap"});const s=document.createElement("span");s.style.overflow="hidden",s.style.textOverflow="ellipsis";const f=document.createElement("span");Object.assign(f.style,{fontSize:"12px",lineHeight:"1",color:c.label,flexShrink:"0"}),l.appendChild(s),l.appendChild(f);const i=document.createElement("div");Object.assign(i.style,{marginTop:"5px",display:"none"}),o.appendChild(l),o.appendChild(i);function u(){const d=y(t,e)??"Wafer info";if(s.textContent=d,f.textContent=a?"\u25B4":"\u25BE",o.setAttribute("aria-expanded",String(a)),o.setAttribute("aria-label",`Wafer info: ${d}. ${a?"Click to collapse.":"Click to expand."}`),i.innerHTML="",i.style.display=a?"block":"none",!a)return;if(e){const g=document.createElement("div");Object.assign(g.style,{fontWeight:"600",marginBottom:"3px"}),g.textContent=e.aggrMethod?`${e.lotSize} wafers stacked \xB7 ${e.aggrMethod}`:`${e.lotSize} wafers stacked`,i.appendChild(g)}const p=b(t);p&&i.appendChild(p)}const x=w(o,d=>{a=d,u()});return u(),{el:o,update(d,p){t=d??{},e=p,u()},isEmpty(){return!e&&m(t).length===0},destroy(){x.destroy(),o.remove()}}}
@@ -0,0 +1,181 @@
1
+ import { type SaveImageHandler, type SaveTextHandler, type UserGuideExtension } from './toolbar.js';
2
+ import type { Die } from '../core/dies.js';
3
+ import type { WaferViewOptions } from './renderWaferMap.js';
4
+ import type { LotStatsSummary } from '../stats/types.js';
5
+ import type { SummaryPanelOptions } from './summaryPanel.js';
6
+ import { type InsightsOptions } from './insightsTab.js';
7
+ /**
8
+ * The data and display overrides for a single gallery card.
9
+ *
10
+ * `WaferMapResult` satisfies this interface structurally, so you can pass
11
+ * `buildWaferMap(...)` results directly. Spread in display overrides as needed:
12
+ *
13
+ * ```ts
14
+ * renderWaferGallery(container, [
15
+ * result1,
16
+ * { ...result2, label: 'W02', statsSummary: summary2 },
17
+ * ]);
18
+ * ```
19
+ */
20
+ export interface WaferMapDisplayItem {
21
+ wafer: import('../core/wafer.js').Wafer;
22
+ dies: Die[];
23
+ hbinDefs?: import('../renderer/buildWaferMap.js').BinDef[];
24
+ sbinDefs?: import('../renderer/buildWaferMap.js').BinDef[];
25
+ testDefs?: import('../renderer/buildWaferMap.js').TestDef[];
26
+ metadataFields?: import('../renderer/buildWaferMap.js').MetadataFieldDef[];
27
+ reticles?: import('../core/reticle.js').Reticle[];
28
+ isLotStack?: boolean;
29
+ aggrMethod?: string;
30
+ lotSize?: number;
31
+ label?: string;
32
+ /** Merged on top of the shared gallery options for this card only. */
33
+ viewOptions?: Partial<WaferViewOptions>;
34
+ /** Shown in the findings panel when this card is opened in its own window. */
35
+ statsSummary?: import('../stats/types.js').StatsSummary;
36
+ onClick?: (die: Die, event: MouseEvent) => void;
37
+ onSelect?: (dies: Die[]) => void;
38
+ }
39
+ /**
40
+ * A factory function that builds a WaferMapDisplayItem on demand.
41
+ * The gallery calls each factory in a deferred task (`setTimeout(0)`) so the
42
+ * browser stays responsive while large item sets are built progressively.
43
+ */
44
+ export type WaferMapDisplayItemFactory = () => WaferMapDisplayItem;
45
+ export interface GalleryOptions {
46
+ /** Initial shared scene options applied to all cards. */
47
+ viewOptions?: WaferViewOptions;
48
+ /** Called whenever a shared gallery option changes. */
49
+ onViewOptionsChange?: (opts: WaferViewOptions, changed: (keyof WaferViewOptions)[], category: 'preference' | 'state' | 'mixed') => void;
50
+ /** Legend position for bin modes. Default 'default'. */
51
+ legendPosition?: 'default' | 'compact' | 'bottom' | 'top' | 'left' | 'floating';
52
+ /** Padding inside each card canvas in CSS pixels. Default 6. */
53
+ cardPadding?: number;
54
+ /** Filename stem for the composite gallery PNG. Default 'wafer-gallery'. */
55
+ downloadFilename?: string;
56
+ /**
57
+ * Host hook for persisting the composite gallery PNG. When provided, the save
58
+ * action calls `onSaveImage(blob, suggestedName)` instead of triggering a
59
+ * browser `<a download>` — letting embedded hosts (Tauri, Electron, WebView2)
60
+ * route the image through a native save dialog. `suggestedName` includes the
61
+ * `.png` extension and is derived from `downloadFilename`. When omitted, the
62
+ * default browser download behaviour is unchanged.
63
+ */
64
+ onSaveImage?: SaveImageHandler;
65
+ /**
66
+ * Host hook for saving the Summary/Insights test-values table's "Export CSV"
67
+ * button. Mirrors `onSaveImage` — when provided, called with
68
+ * `(text, suggestedName, mimeType)` instead of triggering a browser
69
+ * `<a download>` (a silent no-op in Tauri/Electron/WebView2). When omitted,
70
+ * the default browser download behaviour is unchanged.
71
+ */
72
+ onSaveText?: SaveTextHandler;
73
+ /**
74
+ * Format to use for unitless values outside the normal display range [0.1, 9999].
75
+ * `'engineering'` (default): multiples-of-3 exponent notation (e.g. `12E-6`).
76
+ * `'si'`: SI prefix with no unit suffix (e.g. `12 µ`).
77
+ * Values with a unit always use SI prefix regardless of this setting.
78
+ */
79
+ fallbackFormat?: 'si' | 'engineering';
80
+ /**
81
+ * Show the plot mode selector in the gallery control bar. Default true.
82
+ * Set to false when the host application manages mode switching itself.
83
+ */
84
+ showPlotModeSelector?: boolean;
85
+ /** Precomputed lot-level stats summary. Enables the summary panel toggle button in the control bar. */
86
+ lotStatsSummary?: LotStatsSummary;
87
+ /**
88
+ * Options for the always-available Summary panel alongside the gallery
89
+ * grid — a "Lot" tab with full lot-level stats (metadata, yield, bin
90
+ * breakdown, ring/quadrant yield, test values, findings) and a combined
91
+ * Report button, plus a "Wafers" tab listing every wafer that has its own
92
+ * per-wafer findings (click a row to open that wafer). Independent of
93
+ * `insights` below: this always shows/hides its own toolbar button
94
+ * regardless of whether Insights is open, since Insights has no per-wafer
95
+ * map for a finding to highlight against.
96
+ */
97
+ summaryPanel?: SummaryPanelOptions;
98
+ /**
99
+ * Bin numbers treated as pass for yield calculation in the summary panel.
100
+ * Defaults to `[1]`. Must match the `passBins` passed to `analyzeWaferLot` / `buildWaferMap`
101
+ * to ensure the summary panel yield label is consistent with the rest of the display.
102
+ */
103
+ passBins?: number[];
104
+ /**
105
+ * Fix the number of columns in the gallery grid. When set, overrides the
106
+ * auto-computed minimum card width and the toolbar columns control.
107
+ * Omit (default) to let the gallery auto-size cards based on die pitch.
108
+ */
109
+ columns?: number;
110
+ /**
111
+ * Cap each gallery card's rendered width and height at this many CSS pixels.
112
+ * Cards pack from the left rather than stretching to fill the grid width.
113
+ *
114
+ * Omit it and the cap is derived from die density: 480px for an ordinary
115
+ * wafer, widening (to at most 720px) for high-DPW wafers that need the room
116
+ * to keep dies readable. Set it to take hard control instead — an explicit
117
+ * value is never widened, so you own the readability trade-off at high DPW.
118
+ */
119
+ maxSize?: number;
120
+ /**
121
+ * Show a help button in the gallery control bar that opens the built-in end-user guide in a modal.
122
+ * Default false. Enable in applications that want to surface the guide without linking externally.
123
+ */
124
+ showHelpButton?: boolean;
125
+ /**
126
+ * Host-supplied content inserted into the built-in end-user guide window
127
+ * (see `showHelpButton`) — e.g. a host app's own documentation, so the user
128
+ * has one help button instead of two. See `UserGuideExtension`.
129
+ */
130
+ userGuideExtension?: UserGuideExtension;
131
+ /**
132
+ * Base `z-index` for wmap's transient overlays — toolbar menus, the die
133
+ * tooltip, the expand modal, and the user-guide modal. wmap layers its own
134
+ * overlays from this value upward. Set this when embedding the gallery inside
135
+ * your own modal/overlay so wmap's menus and tooltips appear above it.
136
+ *
137
+ * Omit it and wmap defaults overlays to a high value (above typical app modal
138
+ * layers). Applied for the lifetime of this render and restored on `destroy()`.
139
+ */
140
+ zIndex?: number;
141
+ /**
142
+ * Show an "Insights" tab in the control bar (see `InsightsOptions`).
143
+ * Selecting it replaces the gallery grid with wmap's own chart suite
144
+ * across Overview/Distributions/Correlation sub-tabs. The tab computes
145
+ * its own "Group by" facets from each item's `wafer.metadata`
146
+ * (`stats/facets.ts`) and, when `lotStatsSummary` is also provided,
147
+ * reuses its precomputed per-wafer yield directly rather than
148
+ * recomputing it — no other host wiring beyond this option.
149
+ */
150
+ insights?: InsightsOptions;
151
+ }
152
+ export interface GalleryController {
153
+ /** Replace all items — destroys existing cards and rebuilds the grid. Accepts pre-built items, factory functions, or a mix. */
154
+ setItems(items: Array<WaferMapDisplayItem | WaferMapDisplayItemFactory>): void;
155
+ /** Merge shared scene option overrides across all cards. */
156
+ setOptions(opts: Partial<WaferViewOptions>): void;
157
+ /** Return the current shared scene options. */
158
+ getOptions(): WaferViewOptions;
159
+ /** Update the fallback format for unitless values across all cards. */
160
+ setFallbackFormat(format: 'si' | 'engineering'): void;
161
+ /** Replace the lot-level stats summary used by the built-in Summary panel. */
162
+ setLotStatsSummary(summary: LotStatsSummary | undefined): void;
163
+ /**
164
+ * Set the number of columns in the gallery grid. Pass `undefined` to restore
165
+ * the auto-computed layout based on die pitch.
166
+ */
167
+ setColumns(columns: number | undefined): void;
168
+ /**
169
+ * Opens the built-in end-user guide window — the same action the help
170
+ * toolbar button performs, but callable directly. Works regardless of
171
+ * `showHelpButton`'s current value, so a host that hides wmap's own help
172
+ * button (e.g. to fold it into its own combined help menu) can still
173
+ * trigger the guide without a DOM query against wmap's internal button
174
+ * markup.
175
+ */
176
+ openUserGuide(): void;
177
+ /** Remove all DOM and event listeners. */
178
+ destroy(): void;
179
+ }
180
+ export declare function renderWaferGallery(container: HTMLElement, items: Array<WaferMapDisplayItem | WaferMapDisplayItemFactory>, options?: GalleryOptions): GalleryController;
181
+ //# sourceMappingURL=renderWaferGallery.d.ts.map