@wafertools/wafermap 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/AGENTS.md +13 -14
  2. package/CHANGELOG.md +291 -0
  3. package/README.md +5 -5
  4. package/dist/packages/canvas-adapter/deprecated.d.ts +10 -0
  5. package/dist/packages/canvas-adapter/deprecated.js +1 -0
  6. package/dist/packages/canvas-adapter/dieList.d.ts +1 -3
  7. package/dist/packages/canvas-adapter/dieList.js +7 -7
  8. package/dist/packages/canvas-adapter/index.d.ts +1 -3
  9. package/dist/packages/canvas-adapter/index.js +1 -1
  10. package/dist/packages/canvas-adapter/insightsTab.d.ts +0 -1
  11. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  12. package/dist/packages/canvas-adapter/maplessSummary.d.ts +3 -0
  13. package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
  14. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +1 -40
  15. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  16. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +81 -104
  17. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  18. package/dist/packages/canvas-adapter/summaryPanel.d.ts +11 -8
  19. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  20. package/dist/packages/canvas-adapter/toCanvas.d.ts +20 -12
  21. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  22. package/dist/packages/canvas-adapter/toolbar.d.ts +2 -0
  23. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  24. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  25. package/dist/packages/canvas-adapter/userGuideHtml.js +14 -10
  26. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  27. package/dist/packages/canvas-adapter/version.js +1 -1
  28. package/dist/packages/canvas-adapter/warnings.js +1 -1
  29. package/dist/packages/core/deprecated.d.ts +64 -0
  30. package/dist/packages/core/deprecated.js +1 -0
  31. package/dist/packages/core/index.d.ts +1 -3
  32. package/dist/packages/core/index.js +1 -1
  33. package/dist/packages/core/passBins.d.ts +33 -0
  34. package/dist/packages/core/passBins.js +1 -0
  35. package/dist/packages/core/waferLabel.d.ts +25 -0
  36. package/dist/packages/core/waferLabel.js +1 -0
  37. package/dist/packages/renderer/binColors.d.ts +67 -13
  38. package/dist/packages/renderer/binColors.js +1 -1
  39. package/dist/packages/renderer/buildView.d.ts +23 -9
  40. package/dist/packages/renderer/buildView.js +1 -1
  41. package/dist/packages/renderer/buildWaferMap.d.ts +47 -13
  42. package/dist/packages/renderer/buildWaferMap.js +1 -1
  43. package/dist/packages/renderer/colorMap.d.ts +4 -3
  44. package/dist/packages/renderer/colorSchemes.d.ts +21 -6
  45. package/dist/packages/renderer/colorSchemes.js +1 -1
  46. package/dist/packages/renderer/deprecate.d.ts +24 -0
  47. package/dist/packages/renderer/deprecate.js +1 -0
  48. package/dist/packages/renderer/deprecated.d.ts +45 -0
  49. package/dist/packages/renderer/deprecated.js +1 -0
  50. package/dist/packages/renderer/index.d.ts +2 -3
  51. package/dist/packages/renderer/index.js +1 -1
  52. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  53. package/dist/packages/stats/binPareto.d.ts +6 -1
  54. package/dist/packages/stats/clusterDetection.d.ts +0 -2
  55. package/dist/packages/stats/deprecated.d.ts +87 -0
  56. package/dist/packages/stats/deprecated.js +1 -0
  57. package/dist/packages/stats/index.d.ts +3 -14
  58. package/dist/packages/stats/index.js +1 -1
  59. package/dist/packages/stats/regions.d.ts +7 -2
  60. package/dist/packages/stats/regions.js +1 -1
  61. package/dist/packages/stats/renderSummaryReport.d.ts +3 -0
  62. package/dist/packages/stats/renderSummaryReport.js +34 -34
  63. package/dist/packages/stats/types.d.ts +0 -43
  64. package/dist/packages/stats/yield.d.ts +12 -6
  65. package/dist/packages/stats/yield.js +1 -1
  66. package/package.json +1 -1
