@wafertools/wafermap 0.30.0 → 0.30.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/AGENTS.md +68 -9
  2. package/CHANGELOG.md +247 -0
  3. package/README.md +3 -3
  4. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  5. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
  6. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  7. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  9. package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
  10. package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +1 -0
  11. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  13. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  15. package/dist/packages/canvas-adapter/chunked.d.ts +45 -0
  16. package/dist/packages/canvas-adapter/chunked.js +1 -0
  17. package/dist/packages/canvas-adapter/deprecated.d.ts +0 -3
  18. package/dist/packages/canvas-adapter/deprecated.js +1 -1
  19. package/dist/packages/canvas-adapter/dieList.js +1 -1
  20. package/dist/packages/canvas-adapter/exportName.d.ts +68 -0
  21. package/dist/packages/canvas-adapter/exportName.js +1 -0
  22. package/dist/packages/canvas-adapter/icons.js +2 -2
  23. package/dist/packages/canvas-adapter/index.d.ts +2 -1
  24. package/dist/packages/canvas-adapter/index.js +1 -1
  25. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  26. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +80 -3
  27. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  28. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +24 -2
  29. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  30. package/dist/packages/canvas-adapter/summaryPanel.d.ts +76 -15
  31. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  32. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  33. package/dist/packages/canvas-adapter/toolbar.d.ts +6 -0
  34. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  35. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  36. package/dist/packages/canvas-adapter/userGuideHtml.js +15 -8
  37. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  38. package/dist/packages/canvas-adapter/version.js +1 -1
  39. package/dist/packages/canvas-adapter/warnings.js +1 -1
  40. package/dist/packages/core/aggregates.js +1 -1
  41. package/dist/packages/core/deprecated.d.ts +8 -12
  42. package/dist/packages/core/deprecated.js +1 -1
  43. package/dist/packages/core/index.d.ts +1 -1
  44. package/dist/packages/core/index.js +1 -1
  45. package/dist/packages/core/utils.d.ts +67 -0
  46. package/dist/packages/core/utils.js +1 -1
  47. package/dist/packages/core/waferLabel.d.ts +12 -2
  48. package/dist/packages/core/waferLabel.js +1 -1
  49. package/dist/packages/renderer/binColors.d.ts +52 -0
  50. package/dist/packages/renderer/binColors.js +1 -1
  51. package/dist/packages/renderer/buildView.js +1 -1
  52. package/dist/packages/renderer/buildWaferMap.d.ts +30 -5
  53. package/dist/packages/renderer/buildWaferMap.js +1 -1
  54. package/dist/packages/renderer/deprecate.d.ts +7 -0
  55. package/dist/packages/renderer/deprecate.js +1 -1
  56. package/dist/packages/renderer/deprecated.d.ts +2 -2
  57. package/dist/packages/renderer/deprecated.js +1 -1
  58. package/dist/packages/renderer/index.d.ts +2 -1
  59. package/dist/packages/renderer/index.js +1 -1
  60. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  61. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  62. package/dist/packages/stats/capability.d.ts +72 -1
  63. package/dist/packages/stats/capability.js +1 -1
  64. package/dist/packages/stats/correlation.d.ts +30 -0
  65. package/dist/packages/stats/correlation.js +1 -1
  66. package/dist/packages/stats/deprecated.d.ts +30 -35
  67. package/dist/packages/stats/deprecated.js +1 -1
  68. package/dist/packages/stats/findingsNarrative.js +1 -1
  69. package/dist/packages/stats/index.d.ts +4 -2
  70. package/dist/packages/stats/index.js +1 -1
  71. package/dist/packages/stats/math.d.ts +27 -2
  72. package/dist/packages/stats/math.js +1 -1
  73. package/dist/packages/stats/normalizeInput.d.ts +10 -0
  74. package/dist/packages/stats/normalizeInput.js +1 -0
  75. package/dist/packages/stats/regions.d.ts +2 -0
  76. package/dist/packages/stats/regions.js +1 -1
  77. package/dist/packages/stats/renderFindingsReport.js +1 -1
  78. package/dist/packages/stats/renderSummaryReport.d.ts +39 -1
  79. package/dist/packages/stats/renderSummaryReport.js +27 -27
  80. package/dist/packages/stats/reportHtml.d.ts +0 -1
  81. package/dist/packages/stats/reportHtml.js +10 -10
  82. package/dist/packages/stats/summaryFigures.d.ts +47 -0
  83. package/dist/packages/stats/summaryFigures.js +1 -0
  84. package/dist/packages/stats/types.d.ts +118 -0
  85. package/llms.txt +12 -7
  86. package/package.json +2 -2
