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