@@ -72,7 +72,7 @@ export const USER_GUIDE_HTML=`<div class="wmap-guide">
72
72
  .wmap-guide-toc{display:none!important}
73
73
  }
74
74
  </style>
75
- <h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-09-11T18:10:20.393Z">v0.28.0</span></h1>
75
+ <h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-09-15T15:29:09.946Z">v0.30.0</span></h1>
76
76
  <p class="wmap-guide-online-link">This is a quick reference. <a href="https://wafertools.github.io/wafermap/user-guide/" target="_blank" rel="noopener">View the full illustrated guide online \u2197</a></p>
77
77
  <p>This guide describes the display and analysis features of the wafer map viewer.
78
78
  It is written for users who may be semiconductor test engineers, device engineers, and yield engineers
@@ -129,7 +129,9 @@ fail; it simply has no result recorded. Edge-excluded dies are shown dimmed and
129
129
  not counted in yield calculations.</p>
130
130
  <h3 id="14-legend-and-colorbar">1.4 Legend and colorbar</h3>
131
131
  <p><strong>Bin modes</strong> show a discrete colour legend: one swatch per bin, with bin number
132
- and name if the application has supplied bin names.</p>
132
+ and name if the application has supplied bin names. Passing bins are listed first,
133
+ then failing bins from the most dies to the fewest \u2014 the same order as the Summary
134
+ panel and reports.</p>
133
135
  <p><strong>Value mode</strong> shows a continuous colorbar: the colour scale runs from the minimum
134
136
  to maximum value, with units when available. The colorbar is informational only \u2014
135
137
  clicking it has no effect. To switch to a pass/fail view, use the <strong>Spec pass/fail</strong>
@@ -273,7 +275,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
273
275
  <tr>
274
276
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xNC42MjIgMTcuODk3LTEwLjY4LTIuOTEzIi8+PHBhdGggZD0iTTE4LjM3NiAyLjYyMmExIDEgMCAxIDEgMy4wMDIgMy4wMDJMMTcuMzYgOS42NDNhLjUuNSAwIDAgMCAwIC43MDdsLjk0NC45NDRhMi40MSAyLjQxIDAgMCAxIDAgMy40MDhsLS45NDQuOTQ0YS41LjUgMCAwIDEtLjcwNyAwTDguMzU0IDcuMzQ4YS41LjUgMCAwIDEgMC0uNzA3bC45NDQtLjk0NGEyLjQxIDIuNDEgMCAwIDEgMy40MDggMGwuOTQ0Ljk0NGEuNS41IDAgMCAwIC43MDcgMHoiLz48cGF0aCBkPSJNOSA4Yy0xLjgwNCAyLjcxLTMuOTcgMy40Ni02LjU4MyAzLjk0OGEuNTA3LjUwNyAwIDAgMC0uMzAyLjgxOWw3LjMyIDguODgzYTEgMSAwIDAgMCAxLjE4NS4yMDRDMTIuNzM1IDIwLjQwNSAxNiAxNi43OTIgMTYgMTUiLz48L3N2Zz4K" width="20" height="20"></td>
275
277
  <td>Colour scheme</td>
276
- <td>Picks the colours for the map on screen. In Hard Bin and Soft Bin modes it lists <strong>bin colours</strong> (Default, Colour-blind safe); in every other mode \u2014 including the stacked modes, which show values \u2014 it lists <strong>value colours</strong>. The two are separate choices, so changing one never resets the other. When bin definitions carry their own colours, <strong>Use colours from bin definitions</strong> turns them on or off. (<strong>Mark failing dies</strong> is in Overlays, beside the pass/fail display it belongs with.)</td>
278
+ <td>Picks the colours for the map on screen. In Hard Bin and Soft Bin modes it lists <strong>bin colours</strong> (Default, Colour-blind safe); in every other mode \u2014 including the stacked modes, which show values \u2014 it lists <strong>value colours</strong>. The two are separate choices, so changing one never resets the other. Value gradients run dark at the low end and bright at the high end, so on a stacked map the healthy wafer sits back and a defect ring or scratch stands out; <strong>Reverse gradient</strong> flips that when the low end is the one you care about. When bin definitions carry their own colours, <strong>Use colours from bin definitions</strong> turns them on or off. (<strong>Mark failing dies</strong> is in Overlays, beside the pass/fail display it belongs with.)</td>
277
279
  </tr>
278
280
  <tr>
279
281
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDIxIEw1IDMiLz48cGF0aCBkPSJNMy41IDUgTDUgMyBMNi41IDUiLz48cGF0aCBkPSJNNSAyMSBMMjEgMjEiLz48cGF0aCBkPSJNMTkgMTkuNSBMMjEgMjEgTDE5IDIyLjUiLz48cGF0aCBkPSJNNiAyMCBDIDYgMTIsIDEwIDcsIDIwIDYiLz48L3N2Zz4K" width="20" height="20"></td>
@@ -367,11 +369,13 @@ failure category across the wafer.</p>
367
369
  <div data-wmap-demo="bin-highlight" class="wmap-demo"></div><p><em>Bin 2 (Fail) highlighted \u2014 all other bins are dimmed.</em></p>
368
370
  <p><strong>Bin colours</strong> \u2014 a bin&#39;s colour tells you whether it passed before you read the legend:
369
371
  passing bins are always shades of green and failing bins never are, whichever bin numbers
370
- your test program uses. The bins with the most dies get the most distinct colours. In a
371
- gallery the colours are worked out across every wafer it shows, so a bin is the same
372
- colour on every card. If there are more bins than the colour scheme has distinct colours, some bins
373
- have to share one \u2014 a <strong>Data warnings</strong> entry names them, and you can tell them apart with
374
- the legend, the tooltip, or by highlighting one bin at a time.</p>
372
+ your test program uses. Each bin number keeps its colour: bin 7 is the same colour in every
373
+ lot, every gallery and every screenshot, however many dies it holds, so you can learn your
374
+ program&#39;s colours. Hard and soft bins are coloured separately, so hard bin 3 and soft bin 3
375
+ are different colours. A colour scheme has a limited number of colours, so bins far enough
376
+ apart in number repeat one \u2014 when two of them are on screen together a <strong>Data warnings</strong>
377
+ entry names them, and you can tell them apart with the legend, the tooltip, or by
378
+ highlighting one bin at a time.</p>
375
379
  <h3 id="overlays">Overlays</h3>
376
380
  <p>Use the <strong>Overlays</strong> menu to toggle optional display layers on and off:</p>
377
381
  <ul>
@@ -452,7 +456,7 @@ the legend, the tooltip, or by highlighting one bin at a time.</p>
452
456
  <tr>
453
457
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xNC42MjIgMTcuODk3LTEwLjY4LTIuOTEzIi8+PHBhdGggZD0iTTE4LjM3NiAyLjYyMmExIDEgMCAxIDEgMy4wMDIgMy4wMDJMMTcuMzYgOS42NDNhLjUuNSAwIDAgMCAwIC43MDdsLjk0NC45NDRhMi40MSAyLjQxIDAgMCAxIDAgMy40MDhsLS45NDQuOTQ0YS41LjUgMCAwIDEtLjcwNyAwTDguMzU0IDcuMzQ4YS41LjUgMCAwIDEgMC0uNzA3bC45NDQtLjk0NGEyLjQxIDIuNDEgMCAwIDEgMy40MDggMGwuOTQ0Ljk0NGEuNS41IDAgMCAwIC43MDcgMHoiLz48cGF0aCBkPSJNOSA4Yy0xLjgwNCAyLjcxLTMuOTcgMy40Ni02LjU4MyAzLjk0OGEuNTA3LjUwNyAwIDAgMC0uMzAyLjgxOWw3LjMyIDguODgzYTEgMSAwIDAgMCAxLjE4NS4yMDRDMTIuNzM1IDIwLjQwNSAxNiAxNi43OTIgMTYgMTUiLz48L3N2Zz4K" width="20" height="20"></td>
454
458
  <td>Colour scheme</td>
455
- <td>Picks bin colours (bin modes) or value colours (other modes) for all cards.</td>
459
+ <td>Picks bin colours (bin modes) or value colours (other modes) for all cards, including <strong>Reverse gradient</strong>.</td>
456
460
  </tr>
457
461
  <tr>
458
462
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBvbHlnb24gcG9pbnRzPSIxNi4yNCA3Ljc2IDE0LjEyIDE0LjEyIDcuNzYgMTYuMjQgOS44OCA5Ljg4IDE2LjI0IDcuNzYiIGZpbGw9IiMzNzQxNTEiIHN0cm9rZT0ibm9uZSIvPjwvc3ZnPgo=" width="20" height="20"></td>
@@ -482,7 +486,7 @@ the legend, the tooltip, or by highlighting one bin at a time.</p>
482
486
  <tr>
483
487
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjciIHg9IjMiIHk9IjMiIHJ4PSIxIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iNyIgeD0iMyIgeT0iMTQiIHJ4PSIxIi8+PHBhdGggZD0iTTE0IDRoNyIvPjxwYXRoIGQ9Ik0xNCA5aDciLz48cGF0aCBkPSJNMTQgMTVoNyIvPjxwYXRoIGQ9Ik0xNCAyMGg3Ii8+PC9zdmc+Cg==" width="20" height="20"></td>
484
488
  <td>Legend style</td>
485
- <td>Bin modes only. Sets where the bin legend sits relative to each card.</td>
489
+ <td><strong>Legend on each map</strong> adds a legend to every card as well as the shared one above the grid (off by default). While it is on, bin modes also let you choose where that legend sits on each card.</td>
486
490
  </tr>
487
491
  <tr>
488
492
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDQgTDUgMjAiLz48cGF0aCBkPSJNNSA0IEw4IDQiLz48cGF0aCBkPSJNNSAyMCBMOCAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxOSAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxNiA0Ii8+PHBhdGggZD0iTTE5IDIwIEwxNiAyMCIvPjxwYXRoIGQ9Ik01IDEyIEwxOSAxMiIgc3Ryb2tlLWRhc2hhcnJheT0iMyAyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
@@ -1,3 +1,3 @@
1
- export declare const WMAP_VERSION = "0.28.0";
2
- export declare const WMAP_BUILD_TIME = "2026-09-11T18:13:58.943Z";
1
+ export declare const WMAP_VERSION = "0.30.0";
2
+ export declare const WMAP_BUILD_TIME = "2026-09-15T15:32:11.763Z";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- export const WMAP_VERSION="0.28.0",WMAP_BUILD_TIME="2026-09-11T18:13:58.943Z";
1
+ export const WMAP_VERSION="0.30.0",WMAP_BUILD_TIME="2026-09-15T15:32:11.763Z";
@@ -1 +1 @@
1
- import{SHADOW as w,TRACKING as O,SPACE as s,RADIUS as E,FONT as y,CLR as d,Z_BASE as A}from"./toolbar.js";const $={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)=>$[severityOf(e)]-$[severityOf(r)])}function v(t){return t==="error"?{bg:d.errBg,border:d.errBorder,text:d.errText}:{bg:d.warnBg,border:d.warnBorder,text:d.warnText}}function C(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"};function S(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=v(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=`${C(e)} ${S(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",`${S(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:w.menu,zIndex:A,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=v(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=C(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 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}
@@ -0,0 +1,64 @@
1
+ import { createWafer as createWaferImpl } from './wafer.js';
2
+ import { generateDies as generateDiesImpl, isPositionedDie as isPositionedDieImpl } from './dies.js';
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
+ import { applyProbeSequence as applyProbeSequenceImpl } from './probe.js';
5
+ import { generateReticleGrid as generateReticleGridImpl, getReticleCell as getReticleCellImpl } from './reticle.js';
6
+ import { classifyDie as classifyDieImpl, getRingLabel as getRingLabelImpl } from './classify.js';
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';
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`. */
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`. */
13
+ export declare const generateDies: typeof generateDiesImpl;
14
+ /** @deprecated Removed in 0.31.0. Use `hasPosition` to test whether a die has a position. */
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`. */
17
+ export declare const clipDiesToWafer: typeof clipDiesToWaferImpl;
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
+ export declare const applyOrientation: typeof applyOrientationImpl;
20
+ /** @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`. */
21
+ export declare const transformDies: typeof transformDiesImpl;
22
+ /** @deprecated Removed in 0.31.0. `buildWaferMap` maps results onto dies. */
23
+ export declare const mapDataToDies: typeof mapDataToDiesImpl;
24
+ /** @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`. */
25
+ export declare const isInsideWafer: typeof isInsideWaferImpl;
26
+ /** @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`. */
27
+ export declare const affineIdentity: typeof affineIdentityImpl;
28
+ /** @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`. */
29
+ export declare const affineRotation: typeof affineRotationImpl;
30
+ /** @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`. */
31
+ export declare const affineMirror: typeof affineMirrorImpl;
32
+ /** @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`. */
33
+ export declare const affineCompose: typeof affineComposeImpl;
34
+ /** @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`. */
35
+ export declare const affineInvert: typeof affineInvertImpl;
36
+ /** @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`. */
37
+ export declare const affinePoint: typeof affinePointImpl;
38
+ /** @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`. */
39
+ export declare const affineVector: typeof affineVectorImpl;
40
+ /** @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`. */
41
+ export declare const affineSwapsAxes: typeof affineSwapsAxesImpl;
42
+ /** @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`. */
43
+ export declare const applyProbeSequence: typeof applyProbeSequenceImpl;
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
+ 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. */
49
+ 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. */
51
+ export declare const getRingLabel: typeof getRingLabelImpl;
52
+ /** @deprecated Removed in 0.31.0. Stack wafers with `buildWaferMap`'s `lotStack` option. */
53
+ export declare const aggregateValues: typeof aggregateValuesImpl;
54
+ /** @deprecated Removed in 0.31.0. Stack wafers with `buildWaferMap`'s `lotStack` option. */
55
+ export declare const aggregateBinCounts: typeof aggregateBinCountsImpl;
56
+ /** @deprecated Removed in 0.31.0. Stack wafers with `buildWaferMap`'s `lotStack` option. */
57
+ 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. */
61
+ export declare const metadataCategoricalValue: typeof metadataCategoricalValueImpl;
62
+ /** @deprecated Removed in 0.31.0. `buildWaferMap` resolves the die pitch itself; each built die's width and height carry it. */
63
+ export declare const resolveGridPitch: typeof resolveGridPitchImpl;
64
+ //# sourceMappingURL=deprecated.d.ts.map
@@ -0,0 +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}`);
@@ -5,9 +5,7 @@ export * from './metadata.js';
5
5
  export * from './probe.js';
6
6
  export * from './reticle.js';
7
7
  export type { DataRow, MapOptions, TransformOptions, Affine, CoordFrame } from './transforms.js';
8
- export { isInsideWafer, clipDiesToWafer, applyOrientation, transformDies, mapDataToDies } from './transforms.js';
9
- export { affineIdentity, affineRotation, affineMirror, affineCompose, affineInvert, affinePoint, affineVector, affineSwapsAxes, } from './transforms.js';
10
8
  export * from './wafer.js';
11
9
  export type { PitchResult } from './inference/pitch.js';
12
- export { resolveGridPitch } 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';
13
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{isInsideWafer,clipDiesToWafer,applyOrientation,transformDies,mapDataToDies}from"./transforms.js";export{affineIdentity,affineRotation,affineMirror,affineCompose,affineInvert,affinePoint,affineVector,affineSwapsAxes}from"./transforms.js";export*from"./wafer.js";export{resolveGridPitch}from"./inference/pitch.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,getReticleCell,classifyDie,getRingLabel,aggregateValues,aggregateBinCounts,getUniqueBins,metadataDisplayValue,metadataCategoricalValue,resolveGridPitch}from"./deprecated.js";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Which bins pass, per wafer — and how to name a set of them.
3
+ *
4
+ * The rule this file exists for: `[1]` is a default only where data ENTERS the
5
+ * library (`buildWaferMap`'s input). From there the value travels on the result
6
+ * (`WaferMapResult.passBins`), and every display, analysis and report reads it —
7
+ * per wafer, because a gallery can hold wafers from programs with different pass
8
+ * bins. Every downstream surface used to default `passBins` to `[1]` on its own
9
+ * unless the caller repeated it, so a host that told `buildWaferMap` bins 1 and 2
10
+ * pass saw bin 2 drawn, sorted and totalled as a failure everywhere else — with
11
+ * `result.yield`, the one figure that did use them, disagreeing beside it.
12
+ *
13
+ * Deliberately internal (not re-exported from `core/index.ts`).
14
+ */
15
+ /** The industry convention, applied ONLY to input that states no pass bins. */
16
+ export declare const INPUT_DEFAULT_PASS_BINS: readonly number[];
17
+ /**
18
+ * One wafer's pass bins: its own (carried from `buildWaferMap`), else the
19
+ * caller's default for items built without any, else the input convention.
20
+ */
21
+ export declare function itemPassBins(item: {
22
+ passBins?: readonly number[];
23
+ } | null | undefined, fallback?: readonly number[]): readonly number[];
24
+ /** Same bins, in any order. */
25
+ export declare function sameBins(a: readonly number[], b: readonly number[]): boolean;
26
+ /**
27
+ * Names the pass bins in use, for a yield label: "bin 1", "bins 1, 2", or —
28
+ * when the wafers shown disagree — "per wafer: bin 1 · bins 1, 2". A label that
29
+ * named one wafer's set while the figure pooled wafers judged differently would
30
+ * be the exact mislabelled population the library must never show.
31
+ */
32
+ export declare function passBinsLabel(sets: Iterable<readonly number[]>): string;
33
+ //# sourceMappingURL=passBins.d.ts.map
@@ -0,0 +1 @@
1
+ export const INPUT_DEFAULT_PASS_BINS=[1];export function itemPassBins(s,e=INPUT_DEFAULT_PASS_BINS){return s?.passBins??e}export function sameBins(s,e){const t=new Set(s),n=new Set(e);return t.size===n.size&&[...t].every(i=>n.has(i))}export function passBinsLabel(s){const e=[];for(const n of s)e.some(i=>sameBins(i,n))||e.push(n);const t=n=>n.length===0?"no bins":n.length===1?`bin ${n[0]}`:`bins ${n.join(", ")}`;return e.length<=1?t(e[0]??INPUT_DEFAULT_PASS_BINS):`per wafer: ${e.map(t).join(" \xB7 ")}`}
@@ -0,0 +1,25 @@
1
+ import { type WaferMetadata } from './metadata.js';
2
+ /**
3
+ * The name to show for one wafer among several — a gallery card's header, a
4
+ * findings or yield list row, a report's Wafer column, a die list's Wafer
5
+ * column, a detached window's title.
6
+ *
7
+ * Deliberately internal (not re-exported from `core/index.ts`): it is the one
8
+ * rule for every surface that names a wafer, not an API a host needs.
9
+ *
10
+ * Every one of those surfaces used to supply its own fallback for an item with
11
+ * no `label`, and they disagreed: the gallery card header was blank, the
12
+ * findings list and reports said `W3` (a position, which reads as a wafer ID
13
+ * and need not match one), a detached window said "Wafer map", and the
14
+ * Insights tab used the wafer ID. The wafer ID was on the item the whole time.
15
+ *
16
+ * Order: the caller's `label` (a blank one counts as absent), then the wafer's
17
+ * own `metadata.waferId`, then a position that says plainly it is not an ID.
18
+ */
19
+ export declare function waferDisplayLabel(item: {
20
+ label?: string;
21
+ wafer?: {
22
+ metadata?: WaferMetadata | null;
23
+ } | null;
24
+ } | null | undefined, index: number): string;
25
+ //# sourceMappingURL=waferLabel.d.ts.map
@@ -0,0 +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)`}
@@ -12,15 +12,26 @@ export interface BinColors {
12
12
  hard: Map<number, string>;
13
13
  soft: Map<number, string>;
14
14
  /**
15
- * Bins drawn in a colour that another bin of the same type also has — the
16
- * population has more pass or fail bins than the palette has colours, or a
17
- * defined colour duplicates one. Empty when every bin is distinguishable by
18
- * colour alone. Ascending bin number.
15
+ * Bins drawn in a colour that another bin of the same type also has — two
16
+ * bins present whose numbers land on the same palette slot (they differ by a
17
+ * multiple of the palette's length), or a defined colour that duplicates one.
18
+ * Empty when every bin is distinguishable by colour alone. Ascending bin number.
19
19
  */
20
20
  shared: {
21
21
  hard: number[];
22
22
  soft: number[];
23
23
  };
24
+ /**
25
+ * Bins that pass, per type — the verdict the colours were chosen with. Hard:
26
+ * the bins in `passBins`. Soft: bins whose every die passes. Read this, never
27
+ * `passBins`, to order, total or judge SOFT bins: `passBins` holds hard-bin
28
+ * numbers, so testing a soft bin against it treats soft bin 1 as a pass and
29
+ * soft bin 100 as a fail whatever their dies did.
30
+ */
31
+ pass: {
32
+ hard: Set<number>;
33
+ soft: Set<number>;
34
+ };
24
35
  }