@@ -1,3 +1,3 @@
1
- export declare const WMAP_VERSION = "0.30.0";
2
- export declare const WMAP_BUILD_TIME = "2026-09-15T15:32:11.763Z";
1
+ export declare const WMAP_VERSION = "0.30.2";
2
+ export declare const WMAP_BUILD_TIME = "2026-09-21T06:19:56.056Z";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- export const WMAP_VERSION="0.30.0",WMAP_BUILD_TIME="2026-09-15T15:32:11.763Z";
1
+ export const WMAP_VERSION="0.30.2",WMAP_BUILD_TIME="2026-09-21T06:19:56.056Z";
@@ -1 +1 @@
1
- import{SHADOW as S,TRACKING as O,SPACE as s,RADIUS as E,FONT as y,CLR as d,Z_BASE as B}from"./toolbar.js";const v={error:0,warning:1,info:2};export function severityOf(t){return t.severity??"warning"}export function collectWarnings(t){const o=[],i=new Set,n=e=>{if(!e?.message)return;const r=`${e.code}\0${e.message}`;i.has(r)||(i.add(r),o.push(e))};for(const e of t.result?.warnings??[])n(e);for(const e of t.statsSummary?.stats.warnings??[])n(e);for(const e of t.lotStatsSummary?.perWafer??[])for(const r of e.summary.stats.warnings??[])n(r);for(const e of t.extra??[])n(e);return o.sort((e,r)=>v[severityOf(e)]-v[severityOf(r)])}function $(t){return t==="error"?{bg:d.errBg,border:d.errBorder,text:d.errText}:{bg:d.warnBg,border:d.warnBorder,text:d.warnText}}function w(t){return t==="error"?"\u26D4":t==="info"?"\u2139":"\u26A0"}export const SHORT_LABEL={"partial-coverage":"Partial wafer coverage","geometry-conflict":"Geometry conflict","non-standard-diameter":"Non-standard wafer diameter","diameter-exceeds-die-extent":"Diameter exceeds die extent","edge-exclusion-exceeds-radius":"Edge exclusion exceeds radius","test-count-capped":"Test analysis skipped","test-def-collision":"Test definitions disagree","analysis-option-corrected":"Analysis option corrected","bin-colors-shared":"Bins share a colour","pass-bins-mixed":"Pass bins differ by wafer","ring-count-mixed":"Ring counts differ by wafer","input-field-removed":"Removed input name ignored"};function C(t){const o=t.code?SHORT_LABEL[t.code]:void 0;if(o)return o;const i=t.message.split(/(?<=\.)\s/)[0]??t.message;return i.length>60?`${i.slice(0,57)}\u2026`:i}export function buildWarningsBanner(t,o=document){const i=o.createElement("div");Object.assign(i.style,{marginBottom:s.lg});for(const n of t){const e=severityOf(n),r=$(e),m=o.createElement("div");Object.assign(m.style,{background:r.bg,border:`1px solid ${r.border}`,borderRadius:E.control,marginBottom:s.sm,fontSize:y.body,color:r.text,lineHeight:"1.5"}),m.setAttribute("role","status");const a=o.createElement("button");a.type="button",Object.assign(a.style,{display:"flex",alignItems:"center",gap:s.sm,width:"100%",background:"none",border:"none",font:"inherit",color:"inherit",textAlign:"left",padding:`${s.sm} ${s.lg}`,cursor:"pointer"});const g=o.createElement("span");Object.assign(g.style,{flex:"1",minWidth:"0"}),g.textContent=`${w(e)} ${C(n)}`;const l=o.createElement("span");Object.assign(l.style,{flexShrink:"0"}),a.append(g,l);const p=o.createElement("div");Object.assign(p.style,{padding:`0 ${s.lg} ${s.sm}`}),p.textContent=n.message;let c=!1;const f=()=>{l.textContent=c?"\u25B4":"\u25BE",p.style.display=c?"block":"none",a.setAttribute("aria-expanded",String(c)),a.setAttribute("aria-label",`${C(n)}. ${c?"Click to collapse.":"Click to expand for detail."}`)};f(),a.addEventListener("click",()=>{c=!c,f()}),a.addEventListener("mouseenter",()=>{l.style.opacity="0.65"}),a.addEventListener("mouseleave",()=>{l.style.opacity="1"}),m.append(a,p),i.appendChild(m)}return i}export function buildWarningsMenuEl(t,o,i=window){const n=i.document,e=n.createElement("div"),r=280,a=t.left+r<=(i.innerWidth??1/0)?t.left:Math.max(4,t.right-r);Object.assign(e.style,{position:"fixed",top:`${t.bottom+4}px`,left:`${a}px`,background:d.menuBg,border:`1px solid ${d.menuBorder}`,borderRadius:E.control,boxShadow:S.menu,zIndex:B,width:`${r}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),e.setAttribute("role","dialog"),e.setAttribute("aria-label","Data warnings");const g=n.createElement("div");Object.assign(g.style,{padding:`${s.sm} ${s.xl} ${s.xs}`,fontSize:y.body,fontWeight:"700",letterSpacing:O,textTransform:"uppercase",color:d.label}),g.textContent=o.length===1?"1 warning":`${o.length} warnings`,e.appendChild(g);for(const l of o){const p=severityOf(l),c=$(p),f=n.createElement("div");Object.assign(f.style,{display:"flex",gap:s.md,padding:`${s.md} ${s.xl}`,fontSize:y.body,lineHeight:"1.5",color:d.text,borderTop:`1px solid ${d.menuBorder}`,alignItems:"flex-start"});const x=n.createElement("span");Object.assign(x.style,{color:c.text,flexShrink:"0"}),x.textContent=w(p),f.appendChild(x);const u=n.createElement("div"),b=n.createElement("div");Object.assign(b.style,{fontWeight:"700",color:c.text,marginBottom:s.xxs}),b.textContent=l.code,u.appendChild(b);const h=n.createElement("div");h.textContent=l.message,u.appendChild(h),f.appendChild(u),e.appendChild(f)}return e}
1
+ import{SHADOW as S,TRACKING as O,SPACE as s,RADIUS as v,FONT as y,CLR as d,Z_BASE as B}from"./toolbar.js";const E={error:0,warning:1,info:2};export function severityOf(t){return t.severity??"warning"}export function collectWarnings(t){const o=[],i=new Set,n=e=>{if(!e?.message)return;const r=`${e.code}\0${e.message}`;i.has(r)||(i.add(r),o.push(e))};for(const e of t.result?.warnings??[])n(e);for(const e of t.statsSummary?.stats.warnings??[])n(e);for(const e of t.lotStatsSummary?.perWafer??[])for(const r of e.summary.stats.warnings??[])n(r);for(const e of t.extra??[])n(e);return o.sort((e,r)=>E[severityOf(e)]-E[severityOf(r)])}function $(t){return t==="error"?{bg:d.errBg,border:d.errBorder,text:d.errText}:{bg:d.warnBg,border:d.warnBorder,text:d.warnText}}function w(t){return t==="error"?"\u26D4":t==="info"?"\u2139":"\u26A0"}export const SHORT_LABEL={"partial-coverage":"Partial wafer coverage","geometry-conflict":"Geometry conflict","non-standard-diameter":"Non-standard wafer diameter","diameter-exceeds-die-extent":"Diameter exceeds die extent","edge-exclusion-exceeds-radius":"Edge exclusion exceeds radius","test-count-capped":"Test analysis skipped","test-def-collision":"Test definitions disagree","analysis-option-corrected":"Analysis option corrected","bin-colors-shared":"Bins share a colour","pass-bins-mixed":"Pass bins differ by wafer","ring-count-mixed":"Ring counts differ by wafer","input-field-removed":"Removed input name ignored","input-values-not-numbers":"Bins or test values are text"};function C(t){const o=t.code?SHORT_LABEL[t.code]:void 0;if(o)return o;const i=t.message.split(/(?<=\.)\s/)[0]??t.message;return i.length>60?`${i.slice(0,57)}\u2026`:i}export function buildWarningsBanner(t,o=document){const i=o.createElement("div");Object.assign(i.style,{marginBottom:s.lg});for(const n of t){const e=severityOf(n),r=$(e),m=o.createElement("div");Object.assign(m.style,{background:r.bg,border:`1px solid ${r.border}`,borderRadius:v.control,marginBottom:s.sm,fontSize:y.body,color:r.text,lineHeight:"1.5"}),m.setAttribute("role","status");const a=o.createElement("button");a.type="button",Object.assign(a.style,{display:"flex",alignItems:"center",gap:s.sm,width:"100%",background:"none",border:"none",font:"inherit",color:"inherit",textAlign:"left",padding:`${s.sm} ${s.lg}`,cursor:"pointer"});const g=o.createElement("span");Object.assign(g.style,{flex:"1",minWidth:"0"}),g.textContent=`${w(e)} ${C(n)}`;const l=o.createElement("span");Object.assign(l.style,{flexShrink:"0"}),a.append(g,l);const p=o.createElement("div");Object.assign(p.style,{padding:`0 ${s.lg} ${s.sm}`}),p.textContent=n.message;let c=!1;const f=()=>{l.textContent=c?"\u25B4":"\u25BE",p.style.display=c?"block":"none",a.setAttribute("aria-expanded",String(c)),a.setAttribute("aria-label",`${C(n)}. ${c?"Click to collapse.":"Click to expand for detail."}`)};f(),a.addEventListener("click",()=>{c=!c,f()}),a.addEventListener("mouseenter",()=>{l.style.opacity="0.65"}),a.addEventListener("mouseleave",()=>{l.style.opacity="1"}),m.append(a,p),i.appendChild(m)}return i}export function buildWarningsMenuEl(t,o,i=window){const n=i.document,e=n.createElement("div"),r=280,a=t.left+r<=(i.innerWidth??1/0)?t.left:Math.max(4,t.right-r);Object.assign(e.style,{position:"fixed",top:`${t.bottom+4}px`,left:`${a}px`,background:d.menuBg,border:`1px solid ${d.menuBorder}`,borderRadius:v.control,boxShadow:S.menu,zIndex:B,width:`${r}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),e.setAttribute("role","dialog"),e.setAttribute("aria-label","Data warnings");const g=n.createElement("div");Object.assign(g.style,{padding:`${s.sm} ${s.xl} ${s.xs}`,fontSize:y.body,fontWeight:"700",letterSpacing:O,textTransform:"uppercase",color:d.label}),g.textContent=o.length===1?"1 warning":`${o.length} warnings`,e.appendChild(g);for(const l of o){const p=severityOf(l),c=$(p),f=n.createElement("div");Object.assign(f.style,{display:"flex",gap:s.md,padding:`${s.md} ${s.xl}`,fontSize:y.body,lineHeight:"1.5",color:d.text,borderTop:`1px solid ${d.menuBorder}`,alignItems:"flex-start"});const x=n.createElement("span");Object.assign(x.style,{color:c.text,flexShrink:"0"}),x.textContent=w(p),f.appendChild(x);const u=n.createElement("div"),b=n.createElement("div");Object.assign(b.style,{fontWeight:"700",color:c.text,marginBottom:s.xxs}),b.textContent=l.code,u.appendChild(b);const h=n.createElement("div");h.textContent=l.message,u.appendChild(h),f.appendChild(u),e.appendChild(f)}return e}
@@ -1 +1 @@
1
- import{getDieKey as g}from"./dies.js";export function aggregateValues(u,s,l=0){if(!u.length)return[];const n=new Map,t=new Map;for(const d of u)for(const o of d){const e=g(o),f=o.testValues?.[l];f!==void 0?(n.has(e)||(n.set(e,[]),t.set(e,o)),n.get(e).push(f)):t.has(e)||t.set(e,o)}const r=[];for(const[d,o]of t){const e=n.get(d);if(!e?.length){r.push({...o,testValues:void 0,testPass:void 0});continue}let f;if(s==="mean")f=e.reduce((i,a)=>i+a,0)/e.length;else if(s==="median"){const i=[...e].sort((c,h)=>c-h),a=Math.floor(i.length/2);f=i.length%2?i[a]:(i[a-1]+i[a])/2}else if(s==="stddev"){if(e.length<2){r.push({...o,testValues:{0:0},testPass:void 0});continue}const i=e.reduce((a,c)=>a+c,0)/e.length;f=Math.sqrt(e.reduce((a,c)=>a+(c-i)**2,0)/(e.length-1))}else s==="min"?f=Math.min(...e):s==="max"?f=Math.max(...e):f=e.length;r.push({...o,testValues:{0:f},testPass:void 0})}return r}export function getUniqueBins(u,s="hard"){const l=new Set;for(const n of u){const t=s==="soft"?n.sbin:n.hbin;t!==void 0&&l.add(t)}return[...l].sort((n,t)=>n-t)}export function aggregateBinCounts(u,s,l="hard"){if(!u.length)return[];const n=new Map;for(const t of u)for(const r of t)if((l==="soft"?r.sbin:r.hbin)===s){const o=g(r);n.set(o,(n.get(o)??0)+1)}return(u[0]??[]).map(t=>({...t,testValues:{0:n.get(g(t))??0},testPass:void 0,...l==="soft"?{sbin:s}:{hbin:s}}))}
1
+ import{minOf as p,maxOf as m}from"./utils.js";import{getDieKey as g}from"./dies.js";export function aggregateValues(a,s,l=0){if(!a.length)return[];const n=new Map,t=new Map;for(const d of a)for(const o of d){const e=g(o),f=o.testValues?.[l];f!==void 0?(n.has(e)||(n.set(e,[]),t.set(e,o)),n.get(e).push(f)):t.has(e)||t.set(e,o)}const r=[];for(const[d,o]of t){const e=n.get(d);if(!e?.length){r.push({...o,testValues:void 0,testPass:void 0});continue}let f;if(s==="mean")f=e.reduce((i,u)=>i+u,0)/e.length;else if(s==="median"){const i=[...e].sort((c,h)=>c-h),u=Math.floor(i.length/2);f=i.length%2?i[u]:(i[u-1]+i[u])/2}else if(s==="stddev"){if(e.length<2){r.push({...o,testValues:{0:0},testPass:void 0});continue}const i=e.reduce((u,c)=>u+c,0)/e.length;f=Math.sqrt(e.reduce((u,c)=>u+(c-i)**2,0)/(e.length-1))}else s==="min"?f=p(e):s==="max"?f=m(e):f=e.length;r.push({...o,testValues:{0:f},testPass:void 0})}return r}export function getUniqueBins(a,s="hard"){const l=new Set;for(const n of a){const t=s==="soft"?n.sbin:n.hbin;t!==void 0&&l.add(t)}return[...l].sort((n,t)=>n-t)}export function aggregateBinCounts(a,s,l="hard"){if(!a.length)return[];const n=new Map;for(const t of a)for(const r of t)if((l==="soft"?r.sbin:r.hbin)===s){const o=g(r);n.set(o,(n.get(o)??0)+1)}return(a[0]??[]).map(t=>({...t,testValues:{0:n.get(g(t))??0},testPass:void 0,...l==="soft"?{sbin:s}:{hbin:s}}))}
@@ -2,18 +2,18 @@ import { createWafer as createWaferImpl } from './wafer.js';
2
2
  import { generateDies as generateDiesImpl, isPositionedDie as isPositionedDieImpl } from './dies.js';
3
3
  import { clipDiesToWafer as clipDiesToWaferImpl, applyOrientation as applyOrientationImpl, transformDies as transformDiesImpl, mapDataToDies as mapDataToDiesImpl, isInsideWafer as isInsideWaferImpl, affineIdentity as affineIdentityImpl, affineRotation as affineRotationImpl, affineMirror as affineMirrorImpl, affineCompose as affineComposeImpl, affineInvert as affineInvertImpl, affinePoint as affinePointImpl, affineVector as affineVectorImpl, affineSwapsAxes as affineSwapsAxesImpl } from './transforms.js';
4
4
  import { applyProbeSequence as applyProbeSequenceImpl } from './probe.js';
5
- import { generateReticleGrid as generateReticleGridImpl, getReticleCell as getReticleCellImpl } from './reticle.js';
5
+ import { generateReticleGrid as generateReticleGridImpl } from './reticle.js';
6
6
  import { classifyDie as classifyDieImpl, getRingLabel as getRingLabelImpl } from './classify.js';
7
7
  import { aggregateValues as aggregateValuesImpl, aggregateBinCounts as aggregateBinCountsImpl, getUniqueBins as getUniqueBinsImpl } from './aggregates.js';
8
- import { metadataDisplayValue as metadataDisplayValueImpl, metadataCategoricalValue as metadataCategoricalValueImpl } from './metadata.js';
8
+ import { metadataCategoricalValue as metadataCategoricalValueImpl } from './metadata.js';
9
9
  import { resolveGridPitch as resolveGridPitchImpl } from './inference/pitch.js';
10
- /** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
10
+ /** @deprecated Removed in 0.31.0. For a die layout with no test data use `buildWaferMap({ layout: true, waferConfig, dieConfig })`; otherwise build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
11
11
  export declare const createWafer: typeof createWaferImpl;
12
- /** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
12
+ /** @deprecated Removed in 0.31.0. For a die layout with no test data use `buildWaferMap({ layout: true, waferConfig, dieConfig })`, which keeps every site fully on the wafer. */
13
13
  export declare const generateDies: typeof generateDiesImpl;
14
14
  /** @deprecated Removed in 0.31.0. Use `hasPosition` to test whether a die has a position. */
15
15
  export declare const isPositionedDie: typeof isPositionedDieImpl;
16
- /** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
16
+ /** @deprecated Removed in 0.31.0. For a die layout with no test data use `buildWaferMap({ layout: true, waferConfig, dieConfig })`, which keeps every site fully on the wafer. */
17
17
  export declare const clipDiesToWafer: typeof clipDiesToWaferImpl;
18
18
  /** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
19
19
  export declare const applyOrientation: typeof applyOrientationImpl;
@@ -43,11 +43,9 @@ export declare const affineSwapsAxes: typeof affineSwapsAxesImpl;
43
43
  export declare const applyProbeSequence: typeof applyProbeSequenceImpl;
44
44
  /** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
45
45
  export declare const generateReticleGrid: typeof generateReticleGridImpl;
46
- /** @deprecated Removed in 0.31.0. With `reticleConfig` set, each die's tooltip shows its reticle cell. */
47
- export declare const getReticleCell: typeof getReticleCellImpl;
48
- /** @deprecated Removed in 0.31.0. It is an internal helper that was exported by accident; `buildWaferMap`, `analyzeWaferMap` and the renderers already apply it. */
46
+ /** @deprecated Removed in 0.31.0. A die's ring and quadrant are in the die list's CSV export (Ring and Quadrant columns); ring and quadrant yield is `stats.regionYield.` */
49
47
  export declare const classifyDie: typeof classifyDieImpl;
50
- /** @deprecated Removed in 0.31.0. It is an internal helper that was exported by accident; `buildWaferMap`, `analyzeWaferMap` and the renderers already apply it. */
48
+ /** @deprecated Removed in 0.31.0. Ring labels come with ring yield (`stats.regionYield.ring[].label`) and ring findings (`comparison.left`). */
51
49
  export declare const getRingLabel: typeof getRingLabelImpl;
52
50
  /** @deprecated Removed in 0.31.0. Stack wafers with `buildWaferMap`'s `lotStack` option. */
53
51
  export declare const aggregateValues: typeof aggregateValuesImpl;
@@ -55,9 +53,7 @@ export declare const aggregateValues: typeof aggregateValuesImpl;
55
53
  export declare const aggregateBinCounts: typeof aggregateBinCountsImpl;
56
54
  /** @deprecated Removed in 0.31.0. Stack wafers with `buildWaferMap`'s `lotStack` option. */
57
55
  export declare const getUniqueBins: typeof getUniqueBinsImpl;
58
- /** @deprecated Removed in 0.31.0. It is an internal helper that was exported by accident; `buildWaferMap`, `analyzeWaferMap` and the renderers already apply it. */
59
- export declare const metadataDisplayValue: typeof metadataDisplayValueImpl;
60
- /** @deprecated Removed in 0.31.0. It is an internal helper that was exported by accident; `buildWaferMap`, `analyzeWaferMap` and the renderers already apply it. */
56
+ /** @deprecated Removed in 0.31.0. Use `metadataDisplayValue` for a metadata value's text. */
61
57
  export declare const metadataCategoricalValue: typeof metadataCategoricalValueImpl;
62
58
  /** @deprecated Removed in 0.31.0. `buildWaferMap` resolves the die pitch itself; each built die's width and height carry it. */
63
59
  export declare const resolveGridPitch: typeof resolveGridPitchImpl;
@@ -1 +1 @@
1
- import{deprecated as e}from"../renderer/deprecate.js";import{createWafer as s}from"./wafer.js";import{generateDies as r,isPositionedDie as n}from"./dies.js";import{clipDiesToWafer as p,applyOrientation as l,transformDies as f,mapDataToDies as c,isInsideWafer as m,affineIdentity as g,affineRotation as I,affineMirror as d,affineCompose as $,affineInvert as D,affinePoint as u,affineVector as x,affineSwapsAxes as h}from"./transforms.js";import{applyProbeSequence as C}from"./probe.js";import{generateReticleGrid as y,getReticleCell as P}from"./reticle.js";import{classifyDie as V,getRingLabel as W}from"./classify.js";import{aggregateValues as w,aggregateBinCounts as b,getUniqueBins as R}from"./aggregates.js";import{metadataDisplayValue as A,metadataCategoricalValue as E}from"./metadata.js";import{resolveGridPitch as T}from"./inference/pitch.js";const a="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.",t="It belongs to the low-level drawing pipeline, which is being withdrawn: build with buildWaferMap and draw with renderWaferMap or renderWaferGallery.",S="Use hasPosition to test whether a die has a position.",M="buildWaferMap maps results onto dies.",_="With reticleConfig set, each die's tooltip shows its reticle cell.",i="It is an internal helper that was exported by accident; buildWaferMap, analyzeWaferMap and the renderers already apply it.",o="Stack wafers with buildWaferMap's lotStack option.",v="buildWaferMap resolves the die pitch itself; each built die's width and height carry it.";export const createWafer=e(s,"createWafer",`${t} ${a}`),generateDies=e(r,"generateDies",`${t} ${a}`),isPositionedDie=e(n,"isPositionedDie",`${S} ${a}`),clipDiesToWafer=e(p,"clipDiesToWafer",`${t} ${a}`),applyOrientation=e(l,"applyOrientation",`${t} ${a}`),transformDies=e(f,"transformDies",`${t} ${a}`),mapDataToDies=e(c,"mapDataToDies",`${M} ${a}`),isInsideWafer=e(m,"isInsideWafer",`${t} ${a}`),affineIdentity=e(g,"affineIdentity",`${t} ${a}`),affineRotation=e(I,"affineRotation",`${t} ${a}`),affineMirror=e(d,"affineMirror",`${t} ${a}`),affineCompose=e($,"affineCompose",`${t} ${a}`),affineInvert=e(D,"affineInvert",`${t} ${a}`),affinePoint=e(u,"affinePoint",`${t} ${a}`),affineVector=e(x,"affineVector",`${t} ${a}`),affineSwapsAxes=e(h,"affineSwapsAxes",`${t} ${a}`),applyProbeSequence=e(C,"applyProbeSequence",`${t} ${a}`),generateReticleGrid=e(y,"generateReticleGrid",`${t} ${a}`),getReticleCell=e(P,"getReticleCell",`${_} ${a}`),classifyDie=e(V,"classifyDie",`${i} ${a}`),getRingLabel=e(W,"getRingLabel",`${i} ${a}`),aggregateValues=e(w,"aggregateValues",`${o} ${a}`),aggregateBinCounts=e(b,"aggregateBinCounts",`${o} ${a}`),getUniqueBins=e(R,"getUniqueBins",`${o} ${a}`),metadataDisplayValue=e(A,"metadataDisplayValue",`${i} ${a}`),metadataCategoricalValue=e(E,"metadataCategoricalValue",`${i} ${a}`),resolveGridPitch=e(T,"resolveGridPitch",`${v} ${a}`);
1
+ import{deprecated as e}from"../renderer/deprecate.js";import{createWafer as o}from"./wafer.js";import{generateDies as r,isPositionedDie as n}from"./dies.js";import{clipDiesToWafer as s,applyOrientation as f,transformDies as p,mapDataToDies as l,isInsideWafer as d,affineIdentity as c,affineRotation as g,affineMirror as m,affineCompose as u,affineInvert as I,affinePoint as $,affineVector as h,affineSwapsAxes as D}from"./transforms.js";import{applyProbeSequence as y}from"./probe.js";import{generateReticleGrid as w}from"./reticle.js";import{classifyDie as x,getRingLabel as C}from"./classify.js";import{aggregateValues as b,aggregateBinCounts as W,getUniqueBins as P}from"./aggregates.js";import{metadataCategoricalValue as V}from"./metadata.js";import{resolveGridPitch as S}from"./inference/pitch.js";const a="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.",t="It belongs to the low-level drawing pipeline, which is being withdrawn: build with buildWaferMap and draw with renderWaferMap or renderWaferGallery.",A="Use hasPosition to test whether a die has a position.",M="buildWaferMap maps results onto dies.",i="Stack wafers with buildWaferMap's lotStack option.",R="buildWaferMap resolves the die pitch itself; each built die's width and height carry it.";export const createWafer=e(o,"createWafer",`For a die layout with no test data use buildWaferMap({ layout: true, waferConfig, dieConfig }); otherwise build with buildWaferMap and draw with renderWaferMap or renderWaferGallery. ${a}`),generateDies=e(r,"generateDies",`For a die layout with no test data use buildWaferMap({ layout: true, waferConfig, dieConfig }), which keeps every site fully on the wafer. ${a}`),isPositionedDie=e(n,"isPositionedDie",`${A} ${a}`),clipDiesToWafer=e(s,"clipDiesToWafer",`For a die layout with no test data use buildWaferMap({ layout: true, waferConfig, dieConfig }), which keeps every site fully on the wafer. ${a}`),applyOrientation=e(f,"applyOrientation",`${t} ${a}`),transformDies=e(p,"transformDies",`${t} ${a}`),mapDataToDies=e(l,"mapDataToDies",`${M} ${a}`),isInsideWafer=e(d,"isInsideWafer",`${t} ${a}`),affineIdentity=e(c,"affineIdentity",`${t} ${a}`),affineRotation=e(g,"affineRotation",`${t} ${a}`),affineMirror=e(m,"affineMirror",`${t} ${a}`),affineCompose=e(u,"affineCompose",`${t} ${a}`),affineInvert=e(I,"affineInvert",`${t} ${a}`),affinePoint=e($,"affinePoint",`${t} ${a}`),affineVector=e(h,"affineVector",`${t} ${a}`),affineSwapsAxes=e(D,"affineSwapsAxes",`${t} ${a}`),applyProbeSequence=e(y,"applyProbeSequence",`${t} ${a}`),generateReticleGrid=e(w,"generateReticleGrid",`${t} ${a}`),classifyDie=e(x,"classifyDie",`A die's ring and quadrant are in the die list's CSV export (Ring and Quadrant columns); ring and quadrant yield is stats.regionYield. ${a}`),getRingLabel=e(C,"getRingLabel",`Ring labels come with ring yield (stats.regionYield.ring[].label) and ring findings (comparison.left). ${a}`),aggregateValues=e(b,"aggregateValues",`${i} ${a}`),aggregateBinCounts=e(W,"aggregateBinCounts",`${i} ${a}`),getUniqueBins=e(P,"getUniqueBins",`${i} ${a}`),metadataCategoricalValue=e(V,"metadataCategoricalValue",`Use metadataDisplayValue for a metadata value's text. ${a}`),resolveGridPitch=e(S,"resolveGridPitch",`${R} ${a}`);
@@ -7,5 +7,5 @@ export * from './reticle.js';
7
7
  export type { DataRow, MapOptions, TransformOptions, Affine, CoordFrame } from './transforms.js';
8
8
  export * from './wafer.js';
9
9
  export type { PitchResult } from './inference/pitch.js';
10
- export { createWafer, generateDies, isPositionedDie, clipDiesToWafer, applyOrientation, transformDies, mapDataToDies, isInsideWafer, affineIdentity, affineRotation, affineMirror, affineCompose, affineInvert, affinePoint, affineVector, affineSwapsAxes, applyProbeSequence, generateReticleGrid, getReticleCell, classifyDie, getRingLabel, aggregateValues, aggregateBinCounts, getUniqueBins, metadataDisplayValue, metadataCategoricalValue, resolveGridPitch, } from './deprecated.js';
10
+ export { createWafer, generateDies, isPositionedDie, clipDiesToWafer, applyOrientation, transformDies, mapDataToDies, isInsideWafer, affineIdentity, affineRotation, affineMirror, affineCompose, affineInvert, affinePoint, affineVector, affineSwapsAxes, applyProbeSequence, generateReticleGrid, classifyDie, getRingLabel, aggregateValues, aggregateBinCounts, getUniqueBins, metadataCategoricalValue, resolveGridPitch, } from './deprecated.js';
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- export*from"./aggregates.js";export*from"./classify.js";export*from"./dies.js";export*from"./metadata.js";export*from"./probe.js";export*from"./reticle.js";export*from"./wafer.js";export{createWafer,generateDies,isPositionedDie,clipDiesToWafer,applyOrientation,transformDies,mapDataToDies,isInsideWafer,affineIdentity,affineRotation,affineMirror,affineCompose,affineInvert,affinePoint,affineVector,affineSwapsAxes,applyProbeSequence,generateReticleGrid,getReticleCell,classifyDie,getRingLabel,aggregateValues,aggregateBinCounts,getUniqueBins,metadataDisplayValue,metadataCategoricalValue,resolveGridPitch}from"./deprecated.js";
1
+ export*from"./aggregates.js";export*from"./classify.js";export*from"./dies.js";export*from"./metadata.js";export*from"./probe.js";export*from"./reticle.js";export*from"./wafer.js";export{createWafer,generateDies,isPositionedDie,clipDiesToWafer,applyOrientation,transformDies,mapDataToDies,isInsideWafer,affineIdentity,affineRotation,affineMirror,affineCompose,affineInvert,affinePoint,affineVector,affineSwapsAxes,applyProbeSequence,generateReticleGrid,classifyDie,getRingLabel,aggregateValues,aggregateBinCounts,getUniqueBins,metadataCategoricalValue,resolveGridPitch}from"./deprecated.js";
@@ -25,6 +25,21 @@ export declare function compareNatural(a: string, b: string): number;
25
25
  */
26
26
  export declare function prettyKey(key: string): string;
27
27
  /** Clamp `v` into [0, 1] — the normalization range every colour scale expects. */
28
+ /**
29
+ * Smallest / largest of `values`, by iteration rather than `Math.min(...values)`.
30
+ *
31
+ * **Never spread a per-die array into `Math.min`/`Math.max`.** Spread passes one
32
+ * argument per element, and V8 throws `RangeError: Maximum call stack size
33
+ * exceeded` somewhere above ~125k arguments — so the idiom works on every test
34
+ * fixture and every small wafer, then fails outright on a real production lot.
35
+ * It did: `buildWaferMap` could not build a 400k-die map at all, and the aggregate
36
+ * min/max stacks had the same latent fault.
37
+ *
38
+ * Returns 0 for an empty array, matching the callers that treat "no values" as no
39
+ * extent rather than as ±Infinity.
40
+ */
41
+ export declare function minOf(values: readonly number[]): number;
42
+ export declare function maxOf(values: readonly number[]): number;
28
43
  export declare function clamp01(v: number): number;
29
44
  /**
30
45
  * Arithmetic mean. Returns **NaN** for an empty array — deliberately, not 0.
@@ -68,4 +83,56 @@ export declare function modeOf(values: number[]): number | null;
68
83
  * obvious. Every CSV this library writes must go through it.
69
84
  */
70
85
  export declare function csvField(value: string): string;
86
+ /**
87
+ * Escape text for insertion into HTML — the one copy for the whole library.
88
+ *
89
+ * Every string the library puts into `innerHTML` that did not come from its own
90
+ * literals must go through this: test names, units, bin names, wafer labels and
91
+ * metadata values all arrive from input files (STDF/ATDF/CSV headers, MES
92
+ * fields, operator free text), and a name like `<img src=x onerror=…>` would
93
+ * otherwise run as script when a tooltip showed it — in a Tauri or Electron
94
+ * host, inside the app's own webview. It used to live in stats/reportHtml.ts,
95
+ * where only the reports used it, while every tooltip interpolated raw names.
96
+ */
97
+ export declare function escHtml(value: string): string;
98
+ /**
99
+ * Shallow structural equality for the three collection shapes the library's
100
+ * shared display options are built from — the one copy of each rule.
101
+ *
102
+ * These exist for a single purpose: deciding whether a recomputed option is
103
+ * actually DIFFERENT before pushing it to every live card. A gallery resolves
104
+ * its wafers one at a time and re-derives the lot-wide options (bin colours,
105
+ * value range, metadata order) after each one, and each of those derivations
106
+ * allocates a fresh object — so a reference check always reports "changed" and
107
+ * every card rebuilds its view and redraws. Measured on 50 wafers x 8,000 dies,
108
+ * that pushed the same unchanged bin-colour map 1,275 times and cost 24 s of
109
+ * the load. Identity is the wrong question; value is the right one.
110
+ *
111
+ * Deliberately shallow: every value compared here is a number, a string or a
112
+ * colour, never a nested object. A generic deep-equal would invite use on
113
+ * `Die` or `View`, where it would be both wrong and ruinously slow.
114
+ */
115
+ export declare function arrayEqual<T>(a: readonly T[] | undefined, b: readonly T[] | undefined): boolean;
116
+ /** See {@link arrayEqual}. Same keys, same values — insertion order is not compared. */
117
+ export declare function mapEqual<K, V>(a: ReadonlyMap<K, V> | undefined, b: ReadonlyMap<K, V> | undefined): boolean;
118
+ /** See {@link arrayEqual}. Same members — order is meaningless in a Set. */
119
+ export declare function setEqual<T>(a: ReadonlySet<T> | undefined, b: ReadonlySet<T> | undefined): boolean;
120
+ /**
121
+ * A computation written as a generator so its caller decides how much of it to
122
+ * run at once: each `yield` is a point at which the work may be paused and the
123
+ * thread handed back. The generator's return value is the finished result, so a
124
+ * chunked computation has exactly ONE implementation — a caller that does not
125
+ * care runs it to completion with {@link drain} and cannot observe the
126
+ * difference, while a caller on the main thread drives it a slice at a time
127
+ * (see `runChunked` in `canvas-adapter/chunked.ts`).
128
+ *
129
+ * This shape, rather than a callback or an `async` rewrite, because the work
130
+ * here is synchronous and CPU-bound: there is nothing to await, only somewhere
131
+ * to stop. A second "progressive" copy of the same builder is the alternative,
132
+ * and two copies of a rule is the bug this library keeps finding.
133
+ */
134
+ export type Chunked<T> = Generator<void, T, void>;
135
+ /** Run a {@link Chunked} computation to completion without yielding, and
136
+ * return its result — the synchronous entry point for any chunked builder. */
137
+ export declare function drain<T>(work: Chunked<T>): T;
71
138
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- export function compareNatural(t,n){return t.localeCompare(n,"en",{numeric:!0})}export function prettyKey(t){return t.replace(/([A-Z])/g," $1").replace(/_/g," ").trim().replace(/^./,n=>n.toUpperCase())}export function clamp01(t){return Math.max(0,Math.min(1,t))}export function mean(t){if(t.length===0)return NaN;let n=0;for(const r of t)n+=r;return n/t.length}export function medianOfSorted(t){const n=t.length;if(n===0)return 0;const r=Math.floor(n/2);return n%2===1?t[r]:(t[r-1]+t[r])/2}export function median(t){return medianOfSorted([...t].sort((n,r)=>n-r))}export function percentile98(t){if(t.length===0)return 0;const n=[...t].sort((o,e)=>o-e),r=Math.floor(n.length*.98);return n[Math.min(r,n.length-1)]}export function modeOf(t){if(!t.length)return null;const n=new Map;for(const e of t)n.set(e,(n.get(e)??0)+1);let r=0,o=t[0];for(const[e,c]of n)c>r&&(r=c,o=e);return o}const i=/^[=+\-@]/;export function csvField(t){const n=i.test(t)&&Number.isNaN(Number(t))?`'${t}`:t;return/[",\n\r]/.test(n)?`"${n.replace(/"/g,'""')}"`:n}
1
+ export function compareNatural(t,e){return t.localeCompare(e,"en",{numeric:!0})}export function prettyKey(t){return t.replace(/([A-Z])/g," $1").replace(/_/g," ").trim().replace(/^./,e=>e.toUpperCase())}export function minOf(t){if(t.length===0)return 0;let e=t[0];for(let r=1;r<t.length;r++)t[r]<e&&(e=t[r]);return e}export function maxOf(t){if(t.length===0)return 0;let e=t[0];for(let r=1;r<t.length;r++)t[r]>e&&(e=t[r]);return e}export function clamp01(t){return Math.max(0,Math.min(1,t))}export function mean(t){if(t.length===0)return NaN;let e=0;for(const r of t)e+=r;return e/t.length}export function medianOfSorted(t){const e=t.length;if(e===0)return 0;const r=Math.floor(e/2);return e%2===1?t[r]:(t[r-1]+t[r])/2}export function median(t){return medianOfSorted([...t].sort((e,r)=>e-r))}export function percentile98(t){if(t.length===0)return 0;const e=Float64Array.from(t);e.sort();const r=Math.floor(e.length*.98);return e[Math.min(r,e.length-1)]}export function modeOf(t){if(!t.length)return null;const e=new Map;for(const o of t)e.set(o,(e.get(o)??0)+1);let r=0,n=t[0];for(const[o,f]of e)f>r&&(r=f,n=o);return n}const i=/^[=+\-@]/;export function csvField(t){const e=i.test(t)&&Number.isNaN(Number(t))?`'${t}`:t;return/[",\n\r]/.test(e)?`"${e.replace(/"/g,'""')}"`:e}export function escHtml(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export function arrayEqual(t,e){if(t===e)return!0;if(!t||!e||t.length!==e.length)return!1;for(let r=0;r<t.length;r++)if(t[r]!==e[r])return!1;return!0}export function mapEqual(t,e){if(t===e)return!0;if(!t||!e||t.size!==e.size)return!1;for(const[r,n]of t)if(!e.has(r)||e.get(r)!==n)return!1;return!0}export function setEqual(t,e){if(t===e)return!0;if(!t||!e||t.size!==e.size)return!1;for(const r of t)if(!e.has(r))return!1;return!0}export function drain(t){let e=t.next();for(;!e.done;)e=t.next();return e.value}
@@ -16,10 +16,20 @@ import { type WaferMetadata } from './metadata.js';
16
16
  * Order: the caller's `label` (a blank one counts as absent), then the wafer's
17
17
  * own `metadata.waferId`, then a position that says plainly it is not an ID.
18
18
  */
19
- export declare function waferDisplayLabel(item: {
19
+ export declare function waferDisplayLabel(item: WaferLabelSource, index: number): string;
20
+ type WaferLabelSource = {
20
21
  label?: string;
21
22
  wafer?: {
22
23
  metadata?: WaferMetadata | null;
23
24
  } | null;
24
- } | null | undefined, index: number): string;
25
+ } | null | undefined;
26
+ /**
27
+ * The wafer's real identity — the caller's `label`, else `metadata.waferId` —
28
+ * or `undefined` when it has none. `waferDisplayLabel` without its positional
29
+ * fallback, for places where a position must not stand in for an identity: a
30
+ * saved file's name (see canvas-adapter/exportName.ts), where "Wafer 3 (no ID)"
31
+ * would be written into every export of a lot and read back later as an ID.
32
+ */
33
+ export declare function waferIdentityLabel(item: WaferLabelSource): string | undefined;
34
+ export {};
25
35
  //# sourceMappingURL=waferLabel.d.ts.map
@@ -1 +1 @@
1
- import{metadataDisplayValue as r}from"./metadata.js";export function waferDisplayLabel(a,e){return(a?.label?.trim()||void 0)??r(a?.wafer?.metadata?.waferId)??`Wafer ${e+1} (no ID)`}
1
+ import{metadataDisplayValue as r}from"./metadata.js";export function waferDisplayLabel(a,e){return waferIdentityLabel(a)??`Wafer ${e+1} (no ID)`}export function waferIdentityLabel(a){return(a?.label?.trim()||void 0)??r(a?.wafer?.metadata?.waferId)}
@@ -83,6 +83,44 @@ export interface BinColorOptions {
83
83
  * A lone map resolves over its own dies.
84
84
  */
85
85
  export declare function resolveBinColors(dies: Iterable<Die>, options?: BinColorOptions): BinColors;
86
+ /** The inputs a built map carries for its bin colours. A `WaferMapResult` is one. */
87
+ export interface BinColorSource {
88
+ dies: Iterable<Die>;
89
+ /** The pass bins the map was built with (`WaferMapResult.passBins`). */
90
+ passBins?: readonly number[];
91
+ hbinDefs?: readonly BinDef[];
92
+ sbinDefs?: readonly BinDef[];
93
+ }
94
+ /** Display choices for `binColorsForMaps` — the palette and whether defined colours win. */
95
+ export interface MapBinColorOptions {
96
+ /** Registered bin palette name (`registerBinColorScheme`). Default `'default'`. */
97
+ binColorScheme?: string;
98
+ /** Honour `BinDef.color`. Default true. */
99
+ useDefinedBinColors?: boolean;
100
+ }
101
+ /**
102
+ * The bin colours for one or more built maps — exactly the colours
103
+ * `renderWaferMap` and `renderWaferGallery` draw them in, for a host surface of
104
+ * its own: a table swatch, a PDF, a chart in another library.
105
+ *
106
+ * Takes the maps rather than dies and a `passBins` list, so pass/fail cannot be
107
+ * judged by the wrong bins: each map's dies are judged by that map's own
108
+ * `passBins`, and bin definitions (with any `BinDef.color`) are merged across the
109
+ * maps, first definition of a bin winning — the gallery's own rule. For a live
110
+ * map with the user's palette choice, read `WaferMapController.getBinColors()` or
111
+ * `GalleryController.getBinColors()` instead.
112
+ */
113
+ export declare function binColorsForMaps(maps: BinColorSource | readonly BinColorSource[], options?: MapBinColorOptions): BinColors;
114
+ /** @internal `binColorsForMaps` plus the hard bins that pass on some maps and fail on others. */
115
+ export declare function resolveBinColorsForMaps(maps: readonly BinColorSource[], options?: MapBinColorOptions): {
116
+ colors: BinColors;
117
+ mixedHardBins: number[];
118
+ };
119
+ /**
120
+ * @internal Bin definitions merged across maps: one per bin, the first map's
121
+ * definition winning. The one rule for a multi-map legend, report or colouring.
122
+ */
123
+ export declare function mergeBinDefs(lists: Iterable<readonly BinDef[] | undefined>): BinDef[];
86
124
  /** One wafer's dies and the pass bins that wafer was built with. */
87
125
  export interface BinPassGroup {
88
126
  dies: Iterable<Die>;
@@ -131,4 +169,18 @@ export declare function binColorsCover(colors: BinColors, dies: Iterable<Die>):
131
169
  * degraded, which is what that level means.
132
170
  */
133
171
  export declare function binColorWarning(colors: BinColors, plotMode: string | undefined): WaferWarning | null;
172
+ /**
173
+ * Do two resolved assignments say the same thing? Compares every field of
174
+ * `BinColors` by value — the colour maps, the clash lists and the pass sets.
175
+ *
176
+ * The gallery's check before it pushes `binColors` to every card. Every
177
+ * `resolveBinColors*` call allocates fresh Maps and Sets even when the
178
+ * population's bins have not changed, so a reference comparison can only ever
179
+ * say "different", and each false positive costs a `buildView` plus a full
180
+ * canvas redraw on every card on screen. `pass` is compared as well as
181
+ * `hard`/`soft`: it is the verdict the colours were chosen with, it drives bin
182
+ * ORDER and soft-bin yield downstream, and a lot can change which bins pass
183
+ * (`pass-bins-mixed`) without any colour landing on a different value.
184
+ */
185
+ export declare function binColorsEqual(a: BinColors | undefined, b: BinColors | undefined): boolean;
134
186
  //# sourceMappingURL=binColors.d.ts.map
@@ -1 +1 @@
1
- import{diePassStatus as g}from"../core/dies.js";import{INPUT_DEFAULT_PASS_BINS as m}from"../core/passBins.js";import{getBinColorScheme as B}from"./colorSchemes.js";export function resolveBinColors(s,n={}){return resolveBinColorsByWafer([{dies:s,passBins:n.passBins}],n).colors}export function resolveBinColorsByWafer(s,n={}){const e=B(n.binColorScheme),i=n.useDefinedBinColors??!0,o=h(s),f=b(o.hardBins,a=>o.pass.hard.has(a),e,i?n.hbinDefs:void 0,!1),t=b(o.softBins,a=>o.pass.soft.has(a),e,i?n.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:o.pass},mixedHardBins:o.mixedHardBins}}export function binPassSets(s,n){return h([{dies:s,passBins:n}]).pass}export function binPassSetsByWafer(s){return h(s).pass}function h(s){const n=new Map,e=new Map,i=new Set;for(const t of s){const a=new Set(t.passBins??m);for(const l of t.dies){const r=g(l,a)===!0;l.hbin!=null&&(n.set(l.hbin,(n.get(l.hbin)??!0)&&r),r&&i.add(l.hbin)),l.sbin!=null&&e.set(l.sbin,(e.get(l.sbin)??!0)&&r)}}const o=t=>new Set([...t].filter(([,a])=>a).map(([a])=>a)),f={hard:o(n),soft:o(e)};return{hardBins:[...n.keys()],softBins:[...e.keys()],pass:f,mixedHardBins:[...i].filter(t=>!f.hard.has(t)).sort((t,a)=>t-a)}}function u(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const n=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${n?"bin":"bins"} ${u(s)} ${n?"passes":"pass"} on some wafers and ${n?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${n?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,n){if(!s.pass)return!1;for(const e of n)if(e.hbin!=null&&!s.hard.has(e.hbin)||e.sbin!=null&&!s.soft.has(e.sbin))return!1;return!0}export function binColorWarning(s,n){const e=n==="softBin"?"soft":n==="hardBin"?"hard":null;if(!e)return null;const i=s.shared[e];if(!i.length)return null;const o=u(i);return{code:"bin-colors-shared",severity:"warning",message:`${e==="hard"?"Hard":"Soft"} bins ${o} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const w=1,S=2;function p(s,n,e,i){const o=s.length,f=i?Math.floor(o/2):0;return s[((n-e+f)%o+o)%o]}function b(s,n,e,i,o){const f=new Map;for(const r of i??[])r.color&&f.set(r.bin,r.color);const t=new Map;for(const r of s)t.set(r,f.get(r)??(n(r)?p(e.pass,r,w,o):p(e.fail,r,S,o)));const a=new Map;for(const[r,c]of t){const d=c.toLowerCase();a.set(d,[...a.get(d)??[],r])}const l=[...a.values()].filter(r=>r.length>1).flat().sort((r,c)=>r-c);return{colors:t,shared:l}}
1
+ import{diePassStatus as w}from"../core/dies.js";import{INPUT_DEFAULT_PASS_BINS as S,itemPassBins as x}from"../core/passBins.js";import{getBinColorScheme as y}from"./colorSchemes.js";import{arrayEqual as u,mapEqual as p,setEqual as b}from"../core/utils.js";export function resolveBinColors(s,n={}){return resolveBinColorsByWafer([{dies:s,passBins:n.passBins}],n).colors}export function binColorsForMaps(s,n={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],n).colors}export function resolveBinColorsForMaps(s,n={}){return resolveBinColorsByWafer(s.map(e=>({dies:e.dies,passBins:x(e)})),{...n,hbinDefs:mergeBinDefs(s.map(e=>e.hbinDefs)),sbinDefs:mergeBinDefs(s.map(e=>e.sbinDefs))})}export function mergeBinDefs(s){const n=new Set,e=[];for(const a of s)for(const r of a??[])n.has(r.bin)||(n.add(r.bin),e.push(r));return e}export function resolveBinColorsByWafer(s,n={}){const e=y(n.binColorScheme),a=n.useDefinedBinColors??!0,r=d(s),f=B(r.hardBins,i=>r.pass.hard.has(i),e,a?n.hbinDefs:void 0,!1),t=B(r.softBins,i=>r.pass.soft.has(i),e,a?n.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:r.pass},mixedHardBins:r.mixedHardBins}}export function binPassSets(s,n){return d([{dies:s,passBins:n}]).pass}export function binPassSetsByWafer(s){return d(s).pass}function d(s){const n=new Map,e=new Map,a=new Set;for(const t of s){const i=new Set(t.passBins??S);for(const l of t.dies){const o=w(l,i)===!0;l.hbin!=null&&(n.set(l.hbin,(n.get(l.hbin)??!0)&&o),o&&a.add(l.hbin)),l.sbin!=null&&e.set(l.sbin,(e.get(l.sbin)??!0)&&o)}}const r=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:r(n),soft:r(e)};return{hardBins:[...n.keys()],softBins:[...e.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function g(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const n=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${n?"bin":"bins"} ${g(s)} ${n?"passes":"pass"} on some wafers and ${n?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${n?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,n){if(!s.pass)return!1;for(const e of n)if(e.hbin!=null&&!s.hard.has(e.hbin)||e.sbin!=null&&!s.soft.has(e.sbin))return!1;return!0}export function binColorWarning(s,n){const e=n==="softBin"?"soft":n==="hardBin"?"hard":null;if(!e)return null;const a=s.shared[e];if(!a.length)return null;const r=g(a);return{code:"bin-colors-shared",severity:"warning",message:`${e==="hard"?"Hard":"Soft"} bins ${r} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const C=1,P=2;function m(s,n,e,a){const r=s.length,f=a?Math.floor(r/2):0;return s[((n-e+f)%r+r)%r]}function B(s,n,e,a,r){const f=new Map;for(const o of a??[])o.color&&f.set(o.bin,o.color);const t=new Map;for(const o of s)t.set(o,f.get(o)??(n(o)?m(e.pass,o,C,r):m(e.fail,o,P,r)));const i=new Map;for(const[o,h]of t){const c=h.toLowerCase();i.set(c,[...i.get(c)??[],o])}const l=[...i.values()].filter(o=>o.length>1).flat().sort((o,h)=>o-h);return{colors:t,shared:l}}export function binColorsEqual(s,n){return s===n?!0:!s||!n?!1:p(s.hard,n.hard)&&p(s.soft,n.soft)&&u(s.shared.hard,n.shared.hard)&&u(s.shared.soft,n.shared.soft)&&b(s.pass.hard,n.pass.hard)&&b(s.pass.soft,n.pass.soft)}
@@ -1 +1 @@
1
- import{hasPosition as se}from"../core/dies.js";import{getReticleCell as oe}from"../core/reticle.js";import{metadataCategoricalValue as re,metadataDisplayValue as le}from"../core/metadata.js";import{affineRotation as mt,affineMirror as Lt,affineCompose as it,affinePoint as Y,affineVector as pt,affineSwapsAxes as ce}from"../core/transforms.js";import{contrastTextColor as G,SPEC_PASS_FILL as yt,SPEC_FAIL_LOW as ue,SPEC_FAIL_HIGH as gt}from"./colorMap.js";import{resolveValueColorFn as fe}from"./colorSchemes.js";import{resolveBinColors as de,binColorsCover as he}from"./binColors.js";import{NO_DATA_FILL as j}from"./colorMap.js";import{diePassStatus as me}from"../core/dies.js";import{getDieTestValue as P,getTestPassStatus as Q,isParametricTest as bt}from"./buildWaferMap.js";import{fmt as xt,fmtColorbarAxis as Mt,fmtAggregationMethod as Rt}from"./fmt.js";import{metadataValueColor as pe}from"./colorMap.js";import{compareNatural as ye,clamp01 as Nt}from"../core/utils.js";import{prettyKey as Ot}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const a=new Set;for(const n of t){if(n.partial||n.edgeExcluded)continue;const s=st(n,e);s!==void 0&&a.add(s)}return[...a].sort(ye)}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,a)=>e-a)}const ge="#d3d6db",Ht="#e8e9ea",be="#aab0ba";function st(t,e){return e?re(t.metadata?.[e]):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function xe(t,e,a){const{x:n,y:s}=t.center,i=mt(t.orientation,n,s),o=it(Lt(e?.x??!1,e?.y??!1,n,s),mt(t.orientation,n,s)),l=it(Lt(a?.flipX??!1,a?.flipY??!1,n,s),mt(a?.rotation??0,n,s));return{bakedToScreen:l,physicalToScreen:it(l,i),gridToScreen:it(l,o)}}function z(t,e=!1){return{points:[t],closed:e}}function Me(t,e){const{center:a,radius:n,notch:s}=t;let i=a.x+n*Math.cos(e),o=a.y+n*Math.sin(e);if(!s)return{x:i,y:o};const l=Math.sqrt(n**2-(s.length/2)**2),u=s.length/2,g=i-a.x,p=o-a.y;return s.type==="bottom"&&p<-l?(o=a.y-l,i=a.x+Math.max(-u,Math.min(u,g))):s.type==="top"&&p>l?(o=a.y+l,i=a.x+Math.max(-u,Math.min(u,g))):s.type==="left"&&g<-l?(i=a.x-l,o=a.y+Math.max(-u,Math.min(u,p))):s.type==="right"&&g>l&&(i=a.x+l,o=a.y+Math.max(-u,Math.min(u,p))),{x:i,y:o}}function At(t,e){return t.map(e).join(" / ")}function Se(t,e=256){const a=Array.from({length:e},(n,s)=>t(s/(e-1)));return n=>a[Math.min(e-1,Math.max(0,Math.round(n*(e-1))))]}function ke(t,e){const a=Math.max(1,Math.min(t.width,t.height)),n=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,n*1.8))))}function ve(t,e,a){if(e?.length){const n=e.flatMap(s=>{const i=s.testNumber;if(!bt(s)){const u=Q(t,i,s);return u===void 0?[]:[{key:i,label:s.name,value:u?"Pass":"Fail"}]}const o=P(t,i);if(o===void 0)return[];const l=t.testPass?.[i]===!1?" <i>(recorded fail)</i>":"";return[{key:i,label:s.name,value:`${xt(o,s.unit,a)}${l}`}]});if(n.length)return n}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:xt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:n,hbinDefs:s,sbinDefs:i,fallbackFormat:o,aggrMethod:l,lotSize:u,activeTest:g,reticleConfig:p,metadataFields:B}=a,D=s?new Map(s.map(f=>[f.bin,f])):null,T=i?new Map(i.map(f=>[f.bin,f])):null,b=[`Die (${t.x}, ${t.y})`];if(p&&se(t)){const f=oe(t,p);b.push(`Reticle (${f.column}, ${f.row})`)}if(e==="stackedValues"){const f=P(t,0);if(f!==void 0){const d=n?.[0],h=d?.testNumber,k=d?.name??(h!=null?`Test ${h}`:"Value"),m=l?` (${l})`:"";b.push(`${k}${m}: ${xt(f,d?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=P(t,0),d=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:D,k=d!==void 0?h?.get(d)?.name?`${d} \xB7 ${h.get(d).name}`:`Bin ${d}`:"Bin";let m;if(l==="percent")m=`${f.toFixed(0)}%`;else{const C=u?` (${(f/u*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${C}`}const x=l?` [${Rt(l)}]`:"";b.push(`${k}: ${m}${x}`)}}else{const f=ve(t,n,o);if(e==="value"&&f.length){const{testNumber:d}=resolveTestNumber(g??0,n),h=findTestDef(n,d),k=P(t,d);let m=f.findIndex(y=>y.key===d);m<0&&(m=0);const x=f[m];let C=`<b>${x.label}: ${x.value}</b>`;if(x.key===d&&h){const y=classifySpec(k,h);(y==="failLow"||y==="failHigh")&&(C+=" <i>(out of spec)</i>")}b.push(C);const R=f.length-1;R>0&&b.push(`<i>+${R} more test${R===1?"":"s"}</i>`)}else if(f.length){const d=f.length;b.push(`<i>${d} test result${d===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const d=[];if(t.hbin!==void 0){const h=D?.get(t.hbin)?.name,k=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);d.push(`HBin: ${k}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,k=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);d.push(`SBin: ${k}`)}b.push(d.join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&b.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&b.push(`Site: ${t.siteNum}`),t.partId!==void 0&&b.push(`Part ID: ${t.partId}`),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(`Probe: #${t.probeIndex}`);const $={...t.metadata??{}};for(const[f,d]of Object.entries($)){const h=le(d);if(h===void 0)continue;const k=B?.find(m=>m.key===f)?.label??Ot(f);b.push(`${k}: ${h}`)}return b.join("<br>")}export function buildMapTitle(t,e="engineering",a){const n=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=i=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${i} Bin`:o.name?`${i} Bin ${o.bin} \xB7 ${o.name}`:`${i} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:n};case"stackedSoftBins":return{primary:s("Soft"),secondary:n};case"metadata":{const i=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Ot(t.activeMetadataKey):"Metadata";return{primary:i?.label??o,secondary:""}}case"stackedValues":{const i=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=Mt(o,i?.name,i?.unit,e),u=l||"Value",g=t.aggrMethod?` \xB7 ${Rt(t.aggrMethod)}`:"";return{primary:`${u}${g}`,secondary:n}}default:{const i=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=Mt(o,i?.name,i?.unit,e),u=l||i?.name;if(t.passFailDisplay!=="off"){const g=i?.testNumber??t.activeTest,p=u?`${u} \xB7 #${g}`:`Test ${g}`,B=t.passFailDisplay==="spec"?"Spec pass/fail":i!==void 0&&!bt(i)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B}}return{primary:u??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:n,colorFns:s,normalize:i,activeTest:o,valueRange:l,testDefs:u,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:D,allIntegerValues:T}=a,b=findTestDef(u,o),{tickFmt:$}=Mt(l[1],b?.name,b?.unit,g),f=T?h=>String(Math.round(h)):$,{testNumber:d}=resolveTestNumber(o,u);return t.flatMap((h,k)=>{let m="",x="#111111";if(n==="value"&&p==="test"){const y=Q(h,d,b);if(y===void 0)return[];m=y?"P":"F",x=G(y?yt:gt)}else if(n==="value"){const y=P(h,d);if(y===void 0)return[];m=At([y],f),x=G(s.forValue(i(y)))}else if(n==="hardBin"||n==="softBin"){const y=n==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=G(s.forBin(y))}else if(n==="metadata"){const y=st(h,B);if(y===void 0)return[];m=y,x=G(D?.get(m)??j)}else{const y=P(h,0);if(y===void 0)return[];m=At([y],f),x=G(s.forValue(i(y)))}const C=e?e[k*2]:h.physX,R=e?e[k*2+1]:h.physY;return[{x:C,y:R,text:m,fontSize:ke(h,m),color:x,align:"center"}]})}function Et(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function $e(t,e,a=720){const{center:n,radius:s,notch:i}=t,o=$=>Y(e,$.x,$.y);if(!(i!==void 0&&t.diameter>150)){const $=[];for(let f=0;f<=a;f++){const d=2*Math.PI*f/a;$.push(o(Me(t,d)))}return[{kind:"wafer-boundary",...z($,!0),lineColor:"#888888",lineWidth:1}]}const u=1.25,g=i.length,p=Et(i.type),B=Math.atan2(g,s),D=s-u,T=[];let b=!1;for(let $=0;$<=a;$++){const f=2*Math.PI*$/a;let d=f-p;for(;d>Math.PI;)d-=2*Math.PI;for(;d<-Math.PI;)d+=2*Math.PI;if(Math.abs(d)<=B){if(!b){b=!0;const h={x:n.x+s*Math.cos(p-B),y:n.y+s*Math.sin(p-B)},k={x:n.x+D*Math.cos(p),y:n.y+D*Math.sin(p)},m={x:n.x+s*Math.cos(p+B),y:n.y+s*Math.sin(p+B)};T.push(o(h),o(k),o(m))}continue}T.push(o({x:n.x+s*Math.cos(f),y:n.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...z(T,!0),lineColor:"#888888",lineWidth:1}]}function Be(t,e,a,n=360){const s=[],i=Math.max(1,Math.floor(a));for(let o=1;o<i;o++){const l=t.radius*o/i,u=[];for(let g=0;g<=n;g++){const p=2*Math.PI*g/n,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};u.push(Y(e,B.x,B.y))}s.push({kind:"ring-boundary",...z(u,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Te(t,e,a,n){const{x:s,y:i}=t.center,o=t.radius,l=(f,d)=>Y(e,f,d),u=a-s,g=Math.sqrt(Math.max(0,o*o-u*u)),p=l(a,i-g),B=l(a,i+g),D=n-i,T=Math.sqrt(Math.max(0,o*o-D*D)),b=l(s-T,n),$=l(s+T,n);return[{kind:"quadrant-boundary",...z([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...z([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ie(t,e){return t.map(a=>{const n=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-n,a.y-s],[a.x+n,a.y-s],[a.x+n,a.y+s],[a.x-n,a.y+s]].map(([o,l])=>Y(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Ve(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,n)=>(a.probeIndex??0)-(n.probeIndex??0));return e.length?[{kind:"probe-path",...z(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function Ce(t,e,a,n,s,i,o,l,u,g,p,B,D,T,b="off",$,f,d){const h=e.width-o,k=e.height-o,m=i?k:h,x=i?h:k,C=S=>s==="softBin"?S.sbin:S.hbin,R=S=>st(S,$);if(e.partial){t.push({x:a,y:n,width:m,height:x,fill:ge,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:n,width:m,height:x,fill:be,type:"stacked",metadata:e.metadata});return}if(u!==void 0&&(s==="hardBin"||s==="softBin")&&C(e)!==u){t.push({x:a,y:n,width:m,height:x,fill:Ht,type:"hardBin",metadata:e.metadata});return}if(d!==void 0&&s==="metadata"&&R(e)!==d){t.push({x:a,y:n,width:m,height:x,fill:Ht,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const w=Q(e,p,T),ot=w===void 0?j:w?yt:gt;t.push({x:a,y:n,width:m,height:x,fill:ot,type:"value",metadata:e.metadata});return}const S=P(e,p),N=classifySpec(S,T);let _,U;S===void 0?_=j:b==="spec"?_=N==="failLow"?ue:N==="failHigh"?gt:yt:(_=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(U=N)),t.push({x:a,y:n,width:m,height:x,fill:_,type:"value",specMark:U,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=C(e),N=S!=null?l.forBin(S):j,_=S!=null&&me(e,D)===!1?!0:void 0;t.push({x:a,y:n,width:m,height:x,fill:N,type:s,binFail:_,metadata:e.metadata});return}if(s==="metadata"){const S=R(e),N=S!==void 0?f?.get(S)??j:j;t.push({x:a,y:n,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=P(e,0),X=y!==void 0?l.forValue(g(y)):j;t.push({x:a,y:n,width:m,height:x,fill:X,type:"value",metadata:e.metadata})}function Fe(t,e,a){const n=t.radius*.15,s=pt(e,n,0),i=pt(e,0,n),o=s.x+i.x>=0?-1:1,l=s.y+i.y>=0?-1:1,u={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:u.x+s.x,y:u.y+s.y},p={x:u.x+i.x,y:u.y+i.y};return a.push({x:g.x+s.x*.35,y:g.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+i.x*.35,y:p.y+i.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...z([u,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...z([u,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},n){const s=a.reticles??[],{plotMode:i="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:u=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:$=!1,useDefinedBinColors:f=!0,binColors:d,passBins:h=[1],highlightBin:k,highlightMetadataValue:m,interactiveTransform:x,valueRange:C,metadataValueOrder:R,testDefs:y,activeTest:X=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:_,lotSize:U,dataAxisFlip:w,isLotStack:ot=!1,logScale:_t,colorbarRangeMode:wt="spec",passFailDisplay:Wt,showPartialDies:jt=!0}=a,St=Wt??"off",rt=n?.hbinDefs,lt=n?.sbinDefs,kt=n?.metadataFields,zt=kt?.find(c=>c.key===S),Kt={x:(w?.x??!1)!==(x?.flipX??!1),y:(w?.y??!1)!==(x?.flipY??!1)},qt=rt?new Map(rt.map(c=>[c.bin,c])):null,Xt=lt?new Map(lt.map(c=>[c.bin,c])):null,Yt=i==="softBin"?Xt:qt,Gt=new Set(h),ct=d&&he(d,e)?d:de(e,{passBins:h,binColorScheme:T,hbinDefs:rt,sbinDefs:lt,useDefinedBinColors:f}),Qt=i==="softBin"?ct.soft:ct.hard,vt={forValue:Se(fe(b,$)),forBin:c=>Qt.get(c)??j};let{testNumber:H}=resolveTestNumber(X,y);if(!y?.length&&i==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((M,V)=>{if(!V.testValues)return M;const v=Object.keys(V.testValues).map(Number),L=v.length?Math.min(...v):void 0;return L!==void 0&&(M===void 0||L<M)?L:M},void 0);r!==void 0&&(H=r)}let J;if(Array.isArray(C))J=C;else if(C){const{testNumber:c}=resolveTestNumber(C.test,y);J=c===H?C.range:void 0}const I=findTestDef(y,H),$t=i==="value"&&I!==void 0&&!bt(I),Ut=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let E="off";i==="value"&&($t?E="test":St==="spec"&&Ut?E="spec":St==="test"&&e.some(c=>Q(c,H,I)!==void 0)&&(E="test"));const Bt=E==="spec"?"spec":wt,ut=i==="stackedValues"||i==="stackedBins"||i==="stackedSoftBins";let O,A,Z=!1;if(J){[O,A]=J;let c=!0,r=!1;for(const M of e){const V=ut?P(M,0):P(M,H);if(V!==void 0&&(r=!0,!Number.isInteger(V))){c=!1;break}}Z=r&&c}else{const c=!ut&&i==="value"&&Bt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,V=!0;for(const v of e){const L=ut?P(v,0):P(v,H);L!==void 0&&(L<r&&(r=L),L>M&&(M=L),Number.isInteger(L)||(V=!1))}isFinite(r)||(V=!1),Z=V,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,A=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,A=isFinite(M)?M:1)}O===A&&(A=O+1);const Tt=$t?!1:_t??I?.logScale??!1,It=Tt&&O>0&&A>0,Jt=It;let tt;if(It){const c=Math.log10(O),r=Math.log10(A)-c;tt=M=>M<=0?0:Nt((Math.log10(M)-c)/r)}else tt=c=>Nt((c-O)/(A-O));const F=xe(t,w,x);let Vt=null;if(t.notch){const c=Et(t.notch.type),r=pt(F.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Vt={x:r.x/M,y:r.y/M})}let K=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<K&&(K=r.width),r.height<K&&(K=r.height)}const Ct=1,Zt=K!==1/0?Math.min(Ct,K*.12):Ct,Ft=[],ft=[],te=F.bakedToScreen.a===1&&F.bakedToScreen.b===0&&F.bakedToScreen.c===0&&F.bakedToScreen.d===1&&F.bakedToScreen.e===0&&F.bakedToScreen.f===0;let W=null;if(!te){W=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=Y(F.bakedToScreen,e[c].physX,e[c].physY);W[c*2]=r.x,W[c*2+1]=r.y}}const ee=ce(F.gridToScreen),ne=i==="hardBin"||i==="softBin"||i==="stackedBins"||i==="stackedSoftBins",ae=i==="softBin"||i==="stackedSoftBins";let et=ne?new Map:void 0;const dt=i==="value"&&E==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ht=i==="value"&&E==="test"?{pass:0,fail:0}:void 0,Dt=i==="metadata";let nt=Dt?new Map:void 0,at=null;if(Dt&&S){const c=collectMetadataValues(e,S),r=R?.key===S?R.values:void 0,M=r?[...r,...c.filter(V=>!r.includes(V))]:c;at=new Map(M.map((V,v)=>[V,zt?.values?.find(L=>L.value===V)?.color??pe(v)]))}for(let c=0;c<e.length;c++){const r=e[c],M=W?W[c*2]:r.physX,V=W?W[c*2+1]:r.physY;if(ft.push({x:M,y:V}),et&&!r.partial&&!r.edgeExcluded){const v=ae?r.sbin:r.hbin;v!=null&&et.set(v,(et.get(v)??0)+1)}if(dt&&!r.partial&&!r.edgeExcluded){const v=classifySpec(P(r,H),I);v&&dt[v]++}if(ht&&!r.partial&&!r.edgeExcluded){const v=Q(r,H,I);v!==void 0&&ht[v?"pass":"fail"]++}if(nt&&!r.partial&&!r.edgeExcluded){const v=st(r,S);v!==void 0&&nt.set(v,(nt.get(v)??0)+1)}r.partial&&!jt||Ce(Ft,r,M,V,i,ee,Zt,vt,k,tt,H,Yt,Gt,I,E,S,at,m)}const Pt=o?generateTextOverlay(e,W,{plotMode:i,colorFns:vt,normalize:tt,activeTest:X,valueRange:[O,A],testDefs:y,fallbackFormat:N,passFailDisplay:E,activeMetadataKey:S,metadataColorMap:at,allIntegerValues:Z}):[],q=$e(t,F.physicalToScreen);p&&q.push(...Be(t,F.physicalToScreen,g)),B&&q.push(...Te(t,F.physicalToScreen,t.center.x,t.center.y)),l&&q.push(...Ie(s,F.gridToScreen)),u&&q.push(...Ve(e)),D&&q.push(...Fe(t,F.gridToScreen,Pt));const ie=ft.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Ft,hoverPoints:ft,texts:Pt,overlays:q,plotMode:i,binColorScheme:T,valueColorScheme:b,reverseValueScheme:$,binColors:ct,metadata:t.metadata??null,dies:e,valueRange:[O,A],allIntegerValues:Z,testDefs:y,activeTest:X,logScale:Jt,logScaleRequested:Tt,aggrMethod:_,lotSize:U,axisFlip:Kt,dataAxisFlip:{x:w?.x??!1,y:w?.y??!1},gridToScreen:F.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ot,passFailDisplay:E,colorbarRangeMode:Bt,waferCenter:t.center,waferRadius:t.radius,notchDir:Vt,binCounts:et,metadataCounts:nt,metadataColorMap:at,activeMetadataKey:S,metadataFields:kt,specCounts:dt,passFailCounts:ht,dieBounds:ie}}export{getDieKey}from"../core/dies.js";
1
+ import{hasPosition as oe}from"../core/dies.js";import{getReticleCell as re}from"../core/reticle.js";import{metadataCategoricalValue as le,metadataDisplayValue as ce}from"../core/metadata.js";import{affineRotation as pt,affineMirror as Rt,affineCompose as st,affinePoint as G,affineVector as yt,affineSwapsAxes as ue}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as gt,SPEC_FAIL_LOW as fe,SPEC_FAIL_HIGH as bt}from"./colorMap.js";import{resolveValueColorFn as de}from"./colorSchemes.js";import{resolveBinColors as he,binColorsCover as me}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as pe}from"../core/dies.js";import{getDieTestValue as P,getTestPassStatus as U,isParametricTest as xt}from"./buildWaferMap.js";import{fmt as Mt,fmtColorbarAxis as St,fmtAggregationMethod as Nt}from"./fmt.js";import{metadataValueColor as ye}from"./colorMap.js";import{clamp01 as Ot,compareNatural as ge,escHtml as H,minOf as be}from"../core/utils.js";import{prettyKey as Ht}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const a=new Set;for(const n of t){if(n.partial||n.edgeExcluded)continue;const s=ot(n,e);s!==void 0&&a.add(s)}return[...a].sort(ge)}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,a)=>e-a)}const xe="#d3d6db",At="#e8e9ea",Me="#aab0ba";function ot(t,e){return e?le(t.metadata?.[e]):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function Se(t,e,a){const{x:n,y:s}=t.center,i=pt(t.orientation,n,s),o=st(Rt(e?.x??!1,e?.y??!1,n,s),pt(t.orientation,n,s)),l=st(Rt(a?.flipX??!1,a?.flipY??!1,n,s),pt(a?.rotation??0,n,s));return{bakedToScreen:l,physicalToScreen:st(l,i),gridToScreen:st(l,o)}}function K(t,e=!1){return{points:[t],closed:e}}function ke(t,e){const{center:a,radius:n,notch:s}=t;let i=a.x+n*Math.cos(e),o=a.y+n*Math.sin(e);if(!s)return{x:i,y:o};const l=Math.sqrt(n**2-(s.length/2)**2),d=s.length/2,g=i-a.x,p=o-a.y;return s.type==="bottom"&&p<-l?(o=a.y-l,i=a.x+Math.max(-d,Math.min(d,g))):s.type==="top"&&p>l?(o=a.y+l,i=a.x+Math.max(-d,Math.min(d,g))):s.type==="left"&&g<-l?(i=a.x-l,o=a.y+Math.max(-d,Math.min(d,p))):s.type==="right"&&g>l&&(i=a.x+l,o=a.y+Math.max(-d,Math.min(d,p))),{x:i,y:o}}function Et(t,e){return t.map(e).join(" / ")}function ve(t,e=256){const a=Array.from({length:e},(n,s)=>t(s/(e-1)));return n=>a[Math.min(e-1,Math.max(0,Math.round(n*(e-1))))]}function $e(t,e){const a=Math.max(1,Math.min(t.width,t.height)),n=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,n*1.8))))}function Be(t,e,a){if(e?.length){const n=e.flatMap(s=>{const i=s.testNumber;if(!xt(s)){const l=U(t,i,s);return l===void 0?[]:[{key:i,label:s.name,value:l?"Pass":"Fail"}]}const o=P(t,i);return o===void 0?[]:[{key:i,label:s.name,value:Mt(o,s.unit,a),recordedFail:t.testPass?.[i]===!1}]});if(n.length)return n}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:Mt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:n,hbinDefs:s,sbinDefs:i,fallbackFormat:o,aggrMethod:l,lotSize:d,activeTest:g,reticleConfig:p,metadataFields:B}=a,D=s?new Map(s.map(u=>[u.bin,u])):null,T=i?new Map(i.map(u=>[u.bin,u])):null,b=[H(`Die (${t.x}, ${t.y})`)];if(p&&oe(t)){const u=re(t,p);b.push(H(`Reticle (${u.column}, ${u.row})`))}if(e==="stackedValues"){const u=P(t,0);if(u!==void 0){const f=n?.[0],h=f?.testNumber,k=f?.name??(h!=null?`Test ${h}`:"Value"),m=l?` (${l})`:"";b.push(H(`${k}${m}: ${Mt(u,f?.unit,o)}`))}}else if(e==="stackedBins"||e==="stackedSoftBins"){const u=P(t,0),f=e==="stackedSoftBins"?t.sbin:t.hbin;if(u!==void 0){const h=e==="stackedSoftBins"?T:D,k=f!==void 0?h?.get(f)?.name?`${f} \xB7 ${h.get(f).name}`:`Bin ${f}`:"Bin";let m;if(l==="percent")m=`${u.toFixed(0)}%`;else{const C=d?` (${(u/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${u}${C}`}const x=l?` [${Nt(l)}]`:"";b.push(H(`${k}: ${m}${x}`))}}else{const u=Be(t,n,o);if(e==="value"&&u.length){const{testNumber:f}=resolveTestNumber(g??0,n),h=findTestDef(n,f),k=P(t,f);let m=u.findIndex(y=>y.key===f);m<0&&(m=0);const x=u[m];let C=`<b>${H(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===f&&h){const y=classifySpec(k,h);(y==="failLow"||y==="failHigh")&&(C+=" <i>(out of spec)</i>")}b.push(C);const R=u.length-1;R>0&&b.push(`<i>+${R} more test${R===1?"":"s"}</i>`)}else if(u.length){const f=u.length;b.push(`<i>${f} test result${f===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const f=[];if(t.hbin!==void 0){const h=D?.get(t.hbin)?.name,k=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);f.push(`HBin: ${k}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,k=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);f.push(`SBin: ${k}`)}b.push(f.map(H).join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&b.push(H(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(H(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(H(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(H(`Probe: #${t.probeIndex}`));const $={...t.metadata??{}};for(const[u,f]of Object.entries($)){const h=ce(f);if(h===void 0)continue;const k=B?.find(m=>m.key===u)?.label??Ht(u);b.push(H(`${k}: ${h}`))}return b.join("<br>")}export function buildMapTitle(t,e="engineering",a){const n=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=i=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${i} Bin`:o.name?`${i} Bin ${o.bin} \xB7 ${o.name}`:`${i} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:n};case"stackedSoftBins":return{primary:s("Soft"),secondary:n};case"metadata":{const i=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Ht(t.activeMetadataKey):"Metadata";return{primary:i?.label??o,secondary:""}}case"stackedValues":{const i=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=St(o,i?.name,i?.unit,e),d=l||"Value",g=t.aggrMethod?` \xB7 ${Nt(t.aggrMethod)}`:"";return{primary:`${d}${g}`,secondary:n}}default:{const i=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=St(o,i?.name,i?.unit,e),d=l||i?.name;if(t.passFailDisplay!=="off"){const g=i?.testNumber??t.activeTest,p=d?`${d} \xB7 #${g}`:`Test ${g}`,B=t.passFailDisplay==="spec"?"Spec pass/fail":i!==void 0&&!xt(i)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B}}return{primary:d??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:n,colorFns:s,normalize:i,activeTest:o,valueRange:l,testDefs:d,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:D,allIntegerValues:T}=a,b=findTestDef(d,o),{tickFmt:$}=St(l[1],b?.name,b?.unit,g),u=T?h=>String(Math.round(h)):$,{testNumber:f}=resolveTestNumber(o,d);return t.flatMap((h,k)=>{let m="",x="#111111";if(n==="value"&&p==="test"){const y=U(h,f,b);if(y===void 0)return[];m=y?"P":"F",x=Q(y?gt:bt)}else if(n==="value"){const y=P(h,f);if(y===void 0)return[];m=Et([y],u),x=Q(s.forValue(i(y)))}else if(n==="hardBin"||n==="softBin"){const y=n==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=Q(s.forBin(y))}else if(n==="metadata"){const y=ot(h,B);if(y===void 0)return[];m=y,x=Q(D?.get(m)??z)}else{const y=P(h,0);if(y===void 0)return[];m=Et([y],u),x=Q(s.forValue(i(y)))}const C=e?e[k*2]:h.physX,R=e?e[k*2+1]:h.physY;return[{x:C,y:R,text:m,fontSize:$e(h,m),color:x,align:"center"}]})}function _t(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Te(t,e,a=720){const{center:n,radius:s,notch:i}=t,o=$=>G(e,$.x,$.y);if(!(i!==void 0&&t.diameter>150)){const $=[];for(let u=0;u<=a;u++){const f=2*Math.PI*u/a;$.push(o(ke(t,f)))}return[{kind:"wafer-boundary",...K($,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,g=i.length,p=_t(i.type),B=Math.atan2(g,s),D=s-d,T=[];let b=!1;for(let $=0;$<=a;$++){const u=2*Math.PI*$/a;let f=u-p;for(;f>Math.PI;)f-=2*Math.PI;for(;f<-Math.PI;)f+=2*Math.PI;if(Math.abs(f)<=B){if(!b){b=!0;const h={x:n.x+s*Math.cos(p-B),y:n.y+s*Math.sin(p-B)},k={x:n.x+D*Math.cos(p),y:n.y+D*Math.sin(p)},m={x:n.x+s*Math.cos(p+B),y:n.y+s*Math.sin(p+B)};T.push(o(h),o(k),o(m))}continue}T.push(o({x:n.x+s*Math.cos(u),y:n.y+s*Math.sin(u)}))}return[{kind:"wafer-boundary",...K(T,!0),lineColor:"#888888",lineWidth:1}]}function Ie(t,e,a,n=360){const s=[],i=Math.max(1,Math.floor(a));for(let o=1;o<i;o++){const l=t.radius*o/i,d=[];for(let g=0;g<=n;g++){const p=2*Math.PI*g/n,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...K(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Ve(t,e,a,n){const{x:s,y:i}=t.center,o=t.radius,l=(u,f)=>G(e,u,f),d=a-s,g=Math.sqrt(Math.max(0,o*o-d*d)),p=l(a,i-g),B=l(a,i+g),D=n-i,T=Math.sqrt(Math.max(0,o*o-D*D)),b=l(s-T,n),$=l(s+T,n);return[{kind:"quadrant-boundary",...K([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...K([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ce(t,e){return t.map(a=>{const n=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-n,a.y-s],[a.x+n,a.y-s],[a.x+n,a.y+s],[a.x-n,a.y+s]].map(([o,l])=>G(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Fe(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,n)=>(a.probeIndex??0)-(n.probeIndex??0));return e.length?[{kind:"probe-path",...K(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function De(t,e,a,n,s,i,o,l,d,g,p,B,D,T,b="off",$,u,f){const h=e.width-o,k=e.height-o,m=i?k:h,x=i?h:k,C=S=>s==="softBin"?S.sbin:S.hbin,R=S=>ot(S,$);if(e.partial){t.push({x:a,y:n,width:m,height:x,fill:xe,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:n,width:m,height:x,fill:Me,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&C(e)!==d){t.push({x:a,y:n,width:m,height:x,fill:At,type:"hardBin",metadata:e.metadata});return}if(f!==void 0&&s==="metadata"&&R(e)!==f){t.push({x:a,y:n,width:m,height:x,fill:At,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const W=U(e,p,T),rt=W===void 0?z:W?gt:bt;t.push({x:a,y:n,width:m,height:x,fill:rt,type:"value",metadata:e.metadata});return}const S=P(e,p),N=classifySpec(S,T);let w,J;S===void 0?w=z:b==="spec"?w=N==="failLow"?fe:N==="failHigh"?bt:gt:(w=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(J=N)),t.push({x:a,y:n,width:m,height:x,fill:w,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=C(e),N=S!=null?l.forBin(S):z,w=S!=null&&pe(e,D)===!1?!0:void 0;t.push({x:a,y:n,width:m,height:x,fill:N,type:s,binFail:w,metadata:e.metadata});return}if(s==="metadata"){const S=R(e),N=S!==void 0?u?.get(S)??z:z;t.push({x:a,y:n,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=P(e,0),Y=y!==void 0?l.forValue(g(y)):z;t.push({x:a,y:n,width:m,height:x,fill:Y,type:"value",metadata:e.metadata})}function Pe(t,e,a){const n=t.radius*.15,s=yt(e,n,0),i=yt(e,0,n),o=s.x+i.x>=0?-1:1,l=s.y+i.y>=0?-1:1,d={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:d.x+s.x,y:d.y+s.y},p={x:d.x+i.x,y:d.y+i.y};return a.push({x:g.x+s.x*.35,y:g.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+i.x*.35,y:p.y+i.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...K([d,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...K([d,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},n){const s=a.reticles??[],{plotMode:i="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:d=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:$=!1,useDefinedBinColors:u=!0,binColors:f,passBins:h=[1],highlightBin:k,highlightMetadataValue:m,interactiveTransform:x,valueRange:C,metadataValueOrder:R,testDefs:y,activeTest:Y=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:w,lotSize:J,dataAxisFlip:W,isLotStack:rt=!1,logScale:wt,colorbarRangeMode:Wt="spec",passFailDisplay:jt,showPartialDies:zt=!0}=a,kt=jt??"off",lt=n?.hbinDefs,ct=n?.sbinDefs,vt=n?.metadataFields,Kt=vt?.find(c=>c.key===S),qt={x:(W?.x??!1)!==(x?.flipX??!1),y:(W?.y??!1)!==(x?.flipY??!1)},Xt=lt?new Map(lt.map(c=>[c.bin,c])):null,Yt=ct?new Map(ct.map(c=>[c.bin,c])):null,Gt=i==="softBin"?Yt:Xt,Qt=new Set(h),ut=f&&me(f,e)?f:he(e,{passBins:h,binColorScheme:T,hbinDefs:lt,sbinDefs:ct,useDefinedBinColors:u}),Ut=i==="softBin"?ut.soft:ut.hard,$t={forValue:ve(de(b,$)),forBin:c=>Ut.get(c)??z};let{testNumber:A}=resolveTestNumber(Y,y);if(!y?.length&&i==="value"&&!e.some(r=>r.testValues&&A in r.testValues)){const r=e.reduce((M,V)=>{if(!V.testValues)return M;const v=Object.keys(V.testValues).map(Number),L=v.length?be(v):void 0;return L!==void 0&&(M===void 0||L<M)?L:M},void 0);r!==void 0&&(A=r)}let Z;if(Array.isArray(C))Z=C;else if(C){const{testNumber:c}=resolveTestNumber(C.test,y);Z=c===A?C.range:void 0}const I=findTestDef(y,A),Bt=i==="value"&&I!==void 0&&!xt(I),Jt=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let _="off";i==="value"&&(Bt?_="test":kt==="spec"&&Jt?_="spec":kt==="test"&&e.some(c=>U(c,A,I)!==void 0)&&(_="test"));const Tt=_==="spec"?"spec":Wt,ft=i==="stackedValues"||i==="stackedBins"||i==="stackedSoftBins";let O,E,tt=!1;if(Z){[O,E]=Z;let c=!0,r=!1;for(const M of e){const V=ft?P(M,0):P(M,A);if(V!==void 0&&(r=!0,!Number.isInteger(V))){c=!1;break}}tt=r&&c}else{const c=!ft&&i==="value"&&Tt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,V=!0;for(const v of e){const L=ft?P(v,0):P(v,A);L!==void 0&&(L<r&&(r=L),L>M&&(M=L),Number.isInteger(L)||(V=!1))}isFinite(r)||(V=!1),tt=V,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,E=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,E=isFinite(M)?M:1)}O===E&&(E=O+1);const It=Bt?!1:wt??I?.logScale??!1,Vt=It&&O>0&&E>0,Zt=Vt;let et;if(Vt){const c=Math.log10(O),r=Math.log10(E)-c;et=M=>M<=0?0:Ot((Math.log10(M)-c)/r)}else et=c=>Ot((c-O)/(E-O));const F=Se(t,W,x);let Ct=null;if(t.notch){const c=_t(t.notch.type),r=yt(F.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Ct={x:r.x/M,y:r.y/M})}let q=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<q&&(q=r.width),r.height<q&&(q=r.height)}const Ft=1,te=q!==1/0?Math.min(Ft,q*.12):Ft,Dt=[],dt=[],ee=F.bakedToScreen.a===1&&F.bakedToScreen.b===0&&F.bakedToScreen.c===0&&F.bakedToScreen.d===1&&F.bakedToScreen.e===0&&F.bakedToScreen.f===0;let j=null;if(!ee){j=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=G(F.bakedToScreen,e[c].physX,e[c].physY);j[c*2]=r.x,j[c*2+1]=r.y}}const ne=ue(F.gridToScreen),ae=i==="hardBin"||i==="softBin"||i==="stackedBins"||i==="stackedSoftBins",ie=i==="softBin"||i==="stackedSoftBins";let nt=ae?new Map:void 0;const ht=i==="value"&&_==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,mt=i==="value"&&_==="test"?{pass:0,fail:0}:void 0,Pt=i==="metadata";let at=Pt?new Map:void 0,it=null;if(Pt&&S){const c=collectMetadataValues(e,S),r=R?.key===S?R.values:void 0,M=r?[...r,...c.filter(V=>!r.includes(V))]:c;it=new Map(M.map((V,v)=>[V,Kt?.values?.find(L=>L.value===V)?.color??ye(v)]))}for(let c=0;c<e.length;c++){const r=e[c],M=j?j[c*2]:r.physX,V=j?j[c*2+1]:r.physY;if(dt.push({x:M,y:V}),nt&&!r.partial&&!r.edgeExcluded){const v=ie?r.sbin:r.hbin;v!=null&&nt.set(v,(nt.get(v)??0)+1)}if(ht&&!r.partial&&!r.edgeExcluded){const v=classifySpec(P(r,A),I);v&&ht[v]++}if(mt&&!r.partial&&!r.edgeExcluded){const v=U(r,A,I);v!==void 0&&mt[v?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const v=ot(r,S);v!==void 0&&at.set(v,(at.get(v)??0)+1)}r.partial&&!zt||De(Dt,r,M,V,i,ne,te,$t,k,et,A,Gt,Qt,I,_,S,it,m)}const Lt=o?generateTextOverlay(e,j,{plotMode:i,colorFns:$t,normalize:et,activeTest:Y,valueRange:[O,E],testDefs:y,fallbackFormat:N,passFailDisplay:_,activeMetadataKey:S,metadataColorMap:it,allIntegerValues:tt}):[],X=Te(t,F.physicalToScreen);p&&X.push(...Ie(t,F.physicalToScreen,g)),B&&X.push(...Ve(t,F.physicalToScreen,t.center.x,t.center.y)),l&&X.push(...Ce(s,F.gridToScreen)),d&&X.push(...Fe(e)),D&&X.push(...Pe(t,F.gridToScreen,Lt));const se=dt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Dt,hoverPoints:dt,texts:Lt,overlays:X,plotMode:i,binColorScheme:T,valueColorScheme:b,reverseValueScheme:$,binColors:ut,metadata:t.metadata??null,dies:e,valueRange:[O,E],allIntegerValues:tt,testDefs:y,activeTest:Y,logScale:Zt,logScaleRequested:It,aggrMethod:w,lotSize:J,axisFlip:qt,dataAxisFlip:{x:W?.x??!1,y:W?.y??!1},gridToScreen:F.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:rt,passFailDisplay:_,colorbarRangeMode:Tt,waferCenter:t.center,waferRadius:t.radius,notchDir:Ct,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:S,metadataFields:vt,specCounts:ht,passFailCounts:mt,dieBounds:se}}export{getDieKey}from"../core/dies.js";
@@ -393,19 +393,40 @@ export interface WaferMapInputSingle extends WaferMapInputBase {
393
393
  /** Per-die test results from the prober. */
394
394
  results?: DieResult[];
395
395
  lotStack?: never;
396
+ layout?: never;
396
397
  }
397
398
  /** Lot-stack input — collapse results from multiple wafers into a single aggregated map. */
398
399
  export interface WaferMapInputLotStack extends WaferMapInputBase {
399
400
  /** Lot-level stacking — collapse results from several wafers into a single map. */
400
401
  lotStack: LotStackConfig;
401
402
  results?: never;
403
+ layout?: never;
402
404
  }
403
405
  /**
404
- * Input accepted by {@link buildWaferMap}.
405
- * Use {@link WaferMapInputSingle} for a single wafer or {@link WaferMapInputLotStack}
406
- * for an aggregated lot-stack map. Passing both `results` and `lotStack` is a type error.
406
+ * Layout input — a wafer's die sites with no test data: gross die per wafer,
407
+ * reticle and step planning, the expected die map before data exists.
408
+ *
409
+ * Requires `waferConfig.diameter` and `dieConfig.width`/`height`. The map holds
410
+ * every die site lying **fully** on the wafer (notch and flat included), which is
411
+ * the gross die count and the set of sites a prober can step to — so, like a map
412
+ * built from results, it never contains edge-straddling dies. Die `(0, 0)` is the
413
+ * site centred on the wafer, and `x`/`y` count sites in the directions
414
+ * `dieConfig.xAxisDirection`/`yAxisDirection` give. `waferConfig.center` is
415
+ * ignored: the layout defines the centre. `edgeExclusion`, `reticleConfig` and
416
+ * orientation apply as for any map.
417
+ */
418
+ export interface WaferMapInputLayout extends WaferMapInputBase {
419
+ layout: true;
420
+ results?: never;
421
+ lotStack?: never;
422
+ }
423
+ /**
424
+ * Input accepted by {@link buildWaferMap}: one wafer's results
425
+ * ({@link WaferMapInputSingle}), an aggregated lot stack
426
+ * ({@link WaferMapInputLotStack}), or a die layout with no data
427
+ * ({@link WaferMapInputLayout}). Combining them is a type error.
407
428
  */
408
- export type WaferMapInput = WaferMapInputSingle | WaferMapInputLotStack;
429
+ export type WaferMapInput = WaferMapInputSingle | WaferMapInputLotStack | WaferMapInputLayout;
409
430
  /** Options forwarded to {@link buildView}. */
410
431
  export interface WaferMapOptions extends ViewOptions {
411
432
  debug?: boolean;
@@ -494,6 +515,10 @@ export interface WaferWarning {
494
515
  * lot-level ring figures (Summary panel, report, Insights) use the count named in
495
516
  * the message.
496
517
  *
518
+ * - `'input-values-not-numbers'` — raised by `buildWaferMap`: bins or test values
519
+ * were given as text (or verdicts as something other than true/false), as a
520
+ * CSV parser produces. They are not converted, so those dies are judged and
521
+ * plotted wrongly — a bin of "1" is not pass bin 1. The message counts them.
497
522
  * - `'input-field-removed'` — raised by `buildWaferMap`: the input used a name
498
523
  * removed in an earlier release (`data`, `die`, `stack`, `values`,
499
524
  * `TestDef.index`, `dieConfig.origin`, `waferConfig.flat`,
@@ -504,7 +529,7 @@ export interface WaferWarning {
504
529
  * The union is intentionally open to string so future advisory codes can be
505
530
  * added without a breaking change; switch with a `default` branch.
506
531
  */
507
- code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | 'bin-colors-shared' | 'pass-bins-mixed' | 'ring-count-mixed' | 'input-field-removed' | (string & {});
532
+ code: 'partial-coverage' | 'geometry-conflict' | 'non-standard-diameter' | 'diameter-exceeds-die-extent' | 'test-count-capped' | 'edge-exclusion-exceeds-radius' | 'analysis-option-corrected' | 'bin-colors-shared' | 'pass-bins-mixed' | 'ring-count-mixed' | 'input-field-removed' | 'input-values-not-numbers' | (string & {});
508
533
  /** Human-readable explanation, suitable for direct display. */
509
534
  message: string;
510
535
  /**