@wafertools/wafermap 0.26.0 → 0.27.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 +367 -0
- package/README.md +46 -3
- package/dist/packages/canvas-adapter/canvasTheme.d.ts +4 -0
- package/dist/packages/canvas-adapter/canvasTheme.js +1 -1
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +13 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.d.ts +10 -10
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +337 -3
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.d.ts +8 -9
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -1
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +50 -0
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -0
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +12 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +20 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -0
- package/dist/packages/canvas-adapter/charts/trend.d.ts +30 -0
- package/dist/packages/canvas-adapter/charts/trend.js +1 -0
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/identityHeader.d.ts +46 -0
- package/dist/packages/canvas-adapter/identityHeader.js +1 -0
- package/dist/packages/canvas-adapter/index.d.ts +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +27 -3
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +15 -2
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -20
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +72 -17
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +219 -33
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +6 -0
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +310 -6
- 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 +98 -35
- 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 +0 -4
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/utils.d.ts +11 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +34 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +104 -5
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorSchemes.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +15 -0
- package/dist/packages/stats/binPareto.js +1 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.d.ts +14 -0
- package/dist/packages/stats/connectedComponents.js +1 -0
- package/dist/packages/stats/correlation.d.ts +26 -0
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/facets.js +1 -1
- package/dist/packages/stats/filterFindings.d.ts +18 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingsNarrative.js +1 -1
- package/dist/packages/stats/histogram.d.ts +19 -1
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +6 -0
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +44 -0
- package/dist/packages/stats/mergeTestDefs.js +1 -0
- package/dist/packages/stats/metadataColumns.d.ts +20 -4
- package/dist/packages/stats/metadataColumns.js +1 -1
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +14 -14
- package/dist/packages/stats/renderSummaryReport.js +32 -29
- package/dist/packages/stats/reportHtml.js +38 -13
- package/dist/packages/stats/testPassRate.d.ts +85 -0
- package/dist/packages/stats/testPassRate.js +1 -0
- package/dist/packages/stats/trend.d.ts +40 -0
- package/dist/packages/stats/trend.js +1 -0
- package/dist/packages/stats/types.d.ts +117 -6
- package/llms.txt +6 -0
- package/package.json +7 -5
- package/dist/packages/canvas-adapter/metadataBadge.d.ts +0 -30
- package/dist/packages/canvas-adapter/metadataBadge.js +0 -1
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { type HistogramItem } from '../../stats/histogram.js';
|
|
2
2
|
import type { TestDef } from '../../renderer/buildWaferMap.js';
|
|
3
|
-
import { type SaveImageHandler } from './chartShell.js';
|
|
3
|
+
import { type SaveImageHandler, type AxisPrefs } from './chartShell.js';
|
|
4
4
|
export interface HistogramPanelOptions {
|
|
5
|
+
/** Initial axis toggles, shared with the sibling distribution panels. */
|
|
6
|
+
axisPrefs?: AxisPrefs;
|
|
7
|
+
/** Fired when the user changes an axis toggle here, so siblings can follow. */
|
|
8
|
+
onAxisPrefsChange?: (prefs: AxisPrefs) => void;
|
|
9
|
+
/** Fired when the USER picks a test here, so siblings can follow. */
|
|
10
|
+
onTestChange?: (testNumber: number) => void;
|
|
5
11
|
title?: string;
|
|
6
12
|
items: HistogramItem[];
|
|
7
13
|
testDefs: TestDef[];
|
|
@@ -19,6 +25,9 @@ export interface HistogramPanelOptions {
|
|
|
19
25
|
key: string;
|
|
20
26
|
items: HistogramItem[];
|
|
21
27
|
}[];
|
|
28
|
+
/** Fired when the user narrows to one group by clicking it in the legend.
|
|
29
|
+
* The Insights tab owns the scope; this panel only reports the gesture. */
|
|
30
|
+
onGroupChange?: (key: string | null) => void;
|
|
22
31
|
/** Document to build this panel's DOM into. Default `document` — pass the
|
|
23
32
|
* host's own `ownerDocument` when the container might live in a
|
|
24
33
|
* different document (e.g. a gallery card detached into its own popup
|
|
@@ -29,6 +38,8 @@ export interface HistogramPanelHandle {
|
|
|
29
38
|
card: HTMLElement;
|
|
30
39
|
/** Cross-panel link (e.g. from the capability panel): switch to `testNumber` in place. */
|
|
31
40
|
setTest: (testNumber: number) => void;
|
|
41
|
+
/** Adopt the shared axis toggles (see `AxisPrefs`). */
|
|
42
|
+
setAxisPrefs: (prefs: AxisPrefs) => void;
|
|
32
43
|
destroy: () => void;
|
|
33
44
|
}
|
|
34
45
|
export declare function renderHistogramPanel(options: HistogramPanelOptions): HistogramPanelHandle;
|
|
@@ -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 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()}}
|
|
1
|
+
import{buildTestHistogramData as Kt,collectTestValues as Ot,buildTestHistogramSeries as Rt}from"../../stats/histogram.js";import{SPACE as et,fontPx as X,FONT as vt,CLR as wt}from"../toolbar.js";import{fmt as ht}from"../../renderer/fmt.js";import{QUANTITY as Ft,categorical as zt}from"./palette.js";import{cardShell as Gt,observeResize as kt,makeTooltip as Vt,attachChartTip as jt,positionChartTooltip as Ht,makeLinkedTestSelect as _t,makeWaferSelect as Ut,makeLinkedAxisPrefs as Xt,renderEmptyState as yt,chartFillHeight as At,applyCanvasFlow as Ct,makeAxisFormat as $t,PADDING as at,robustFence as Yt,shouldIncludeLimitsByDefault as Qt,drawOffAxisLimits as qt,resolveAxisRange as Jt,chartSwatchCss as Zt,makeSeriesLegendItem as te,prepareCanvas as Pt}from"./chartShell.js";const Mt=230,Wt=36,gt=18;function Dt(s,N,Y,Q,ft,E,a,y=4){const q=Math.max(1,E/y),nt=Math.pow(10,Math.floor(Math.log10(q))),g=q/nt,J=(g<=1?1:g<=2?2:g<=5?5:10)*nt,Z=Q-Y;s.save(),s.font=`${X(-1)}px system-ui, sans-serif`,s.textAlign="right",s.textBaseline="middle";for(let K=0;K<=E+1e-9;K+=J){const ot=Q-K/(E||1)*Z;s.strokeStyle=a.grid,s.lineWidth=1,s.beginPath(),s.moveTo(N,ot),s.lineTo(N+ft,ot),s.stroke(),s.fillStyle=a.text,s.fillText(String(Math.round(K)),N-4,ot)}s.strokeStyle=a.axis,s.lineWidth=1,s.beginPath(),s.moveTo(N,Y),s.lineTo(N,Q),s.stroke(),s.restore()}export function renderHistogramPanel(s){const{title:N="Test value distribution",items:Y,testDefs:Q,onSaveImage:ft,groups:E}=s,{card:a,body:y,controlsRow:q}=Gt(N,ft,s.ownerDocument),nt=Q.filter(l=>l.testNumber!==void 0);let g=s.selectedTestNumber??nt[0]?.testNumber??null,J=null,Z=0;const K=_t(nt,g,l=>{g=l,O(),s.onTestChange?.(l)},{maxWidth:"200px",ownerDocument:a.ownerDocument}),ot=K.el;q.appendChild(ot);const Tt=Ut(Y,J,l=>{J=l,O()},{ownerDocument:a.ownerDocument});q.appendChild(Tt);function Et(l){return L===l||l!==null&&!(E??[]).some(A=>A.key===l)?!1:(L=l,O(),!0)}const pt=a.ownerDocument.createElement("span");Object.assign(pt.style,{display:"inline-flex",gap:et.lg,alignItems:"center"}),q.appendChild(pt);const rt=Xt(pt,s.axisPrefs,l=>{s.onAxisPrefsChange?.(l),O()},a.ownerDocument),H=Vt(a);let it=null,L=null;function lt(l){const A=Q.find(B=>B.testNumber===l);return{unit:A?.unit,limitLow:A?.limitLow,limitHigh:A?.limitHigh}}function O(){const{includeLimits:l,clipOutliers:A}=rt.get();if(it?.disconnect(),it=null,y.innerHTML="",g===null){yt(y,"No parametric test data available for a histogram.");return}const B=E&&E.length>0?Rt(E,g,16,l?lt(g).limitLow:void 0,l?lt(g).limitHigh:void 0):null;if(Tt.style.display=B?"none":"",B){const{limitLow:n,limitHigh:i}=lt(g);if(rt.sync(l??!1,n!==void 0||i!==void 0),B.series.length===0){yt(y,"No parametric test data available for a histogram.");return}Bt(B);return}const ct=J!==null?[Y[J]]:Y,{unit:f,limitLow:w,limitHigh:D}=lt(g),C=Ot(ct,g),R=C.length?Math.min(...C):NaN,F=C.length?Math.max(...C):NaN,v=l??Qt(R,F,w,D);rt.sync(v,w!==void 0||D!==void 0);const z=A?Yt(C):null,G=z?{lo:Math.max(R,z.lo),hi:Math.min(F,z.hi)}:void 0;Z=G?C.filter(n=>n<G.lo||n>G.hi).length:0;const u=Kt(ct,g,16,v?w:void 0,v?D:void 0,G);if(u.length===0){yt(y,"No parametric test data available for a histogram.");return}const V=Math.max(...u.map(n=>n.count),1),j=a.ownerDocument.createElement("div");Object.assign(j.style,{fontSize:vt.body,color:wt.label,marginBottom:et.xxs}),j.textContent=`max ${V} dies/bucket`+(Z?` \xB7 ${Z} value${Z===1?"":"s"} outside clipped range`:""),y.appendChild(j);const k=a.ownerDocument.createElement("canvas");k.style.display="block",k.style.cursor="default",y.appendChild(k);let S=-1;const _=u[0].rangeLow,I=u[u.length-1].rangeHigh,xt=I-_||1,o=$t(Math.max(Math.abs(_),Math.abs(I)),f);function p(n){const i=at+36,r=k.clientWidth-i-at,e=n-Wt-gt;return{plotX:i,plotMaxWidth:Math.max(10,r),plotMaxHeight:e,plotTop:gt}}function M(n){const{plotX:i,plotMaxWidth:r}=p(Mt);if(n<i||n>i+r)return-1;const e=Math.floor((n-i)/r*u.length);return e>=0&&e<u.length?e:-1}function t(){Ct(k,j);const n=y.clientWidth,i=At(a,y,k,Mt),r=Pt(k,a,n,i);if(!r)return;const{ctx:e,theme:x}=r,{plotX:c,plotMaxWidth:b,plotMaxHeight:dt,plotTop:U}=p(i),$=U+dt,tt=b/u.length;Dt(e,c,U,$,b,V,{text:x.textMuted,axis:x.border,grid:x.border}),u.forEach((m,T)=>{const h=m.count/V*dt,Lt=c+T*tt,St=$-h;e.fillStyle=T===S?x.text:Ft,e.globalAlpha=T===S?1:.7,e.fillRect(Lt+1,St,Math.max(1,tt-2),h),e.globalAlpha=1,T===S&&(e.fillStyle=x.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${m.count}`,Lt+tt/2,St-2))});const bt=m=>c+(m-_)/xt*b;e.font=`${X(-1)}px system-ui, sans-serif`;const{offAxis:d}=Jt({dataMin:_,dataMax:I,limitLow:w,limitHigh:D,includeLimits:!1});qt(e,d,{left:c,right:c+b,top:U,bottom:$},"horizontal",x.limitLine,o.tick);const P=new Set(d.map(m=>m.value));for(const[m,T]of[[w,"LSL"],[D,"USL"]]){if(m===void 0||P.has(m))continue;const h=bt(m);e.strokeStyle=x.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(h,U),e.lineTo(h,$),e.stroke(),e.setLineDash([]),e.fillStyle=x.limitLine,e.textAlign="center",e.textBaseline="bottom",e.fillText(T,h,U-2)}e.font=`${X(-1)}px system-ui, sans-serif`,e.strokeStyle=x.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,$),e.lineTo(c+b,$),e.stroke(),e.fillStyle=x.textMuted,e.textAlign="center",e.textBaseline="top";const mt=Math.max(2,Math.floor(b/44)),ut=Math.ceil(u.length/mt),st=Math.max(1,ut);for(let m=0;m<=u.length;m+=st){const T=m<u.length?u[m].rangeLow:u[u.length-1].rangeHigh,h=c+m*tt;e.beginPath(),e.moveTo(h,$),e.lineTo(h,$+4),e.stroke(),e.fillText(o.tick(T),h,$+6)}o.unitLabel&&(e.save(),e.font=`${X(-1)}px system-ui, sans-serif`,e.fillStyle=x.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${o.unitLabel})`,c+b,$+20),e.restore())}k.addEventListener("mousemove",n=>{const i=k.getBoundingClientRect(),r=M(n.clientX-i.left);if(r!==S&&(S=r,t()),r>=0){const e=u[r];H.innerHTML=`<strong>${ht(e.rangeLow,f,"engineering")} \u2013 ${ht(e.rangeHigh,f,"engineering")}</strong><br>${e.count} dies`,H.style.display="block",Ht(H,a,n.clientX,n.clientY)}else H.style.display="none"}),k.addEventListener("mouseleave",()=>{S!==-1&&(S=-1,t()),H.style.display="none"}),it=kt(a,()=>t()),t()}function Bt(l){const{unit:A,limitLow:B,limitHigh:ct}=lt(g),f=l.ranges,w=l.series,D=zt;L&&!w.some(o=>o.groupKey===L)&&(L=null);const C=Math.max(1,...w.flatMap(o=>o.counts)),R=a.ownerDocument.createElement("div");Object.assign(R.style,{fontSize:vt.body,color:wt.label,marginBottom:et.xxs}),R.textContent=`${w.length} groups \xB7 max ${C} dies/bucket`,y.appendChild(R);const F=a.ownerDocument.createElement("div");Object.assign(F.style,{display:"flex",flexWrap:"wrap",gap:et.sm,marginTop:et.md,marginBottom:et.xl}),w.forEach((o,p)=>{const M=te(o.groupKey,D(p),a.ownerDocument),t=M.el;jt(t,a,H,`${o.groupKey} \u2014 click to emphasize (dim the rest)`),M.setState({selected:L===o.groupKey,dimmed:L!==null&&L!==o.groupKey}),t.addEventListener("click",()=>{const n=L===o.groupKey?null:o.groupKey;Et(n),s.onGroupChange?.(n)}),F.appendChild(t)}),y.appendChild(F);const v=a.ownerDocument.createElement("canvas");v.style.display="block",v.style.cursor="default",y.appendChild(v);const z=f[0].rangeLow,G=f[f.length-1].rangeHigh,u=G-z||1,V=$t(Math.max(Math.abs(z),Math.abs(G)),A),j=o=>{const p=(o.ownerDocument.defaultView??window).getComputedStyle(o);return o.offsetHeight+(parseFloat(p.marginTop)||0)+(parseFloat(p.marginBottom)||0)},k=()=>j(R)+j(F);let S=-1,_={plotX:at+36,plotMaxWidth:10,barWidth:10};function I(){Ct(v,k());const o=y.clientWidth,p=At(a,y,v,Mt),M=Pt(v,a,o,p);if(!M)return;const{ctx:t,theme:n}=M,i=at+36,r=Math.max(10,o-i-at),e=gt,x=p-Wt-gt,c=e+x,b=r/f.length;_={plotX:i,plotMaxWidth:r,barWidth:b},Dt(t,i,e,c,r,C,{text:n.textMuted,axis:n.border,grid:n.border}),S>=0&&(t.fillStyle=n.bgHover,t.globalAlpha=.5,t.fillRect(i+S*b,e,b,x),t.globalAlpha=1);const dt=w.map((d,P)=>({s:d,i:P})).sort((d,P)=>(d.s.groupKey===L?1:0)-(P.s.groupKey===L?1:0));for(const{s:d,i:P}of dt){const W=L===d.groupKey,mt=L!==null&&!W,ut=D(P);t.strokeStyle=ut,t.fillStyle=ut,t.lineWidth=W?2:1,t.globalAlpha=mt?.12:L?.32:.22,t.beginPath(),t.moveTo(i,c),d.counts.forEach((st,m)=>{const T=i+m*b,h=c-st/C*x;t.lineTo(T,h),t.lineTo(T+b,h)}),t.lineTo(i+r,c),t.closePath(),t.fill(),t.globalAlpha=mt?.3:1,t.beginPath(),d.counts.forEach((st,m)=>{const T=i+m*b,h=c-st/C*x;m===0?t.moveTo(T,h):t.lineTo(T,h),t.lineTo(T+b,h)}),t.stroke()}t.globalAlpha=1;const U=d=>i+(d-z)/u*r;t.font=`${X(-1)}px system-ui, sans-serif`;for(const[d,P]of[[B,"LSL"],[ct,"USL"]]){if(d===void 0)continue;const W=U(d);t.strokeStyle=n.limitLine,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(W,e),t.lineTo(W,c),t.stroke(),t.setLineDash([]),t.fillStyle=n.limitLine,t.textAlign="center",t.textBaseline="bottom",t.fillText(P,W,e-2)}t.font=`${X(-1)}px system-ui, sans-serif`,t.strokeStyle=n.border,t.lineWidth=1,t.beginPath(),t.moveTo(i,c),t.lineTo(i+r,c),t.stroke(),t.fillStyle=n.textMuted,t.textAlign="center",t.textBaseline="top";const tt=Math.max(2,Math.floor(r/44)),bt=Math.max(1,Math.ceil(f.length/tt));for(let d=0;d<=f.length;d+=bt){const P=d<f.length?f[d].rangeLow:f[f.length-1].rangeHigh,W=i+d*b;t.beginPath(),t.moveTo(W,c),t.lineTo(W,c+4),t.stroke(),t.fillText(V.tick(P),W,c+6)}V.unitLabel&&(t.save(),t.font=`${X(-1)}px system-ui, sans-serif`,t.fillStyle=n.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${V.unitLabel})`,i+r,c+20),t.restore())}function xt(o){const{plotX:p,plotMaxWidth:M,barWidth:t}=_;if(o<p||o>p+M)return-1;const n=Math.floor((o-p)/t);return n>=0&&n<f.length?n:-1}v.addEventListener("mousemove",o=>{const p=v.getBoundingClientRect(),M=xt(o.clientX-p.left);if(M!==S&&(S=M,I()),M>=0){const t=f[M],n=w.map((i,r)=>`<span style="${Zt(D(r))};margin-right:4px"></span>${i.groupKey}: ${i.counts[M]}`).join("<br>");H.innerHTML=`<strong>${ht(t.rangeLow,A,"engineering")} \u2013 ${ht(t.rangeHigh,A,"engineering")}</strong><br>${n}`,H.style.display="block",Ht(H,a,o.clientX,o.clientY)}else H.style.display="none"}),v.addEventListener("mouseleave",()=>{S!==-1&&(S=-1,I()),H.style.display="none"}),it=kt(a,()=>I()),I()}O();function It(l){K.set(l)&&(g=l,O())}function Nt(l){rt.set(l)&&O()}return{card:a,setTest:It,setAxisPrefs:Nt,destroy:()=>it?.disconnect()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{parseRegionKey as
|
|
1
|
+
import{parseRegionKey as E}from"../../stats/regions.js";import{yieldFill as A}from"./palette.js";import{fontPx as T}from"../toolbar.js";import{cardShell as D,resolveChartCanvasColors as N,observeResize as F,growCardToFitContent as $,renderEmptyState as j,prepareCanvas as z}from"./chartShell.js";const v=320,O=220,_={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 q(e,a,n,r,l,o){e.beginPath(),e.moveTo(a+o,n),e.arcTo(a+r,n,a+r,n+l,o),e.arcTo(a+r,n+l,a,n+l,o),e.arcTo(a,n+l,a,n,o),e.arcTo(a,n,a+r,n,o),e.closePath()}function w(e,a,n,r,l,o){e.font=`${T(-1)}px system-ui, sans-serif`;const h=e.measureText(r).width;e.font="700 11px system-ui, sans-serif";const M=e.measureText(l).width,g=Math.max(h,M)+12,b=30;e.fillStyle=o.bg,q(e,a-g/2,n-b/2,g,b,4),e.fill(),e.strokeStyle=o.border,e.lineWidth=1,e.stroke(),e.textAlign="center",e.textBaseline="middle",e.fillStyle=o.textMuted,e.font=`${T(-1)}px system-ui, sans-serif`,e.fillText(r,a,n-7),e.fillStyle=o.text,e.font="700 11px system-ui, sans-serif",e.fillText(l,a,n+7)}export function renderRegionYieldDiagram(e){const{mode:a,rows:n,onSaveImage:r}=e,l=e.title??(a==="ring"?"Ring yield":"Quadrant yield"),{card:o,body:h}=D(l,r,e.ownerDocument),M=n.length>0;if(!M)return j(h,"No bin data available for regional yield."),{card:o,hasData:M,destroy:()=>{}};Object.assign(h.style,{display:"flex",justifyContent:"center",alignItems:"center"});const g=o.ownerDocument.createElement("canvas");Object.assign(g.style,{display:"block",flex:"none"}),h.appendChild(g);function b(){const u=Math.max(O,Math.min(h.clientWidth||v,v));$(o,h,u);const S=z(g,o,u,u);if(!S)return;const{ctx:t}=S,y=N(o),k=A,c=u/2,d=u/2,f=u/2-34;if(a==="ring"){const i=n.length;for(let s=0;s<i;s++){const m=s/i*f,P=(s+1)/i*f;t.beginPath(),t.arc(c,d,P,0,2*Math.PI),t.arc(c,d,m,0,2*Math.PI,!0),t.closePath(),t.fillStyle=k(n[s].yieldPercent),t.fill(),t.strokeStyle=y.border,t.lineWidth=1,t.stroke()}t.beginPath(),t.arc(c,d,f,0,2*Math.PI),t.strokeStyle=y.text,t.lineWidth=1.5,t.stroke();for(let s=0;s<i;s++){const m=(s+.5)/i*f,P=-Math.PI/2+s/i*2*Math.PI,I=c+Math.cos(P)*m,p=d+Math.sin(P)*m;w(t,I,p,n[s].label,`${n[s].yieldPercent.toFixed(1)}%`,y)}}else{for(const i of n){const s=E(i.key).quadrant,m=s?_[s]:void 0;if(!m)continue;const[P,I]=m;t.beginPath(),t.moveTo(c,d),t.arc(c,d,f,P,I),t.closePath(),t.fillStyle=k(i.yieldPercent),t.fill(),t.strokeStyle=y.border,t.lineWidth=1,t.stroke();const p=(P+I)/2,R=c+Math.cos(p)*f*.6,W=d+Math.sin(p)*f*.6;w(t,R,W,i.label,`${i.yieldPercent.toFixed(1)}%`,y)}t.beginPath(),t.arc(c,d,f,0,2*Math.PI),t.strokeStyle=y.text,t.lineWidth=1.5,t.stroke()}}const C=F(o,()=>b());return b(),{card:o,hasData:M,destroy:()=>C.disconnect()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getColorScheme as
|
|
1
|
+
import{getColorScheme as we}from"../../renderer/colorSchemes.js";import{categorical as Me}from"./palette.js";import{buildScatterData as ve,buildScatterDataGrouped as Le}from"../../stats/scatter.js";import{pearsonOfPairs as ke}from"../../stats/correlation.js";import{buildFacetTable as Ce}from"../../stats/facets.js";import{SPACE as T,RADIUS as Ae,fontPx as U,FONT as q,CLR as H}from"../toolbar.js";import{cardShell as De,observeResize as Be,makeTooltip as Oe,attachChartTip as $e,makeTestSelect as Pe,makeWaferSelect as He,chartFillHeight as We,applyCanvasFlow as Ee,drawAxisUnit as Fe,resolveChartCanvasColors as Ie,makeAxisFormat as ae,makeSeriesLegendItem as Re,prepareCanvas as Ne}from"./chartShell.js";const c=52,ze=16,s=16,je=44;export function renderScatterPanel(W){const{title:re="Test scatter",items:C,testDefs:J,colorScheme:ce="default",onSaveImage:de,groups:E}=W,{card:i,body:L,controlsRow:Q}=De(re,de,W.ownerDocument),F=J.filter(t=>t.testNumber!==void 0),y=!!(E&&E.length>0),V=(E??[]).map(t=>t.key),me=new Map(V.map((t,n)=>[t,n]));let p=W.xTestNumber??F[0]?.testNumber??null,x=W.yTestNumber??F[1]?.testNumber??p,A=null;const Z=()=>A!==null?C[A]?[C[A]]:[]:C;function ee(t,n,o){const l=i.ownerDocument.createElement("label");Object.assign(l.style,{display:"inline-flex",alignItems:"center",gap:T.xs,fontSize:q.body,color:H.label});const g=i.ownerDocument.createElement("span");g.textContent=t;const m=Pe(F,n,o,{maxWidth:"180px",emptyText:"No tests",ownerDocument:i.ownerDocument});return l.append(g,m),{wrap:l,select:m}}const{wrap:fe,select:pe}=ee("X:",p,t=>{p=t,O()}),{wrap:ue,select:xe}=ee("Y:",x,t=>{x=t,O()});Q.append(fe,ue),!y&&C.length>1&&Q.appendChild(He(C,A,t=>{A=t,O()},{ownerDocument:i.ownerDocument}));const D=i.ownerDocument.createElement("div");Object.assign(D.style,{color:H.warnText,background:H.warnBg,border:`1px solid ${H.warnBorder}`,borderRadius:Ae.control,padding:`${T.xs} ${T.md}`,fontSize:q.body,marginBottom:T.xs,display:"none"}),i.insertBefore(D,L);function te(){const t=y?[]:Ce(Z(),{facetableOnly:!0}).filter(n=>n.splittable).map(n=>n.label);t.length>0?(D.textContent=`\u26A0 Mixed ${t.join(", ")} across these points \u2014 trends here may be confounded (Simpson's paradox). Use Group by, or the Wafer picker, to narrow to a like-for-like set.`,D.style.display=""):D.style.display="none"}te();const G=i.ownerDocument.createElement("div");Object.assign(G.style,{color:H.label,fontSize:q.body,marginBottom:T.xs}),i.insertBefore(G,L);function _(){const t=y?"One point per die \xB7 coloured by group \xB7 click legend to filter":"One point per die across all wafers \xB7 coloured by hard bin \xB7 click legend to filter",n=h.size===0?f:f.filter(g=>h.has(I(g))),{r:o,n:l}=ke(n);G.textContent=o===null?`${t} \xB7 n = ${l.toLocaleString()}`:`${t} \xB7 r = ${o.toFixed(3)} \xB7 n = ${l.toLocaleString()}`}const he=we(ce),{forBin:ge}=he,I=t=>y?t.group??"\u2014":String(t.hbin??0),ne=t=>y?Me(me.get(t)??0):ge(Number(t)),oe=t=>y?t:t==="0"?"No bin data":`Bin ${t}`,h=new Set,B=i.ownerDocument.createElement("div");Object.assign(B.style,{display:"flex",flexWrap:"wrap",gap:T.sm,marginTop:T.md,marginBottom:T.xl}),L.appendChild(B);const k=i.ownerDocument.createElement("canvas");k.style.display="block",k.style.cursor="crosshair",L.appendChild(k);const be=Oe(i);let f=[],w=0,R=1,M=0,N=1;function z(t){const n=J.find(o=>o.testNumber===t);return{unit:n?.unit,limitLow:n?.limitLow,limitHigh:n?.limitHigh}}function ye(){const t=L.clientWidth,n=Math.max(200,Math.min(400,t*.65)),o=We(i,L,k,n);return{w:t,h:o,plotW:Math.max(10,t-c-ze),plotH:Math.max(10,o-s-je)}}const X=new Map;function ie(){for(const[t,n]of X){const o=h.has(t);n.setState({selected:o,dimmed:h.size>0&&!o})}}function le(t){B.innerHTML="",X.clear(),h.clear();for(const n of t){const o=Re(oe(n),ne(n),i.ownerDocument),l=o.el;l.dataset.cat=n,X.set(n,o),$e(l,i,be,`${oe(n)} \u2014 click to filter`),l.addEventListener("click",()=>{h.has(n)?h.delete(n):h.add(n),ie(),_(),j()}),B.appendChild(l)}ie()}function j(){Ee(k,B);const t=Ie(i),n=R-w,o=N-M,{w:l,h:g,plotW:m,plotH:d}=ye(),$=Ne(k,i,l,g);if(!$)return;const{ctx:e}=$;if(f.length===0){e.font=`${U()}px system-ui, sans-serif`,e.fillStyle=t.textMuted,e.textAlign="center",e.textBaseline="middle",e.fillText("No data \u2014 select two parametric tests with values.",l/2,g/2);return}const b=4,Y=ae(Math.max(Math.abs(w),Math.abs(R)),p!==null?z(p).unit:void 0),K=ae(Math.max(Math.abs(M),Math.abs(N)),x!==null?z(x).unit:void 0);e.font=`${U(-1)}px system-ui, sans-serif`,e.strokeStyle=t.border,e.lineWidth=.5,e.fillStyle=t.textMuted;for(let a=0;a<=b;a++){const S=w+n*a/b,r=c+a/b*m;e.beginPath(),e.moveTo(r,s),e.lineTo(r,s+d),e.stroke(),e.textAlign="center",e.textBaseline="top",e.fillText(Y.tick(S),r,s+d+4);const v=M+o*a/b,u=s+(1-a/b)*d;e.beginPath(),e.moveTo(c,u),e.lineTo(c+m,u),e.stroke(),e.textAlign="right",e.textBaseline="middle",e.fillText(K.tick(v),c-4,u)}Y.unitLabel&&Fe(e,Y.unitLabel,c+m/2,s+d+24,t.textMuted),K.unitLabel&&(e.save(),e.font=`${U(-1)}px system-ui, sans-serif`,e.fillStyle=t.textMuted,e.textAlign="center",e.textBaseline="middle",e.translate(6,s+d/2),e.rotate(-Math.PI/2),e.fillText(`(${K.unitLabel})`,0,0),e.restore());const P=h.size===0?f:f.filter(a=>h.has(I(a))),se=P.length>5e3?Math.ceil(P.length/5e3):1;e.globalAlpha=Math.max(.15,Math.min(.7,200/(P.length/se)));for(let a=0;a<P.length;a+=se){const S=P[a],r=c+(S.x-w)/n*m,v=s+(1-(S.y-M)/o)*d;e.beginPath(),e.arc(r,v,2.5,0,Math.PI*2),e.fillStyle=ne(I(S)),e.fill()}if(e.globalAlpha=1,p!==null&&x!==null){const a=z(p),S=z(x);e.strokeStyle=t.textMuted,e.fillStyle=t.textMuted,e.lineWidth=1,e.setLineDash([4,3]),e.font=`${U(-1)}px system-ui, sans-serif`,e.textBaseline="top";for(const[r,v]of[[a.limitLow,"LSL"],[a.limitHigh,"USL"]]){if(r===void 0||r<w||r>R)continue;const u=c+(r-w)/n*m;e.beginPath(),e.moveTo(u,s),e.lineTo(u,s+d),e.stroke(),e.textAlign="left",e.fillText(v,u+2,s+2)}for(const[r,v]of[[S.limitLow,"LSL"],[S.limitHigh,"USL"]]){if(r===void 0||r<M||r>N)continue;const u=s+(1-(r-M)/o)*d;e.beginPath(),e.moveTo(c,u),e.lineTo(c+m,u),e.stroke(),e.textAlign="left",e.fillText(v,c+3,u+2)}e.setLineDash([])}e.strokeStyle=t.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,s),e.lineTo(c,s+d),e.moveTo(c,s+d),e.lineTo(c+m,s+d),e.stroke()}function O(){if(te(),F.length<2||p===null||x===null){f=[],le([]),_(),j();return}f=y?Le(E,p,x):ve(Z(),p,x);let t;if(y){const n=new Set(f.map(I));t=V.filter(o=>n.has(o))}else t=Array.from(new Set(f.map(n=>n.hbin??0))).sort((n,o)=>n-o).map(String);if(le(t),_(),f.length>0){const n=f.map(b=>b.x),o=f.map(b=>b.y),l=Math.min(...n),g=Math.max(...n),m=Math.min(...o),d=Math.max(...o),$=(g-l)*.05||1,e=(d-m)*.05||1;w=l-$,R=g+$,M=m-e,N=d+e}j()}const Se=Be(i,()=>j());O();function Te(t,n){p=t,x=n,pe.value=String(t),xe.value=String(n),O()}return{card:i,setXY:Te,destroy:()=>Se.disconnect()}}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TestPassRateItem } from '../../stats/testPassRate.js';
|
|
2
|
+
import type { TestDef } from '../../renderer/buildWaferMap.js';
|
|
3
|
+
import { type SaveImageHandler } from './chartShell.js';
|
|
4
|
+
export interface TestPassRatePanelOptions {
|
|
5
|
+
title?: string;
|
|
6
|
+
/** One entry per group. Pass a single group for the ungrouped case. */
|
|
7
|
+
groups: {
|
|
8
|
+
key: string;
|
|
9
|
+
items: TestPassRateItem[];
|
|
10
|
+
}[];
|
|
11
|
+
testDefs: TestDef[] | undefined;
|
|
12
|
+
onSaveImage?: SaveImageHandler;
|
|
13
|
+
ownerDocument?: Document;
|
|
14
|
+
}
|
|
15
|
+
export interface TestPassRatePanelHandle {
|
|
16
|
+
card: HTMLElement;
|
|
17
|
+
destroy: () => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function renderTestPassRatePanel(options: TestPassRatePanelOptions): TestPassRatePanelHandle;
|
|
20
|
+
//# sourceMappingURL=testPassRate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{buildTestPassRateData as E,hasJudgeableTests as F}from"../../stats/testPassRate.js";import{categorical as B,QUANTITY as I}from"./palette.js";import{SPACE as N,FONT as _,CLR as T}from"../toolbar.js";import{cardShell as A,makeTooltip as H,makeSegmented as O,renderEmptyState as w}from"./chartShell.js";import{renderGroupedBarPlot as j}from"./groupedBarPlot.js";const M=100,k=8,G={spec:"Spec limits",testFlag:"Tester flag",functional:"Functional"},U={spec:"Parametric pass rate \xB7 spec limits",testFlag:"Parametric pass rate \xB7 tester flag",functional:"Functional test pass rate"};export function renderTestPassRatePanel(g){const{groups:f,testDefs:u,onSaveImage:y}=g,p=g.ownerDocument??document,$=f.flatMap(e=>e.items.flatMap(a=>a.dies??[])),d=["spec","testFlag","functional"].filter(e=>F(u,e,$));let s=d[0]??"spec";const b=e=>U[e],{card:o,heading:P,body:i,controlsRow:v}=A(b(s),y,p);o.style.alignSelf="start",d.length>1&&v.appendChild(O(d.map(e=>[e,G[e]]),s,e=>{s=e,P.textContent=b(s),D()},p));const n=p.createElement("div");Object.assign(n.style,{color:T.label,fontSize:_.body,marginBottom:N.sm}),o.insertBefore(n,i);const h=H(o);let m=null;function D(){if(m?.destroy(),m=null,i.innerHTML="",d.length===0){n.textContent="",w(i,"No tests with a pass/fail verdict \u2014 parametric tests need spec limits, functional tests need recorded results.");return}const e=E(f,u,s);if(e.rows.length===0){n.textContent="",w(i,s==="spec"?"No parametric test has spec limits to judge against.":s==="testFlag"?"No parametric test has a recorded tester verdict.":"No functional test has recorded results.");return}const a=e.groups.length>1,x=s==="spec"?"Dies within spec limits, per test":s==="testFlag"?"Dies the tester marked pass, per test":"Dies passing the recorded verdict, per test";if(n.textContent=x+(a?" \xB7 one bar per group":"")+" \xB7 worst first",e.disagreementDies){const t=p.createElement("span");t.textContent=` \xB7 ${e.disagreementDies.toLocaleString()} die${e.disagreementDies===1?"":"s"} judged differently by limits and tester flag`,Object.assign(t.style,{color:T.warnText}),n.appendChild(t)}const L=e.rows,R=t=>a?B(t):I;m=j(o,i,h,{rows:L.map(t=>({label:t.label,bars:t.byGroup.map(l=>({fraction:l.passRatePercent===null?null:l.passRatePercent/100})),trailing:t.overall.passRatePercent===null?"\u2014":`${t.overall.passRatePercent.toFixed(1)}%`})),groups:e.groups,labelWidth:M,maxVisibleRows:k,showLegend:a,maxLabelChars:14,emptyBarText:"no data",colorOf:R,tooltipHtml:(t,l)=>{const r=L[t],c=r.byGroup[l],S=a?` \xB7 ${e.groups[l]}`:"";if(c.passRatePercent===null)return`<strong>${r.label}</strong>${S}<br>no dies with a verdict`;const C=s==="spec"&&!a&&(r.failLowDies||r.failHighDies)?`<br>${r.failLowDies??0} below LSL \xB7 ${r.failHighDies??0} above USL`:"";return`<strong>${r.label}</strong>${S}<br>${c.passRatePercent.toFixed(1)}% pass<br>${c.passDies.toLocaleString()} pass \xB7 ${c.failDies.toLocaleString()} fail \xB7 n = ${c.totalDies.toLocaleString()}`+C}})}return D(),{card:o,destroy:()=>{m?.destroy(),h.remove(),o.remove()}}}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type TrendItem } from '../../stats/trend.js';
|
|
2
|
+
import { type TestDef } from '../../renderer/buildWaferMap.js';
|
|
3
|
+
import { type AxisPrefs, type SaveImageHandler } from './chartShell.js';
|
|
4
|
+
export interface TrendPanelOptions {
|
|
5
|
+
title?: string;
|
|
6
|
+
items: TrendItem[];
|
|
7
|
+
testDefs: TestDef[];
|
|
8
|
+
selectedTestNumber?: number;
|
|
9
|
+
onSaveImage?: SaveImageHandler;
|
|
10
|
+
/** Click a point to open that wafer's map on this test. */
|
|
11
|
+
onOpen?: (key: number, testNumber: number) => void;
|
|
12
|
+
/** Initial axis toggles, shared with the sibling distribution panels. */
|
|
13
|
+
axisPrefs?: AxisPrefs;
|
|
14
|
+
/** Fired when the user changes an axis toggle here, so siblings can follow. */
|
|
15
|
+
onAxisPrefsChange?: (prefs: AxisPrefs) => void;
|
|
16
|
+
/** Fired when the USER picks a test here, so siblings can follow. Not fired
|
|
17
|
+
* by `setTest`, which is how a broadcast avoids bouncing back. */
|
|
18
|
+
onTestChange?: (testNumber: number) => void;
|
|
19
|
+
ownerDocument?: Document;
|
|
20
|
+
}
|
|
21
|
+
export interface TrendPanelHandle {
|
|
22
|
+
card: HTMLElement;
|
|
23
|
+
destroy: () => void;
|
|
24
|
+
/** Cross-panel link — switch the displayed test in place. */
|
|
25
|
+
setTest: (testNumber: number) => void;
|
|
26
|
+
/** Adopt the shared axis toggles (see `AxisPrefs`). */
|
|
27
|
+
setAxisPrefs: (prefs: AxisPrefs) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare function renderTrendPanel(options: TrendPanelOptions): TrendPanelHandle;
|
|
30
|
+
//# sourceMappingURL=trend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{buildTestTrendData as me,trendCentre as de}from"../../stats/trend.js";import{isParametricTest as fe}from"../../renderer/buildWaferMap.js";import{fmt as D}from"../../renderer/fmt.js";import{SPACE as ne,fontPx as ue,FONT as he,CLR as pe}from"../toolbar.js";import{QUANTITY as _}from"./palette.js";import{cardShell as ge,observeResize as xe,makeTooltip as be,positionChartTooltip as ye,makeLinkedTestSelect as ve,makeLinkedAxisPrefs as Te,renderEmptyState as z,growCardToFitContent as Le,chartFillHeight as we,PADDING as X,resolveAxisRange as ke,shouldIncludeLimitsByDefault as Ce,drawOffAxisLimits as Se,prepareCanvas as Ae}from"./chartShell.js";const ie=220,Pe=56,Ne=34,De=3.5;export function renderTrendPanel(g){const{items:j,testDefs:oe,onSaveImage:le,onOpen:C}=g,se=g.title??"Wafer-to-wafer trend",{card:l,body:f,controlsRow:G}=ge(se,le,g.ownerDocument);l.style.alignSelf="start";const M=oe.filter(o=>o.testNumber!==void 0&&fe(o));let h=g.selectedTestNumber??M[0]?.testNumber??null,I=0;const U=ve(M,h,o=>{h=o,S(),g.onTestChange?.(o)},{maxWidth:"240px",emptyText:"No parametric tests",ownerDocument:l.ownerDocument});G.appendChild(U.el);const O=l.ownerDocument.createElement("span");Object.assign(O.style,{display:"inline-flex",gap:ne.lg,alignItems:"center"}),G.appendChild(O);const $=Te(O,g.axisPrefs,o=>{g.onAxisPrefsChange?.(o),S()},l.ownerDocument),T=l.ownerDocument.createElement("div");Object.assign(T.style,{color:pe.label,fontSize:he.body,marginBottom:ne.sm}),l.insertBefore(T,f);const L=be(l);let H=null,n=[];function re(o){return M.find(B=>B.testNumber===o)}function S(){const{includeLimits:o,clipOutliers:B}=$.get();if(f.innerHTML="",H?.disconnect(),H=null,M.length===0||h===null){T.textContent="",z(f,"No parametric test data available for a trend.");return}if(j.length<2){T.textContent="",z(f,"A wafer-to-wafer trend needs at least two wafers.");return}if(n=me(j,h),!n.some(r=>r.count>0)){T.textContent="",z(f,"No values recorded for this test.");return}const s=re(h),x=de(n),Y=()=>{T.textContent="Point = wafer mean, whisker = \xB11\u03C3, dashed = lot mean \xB7 wafers in slot order"+(C?" \xB7 click a point to open that wafer":"")+(I?` \xB7 axis clipped, ${I} outside`:"")};Y();const u=l.ownerDocument.createElement("canvas");u.style.display="block",f.appendChild(u);let A=null;const Q=()=>{const r=Math.max(1,f.clientWidth),d=Math.max(ie,we(l,f,u,ie)),i=Ae(u,l,r,d);if(!i)return;const{ctx:e,theme:b}=i;let P=1/0,N=-1/0;const J=[];for(const t of n)!t.count||!Number.isFinite(t.mean)||(P=Math.min(P,t.mean-t.stddev),N=Math.max(N,t.mean+t.stddev),J.push(t.mean-t.stddev,t.mean+t.stddev));if(!Number.isFinite(P)||!Number.isFinite(N))return;const K=o??Ce(P,N,s?.limitLow,s?.limitHigh);$.sync(K,s?.limitLow!==void 0||s?.limitHigh!==void 0);const E=ke({dataMin:P,dataMax:N,limitLow:s?.limitLow,limitHigh:s?.limitHigh,includeLimits:K,clipOutliers:B,values:J});I=E.clippedCount,Y();let p=E.lo,w=E.hi;const V=(w-p)*.08;p-=V,w+=V;const a=X+Pe;e.font=`${ue(-1)}px system-ui, sans-serif`;const Z=t=>t.length>8?`${t.slice(0,7)}\u2026`:t,ae=n.length?Math.ceil(Math.max(e.measureText(Z(n[0].label)).width,e.measureText(Z(n[n.length-1].label)).width)/2):0,y=r-X-ae,F=X,k=d-Ne,v=t=>k-(t-p)/(w-p)*(k-F),ee=n.length>1?(y-a)/(n.length-1):0;A={left:a,step:ee};const R=t=>n.length>1?a+t*ee:(a+y)/2;e.strokeStyle=b.border,e.lineWidth=1,e.beginPath(),e.moveTo(a,F),e.lineTo(a,k),e.lineTo(y,k),e.stroke(),e.fillStyle=b.text,e.textAlign="right",e.textBaseline="middle";for(const t of[0,.5,1]){const c=p+(w-p)*t;e.fillText(D(c,s?.unit),a-6,v(c))}Se(e,E.offAxis,{left:a,right:y,top:F,bottom:k},"vertical",b.limitLine,t=>D(t,s?.unit));for(const[t,c]of[[s?.limitLow,"LSL"],[s?.limitHigh,"USL"]]){if(t===void 0||t<p||t>w)continue;const m=v(t);e.save(),e.strokeStyle=b.limitLine,e.globalAlpha=.7,e.setLineDash([4,3]),e.beginPath(),e.moveTo(a,m),e.lineTo(y,m),e.stroke(),e.restore(),e.save(),e.fillStyle=b.limitLine,e.textAlign="left",e.fillText(c,a+3,m-6),e.restore()}if(x!==null&&x>=p&&x<=w){const t=v(x);e.save(),e.strokeStyle=b.text,e.globalAlpha=.5,e.setLineDash([3,3]),e.beginPath(),e.moveTo(a,t),e.lineTo(y,t),e.stroke(),e.restore()}e.strokeStyle=_,e.lineWidth=1.5,e.beginPath();let W=!1;n.forEach((t,c)=>{if(!t.count||!Number.isFinite(t.mean)){W=!1;return}const m=R(c),te=v(t.mean);W?e.lineTo(m,te):e.moveTo(m,te),W=!0}),e.stroke(),n.forEach((t,c)=>{if(!t.count||!Number.isFinite(t.mean))return;const m=R(c);t.stddev>0&&(e.save(),e.strokeStyle=_,e.globalAlpha=.55,e.lineWidth=1,e.beginPath(),e.moveTo(m,v(t.mean-t.stddev)),e.lineTo(m,v(t.mean+t.stddev)),e.stroke(),e.restore()),e.fillStyle=_,e.beginPath(),e.arc(m,v(t.mean),De,0,Math.PI*2),e.fill()}),e.fillStyle=b.text,e.textBaseline="top",e.textAlign="center";const ce=Math.max(1,Math.ceil(n.length*42/Math.max(1,y-a)));n.forEach((t,c)=>{if(c%ce!==0&&c!==n.length-1)return;const m=t.label.length>8?`${t.label.slice(0,7)}\u2026`:t.label;e.fillText(m,R(c),k+6)}),Le(l,f,d)},q=r=>{if(!A||A.step<=0)return-1;const d=u.getBoundingClientRect(),i=Math.round((r.clientX-d.left-A.left)/A.step);return i>=0&&i<n.length&&n[i].count>0?i:-1};u.addEventListener("mousemove",r=>{const d=q(r);if(d<0){L.style.display="none",u.style.cursor="default";return}const i=n[d];u.style.cursor=C&&i.key!==void 0?"pointer":"default",L.innerHTML=`<strong>${i.label}</strong><br>mean ${D(i.mean,s?.unit)} \xB7 \u03C3 ${D(i.stddev,s?.unit)}<br>n = ${i.count.toLocaleString()}`+(x!==null?`<br>\u0394 vs lot mean ${i.mean-x>=0?"+":""}${D(i.mean-x,s?.unit)}`:"")+(C&&i.key!==void 0?"<br><em>click to open this wafer</em>":""),L.style.display="block",ye(L,l,r.clientX,r.clientY)}),u.addEventListener("mouseleave",()=>{L.style.display="none"}),u.addEventListener("click",r=>{const d=q(r);if(d<0||!C)return;const i=n[d];i.key!==void 0&&h!==null&&C(i.key,h)}),Q(),H=xe(f,Q)}return S(),{card:l,destroy:()=>{H?.disconnect(),L.remove(),l.remove()},setTest:o=>{U.set(o)&&(h=o,S())},setAxisPrefs:o=>{$.set(o)&&S()}}}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{hasPosition as
|
|
2
|
-
.wmap-dielist-table { width: 100%; border-collapse: collapse; font-size:
|
|
1
|
+
import{hasPosition as R,isPositionedDie as O}from"../core/dies.js";import{classifyDie as z}from"../core/index.js";import{isParametricTest as B,getTestPassStatus as x}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as V}from"../stats/metadataColumns.js";import{LEADING as D,TRACKING as W,wireControlHover as G,controlStyle as K,SPACE as X,RADIUS as F,FONT as C,CLR as d,saveTextFile as Y}from"./toolbar.js";import{csvField as I}from"./summaryPanel.js";import{fmt as T}from"../renderer/fmt.js";const _=5e4,k=new WeakSet;function Q(n){if(k.has(n))return;k.add(n);const o=n.createElement("style");o.textContent=`
|
|
2
|
+
.wmap-dielist-table { width: 100%; border-collapse: collapse; font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
3
3
|
.wmap-dielist-th {
|
|
4
4
|
text-align: left; padding: 4px 8px; position: sticky; top: 0;
|
|
5
|
-
background: ${
|
|
6
|
-
border-bottom: 1px solid ${
|
|
5
|
+
background: ${d.panelBg}; color: ${d.label}; font-weight: 600;
|
|
6
|
+
border-bottom: 1px solid ${d.menuBorder}; white-space: nowrap;
|
|
7
7
|
}
|
|
8
8
|
.wmap-dielist-td {
|
|
9
|
-
padding:
|
|
10
|
-
border-bottom: 1px solid ${
|
|
9
|
+
padding: 4px 8px; color: ${d.text};
|
|
10
|
+
border-bottom: 1px solid ${d.menuBorder}; white-space: nowrap;
|
|
11
11
|
}
|
|
12
|
-
`,n.head.appendChild(o)}function
|
|
12
|
+
`,n.head.appendChild(o)}function c(n,o,t,a){const i=n.createElement(o);return t&&Object.assign(i.style,t),a!==void 0&&(i.textContent=a),i}function U(n){return R(n)?String(n.x):""}function q(n){return R(n)?String(n.y):""}function J(n,o){if(o!==void 0)return o.filter(a=>a.testNumber!==void 0);const t=new Map;for(const a of n)for(const i of Object.keys(a.testValues??{})){const s=Number(i);t.has(s)||t.set(s,{testNumber:s,name:`Test ${s}`})}return[...t.values()].sort((a,i)=>a.testNumber-i.testNumber)}function Z(n,o,t){if(!o)return;const a=new Map;for(const i of n){if(!O(i))continue;const s=o(i);s&&a.set(i,z(i,s,{ringCount:t}))}return a.size>0?a:void 0}export function buildDieListSection(n,o,t={}){if(!n.length)return null;const a=t.ownerDocument??document;Q(a);const i=J(n,o),s=t.maxRows??_,S=Math.max(0,n.length-s),H=S>0?n.slice(0,s):n,m=c(a,"div",{display:"flex",flexDirection:"column",gap:X.md,flex:"1",minHeight:"0",minWidth:"0"}),w=c(a,"div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});w.appendChild(c(a,"div",{fontSize:C.meta,fontWeight:"700",letterSpacing:W,textTransform:"uppercase",color:d.label},t.title??`Die list (${n.length})`));const g=c(a,"button",{fontSize:C.body,padding:"3px 8px",borderRadius:F.control,...K("outlined"),background:d.menuBg},"Export CSV");g.type="button",G(g),w.appendChild(g),m.appendChild(w),t.note&&m.appendChild(c(a,"div",{fontSize:C.body,color:d.label,lineHeight:D.base,flexShrink:"0"},t.note));const v=Z(n,t.getWafer,t.ringCount??4),P=n.some(e=>e.edgeExcluded===!0),j=[...t.extraColumn?[t.extraColumn.label]:[],"X","Y","Ring","Quadrant","Edge excluded","Site","Hard bin","Soft bin",...i.map(e=>e.unit?`${e.name} (${e.unit})`:e.name)],{columns:M,truncatedKeys:h}=V({dies:n,waferMetadata:t.waferMetadata,metadataFields:t.metadataFields,dieKeys:t.metadataColumns,waferKeys:t.waferMetadataColumns==="none"?"none":void 0,waferPlacement:t.waferMetadataColumns??"csv",reservedLabels:j}),y=[...t.extraColumn?[{label:t.extraColumn.label,get:e=>t.extraColumn.get(e)??""}]:[],{label:"X",get:U},{label:"Y",get:q},...v?[{label:"Ring",get:e=>{const l=v.get(e);return l?String(l.ring):""}},{label:"Quadrant",get:e=>v.get(e)?.quadrant??""}]:[],...P?[{label:"Edge excluded",get:e=>e.edgeExcluded?"Yes":"No"}]:[],{label:"Site",get:e=>e.siteNum!==void 0?String(e.siteNum):""},{label:"Hard bin",get:e=>e.hbin!==void 0?String(e.hbin):""},{label:"Soft bin",get:e=>e.sbin!==void 0?String(e.sbin):""},...i.map(e=>{const l=!B(e);return{label:e.unit?`${e.name} (${e.unit})`:e.name,get:r=>{if(l){const p=x(r,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const u=r.testValues?.[e.testNumber];if(u!==void 0)return T(u,e.unit);const b=x(r,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"},csvGet:r=>{if(l){const p=x(r,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const u=r.testValues?.[e.testNumber];if(u!==void 0)return T(u,void 0,"engineering");const b=x(r,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"}}}),...M.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],N=y.filter(e=>!e.csvOnly),$=c(a,"div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${d.menuBorder}`,borderRadius:F.control,...t.maxHeight?{maxHeight:t.maxHeight}:{}}),f=a.createElement("table");f.className="wmap-dielist-table",f.setAttribute("aria-label",t.title??`Die list (${n.length} dies)`);const E=c(a,"thead"),A=c(a,"tr");for(const e of N){const l=a.createElement("th");l.className="wmap-dielist-th",l.scope="col",l.textContent=e.label,A.appendChild(l)}E.appendChild(A),f.appendChild(E);const L=c(a,"tbody");for(const e of H){const l=c(a,"tr");for(const r of N){const u=a.createElement("td");u.className="wmap-dielist-td",u.textContent=r.get(e),l.appendChild(u)}L.appendChild(l)}if(f.appendChild(L),$.appendChild(f),m.appendChild($),S>0||h.length>0){const e=[];S>0&&e.push(`Showing the first ${s.toLocaleString()} of ${n.length.toLocaleString()} dies. The CSV export contains all ${n.length.toLocaleString()}.`),h.length>0&&e.push(`${h.length} further metadata field${h.length===1?"":"s"} not shown.`),m.appendChild(c(a,"div",{fontSize:C.body,color:d.label,lineHeight:D.base,flexShrink:"0"},e.join(" ")))}return g.addEventListener("click",()=>{const e=[y.map(l=>I(l.label)).join(",")];for(const l of n)e.push(y.map(r=>I((r.csvGet??r.get)(l))).join(","));Y(e.join(`
|
|
13
13
|
`),t.csvFilename??"dies.csv","text/csv",t.onSaveText)}),m}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { WaferMetadata } from '../core/metadata.js';
|
|
2
|
+
export interface IdentityHeaderLotStack {
|
|
3
|
+
lotSize: number;
|
|
4
|
+
aggrMethod?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface IdentityHeaderOptions {
|
|
7
|
+
/** Present when the host result is a lot-stack aggregation rather than a
|
|
8
|
+
* single wafer — the metadata panel then leads with wafer count + method
|
|
9
|
+
* instead of a (nonexistent) single waferId. */
|
|
10
|
+
lotStack?: IdentityHeaderLotStack;
|
|
11
|
+
/** Document to build into. Default `document` — pass the render's own
|
|
12
|
+
* `ownerDocument` when the container might live in a different document
|
|
13
|
+
* (e.g. a gallery card detached into its own popup window). */
|
|
14
|
+
ownerDocument?: Document;
|
|
15
|
+
}
|
|
16
|
+
export interface IdentityHeaderController {
|
|
17
|
+
/** The header row itself: label + chevron (chevron only appears once
|
|
18
|
+
* there's something to expand). Mount wherever the header belongs —
|
|
19
|
+
* normal in-flow layout, its own height never changes. */
|
|
20
|
+
wrap: HTMLDivElement;
|
|
21
|
+
/**
|
|
22
|
+
* The expandable metadata panel. Always created (so callers have a stable
|
|
23
|
+
* element to place), but only ever visible once there's lot-stack context
|
|
24
|
+
* or a metadata field to show. Absolutely positioned (`top:0;left:0;
|
|
25
|
+
* right:0`) — mount it into a `position: relative` ancestor that should be
|
|
26
|
+
* covered while expanded (e.g. the canvas area below the header). Never
|
|
27
|
+
* changes `wrap`'s own height, so expanding never triggers a reflow/resize
|
|
28
|
+
* of anything else.
|
|
29
|
+
*/
|
|
30
|
+
metaPanel: HTMLDivElement;
|
|
31
|
+
/** Re-render with a new label / metadata / lot-stack context. */
|
|
32
|
+
update(label: string, metadata: WaferMetadata | null | undefined, lotStack?: IdentityHeaderLotStack): void;
|
|
33
|
+
/** True when there is nothing at all to show (no label text, no metadata,
|
|
34
|
+
* no lot-stack) — the caller should not mount `wrap`/`metaPanel`. */
|
|
35
|
+
isEmpty(): boolean;
|
|
36
|
+
/** Force the metadata panel closed (chevron back to ▾) — for a caller that
|
|
37
|
+
* needs to cover `metaPanel` with something else (e.g. the Insights tab)
|
|
38
|
+
* and must not leave it expanded-but-hidden, which would desync the
|
|
39
|
+
* chevron/aria state from `metaPanel`'s own visibility. No-op if already
|
|
40
|
+
* collapsed. */
|
|
41
|
+
collapse(): void;
|
|
42
|
+
destroy(): void;
|
|
43
|
+
}
|
|
44
|
+
export declare function collapsedLabel(meta: WaferMetadata, lotStack: IdentityHeaderLotStack | undefined): string | undefined;
|
|
45
|
+
export declare function createIdentityHeader(label: string, metadata: WaferMetadata | null | undefined, opts?: IdentityHeaderOptions): IdentityHeaderController;
|
|
46
|
+
//# sourceMappingURL=identityHeader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{metadataEntries as C,buildCompactMetadataRows as W}from"./summaryPanel.js";import{prettyKey as A}from"../stats/facets.js";import{SHADOW as L,LEADING as k,SPACE as w,FONT as x,CLR as E,Z_ABOVE as M,wireExpandToggle as j}from"./toolbar.js";export function collapsedLabel(r,a){if(a)return a.aggrMethod?`${a.lotSize} wafers \xB7 ${a.aggrMethod}`:`${a.lotSize} wafers`;if(r.lot&&r.waferId!==void 0&&r.waferId!==""){const s=String(r.lot),t=String(r.waferId);return t.includes(s)?t:`${s} \xB7 ${t}`}for(const s of["lot","waferId","product","testProgram"]){const t=r[s];if(t!=null&&t!=="")return String(t)}}export function createIdentityHeader(r,a,s={}){let t=r,b=a??{},o=s.lotStack,i=!1,m=!1;const c=s.ownerDocument??document,e=c.createElement("div");Object.assign(e.style,{display:"flex",alignItems:"center",gap:w.xs,minWidth:"0"});const u=c.createElement("span");Object.assign(u.style,{fontWeight:"700",fontSize:x.sub,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}),e.appendChild(u);const y=c.createElement("span");Object.assign(y.style,{fontSize:x.body,lineHeight:k.none,color:E.label,flexShrink:"0",display:"none"}),e.appendChild(y);const $=4,d=c.createElement("span");Object.assign(d.style,{fontSize:x.body,color:E.label,whiteSpace:"nowrap",overflow:"hidden",flexShrink:"0",display:"none",marginLeft:w.sm}),e.appendChild(d);const l=c.createElement("div");l.dataset.wmapMetaPanel="1",Object.assign(l.style,{position:"absolute",top:"0",left:"0",right:"0",zIndex:M,background:E.menuBg,borderBottom:`1px solid ${E.menuBorder}`,boxShadow:L.menu,padding:`${w.md} ${w.lg}`,fontSize:x.body,display:"none"});function S(){return C(b).length>0||!!o}function O(){return o?[]:C(b).filter(([,n])=>!t.includes(n))}function z(){return e.clientWidth?u.scrollWidth>u.clientWidth||d.scrollWidth>d.clientWidth||e.scrollWidth>e.clientWidth:!0}function g(){u.textContent=t;const n=O();d.textContent=n.map(([p,h])=>`${A(p)}: ${h}`).join(" \xB7 "),d.style.display=n.length>0?"":"none",m=n.length>0&&n.length<=$&&!z(),d.style.display=m?"":"none";const f=S()&&!m;if(y.style.display=f?"":"none",e.style.cursor=f?"pointer":"",f||(i=!1),y.textContent=i?"\u25B4":"\u25BE",l.style.display=i?"block":"none",f?(e.setAttribute("aria-expanded",String(i)),e.setAttribute("aria-label",`Wafer info: ${t||"this wafer"}. ${i?"Click to collapse.":"Click to expand."}`)):(e.removeAttribute("aria-expanded"),e.removeAttribute("aria-label")),l.innerHTML="",i){if(o){const h=c.createElement("div");Object.assign(h.style,{fontWeight:"600",marginBottom:"3px"}),h.textContent=o.aggrMethod?`${o.lotSize} wafers stacked \xB7 ${o.aggrMethod}`:`${o.lotSize} wafers stacked`,l.appendChild(h)}const p=W(b);p&&l.appendChild(p)}}const I=j(e,n=>{!S()||m||(i=n,g())});g();const v=typeof ResizeObserver>"u"?null:new ResizeObserver(()=>g());return v?.observe(e),{wrap:e,metaPanel:l,update(n,f,p){t=n,b=f??{},o=p,g()},isEmpty(){return!t&&!S()},collapse(){i&&(i=!1,g())},destroy(){v?.disconnect(),I.destroy(),e.remove(),l.remove()}}}
|
|
@@ -4,7 +4,7 @@ export { renderWaferMap } from './renderWaferMap.js';
|
|
|
4
4
|
export type { RenderOptions, WaferMapController, WaferViewOptions, WaferPreferences, WaferDisplayState } from './renderWaferMap.js';
|
|
5
5
|
export { renderWaferGallery } from './renderWaferGallery.js';
|
|
6
6
|
export type { WaferMapDisplayItem, WaferMapDisplayItemFactory, GalleryOptions, GalleryController } from './renderWaferGallery.js';
|
|
7
|
-
export type { SummaryPanelOptions } from './summaryPanel.js';
|
|
7
|
+
export type { SummaryPanelOptions, FindingsNotice } from './summaryPanel.js';
|
|
8
8
|
export { buildDieListSection } from './dieList.js';
|
|
9
9
|
export type { DieListOptions, DieListDisplayOptions } from './dieList.js';
|
|
10
10
|
export { collectWarnings, severityOf } from './warnings.js';
|
|
@@ -14,6 +14,17 @@ export interface InsightsOptions {
|
|
|
14
14
|
enabled?: boolean;
|
|
15
15
|
/** Which sub-tab is shown first. Default 'overview'. */
|
|
16
16
|
defaultView?: InsightsView;
|
|
17
|
+
/**
|
|
18
|
+
* Open the Insights view on mount instead of starting on the map/grid.
|
|
19
|
+
* Default false — the tab is offered, the map is what you see first.
|
|
20
|
+
*
|
|
21
|
+
* Set this for an analysis-first surface, where the charts are the point and
|
|
22
|
+
* the map is the secondary view. Symmetric with `summaryPanel.defaultOpen`.
|
|
23
|
+
* The chart suite is a lazily-imported chunk, so opening it on mount also
|
|
24
|
+
* pulls that chunk on load rather than on first click — worth knowing if the
|
|
25
|
+
* page is otherwise weight-sensitive.
|
|
26
|
+
*/
|
|
27
|
+
defaultOpen?: boolean;
|
|
17
28
|
}
|
|
18
29
|
export interface InsightsTabDeps {
|
|
19
30
|
/** 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. */
|
|
@@ -49,13 +60,18 @@ export interface InsightsTabDeps {
|
|
|
49
60
|
label: string;
|
|
50
61
|
onBack: () => void;
|
|
51
62
|
};
|
|
63
|
+
/** Opens the user guide. Rendered as an icon at the end of the tab row —
|
|
64
|
+
* while Insights is showing, the map toolbar is hidden (it held only a
|
|
65
|
+
* back-to-gallery button, which the back tab already provides, and this
|
|
66
|
+
* help button), so this is where Help lives in this view. */
|
|
67
|
+
onOpenGuide?: () => void;
|
|
52
68
|
/**
|
|
53
69
|
* Show this tab's own identity strip (lot/wafer/product/etc.), mounted
|
|
54
70
|
* above the Overview/Distributions/Correlation tab bar so it stays in the
|
|
55
71
|
* same place across every sub-tab. Default true — needed by
|
|
56
|
-
* `renderWaferMap.ts`, whose single-wafer
|
|
57
|
-
*
|
|
58
|
-
*
|
|
72
|
+
* `renderWaferMap.ts`, whose single-wafer identity header is explicitly
|
|
73
|
+
* hidden while Insights is open (see `setInsightsOpen`, to avoid showing
|
|
74
|
+
* the same metadata twice), so this strip is its only identity display there.
|
|
59
75
|
* `renderWaferGallery.ts` sets this false: its own legend strip (built from
|
|
60
76
|
* the same wafer/lot metadata, via the same `buildMetadataStripRow`) stays
|
|
61
77
|
* mounted above the grid/Insights body in both views, so this strip would
|
|
@@ -63,6 +79,14 @@ export interface InsightsTabDeps {
|
|
|
63
79
|
* renderWaferGallery.ts's `rebuildLegend`/`setInsightsOpen`.
|
|
64
80
|
*/
|
|
65
81
|
showMetadataStrip?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Inset for this tab's own bands and content from the edge of the region it
|
|
84
|
+
* fills. Defaults to `EDGE_GUTTER`, which is right when the tab fills a
|
|
85
|
+
* top-level region (the gallery). `renderWaferMap` passes its own chrome
|
|
86
|
+
* inset instead, so the tab bar lines up with the identity row above it
|
|
87
|
+
* rather than sitting a gutter further in.
|
|
88
|
+
*/
|
|
89
|
+
contentInset?: string;
|
|
66
90
|
/** Document to build this tab's DOM into. Default `document` — pass the
|
|
67
91
|
* render's own `ownerDocument` when the container might live in a
|
|
68
92
|
* different document (e.g. a gallery card detached into its own popup
|
|
@@ -1 +1 @@
|
|
|
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()}}}
|
|
1
|
+
import{buildFacetTable as we,facetValueOf as Te,FACET_NONE_VALUE as ve}from"../stats/facets.js";import{mergeTestDefs as ke}from"../stats/mergeTestDefs.js";import{isParametricTest as $e}from"../renderer/buildWaferMap.js";import{getColorScheme as Be}from"../renderer/colorSchemes.js";import{LEADING as De,ALPHA as Ee,SPACE as w,EDGE_GUTTER as K,FONT as H,CLR as B,RADIUS as Oe,SHADOW as Ie,controlStyle as Le,wireControlHover as ee,wireTooltip as Pe}from"./toolbar.js";import{ICONS as Re}from"./icons.js";import{renderCapabilityPanel as Ae}from"./charts/capability.js";import{renderBoxplotPanel as je}from"./charts/boxplot.js";import{renderTrendPanel as We}from"./charts/trend.js";import{renderHistogramPanel as Ge}from"./charts/histogram.js";import{renderCorrelationPanel as He}from"./charts/correlation.js";import{renderScatterPanel as Ye}from"./charts/scatter.js";import{renderBarPanel as te}from"./charts/barPanel.js";import{renderBinClusterPanel as Fe}from"./charts/binCluster.js";import{renderTestPassRatePanel as Me}from"./charts/testPassRate.js";import{QUANTITY as Ne}from"./charts/palette.js";import{cardFrameStyle as Ve,makeChartGridWrap as X,makeLabeledSelect as ze,makeLinkedGroupSelect as Ue}from"./charts/chartShell.js";import{buildYieldData as N,buildYieldDataCombined as ne}from"../stats/yield.js";import{buildBinParetoData as _e}from"../stats/binPareto.js";import{buildLotTestSection as qe,buildLotFunctionalSection as Qe,buildMetadataStripBox as Ke}from"./summaryPanel.js";import{buildRegionYieldData as ae,buildRingRegions as Xe,buildQuadrantRegions as Je}from"../stats/regions.js";import{renderRegionYieldDiagram as oe}from"./charts/regionYieldDiagram.js";const Ze=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}];export function createInsightsTab(D){const{getItems:re,getLotStats:ie,getColorSchemeName:E,passBins:T,getRingCount:se,onSaveImage:v,onSaveText:V,openWafer:Y}=D,le=D.showMetadataStrip??!0,j=D.contentInset??K,d=D.ownerDocument??document,P=d.createElement("div");Object.assign(P.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const de=K,R=d.createElement("div");Object.assign(R.style,{paddingLeft:de});const $=d.createElement("div");Object.assign($.style,{display:"flex",gap:w.xs,alignItems:"center",background:B.menuBg,border:`1px solid ${B.menuBorder}`,borderRadius:Oe.container,boxShadow:Ie.panel,padding:`0 ${w.lg}`,marginBottom:w.lg,marginLeft:j,marginRight:j}),$.setAttribute("role","tablist"),$.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const a=Array.from($.querySelectorAll('[role="tab"]')),r=a.indexOf(d.activeElement);if(r===-1)return;e.preventDefault();const c=a[(r+(e.key==="ArrowRight"?1:-1)+a.length)%a.length];c.focus(),c.click()});const W=d.createElement("div");Object.assign(W.style,{display:"flex",flexDirection:"column",gap:w.lg,marginTop:"0",paddingLeft:j,paddingRight:j}),P.appendChild(R),P.appendChild($),P.appendChild(W);let G=D.defaultView??"overview",A,I=null,O=null;const z=e=>{O!==e&&(O=e,M())};let F=[];function U(e,a,r){Y?.(e,a,r)}function ce(){return re().map((e,a)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:e.label??String(e.wafer.metadata?.waferId??""),waferIndex:a,statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function J(e,a){Object.assign(e.style,{background:"none",border:"none",borderBottom:a?`2px solid ${B.iconActive}`:"2px solid transparent",color:a?B.iconActive:B.label,fontWeight:a?"700":"500",fontSize:H.body,padding:`${w.sm} ${K} ${w.md}`,cursor:"pointer",marginBottom:"-1px"})}function pe(e,a){const r=d.createElement("button");r.type="button",r.textContent=a;const c=e===G;return J(r,c),r.dataset.on=c?"true":"false",ee(r,"bare"),r.dataset.wmapInsightsTab=e,r.setAttribute("role","tab"),r.setAttribute("aria-selected",c?"true":"false"),r.tabIndex=c?0:-1,r.addEventListener("click",()=>{G!==e&&(G=e,M())}),r}function ue(e){const a=d.createElement("button");return a.type="button",a.textContent=`\u2039 ${e.label}`,J(a,!1),a.dataset.wmapInsightsBack="1",a.addEventListener("click",e.onBack),a}function me(e,a,r,c=!0){const s=X(d),u=Be(E());let h="label";const m=r??"group",f=T.length===1?`bin ${T[0]}`:`bins ${T.join(", ")}`,i=ie?.(),b=new Map(i?.lotYieldSeries.map(l=>[l.waferIndex,l.yieldPercent])),y=l=>({...l,label:l.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:b.get(l.waferIndex),key:l.waferIndex}),g=e.map(y),p=a?.map(l=>({key:l.key,items:l.items.map(y)})),t=()=>p?ne(p,T,h):N(g,T,h),S={title:a?`Yield by ${m} (pass: ${f})`:`Yield by wafer (pass: ${f})`,data:t(),selfControl:{current:h,options:[["yield","Sort: yield"],["label",a?`Sort: ${m}`:"Sort: wafer ID"]],onChange:l=>(h=l,{data:t()})},barColor:()=>Ne,valueLabel:l=>`${l.percent.toFixed(1)}%`,reference:l=>{if(l.length<3)return null;const k=l.map(Q=>Q.percent).sort((Q,xe)=>Q-xe),L=k.length%2?k[(k.length-1)/2]:(k[k.length/2-1]+k[k.length/2])/2;return{value:L,label:`median ${L.toFixed(1)}%`}},drill:p?{onOpenGroup:l=>{const k=p.find(L=>L.key===l.label)?.items??[];return{data:N(k,T,h),title:`Yield by wafer \u2014 ${m}: ${l.label} (pass: ${f})`}},onBack:()=>({data:t(),title:`Yield by ${m} (pass: ${f})`}),groupLabelText:m}:void 0,onOpen:Y?l=>{typeof l.key=="number"&&U(l.key,`Wafer ${l.label}`)}:void 0,ownerDocument:d};let C=null;if(c&&(C=te(S,v),C.card.style.minHeight="360px",s.appendChild(C.card)),a){const l=Fe({title:"Hard bin pareto",groups:a.map(k=>({key:k.key,items:k.items.map(L=>({dies:L.dies,hardBinCounts:L.statsSummary?.stats.hardBinCounts,softBinCounts:L.statsSummary?.stats.softBinCounts}))})),colorScheme:E(),onSaveImage:v,ownerDocument:d});return l.card.style.minHeight="360px",s.appendChild(l.card),{card:s,destroy:()=>{C?.destroy(),l.destroy()}}}let n="hbin";const o=u.forBin,x=e.map(l=>({dies:l.dies,hardBinCounts:l.statsSummary?.stats.hardBinCounts,softBinCounts:l.statsSummary?.stats.softBinCounts})),Z=()=>_e(x,n),Se={title:"Hard bin pareto",data:Z(),selfControl:{current:n,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:l=>(n=l,{data:Z(),title:`${n==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:l=>o(l.binCode??0),ownerDocument:d},q=te(Se,v);return q.card.style.minHeight="360px",s.appendChild(q.card),{card:s,destroy:()=>{C?.destroy(),q.destroy()}}}function _(){const e=d.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...Ve(),display:"flex",flexDirection:"column",gap:w.lg,minWidth:"0"}),e}function fe(e){if(R.innerHTML="",R.style.marginBottom="0",!le)return;const a=Ke(e.map(r=>({metadata:r.metadata})),void 0,{facetableOnly:!1});a&&(a.style.marginBottom="0",R.appendChild(a),R.style.marginBottom=w.xs)}function be(e,a,r,c){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const s=e.map(o=>o.wafer),u=e.map(o=>o.dies),h=e.flatMap(o=>o.dies),m=e.map(o=>o.statsSummary),f=m.every(o=>o!==void 0)?m:void 0,i=se?.()??4,b=[],y=[],p=Me({groups:(c??[{key:"",items:e}]).map(o=>({key:o.key,items:o.items.map(x=>({dies:x.dies,testSpecYield:x.statsSummary?.stats.testSpecYield,functionalYield:x.statsSummary?.stats.functionalYield}))})),testDefs:r,onSaveImage:v,ownerDocument:d});p.card.style.minHeight="300px",b.push(p.card),y.push(p.destroy);const t=ae(u,s,i,T,Xe);if(t.length){const o=oe({title:"Ring yield",mode:"ring",rows:t,colorScheme:E(),onSaveImage:v,ownerDocument:d});b.push(o.card),y.push(o.destroy)}const S=ae(u,s,i,T,Je);if(S.length){const o=oe({title:"Quadrant yield",mode:"quadrant",rows:S,colorScheme:E(),onSaveImage:v,ownerDocument:d});b.push(o.card),y.push(o.destroy)}let C=null;if(a.length){const o=qe(h,a,void 0,f,V,u.map(x=>({dies:x})),void 0,"full");if(o){const x=_();x.appendChild(o),C=x}}let n=null;if(r.length){const o=Qe(h,r,f,V);if(o){const x=_();x.appendChild(o),n=x}}return{elements:b,testValuesCard:C,functionalCard:n,destroy:()=>{for(const o of y)o()}}}function ge(e){const a=T.length===1?`bin ${T[0]}`:`bins ${T.join(", ")}`,r=e.length===1,c=e[0],s=_();Object.assign(s.style,{flexDirection:"row",flexWrap:"wrap",gap:w.md});const u=i=>{const b=i.lastElementChild;return b&&Object.assign(b.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),i};function h(i,b,y){const g=d.createElement("div");Object.assign(g.style,{display:"flex",alignItems:"baseline",gap:w.sm,paddingRight:w.xxl,marginRight:w.md,borderRight:`1px solid ${B.menuBorder}`});const p=d.createElement("div");p.textContent=i,Object.assign(p.style,{fontSize:H.heading,fontWeight:"700",color:B.value,lineHeight:De.tight});const t=d.createElement("div");if(t.textContent=b,Object.assign(t.style,{fontSize:H.body,color:B.label}),g.append(p,t),y){const S=d.createElement("div");S.textContent=y,Object.assign(S.style,{fontSize:H.meta,color:B.label,opacity:Ee.muted}),g.appendChild(S)}return g}if(r){const i=N([{...c,key:c.waferIndex}],T)[0]?.percent;return i!==void 0&&s.appendChild(h(`${i.toFixed(1)}%`,`Yield \xB7 pass: ${a}`)),s.appendChild(h(String(c.dies.length),"Total dies")),u(s)}s.appendChild(h(String(e.length),"Wafers"));const m=N(e.map(i=>({...i,key:i.waferIndex})),T).map(i=>i.percent).filter(i=>Number.isFinite(i));if(m.length){const i=m.reduce((g,p)=>g+p,0)/m.length,b=ne([{key:"all",items:e.map(g=>({...g,key:g.waferIndex}))}],T)[0]?.percent,y=b!==void 0&&b.toFixed(1)!==i.toFixed(1);s.appendChild(h(`${i.toFixed(1)}%`,`Mean per-wafer yield \xB7 pass: ${a}`,y?`${b.toFixed(1)}% by dies`:void 0))}const f=e.map(i=>i.statsSummary);if(f.every(i=>i!==void 0)){const i=f.reduce((y,g)=>y+g.stats.analyzedDies,0),b=f.reduce((y,g)=>y+g.stats.excludedDies,0);s.appendChild(h(i.toLocaleString(),"Dies analysed",b?`${b.toLocaleString()} excluded`:void 0))}else s.appendChild(h(e.reduce((i,b)=>i+b.dies.length,0).toLocaleString(),"Total dies"));return u(s)}function he(e,a,r,c,s){const u=d.createElement("div");Object.assign(u.style,{display:"flex",flexDirection:"column",gap:w.lg});const h=e.length===1&&!c;e.length&&!c&&u.appendChild(ge(e));const m=me(e,c,s,!h);u.appendChild(m.card);const f=be(e,a,r,c);for(const i of f.elements)m.card.appendChild(i);return f.testValuesCard&&u.appendChild(f.testValuesCard),f.functionalCard&&u.appendChild(f.functionalCard),{card:u,destroy:()=>{m.destroy(),f.destroy()}}}function ye(e,a,r,c){const s=X(d);let u={includeLimits:void 0,clipOutliers:!1};const h=n=>{u=n;for(const o of[g,p,t])o!==m&&o?.setAxisPrefs(n)};let m=null;const f=n=>o=>{m=n(),h(o),m=null},i=n=>({...n,testStats:n.statsSummary?.stats.perTestStats}),b=e.map(i),y=r?.map(n=>({key:n.key,items:n.items.map(i)})),g=je({title:"Test value distribution",axisPrefs:u,onAxisPrefsChange:f(()=>g),onTestChange:n=>S(n),selectedTestNumber:I??void 0,items:b,testDefs:a,groups:y,groupLabelText:c,colorScheme:E(),onSaveImage:v,onGroupChange:n=>z(n),onOpen:Y?(n,o)=>U(n,`Wafer ${e.find(x=>x.waferIndex===n)?.label??n}`,o):void 0,ownerDocument:d}),p=Ge({title:"Value histogram",axisPrefs:u,onAxisPrefsChange:f(()=>p),onTestChange:n=>S(n),selectedTestNumber:I??void 0,items:e,testDefs:a,groups:r,onGroupChange:n=>z(n),colorScheme:E(),onSaveImage:v,ownerDocument:d}),t=We({title:"Wafer-to-wafer trend",axisPrefs:u,onAxisPrefsChange:f(()=>t),onTestChange:n=>S(n),selectedTestNumber:I??void 0,items:e.map(n=>({label:n.label,key:n.waferIndex,dies:n.dies,testStats:n.statsSummary?.stats.perTestStats})),testDefs:a,onSaveImage:v,onOpen:Y?(n,o)=>U(n,`Wafer ${e.find(x=>x.waferIndex===n)?.label??n}`,o):void 0,ownerDocument:d}),S=n=>{I=n,g.setTest(n),p.setTest(n),t.setTest(n),C.setTest(n)},C=Ae({title:"Process capability",items:e,testDefs:a,colorScheme:E(),onSaveImage:v,selectedTestNumber:I??void 0,onSelectTest:n=>S(n),ownerDocument:d});return s.append(C.card,g.card,p.card,t.card),{card:s,destroy:()=>{C.destroy(),g.destroy(),p.destroy(),t.destroy()}}}function Ce(e,a,r){const c=X(d),s=Ye({title:"Test scatter",items:e,testDefs:a,groups:r,colorScheme:E(),onSaveImage:v,ownerDocument:d}),u=He({title:"Test correlation matrix",items:e,testDefs:a,colorScheme:E(),onSaveImage:v,onSaveText:V,onSelectPair:(h,m)=>s.setXY(h,m),ownerDocument:d});return c.append(u.card,s.card),{card:c,destroy:()=>{u.destroy(),s.destroy()}}}function M(){for(const t of F)t.destroy();F=[],W.innerHTML="",$.innerHTML="",D.backTab&&$.appendChild(ue(D.backTab));for(const t of Ze)$.appendChild(pe(t.key,t.label));const e=ce();fe(e);const a=we(e,{facetableOnly:!0}).filter(t=>t.splittable),r=d.createElement("div");Object.assign(r.style,{display:"flex",gap:w.md,alignItems:"center",marginLeft:w.xxl,paddingBottom:w.xs});let c;a.length>0?(r.appendChild(ze("Group by:",[{value:"",label:"None"},...a.map(t=>({value:t.key,label:`${t.label} (${t.values.length})`}))],A??"",t=>{A=t||void 0,O=null,M()},{hook:"group-by",ownerDocument:d})),c=a.find(t=>t.key===A)?.label):A=void 0;let s;if(A){const t=new Map,S=[];for(const C of e){const n=Te(C.metadata,A)??ve;t.has(n)||(t.set(n,[]),S.push(n)),t.get(n).push(C)}s=S.map(C=>({key:C,items:t.get(C)}))}O!==null&&!s?.some(t=>t.key===O)&&(O=null),s&&s.length>0&&r.appendChild(Ue(s.map(t=>t.key),"Show:",O,t=>z(t),{ownerDocument:d}).el);let u=null;if(D.onOpenGuide){const t=d.createElement("button");t.type="button",t.innerHTML=Re.help,t.setAttribute("aria-label","User guide"),Object.assign(t.style,{...Le("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),ee(t,"bare"),Pe(t,"User guide"),t.addEventListener("click",()=>D.onOpenGuide()),Object.assign(t.style,{marginLeft:"auto"}),u=t}$.appendChild(r),u&&$.appendChild(u);const h=O!==null?s?.find(t=>t.key===O):void 0,m=h?.items??e,f=h?void 0:s,i=ke(m),b=i.defs,y=b.filter($e);I!==null&&!y.some(t=>t.testNumber===I)&&(I=null);const g=G==="overview"?he(m,y,b,f,c):G==="distributions"?ye(m,y,f,c):Ce(m,y,f),p=i.conflicts.filter(t=>t.excluded);if(p.length>0){const t=d.createElement("div");Object.assign(t.style,{color:B.label,fontSize:H.body,padding:`0 ${j} ${w.sm}`});const S=p.slice(0,6).map(o=>o.testNumber).join(", "),C=p.length>6?`, and ${p.length-6} more`:"",n=p.length===1?"it":"them";t.textContent=`${p.length} test ${p.length===1?"number is":"numbers are"} hidden (${S}${C}) \u2014 the wafers in view describe ${n} differently, so their values cannot be pooled. `+(s&&s.length>0?`Pick one ${c??"group"} under "Show:" to analyse ${n}.`:"Load one test program at a time to analyse them."),t.dataset.wmapWithheldTests=String(p.length),W.appendChild(t)}F.push(g),W.appendChild(g.card)}return{el:P,render:M,destroy:()=>{for(const e of F)e.destroy();P.remove()}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getDieTestValue as
|
|
1
|
+
import{getDieTestValue as y}from"../renderer/buildWaferMap.js";import{resolveTestNumber as $,findTestDef as T}from"../renderer/buildView.js";import{buildBinSection as B}from"./summaryPanel.js";import{buildTestHistogramData as z}from"../stats/histogram.js";import{getColorScheme as N}from"../renderer/colorSchemes.js";import{TRACKING as F,SPACE as H,RADIUS as I,FONT as M,CLR as f,wireTooltip as V}from"./toolbar.js";import{fmt as b}from"../renderer/fmt.js";function d(o,n,e){const r=document.createElement(o);return n&&Object.assign(r.style,n),e!==void 0&&(r.textContent=e),r}function A(o,n,e,r,u){let p=1/0,m=-1/0;for(const l of o){const t=y(l,n);t!==void 0&&(t<p&&(p=t),t>m&&(m=t))}const c=r==="spec"&&e&&(e.limitLow!==void 0||e.limitHigh!==void 0);let a,s;c?(a=e.limitLow!==void 0?e.limitLow:isFinite(p)?p:0,s=e.limitHigh!==void 0?e.limitHigh:isFinite(m)?m:1):(a=isFinite(p)?p:0,s=isFinite(m)?m:1),a===s&&(s=a+1);const x=l=>Math.min(1,Math.max(0,l));if(u&&a>0&&s>0){const l=Math.log10(a),t=Math.log10(s)-l;return h=>h<=0?0:x((Math.log10(h)-l)/t)}return l=>x((l-a)/(s-a))}function W(o,n,e,r,u,p,m){const c=d("div",{display:"flex",flexDirection:"column",gap:H.xxs,minWidth:"0",flex:"1",minHeight:"0"});c.appendChild(d("div",{fontSize:M.meta,fontWeight:"700",letterSpacing:F,textTransform:"uppercase",color:f.label},n?.name?`${n.name} distribution`:"Value distribution"));const a=o[0].rangeLow,s=o[o.length-1].rangeHigh,x=s-a||1,l=i=>Math.min(1,Math.max(0,(i-a)/x)),t=N(r),h=A(u,e,n,p,m),R=Math.max(1,...o.map(i=>i.count)),S=d("div",{position:"relative",display:"flex",alignItems:"flex-end",gap:H.xxs,flex:"1",minHeight:"48px"});for(const i of o){const g=d("div",{flex:"1",minWidth:"2px",background:t.forValue(h((i.rangeLow+i.rangeHigh)/2)),height:`${i.count/R*100}%`,borderRadius:"1px 1px 0 0"});g.dataset.wmapBar="1",V(g,`${b(i.rangeLow,n?.unit)} \u2013 ${b(i.rangeHigh,n?.unit)}: ${i.count}`,{asDataPoint:!0}),S.appendChild(g)}const C=[];for(const[i,g]of[[n?.limitLow,"LSL"],[n?.limitHigh,"USL"]]){if(i===void 0||i<a||i>s)continue;const w=l(i)*100,v=d("div",{position:"absolute",top:"0",bottom:"0",left:`${w}%`,width:"2px",marginLeft:"-1px",background:f.value,boxShadow:`0 0 0 1px ${f.panelBg}`,pointerEvents:"none"});v.dataset.wmapLimitMarker="1",S.appendChild(v),C.push({pct:w,text:`${g} ${b(i,n?.unit)}`})}if(c.appendChild(S),C.length){const i=d("div",{position:"relative",height:"14px"});for(const{pct:g,text:w}of C){const v=Math.min(96,Math.max(4,g));i.appendChild(d("span",{position:"absolute",left:`${v}%`,top:"0",transform:"translateX(-50%)",whiteSpace:"nowrap",fontSize:M.body,fontWeight:"600",color:f.text,background:f.panelBg,border:`1px solid ${f.value}`,borderRadius:I.control,padding:"0 3px",lineHeight:"12px"},w))}c.appendChild(i)}const L=d("div",{display:"flex",justifyContent:"space-between",fontSize:M.body,color:f.label});return L.appendChild(d("span",{},b(o[0].rangeLow,n?.unit))),L.appendChild(d("span",{},b(o[o.length-1].rangeHigh,n?.unit))),c.appendChild(L),c}export function buildMaplessSummary(o,n,e){const{plotMode:r,activeTest:u,hbinDefs:p,sbinDefs:m,colorScheme:c,logScale:a=!1,colorbarRangeMode:s="spec",valueRangeDies:x}=e;if(r==="hardBin"||r==="softBin"){const t=B(o,r==="hardBin"?p:m,r==="hardBin"?"hard":"soft",c);if(t)return t}else if(r==="value"){const{testNumber:l}=$(u??0,n),t=T(n,u??0),h=z([{dies:o}],l,12,t?.limitLow,t?.limitHigh);if(h.length)return W(h,t,l,c,x??o,s,a)}return d("div",{padding:H.xl,fontSize:M.body,color:f.label,textAlign:"center"},"No summary available for this view.")}
|
|
@@ -4,8 +4,8 @@ import type { WaferViewOptions } from './renderWaferMap.js';
|
|
|
4
4
|
import type { RenderableWaferMap } from './renderWaferMap.js';
|
|
5
5
|
import type { LotStatsSummary } from '../stats/types.js';
|
|
6
6
|
import { type WarningsOptions } from './warnings.js';
|
|
7
|
-
import type { SummaryPanelOptions } from './summaryPanel.js';
|
|
8
|
-
import {
|
|
7
|
+
import type { SummaryPanelOptions, FindingsNotice } from './summaryPanel.js';
|
|
8
|
+
import type { InsightsOptions } from './insightsTab.js';
|
|
9
9
|
import type { DieListDisplayOptions } from './dieList.js';
|
|
10
10
|
/**
|
|
11
11
|
* The data and display overrides for a single gallery card.
|
|
@@ -101,6 +101,14 @@ export interface GalleryOptions {
|
|
|
101
101
|
* map for a finding to highlight against.
|
|
102
102
|
*/
|
|
103
103
|
summaryPanel?: SummaryPanelOptions;
|
|
104
|
+
/**
|
|
105
|
+
* A row at the top of the Findings section stating that a category of finding
|
|
106
|
+
* is not present, and optionally offering to compute it — see
|
|
107
|
+
* {@link FindingsNotice}. wmap never raises this itself: only the host knows
|
|
108
|
+
* what analysis it chose to skip and what running it would cost. Replaceable
|
|
109
|
+
* afterwards via the controller's `setFindingsNotice`.
|
|
110
|
+
*/
|
|
111
|
+
findingsNotice?: FindingsNotice;
|
|
104
112
|
/**
|
|
105
113
|
* Display preferences for the lot-wide "View die list" link inside the
|
|
106
114
|
* Summary panel — every die across every wafer, pooled, with a Wafer
|
|
@@ -181,6 +189,11 @@ export interface GalleryController {
|
|
|
181
189
|
setFallbackFormat(format: 'si' | 'engineering'): void;
|
|
182
190
|
/** Replace the lot-level stats summary used by the built-in Summary panel. */
|
|
183
191
|
setLotStatsSummary(summary: LotStatsSummary | undefined): void;
|
|
192
|
+
/**
|
|
193
|
+
* Replace the lot Findings-section notice (see `FindingsNotice`). Pass
|
|
194
|
+
* `undefined` to clear it once the offered analysis has been run.
|
|
195
|
+
*/
|
|
196
|
+
setFindingsNotice(notice: FindingsNotice | undefined): void;
|
|
184
197
|
/**
|
|
185
198
|
* Set the number of columns in the gallery grid. Pass `undefined` to restore
|
|
186
199
|
* the auto-computed layout based on die pitch.
|