25
36
  export interface BinColorOptions {
26
37
  /** Which bins pass. Default `[1]`. Must match the `passBins` behind the yield figure. */
@@ -40,26 +51,69 @@ export interface BinColorOptions {
40
51
  * Assign every bin present in `dies` a colour — the ONE rule for bin colour,
41
52
  * shared by the map, its legends, the summary panels and the Insights charts.
42
53
  *
43
- * Why not a function of the bin number, as it used to be:
44
54
  * - **Pass/fail comes from `passBins`, never the number.** Passing bins take
45
55
  * the palette's pass colours (greens) and failing bins its fail colours, so
46
- * with `passBins: [1, 3]` bin 3 is green and a failing bin 12 is not. Keying
47
- * colour on the number made bin 1 green even when it failed.
48
- * - **Rank, not hash.** Bins are ordered by die count (then bin number) and
49
- * take palette slots in that order, so the bins that dominate the map get
50
- * the most distinct colours and no two bins collide until the palette is
51
- * exhausted. Hashing collided from the start: two of the first sixteen soft
52
- * bins shared a colour exactly.
56
+ * with `passBins: [1, 3]` bin 3 is green and a failing bin 1 is not.
57
+ * - **Which colour is keyed by bin number, never by die count.** A pass bin
58
+ * takes `pass[(bin − 1) mod n]` and a fail bin `fail[(bin − 2) mod n]`: bin 1
59
+ * (the conventional pass bin) and bin 2 (the conventional first fail bin)
60
+ * land on the front, most distinct colour of each list, and the low bin
61
+ * numbers most programs use get the clearest colours. So bin 7 is the same
62
+ * colour in every lot, gallery subset and screenshot of a program, which is
63
+ * how engineers learn to read a bin map. This used to rank bins by die count,
64
+ * which gave "the biggest fail bin" a colour rather than any bin — two lots of
65
+ * one program drew the same bin in different colours. Before that it hashed
66
+ * the number, which collided two of the first sixteen soft bins; a modular
67
+ * index collides only at a palette's length apart, and `shared` names those.
68
+ * - **The slot counts from the bin number alone**, not its position among the
69
+ * pass or fail bins, so changing `passBins` recolours only the bins whose
70
+ * verdict changed.
53
71
  * - **Soft bins are pass-aware too.** A soft bin passes when every die
54
72
  * carrying it passes (by the same per-die rule yield uses); otherwise, or
55
73
  * when no die says, it takes a fail colour. The library has the data to
56
74
  * decide, so the caller never supplies it.
75
+ * - **Soft bins read the palette shifted by half its length**, so hard bin n
76
+ * and soft bin n are different colours by default. They are separate number
77
+ * spaces and are never on screen together, but a shared colour invites
78
+ * reading one as the other.
57
79
  *
58
80
  * A gallery resolves ONCE over every wafer it shows and hands the result to every
59
- * card (`ViewOptions.binColors`), so a bin is the same colour on every wafer.
81
+ * card (`ViewOptions.binColors`): a `BinDef.color` supplied by one item then
82
+ * colours that bin on every card, and `shared` describes the whole gallery.
60
83
  * A lone map resolves over its own dies.
61
84
  */
62
85
  export declare function resolveBinColors(dies: Iterable<Die>, options?: BinColorOptions): BinColors;
86
+ /** One wafer's dies and the pass bins that wafer was built with. */
87
+ export interface BinPassGroup {
88
+ dies: Iterable<Die>;
89
+ /** Omitted ⇒ the input convention `[1]` — pass `WaferMapResult.passBins`. */
90
+ passBins?: readonly number[];
91
+ }
92
+ /**
93
+ * `resolveBinColors` over several wafers that may carry DIFFERENT pass bins — a
94
+ * gallery of wafers from more than one test program. Each die is judged by its
95
+ * own wafer's `passBins`, and a bin passes only when every die carrying it
96
+ * passes (for one wafer that is exactly "the bin is in `passBins`").
97
+ *
98
+ * `mixedHardBins` names hard bins that pass on some wafers and fail on others.
99
+ * One colour and one legend row cannot be right for both, so the gallery states
100
+ * it (`mixedPassBinsWarning`) rather than letting either wafer's verdict
101
+ * silently stand for all of them.
102
+ */
103
+ export declare function resolveBinColorsByWafer(groups: Iterable<BinPassGroup>, options?: Omit<BinColorOptions, 'passBins'>): {
104
+ colors: BinColors;
105
+ mixedHardBins: number[];
106
+ };
107
+ /**
108
+ * Which hard and soft bins pass — `BinColors.pass` without the colours, for a
109
+ * caller that has dies but no resolved assignment (the summary report). The same
110
+ * tally `resolveBinColors` uses, so the two cannot disagree about a verdict.
111
+ */
112
+ export declare function binPassSets(dies: Iterable<Die>, passBins?: readonly number[]): BinColors['pass'];
113
+ /** `binPassSets` over wafers that each carry their own pass bins. */
114
+ export declare function binPassSetsByWafer(groups: Iterable<BinPassGroup>): BinColors['pass'];
115
+ /** The `pass-bins-mixed` advisory for `resolveBinColorsByWafer`'s `mixedHardBins`, or null. */
116
+ export declare function mixedPassBinsWarning(bins: readonly number[]): WaferWarning | null;
63
117
  /**
64
118
  * True when `colors` has an entry for every bin on these dies — i.e. it was
65
119
  * resolved over a population that includes them. `buildView` checks this
@@ -1 +1 @@
1
- import{diePassStatus as p}from"../core/dies.js";import{getBinColorScheme as m}from"./colorSchemes.js";export function resolveBinColors(r,o={}){const s=new Set(o.passBins??[1]),i=m(o.binColorScheme),l=o.useDefinedBinColors??!0,a=new Map,f=new Map,c=new Map;for(const e of r)if(e.hbin!=null&&a.set(e.hbin,(a.get(e.hbin)??0)+1),e.sbin!=null){f.set(e.sbin,(f.get(e.sbin)??0)+1);const n=p(e,s)===!0;c.set(e.sbin,(c.get(e.sbin)??!0)&&n)}const d=b(a,e=>s.has(e),i,l?o.hbinDefs:void 0),h=b(f,e=>c.get(e)===!0,i,l?o.sbinDefs:void 0);return{hard:d.colors,soft:h.colors,shared:{hard:d.shared,soft:h.shared}}}export function binColorsCover(r,o){for(const s of o)if(s.hbin!=null&&!r.hard.has(s.hbin)||s.sbin!=null&&!r.soft.has(s.sbin))return!1;return!0}export function binColorWarning(r,o){const s=o==="softBin"?"soft":o==="hardBin"?"hard":null;if(!s)return null;const i=r.shared[s];if(!i.length)return null;const l=i.length>8?`${i.slice(0,8).join(", ")} and ${i.length-8} more`:i.join(", ");return{code:"bin-colors-shared",severity:"warning",message:`${s==="hard"?"Hard":"Soft"} bins ${l} 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.`}}function b(r,o,s,i){const l=new Map;for(const n of i??[])n.color&&l.set(n.bin,n.color);const a=new Map,f=[],c=[];for(const n of r.keys()){const t=l.get(n);t!==void 0?a.set(n,t):(o(n)?f:c).push(n)}const d=(n,t)=>r.get(t)-r.get(n)||n-t;for(const[n,t]of[[f,s.pass],[c,s.fail]])n.sort(d),n.forEach((u,g)=>a.set(u,t[g%t.length]));const h=new Map;for(const[n,t]of a){const u=t.toLowerCase();h.set(u,[...h.get(u)??[],n])}const e=[...h.values()].filter(n=>n.length>1).flat().sort((n,t)=>n-t);return{colors:a,shared:e}}
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}}
@@ -128,6 +128,13 @@ export interface View {
128
128
  binColorScheme: string;
129
129
  /** Value gradient used for value and stacked modes, as registered with `registerValueColorScheme`. */
130
130
  valueColorScheme: string;
131
+ /**
132
+ * Whether that gradient was flipped (high value takes the gradient's low-end
133
+ * colour). Any surface colouring by value must pass this to
134
+ * `resolveValueColorFn` alongside {@link valueColorScheme}, or it will
135
+ * disagree with the die fills.
136
+ */
137
+ reverseValueScheme: boolean;
131
138
  /**
132
139
  * Every bin's resolved colour, both bin types — the single source for bin
133
140
  * colour. Die fills are built from it, and legends, panels and charts must
@@ -148,7 +155,7 @@ export interface View {
148
155
  allIntegerValues?: boolean;
149
156
  /** Named test definitions — populated when `testDefs` is passed to `buildWaferMap`. */
150
157
  testDefs?: TestDef[];
151
- /** Which `values[]` index is being displayed (for `value` plot mode). Default 0. */
158
+ /** The test number being displayed in `value` plot mode — a `die.testValues` key, not a position. */
152
159
  activeTest: number;
153
160
  /** True when log₁₀ scale is both requested and valid (vMin > 0). */
154
161
  logScale: boolean;
@@ -210,8 +217,6 @@ export interface View {
210
217
  * - `'off'` — continuous value gradient (or a non-value plot mode).
211
218
  */
212
219
  passFailDisplay: 'off' | 'spec' | 'test';
213
- /** True when `passFailDisplay === 'spec'` — kept for back-compat with existing consumers. */
214
- colorBySpec: boolean;
215
220
  /** Whether the colorbar range is anchored to spec limits ('spec') or data extents ('data'). */
216
221
  colorbarRangeMode: 'spec' | 'data';
217
222
  /** Wafer centre in scene/display mm coordinates (the rotation pivot). */
@@ -277,11 +282,18 @@ export interface ViewOptions {
277
282
  showRingBoundaries?: boolean;
278
283
  showQuadrantBoundaries?: boolean;
279
284
  showXYIndicator?: boolean;
280
- dieGap?: number;
281
285
  /** Bin palette for `hardBin`/`softBin` — any name registered via `registerBinColorScheme()`. Default `'default'`. */
282
286
  binColorScheme?: string;
283
287
  /** Value gradient for `value` and the stacked modes — any name registered via `registerValueColorScheme()`. Default `'default'`. */
284
288
  valueColorScheme?: string;
289
+ /**
290
+ * Flip the value gradient so the high end takes its low-end colour. Default
291
+ * false: every built-in reads low = dark, high = light. Set this for a
292
+ * parameter where low is the notable end, or for monochrome print output
293
+ * where more ink should mean more. Applies to the dies, the colorbar and the
294
+ * mapless summary together.
295
+ */
296
+ reverseValueScheme?: boolean;
285
297
  /** Honour `BinDef.color` where a bin definition supplies one. Default true. See `resolveBinColors`. */
286
298
  useDefinedBinColors?: boolean;
287
299
  /**
@@ -349,9 +361,10 @@ export interface ViewOptions {
349
361
  * Which bins count as passing. Default `[1]`.
350
362
  *
351
363
  * Sets `ViewRect.binFail` and decides which bins take the palette's pass
352
- * colours (see `resolveBinColors`) — never the bin number. Must match the
353
- * `passBins` given to `buildWaferMap` / `analyzeWaferMap`, or the map will
354
- * disagree with the yield figure beside it.
364
+ * colours (see `resolveBinColors`) — never the bin number. `buildWaferMap` and
365
+ * the renderers set this from `WaferMapResult.passBins`, so a map agrees with
366
+ * its own yield figure; only a hand-rolled pipeline calling `buildView`
367
+ * directly supplies it.
355
368
  */
356
369
  passBins?: number[];
357
370
  /**
@@ -359,10 +372,11 @@ export interface ViewOptions {
359
372
  * Generated by `generateReticleGrid` or the `reticle` option on `buildWaferMap`.
360
373
  */
361
374
  reticles?: Reticle[];
362
- /** Named test definitions — one per `values[]` entry. */
375
+ /** Named test definitions — one per test number in `die.testValues`. */
363
376
  testDefs?: TestDef[];
364
377
  /**
365
- * Which `values[]` index to display in `value` plot mode. Default `0`.
378
+ * The test number to display in `value` plot mode — a `die.testValues` key
379
+ * (`TestDef.testNumber`), not a position. Default `0`.
366
380
  * When `testDefs` is provided, the toolbar mode dropdown offers one item per test.
367
381
  */
368
382
  activeTest?: number;
@@ -1 +1 @@
1
- import{hasPosition as ie}from"../core/dies.js";import{getReticleCell as se}from"../core/reticle.js";import{metadataCategoricalValue as oe,metadataDisplayValue as re}from"../core/metadata.js";import{affineRotation as mt,affineMirror as Lt,affineCompose as it,affinePoint as Y,affineVector as pt,affineSwapsAxes as le}from"../core/transforms.js";import{contrastTextColor as G,SPEC_PASS_FILL as yt,SPEC_FAIL_LOW as ce,SPEC_FAIL_HIGH as gt}from"./colorMap.js";import{getValueColorScheme as ue}from"./colorSchemes.js";import{resolveBinColors as fe,binColorsCover as de}from"./binColors.js";import{NO_DATA_FILL as j}from"./colorMap.js";import{diePassStatus as he}from"../core/dies.js";import{getDieTestValue as L,getTestPassStatus as Q,isParametricTest as bt}from"./buildWaferMap.js";import{fmt as xt,fmtColorbarAxis as Mt,fmtAggregationMethod as Pt}from"./fmt.js";import{metadataValueColor as me}from"./colorMap.js";import{compareNatural as pe,clamp01 as Rt}from"../core/utils.js";import{prettyKey as Nt}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(pe)}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 ye="#d3d6db",Ot="#e8e9ea",ge="#aab0ba";function st(t,e){return e?oe(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 be(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 xe(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 Ht(t,e){return t.map(e).join(" / ")}function Me(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 Se(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 ke(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=L(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&&ie(t)){const f=se(t,p);b.push(`Reticle (${f.column}, ${f.row})`)}if(e==="stackedValues"){const f=L(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=L(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?` [${Pt(l)}]`:"";b.push(`${k}: ${m}${x}`)}}else{const f=ke(t,n,o);if(e==="value"&&f.length){const{testNumber:d}=resolveTestNumber(g??0,n),h=findTestDef(n,d),k=L(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=re(d);if(h===void 0)continue;const k=B?.find(m=>m.key===f)?.label??Nt(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?Nt(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 ${Pt(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=L(h,d);if(y===void 0)return[];m=Ht([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=L(h,0);if(y===void 0)return[];m=Ht([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:Se(h,m),color:x,align:"center"}]})}function At(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function ve(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(xe(t,d)))}return[{kind:"wafer-boundary",...z($,!0),lineColor:"#888888",lineWidth:1}]}const u=1.25,g=i.length,p=At(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 $e(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 Be(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 Te(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 Ie(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 Ve(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:ye,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:n,width:m,height:x,fill:ge,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:Ot,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:Ot,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=L(e,p),N=classifySpec(S,T);let _,U;S===void 0?_=j:b==="spec"?_=N==="failLow"?ce: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&&he(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=L(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 Ce(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,dieGap:T=1,binColorScheme:b="default",valueColorScheme:$="default",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:Et,colorbarRangeMode:_t="spec",passFailDisplay:wt,showPartialDies:Wt=!0}=a,St=wt??"off",rt=n?.hbinDefs,lt=n?.sbinDefs,kt=n?.metadataFields,jt=kt?.find(c=>c.key===S),zt={x:(w?.x??!1)!==(x?.flipX??!1),y:(w?.y??!1)!==(x?.flipY??!1)},Kt=rt?new Map(rt.map(c=>[c.bin,c])):null,qt=lt?new Map(lt.map(c=>[c.bin,c])):null,Xt=i==="softBin"?qt:Kt,Yt=new Set(h),ct=d&&de(d,e)?d:fe(e,{passBins:h,binColorScheme:b,hbinDefs:rt,sbinDefs:lt,useDefinedBinColors:f}),Gt=i==="softBin"?ct.soft:ct.hard,vt={forValue:Me(ue($).forValue),forBin:c=>Gt.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),P=v.length?Math.min(...v):void 0;return P!==void 0&&(M===void 0||P<M)?P: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),Qt=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let A="off";i==="value"&&($t?A="test":St==="spec"&&Qt?A="spec":St==="test"&&e.some(c=>Q(c,H,I)!==void 0)&&(A="test"));const Bt=A==="spec"?"spec":_t,ut=i==="stackedValues"||i==="stackedBins"||i==="stackedSoftBins";let O,E,Z=!1;if(J){[O,E]=J;let c=!0,r=!1;for(const M of e){const V=ut?L(M,0):L(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 P=ut?L(v,0):L(v,H);P!==void 0&&(P<r&&(r=P),P>M&&(M=P),Number.isInteger(P)||(V=!1))}isFinite(r)||(V=!1),Z=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 Tt=$t?!1:Et??I?.logScale??!1,It=Tt&&O>0&&E>0,Ut=It;let tt;if(It){const c=Math.log10(O),r=Math.log10(E)-c;tt=M=>M<=0?0:Rt((Math.log10(M)-c)/r)}else tt=c=>Rt((c-O)/(E-O));const F=be(t,w,x);let Vt=null;if(t.notch){const c=At(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 Jt=K!==1/0?Math.min(T,K*.12):T,Ct=[],ft=[],Zt=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(!Zt){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 te=le(F.gridToScreen),ee=i==="hardBin"||i==="softBin"||i==="stackedBins"||i==="stackedSoftBins",ne=i==="softBin"||i==="stackedSoftBins";let et=ee?new Map:void 0;const dt=i==="value"&&A==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ht=i==="value"&&A==="test"?{pass:0,fail:0}:void 0,Ft=i==="metadata";let nt=Ft?new Map:void 0,at=null;if(Ft&&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,jt?.values?.find(P=>P.value===V)?.color??me(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=ne?r.sbin:r.hbin;v!=null&&et.set(v,(et.get(v)??0)+1)}if(dt&&!r.partial&&!r.edgeExcluded){const v=classifySpec(L(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&&!Wt||Ve(Ct,r,M,V,i,te,Jt,vt,k,tt,H,Xt,Yt,I,A,S,at,m)}const Dt=o?generateTextOverlay(e,W,{plotMode:i,colorFns:vt,normalize:tt,activeTest:X,valueRange:[O,E],testDefs:y,fallbackFormat:N,passFailDisplay:A,activeMetadataKey:S,metadataColorMap:at,allIntegerValues:Z}):[],q=ve(t,F.physicalToScreen);p&&q.push(...$e(t,F.physicalToScreen,g)),B&&q.push(...Be(t,F.physicalToScreen,t.center.x,t.center.y)),l&&q.push(...Te(s,F.gridToScreen)),u&&q.push(...Ie(e)),D&&q.push(...Ce(t,F.gridToScreen,Dt));const ae=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:Ct,hoverPoints:ft,texts:Dt,overlays:q,plotMode:i,binColorScheme:b,valueColorScheme:$,binColors:ct,metadata:t.metadata??null,dies:e,valueRange:[O,E],allIntegerValues:Z,testDefs:y,activeTest:X,logScale:Ut,logScaleRequested:Tt,aggrMethod:_,lotSize:U,axisFlip:zt,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:A,colorBySpec:A==="spec",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:ae}}export{getDieKey}from"../core/dies.js";
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";