@wafertools/wafermap 0.27.0 → 0.29.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 (61) hide show
  1. package/AGENTS.md +10 -2
  2. package/CHANGELOG.md +366 -0
  3. package/README.md +1 -1
  4. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +0 -1
  5. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +8 -1
  6. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  7. package/dist/packages/canvas-adapter/charts/capability.d.ts +0 -1
  8. package/dist/packages/canvas-adapter/charts/correlation.d.ts +0 -1
  9. package/dist/packages/canvas-adapter/charts/histogram.d.ts +0 -1
  10. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +0 -1
  12. package/dist/packages/canvas-adapter/charts/scatter.d.ts +6 -1
  13. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/trend.d.ts +7 -0
  15. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  16. package/dist/packages/canvas-adapter/hitGrid.d.ts +22 -0
  17. package/dist/packages/canvas-adapter/hitGrid.js +1 -0
  18. package/dist/packages/canvas-adapter/index.d.ts +1 -0
  19. package/dist/packages/canvas-adapter/index.js +1 -1
  20. package/dist/packages/canvas-adapter/insightsTab.d.ts +24 -2
  21. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  22. package/dist/packages/canvas-adapter/maplessSummary.d.ts +8 -1
  23. package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
  24. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  25. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +45 -1
  26. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  27. package/dist/packages/canvas-adapter/summaryPanel.d.ts +15 -5
  28. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  29. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  30. package/dist/packages/canvas-adapter/toolbar.d.ts +46 -11
  31. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  32. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  33. package/dist/packages/canvas-adapter/userGuideHtml.js +27 -16
  34. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  35. package/dist/packages/canvas-adapter/version.js +1 -1
  36. package/dist/packages/canvas-adapter/warnings.d.ts +25 -0
  37. package/dist/packages/canvas-adapter/warnings.js +1 -1
  38. package/dist/packages/core/dies.d.ts +11 -0
  39. package/dist/packages/core/dies.js +1 -1
  40. package/dist/packages/renderer/binColors.d.ts +80 -0
  41. package/dist/packages/renderer/binColors.js +1 -0
  42. package/dist/packages/renderer/buildView.d.ts +82 -6
  43. package/dist/packages/renderer/buildView.js +1 -1
  44. package/dist/packages/renderer/buildWaferMap.d.ts +8 -2
  45. package/dist/packages/renderer/buildWaferMap.js +1 -1
  46. package/dist/packages/renderer/colorMap.d.ts +16 -15
  47. package/dist/packages/renderer/colorMap.js +1 -1
  48. package/dist/packages/renderer/colorSchemes.d.ts +57 -25
  49. package/dist/packages/renderer/colorSchemes.js +1 -1
  50. package/dist/packages/renderer/index.d.ts +3 -1
  51. package/dist/packages/renderer/index.js +1 -1
  52. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  53. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  54. package/dist/packages/stats/histogram.d.ts +28 -1
  55. package/dist/packages/stats/histogram.js +1 -1
  56. package/dist/packages/stats/population.d.ts +23 -0
  57. package/dist/packages/stats/population.js +1 -0
  58. package/dist/packages/stats/renderFindingsReport.js +10 -10
  59. package/dist/packages/stats/renderSummaryReport.js +22 -22
  60. package/dist/packages/stats/yield.js +1 -1
  61. package/package.json +5 -3
@@ -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-09T07:38:59.875Z">v0.27.0</span></h1>
75
+ <h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-09-12T08:32:38.570Z">v0.29.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
@@ -268,12 +268,12 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
268
268
  <tr>
269
269
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yIDEyczMuNi03IDEwLTcgMTAgNyAxMCA3LTMuNiA3LTEwIDctMTAtNy0xMC03eiIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjMiLz48L3N2Zz4K" width="20" height="20"></td>
270
270
  <td>Overlays</td>
271
- <td>Check-menu of optional display layers: XY axis indicator, ring boundaries, quadrant lines, die coordinate labels, reticle grid (when geometry is configured), Spec pass/fail (Test Value mode with limits), and Test pass/fail (Test Value mode, active test is functional or has recorded verdicts).</td>
271
+ <td>Check-menu of optional display layers: XY axis indicator, ring boundaries, quadrant lines, die coordinate labels, reticle grid (when geometry is configured), Spec pass/fail (Test Value mode with limits), Test pass/fail (Test Value mode, active test is functional or has recorded verdicts), and <strong>Mark failing dies</strong> (bin modes only). <strong>Clear overlays</strong> at the foot turns off everything in the menu at once, and is greyed when nothing is on \u2014 so the menu also answers &quot;is anything active?&quot; without you auditing every row.</td>
272
272
  </tr>
273
273
  <tr>
274
274
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xNC42MjIgMTcuODk3LTEwLjY4LTIuOTEzIi8+PHBhdGggZD0iTTE4LjM3NiAyLjYyMmExIDEgMCAxIDEgMy4wMDIgMy4wMDJMMTcuMzYgOS42NDNhLjUuNSAwIDAgMCAwIC43MDdsLjk0NC45NDRhMi40MSAyLjQxIDAgMCAxIDAgMy40MDhsLS45NDQuOTQ0YS41LjUgMCAwIDEtLjcwNyAwTDguMzU0IDcuMzQ4YS41LjUgMCAwIDEgMC0uNzA3bC45NDQtLjk0NGEyLjQxIDIuNDEgMCAwIDEgMy40MDggMGwuOTQ0Ljk0NGEuNS41IDAgMCAwIC43MDcgMHoiLz48cGF0aCBkPSJNOSA4Yy0xLjgwNCAyLjcxLTMuOTcgMy40Ni02LjU4MyAzLjk0OGEuNTA3LjUwNyAwIDAgMC0uMzAyLjgxOWw3LjMyIDguODgzYTEgMSAwIDAgMCAxLjE4NS4yMDRDMTIuNzM1IDIwLjQwNSAxNiAxNi43OTIgMTYgMTUiLz48L3N2Zz4K" width="20" height="20"></td>
275
275
  <td>Colour scheme</td>
276
- <td>Switches the colour palette used for value and stacked modes. Available schemes depend on what the application has registered.</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. 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
277
  </tr>
278
278
  <tr>
279
279
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDIxIEw1IDMiLz48cGF0aCBkPSJNMy41IDUgTDUgMyBMNi41IDUiLz48cGF0aCBkPSJNNSAyMSBMMjEgMjEiLz48cGF0aCBkPSJNMTkgMTkuNSBMMjEgMjEgTDE5IDIyLjUiLz48cGF0aCBkPSJNNiAyMCBDIDYgMTIsIDEwIDcsIDIwIDYiLz48L3N2Zz4K" width="20" height="20"></td>
@@ -293,7 +293,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
293
293
  <tr>
294
294
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBvbHlnb24gcG9pbnRzPSIxNi4yNCA3Ljc2IDE0LjEyIDE0LjEyIDcuNzYgMTYuMjQgOS44OCA5Ljg4IDE2LjI0IDcuNzYiIGZpbGw9IiMzNzQxNTEiIHN0cm9rZT0ibm9uZSIvPjwvc3ZnPgo=" width="20" height="20"></td>
295
295
  <td>Orientation</td>
296
- <td>Menu of display transforms: <strong>Rotate 90\xB0 clockwise</strong> (applies cumulatively), <strong>Flip horizontal</strong>, <strong>Flip vertical</strong>. These change only how the wafer is drawn \u2014 die coordinates in tooltips and labels are always the original values, whatever the orientation.</td>
296
+ <td>Menu of display transforms: <strong>Rotate 90\xB0 clockwise</strong> (applies cumulatively), <strong>Flip horizontal</strong>, <strong>Flip vertical</strong>, and <strong>Reset orientation</strong>. These change only how the wafer is drawn \u2014 die coordinates in tooltips and labels are always the original values, whatever the orientation. Reset is worth knowing about: rotation and mirroring do not combine in the order you applied them, so undoing a few clicks by clicking again does not reliably get you back \u2014 Reset does, and is greyed when you are already there.</td>
297
297
  </tr>
298
298
  <tr>
299
299
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xMyAxMy41IDItMi41LTItMi41Ii8+PHBhdGggZD0ibTIxIDIxLTQuMy00LjMiLz48cGF0aCBkPSJNOSA4LjUgNyAxMWwyIDIuNSIvPjxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjgiLz48L3N2Zz4K" width="20" height="20"></td>
@@ -365,6 +365,13 @@ without closing it; click it again to restore.</p>
365
365
  that bin and dim all others. Click again to clear. Useful for isolating a specific
366
366
  failure category across the wafer.</p>
367
367
  <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
+ <p><strong>Bin colours</strong> \u2014 a bin&#39;s colour tells you whether it passed before you read the legend:
369
+ 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>
368
375
  <h3 id="overlays">Overlays</h3>
369
376
  <p>Use the <strong>Overlays</strong> menu to toggle optional display layers on and off:</p>
370
377
  <ul>
@@ -445,7 +452,7 @@ failure category across the wafer.</p>
445
452
  <tr>
446
453
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xNC42MjIgMTcuODk3LTEwLjY4LTIuOTEzIi8+PHBhdGggZD0iTTE4LjM3NiAyLjYyMmExIDEgMCAxIDEgMy4wMDIgMy4wMDJMMTcuMzYgOS42NDNhLjUuNSAwIDAgMCAwIC43MDdsLjk0NC45NDRhMi40MSAyLjQxIDAgMCAxIDAgMy40MDhsLS45NDQuOTQ0YS41LjUgMCAwIDEtLjcwNyAwTDguMzU0IDcuMzQ4YS41LjUgMCAwIDEgMC0uNzA3bC45NDQtLjk0NGEyLjQxIDIuNDEgMCAwIDEgMy40MDggMGwuOTQ0Ljk0NGEuNS41IDAgMCAwIC43MDcgMHoiLz48cGF0aCBkPSJNOSA4Yy0xLjgwNCAyLjcxLTMuOTcgMy40Ni02LjU4MyAzLjk0OGEuNTA3LjUwNyAwIDAgMC0uMzAyLjgxOWw3LjMyIDguODgzYTEgMSAwIDAgMCAxLjE4NS4yMDRDMTIuNzM1IDIwLjQwNSAxNiAxNi43OTIgMTYgMTUiLz48L3N2Zz4K" width="20" height="20"></td>
447
454
  <td>Colour scheme</td>
448
- <td>Switches the colour palette for all cards.</td>
455
+ <td>Picks bin colours (bin modes) or value colours (other modes) for all cards, including <strong>Reverse gradient</strong>.</td>
449
456
  </tr>
450
457
  <tr>
451
458
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBvbHlnb24gcG9pbnRzPSIxNi4yNCA3Ljc2IDE0LjEyIDE0LjEyIDcuNzYgMTYuMjQgOS44OCA5Ljg4IDE2LjI0IDcuNzYiIGZpbGw9IiMzNzQxNTEiIHN0cm9rZT0ibm9uZSIvPjwvc3ZnPgo=" width="20" height="20"></td>
@@ -484,18 +491,18 @@ failure category across the wafer.</p>
484
491
  </tr>
485
492
  <tr>
486
493
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNOCAydjQiLz48cGF0aCBkPSJNMTIgMnY0Ii8+PHBhdGggZD0iTTE2IDJ2NCIvPjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxOCIgeD0iNCIgeT0iNCIgcng9IjIiLz48cGF0aCBkPSJNOCAxMGg2Ii8+PHBhdGggZD0iTTggMTRoOCIvPjxwYXRoIGQ9Ik04IDE4aDUiLz48L3N2Zz4K" width="20" height="20"></td>
487
- <td>Findings</td>
488
- <td>Opens or closes the lot-level Summary panel.</td>
494
+ <td>Summary panel</td>
495
+ <td>Opens or closes the Summary panel covering every wafer in the gallery.</td>
489
496
  </tr>
490
497
  <tr>
491
498
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMyAzdjE4aDE4Ii8+PHBhdGggZD0iTTE4IDE3VjkiLz48cGF0aCBkPSJNMTMgMTdWNSIvPjxwYXRoIGQ9Ik04IDE3di0zIi8+PC9zdmc+Cg==" width="20" height="20"></td>
492
499
  <td>Insights</td>
493
- <td>Swaps the grid for a lot-wide chart suite \u2014 yield, bin breakdown, process capability, and more (see <a href="#8-insights-tab" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'8-insights-tab\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 8</a>). Only shown when the application has enabled it.</td>
500
+ <td>Swaps the grid for a chart suite covering every wafer \u2014 yield, bin breakdown, process capability, and more (see <a href="#8-insights-tab" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'8-insights-tab\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 8</a>). Only shown when the application has enabled it.</td>
494
501
  </tr>
495
502
  <tr>
496
503
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTAuMjkgMy44NiAxLjgyIDE4YTIgMiAwIDAgMCAxLjcxIDNoMTYuOTRhMiAyIDAgMCAwIDEuNzEtM0wxMy43MSAzLjg2YTIgMiAwIDAgMC0zLjQyIDB6Ii8+PHBhdGggZD0iTTEyIDl2NCIvPjxwYXRoIGQ9Ik0xMiAxN2guMDEiLz48L3N2Zz4K" width="20" height="20"></td>
497
504
  <td>Data warnings</td>
498
- <td>Appears only when something is worth reporting about the lot. Collected across every wafer and de-duplicated, so a problem affecting the whole lot is stated once rather than repeated per card.</td>
505
+ <td>Appears only when something is worth reporting about the wafers shown. Collected across every wafer and de-duplicated, so a problem affecting all of them is stated once rather than repeated per card.</td>
499
506
  </tr>
500
507
  <tr>
501
508
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjxwYXRoIGQ9Ik05LjA5IDlhMyAzIDAgMCAxIDUuODMgMWMwIDItMyAzLTMgMyIvPjxwYXRoIGQ9Ik0xMiAxN2guMDEiLz48L3N2Zz4K" width="20" height="20"></td>
@@ -608,11 +615,14 @@ finding can highlight the affected dies right there. Open it from the toolbar.</
608
615
  highlight the affected dies.</em></p>
609
616
  <p>Sections, top to bottom:</p>
610
617
  <ul>
611
- <li><strong>Summary / Lot Summary</strong> \u2014 die and wafer counts, yield, and the population
612
- the rest of the panel is computed over. On a lot, &quot;Mean wafer yield&quot; is an
618
+ <li><strong>Summary</strong> \u2014 die and wafer counts, yield, and the population the rest of the
619
+ panel is computed over. For several wafers the heading names them: <strong>Lot
620
+ LOT123 \xB7 13 wafers</strong> when every wafer comes from that one lot, otherwise
621
+ <strong>26 wafers from 2 lots</strong> (or just <strong>13 wafers</strong> when the data records no lot).
622
+ The panel only says &quot;lot&quot; when it really is one lot. &quot;Mean wafer yield&quot; is an
613
623
  <em>unweighted</em> mean of each wafer&#39;s own yield; it is deliberately not the same
614
624
  statistic as the bin breakdown&#39;s pass-bin share, which weights every die
615
- equally. The two agree only when die counts are even across the lot.</li>
625
+ equally. The two agree only when die counts are even across the wafers.</li>
616
626
  <li><strong>Findings</strong> \u2014 detected anomalies, most severe first. Severity chips narrow
617
627
  the list; Kind and Region dropdowns appear once there are enough findings to
618
628
  be worth narrowing.</li>
@@ -625,10 +635,10 @@ overrides that.</li>
625
635
  Ring is the default because edge roll-off is the pattern that dominates real
626
636
  wafer maps; a genuinely asymmetric quadrant is reported as a finding above,
627
637
  with a significance test behind it.</li>
628
- <li><strong>Wafer yield</strong> (lot only) \u2014 one bar per wafer with the lot median marked.
629
- Wafers are in slot order by default, since that is what makes a
638
+ <li><strong>Wafer yield</strong> (several wafers only) \u2014 one bar per wafer with the median
639
+ marked. Wafers are in slot order by default, since that is what makes a
630
640
  slot-correlated pattern visible; a <strong>Slot / Yield</strong> selector re-sorts. Wafers
631
- far below the rest of the lot are labelled &quot;low outlier&quot;.</li>
641
+ far below the rest are labelled &quot;low outlier&quot;.</li>
632
642
  <li><strong>Test values</strong> \u2014 per test: mean, <strong>Ppk</strong>, and spec yield. Ppk (not Cpk)
633
643
  because it measures against the <em>overall</em> spread, including wafer-to-wafer
634
644
  variation, which is what the dies actually ship against. The full descriptive
@@ -761,7 +771,8 @@ ring/quadrant regional yield plus the full per-test statistics table.</p>
761
771
  lower and upper spec limit; tests missing a spec still appear, normalized
762
772
  onto their own range and sorted by variability), a test-value box plot, a
763
773
  value histogram, and a <strong>wafer-to-wafer trend</strong> \u2014 one point per wafer at its
764
- mean, \xB11\u03C3 whiskers, the lot mean dashed across, and spec limits where the
774
+ mean, \xB11\u03C3 whiskers, the mean of all wafers dashed across (labelled the &quot;lot
775
+ mean&quot; when they all come from one lot), and spec limits where the
765
776
  test has them. The trend is always in slot order, deliberately: drift across
766
777
  a cassette only reads in the physical sequence, so there is no sort control
767
778
  to destroy it. Clicking a capability box drives the box plot, histogram and
@@ -1,3 +1,3 @@
1
- export declare const WMAP_VERSION = "0.27.0";
2
- export declare const WMAP_BUILD_TIME = "2026-09-09T07:40:19.349Z";
1
+ export declare const WMAP_VERSION = "0.29.0";
2
+ export declare const WMAP_BUILD_TIME = "2026-09-12T08:42:54.616Z";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- export const WMAP_VERSION="0.27.0",WMAP_BUILD_TIME="2026-09-09T07:40:19.349Z";
1
+ export const WMAP_VERSION="0.29.0",WMAP_BUILD_TIME="2026-09-12T08:42:54.616Z";
@@ -41,7 +41,32 @@ export declare function collectWarnings(sources: {
41
41
  result?: Partial<Pick<WaferMapResult, 'warnings'>> | null;
42
42
  statsSummary?: StatsSummary | null;
43
43
  lotStatsSummary?: LotStatsSummary | null;
44
+ /** Advisories about the VIEW rather than the data — e.g. `bin-colours-shared`,
45
+ * which depends on the plot mode and palette on screen. Raised by the
46
+ * renderer that owns the view. */
47
+ extra?: readonly WaferWarning[];
44
48
  }): WaferWarning[];
49
+ /**
50
+ * The Summary panel banner. One block per severity present, so an "dies may be
51
+ * mis-positioned" error is never visually flattened into an advisory.
52
+ */
53
+ /**
54
+ * One-line summaries, by `code`.
55
+ *
56
+ * These advisories explain a geometry decision and its consequences, so the
57
+ * prose is necessarily long — the `inferred-pitch` message runs to about ten
58
+ * lines in a 300px panel. Rendered in full and undismissable, it pushed the
59
+ * panel's actual content (the yield figures, the findings) below the fold on
60
+ * every open of every wafer in a lot with inferred geometry, which is the
61
+ * common case for the data this library exists to plot. Worse, it is
62
+ * per-result and unchanging: once read it carries no new information, but it
63
+ * cost the same space every time.
64
+ *
65
+ * Keyed on `code` rather than derived from the message, because the messages
66
+ * are prose and may be reworded — the same rule hosts are told to follow when
67
+ * branching on these.
68
+ */
69
+ export declare const SHORT_LABEL: Record<string, string>;
45
70
  export declare function buildWarningsBanner(warnings: WaferWarning[], ownerDocument?: Document): HTMLDivElement;
46
71
  /**
47
72
  * The popup opened by the toolbar's warning indicator.
@@ -1 +1 @@
1
- import{SHADOW as O,TRACKING as w,SPACE as s,RADIUS as E,FONT as y,CLR as l,Z_BASE as B}from"./toolbar.js";const $={error:0,warning:1,info:2};export function severityOf(e){return e.severity??"warning"}export function collectWarnings(e){const n=[],i=new Set,o=t=>{if(!t?.message)return;const r=`${t.code}\0${t.message}`;i.has(r)||(i.add(r),n.push(t))};for(const t of e.result?.warnings??[])o(t);for(const t of e.statsSummary?.stats.warnings??[])o(t);for(const t of e.lotStatsSummary?.perWafer??[])for(const r of t.summary.stats.warnings??[])o(r);return n.sort((t,r)=>$[severityOf(t)]-$[severityOf(r)])}function v(e){return e==="error"?{bg:l.errBg,border:l.errBorder,text:l.errText}:{bg:l.warnBg,border:l.warnBorder,text:l.warnText}}function C(e){return e==="error"?"\u26D4":e==="info"?"\u2139":"\u26A0"}const k={"inferred-pitch":"Die pitch inferred","partial-coverage":"Partial wafer coverage","geometry-conflict":"Geometry conflict","edge-exclusion-exceeds-radius":"Edge exclusion exceeds radius","test-count-capped":"Test analysis skipped"};function S(e){const n=e.code?k[e.code]:void 0;if(n)return n;const i=e.message.split(/(?<=\.)\s/)[0]??e.message;return i.length>60?`${i.slice(0,57)}\u2026`:i}export function buildWarningsBanner(e,n=document){const i=n.createElement("div");Object.assign(i.style,{marginBottom:s.lg});for(const o of e){const t=severityOf(o),r=v(t),m=n.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=n.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=n.createElement("span");Object.assign(g.style,{flex:"1",minWidth:"0"}),g.textContent=`${C(t)} ${S(o)}`;const d=n.createElement("span");Object.assign(d.style,{flexShrink:"0"}),a.append(g,d);const p=n.createElement("div");Object.assign(p.style,{padding:`0 ${s.lg} ${s.sm}`}),p.textContent=o.message;let c=!1;const f=()=>{d.textContent=c?"\u25B4":"\u25BE",p.style.display=c?"block":"none",a.setAttribute("aria-expanded",String(c)),a.setAttribute("aria-label",`${S(o)}. ${c?"Click to collapse.":"Click to expand for detail."}`)};f(),a.addEventListener("click",()=>{c=!c,f()}),a.addEventListener("mouseenter",()=>{d.style.opacity="0.65"}),a.addEventListener("mouseleave",()=>{d.style.opacity="1"}),m.append(a,p),i.appendChild(m)}return i}export function buildWarningsMenuEl(e,n,i=window){const o=i.document,t=o.createElement("div"),r=280,a=e.left+r<=(i.innerWidth??1/0)?e.left:Math.max(4,e.right-r);Object.assign(t.style,{position:"fixed",top:`${e.bottom+4}px`,left:`${a}px`,background:l.menuBg,border:`1px solid ${l.menuBorder}`,borderRadius:E.control,boxShadow:O.menu,zIndex:B,width:`${r}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),t.setAttribute("role","dialog"),t.setAttribute("aria-label","Data warnings");const g=o.createElement("div");Object.assign(g.style,{padding:`${s.sm} ${s.xl} ${s.xs}`,fontSize:y.body,fontWeight:"700",letterSpacing:w,textTransform:"uppercase",color:l.label}),g.textContent=n.length===1?"1 warning":`${n.length} warnings`,t.appendChild(g);for(const d of n){const p=severityOf(d),c=v(p),f=o.createElement("div");Object.assign(f.style,{display:"flex",gap:s.md,padding:`${s.md} ${s.xl}`,fontSize:y.body,lineHeight:"1.5",color:l.text,borderTop:`1px solid ${l.menuBorder}`,alignItems:"flex-start"});const u=o.createElement("span");Object.assign(u.style,{color:c.text,flexShrink:"0"}),u.textContent=C(p),f.appendChild(u);const x=o.createElement("div"),b=o.createElement("div");Object.assign(b.style,{fontWeight:"700",color:c.text,marginBottom:s.xxs}),b.textContent=d.code,x.appendChild(b);const h=o.createElement("div");h.textContent=d.message,x.appendChild(h),f.appendChild(x),t.appendChild(f)}return t}
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}
@@ -144,6 +144,17 @@ export declare function getDieKey(die: {
144
144
  id?: string;
145
145
  }): string;
146
146
  export declare function isYieldEligibleDie(die: Die, options?: DieEligibilityOptions): boolean;
147
+ /**
148
+ * Whether a die passed: its hard bin (else its soft bin) is in `passBins`.
149
+ * `undefined` when the die carries no bin at all — no verdict, never a fail.
150
+ *
151
+ * THE per-die pass rule. Yield, the failing-die hatch and bin colouring all
152
+ * judge a die through this, so the map cannot call a die failing that the yield
153
+ * figure beside it counted as passing. Note it judges the DIE, not whichever
154
+ * bin is being plotted: in soft-bin mode the plotted bin is a soft bin, and
155
+ * testing that number against hard pass bins answers a different question.
156
+ */
157
+ export declare function diePassStatus(die: Pick<Die, 'hbin' | 'sbin'>, passBins: ReadonlySet<number>): boolean | undefined;
147
158
  /**
148
159
  * Generate a rectangular grid of dies centered on the wafer.
149
160
  * Each die carries its width/height for use by the renderer.
@@ -1 +1 @@
1
- export function hasPosition(e){return e.x!=null&&e.y!=null}export function isPositionedDie(e){return e.x!=null&&e.y!=null}export function getDieKey(e){return hasPosition(e)?`${e.x},${e.y}`:`id:${e.id}`}export function isYieldEligibleDie(e,t={}){return!(!t.includePartial&&e.partial||!t.includeEdgeExcluded&&e.edgeExcluded)}export function generateDies(e,t){const{width:u,height:l,offset:o={x:0,y:0}}=t,i=t.gridSize??Math.ceil(e.radius/Math.min(u,l))+1,c=[];for(let n=-i;n<=i;n++)for(let r=-i;r<=i;r++){const s=e.center.x+r*u+o.x,x=e.center.y+n*l+o.y;c.push({id:`${r}_${n}`,x:r,y:n,physX:s,physY:x,width:u,height:l})}return c}
1
+ export function hasPosition(e){return e.x!=null&&e.y!=null}export function isPositionedDie(e){return e.x!=null&&e.y!=null}export function getDieKey(e){return hasPosition(e)?`${e.x},${e.y}`:`id:${e.id}`}export function isYieldEligibleDie(e,n={}){return!(!n.includePartial&&e.partial||!n.includeEdgeExcluded&&e.edgeExcluded)}export function diePassStatus(e,n){const t=e.hbin??e.sbin;return t===void 0?void 0:n.has(t)}export function generateDies(e,n){const{width:t,height:o,offset:s={x:0,y:0}}=n,u=n.gridSize??Math.ceil(e.radius/Math.min(t,o))+1,l=[];for(let i=-u;i<=u;i++)for(let r=-u;r<=u;r++){const c=e.center.x+r*t+s.x,d=e.center.y+i*o+s.y;l.push({id:`${r}_${i}`,x:r,y:i,physX:c,physY:d,width:t,height:o})}return l}
@@ -0,0 +1,80 @@
1
+ import type { Die } from '../core/dies.js';
2
+ import type { BinDef, WaferWarning } from './buildWaferMap.js';
3
+ /**
4
+ * Resolved bin → colour assignments for one population, per bin type.
5
+ *
6
+ * Plain Maps, not functions, so a View carrying them survives the worker's
7
+ * structured clone. Hard and soft bins are independent number spaces, so they
8
+ * are resolved and looked up separately — hard bin 3 and soft bin 3 are
9
+ * different categories and usually different colours.
10
+ */
11
+ export interface BinColors {
12
+ hard: Map<number, string>;
13
+ soft: Map<number, string>;
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.
19
+ */
20
+ shared: {
21
+ hard: number[];
22
+ soft: number[];
23
+ };
24
+ }
25
+ export interface BinColorOptions {
26
+ /** Which bins pass. Default `[1]`. Must match the `passBins` behind the yield figure. */
27
+ passBins?: readonly number[];
28
+ /** Registered bin palette name (`registerBinColorScheme`). Default `'default'`. */
29
+ binColorScheme?: string;
30
+ hbinDefs?: readonly BinDef[];
31
+ sbinDefs?: readonly BinDef[];
32
+ /**
33
+ * Honour `BinDef.color` where a definition supplies one. Default true —
34
+ * colours that arrive with the data (a site's standard bin colour sheet) win
35
+ * over the palette. The palette still colours every bin without one.
36
+ */
37
+ useDefinedBinColors?: boolean;
38
+ }
39
+ /**
40
+ * Assign every bin present in `dies` a colour — the ONE rule for bin colour,
41
+ * shared by the map, its legends, the summary panels and the Insights charts.
42
+ *
43
+ * Why not a function of the bin number, as it used to be:
44
+ * - **Pass/fail comes from `passBins`, never the number.** Passing bins take
45
+ * 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.
53
+ * - **Soft bins are pass-aware too.** A soft bin passes when every die
54
+ * carrying it passes (by the same per-die rule yield uses); otherwise, or
55
+ * when no die says, it takes a fail colour. The library has the data to
56
+ * decide, so the caller never supplies it.
57
+ *
58
+ * 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.
60
+ * A lone map resolves over its own dies.
61
+ */
62
+ export declare function resolveBinColors(dies: Iterable<Die>, options?: BinColorOptions): BinColors;
63
+ /**
64
+ * True when `colors` has an entry for every bin on these dies — i.e. it was
65
+ * resolved over a population that includes them. `buildView` checks this
66
+ * before trusting a caller-supplied assignment, since a stale one would leave
67
+ * real bins without a colour.
68
+ */
69
+ export declare function binColorsCover(colors: BinColors, dies: Iterable<Die>): boolean;
70
+ /**
71
+ * The advisory for bins sharing a colour on the bin map being shown, or null
72
+ * (none share, or `plotMode` is not a bin mode). One builder so the single map
73
+ * and the gallery word it identically.
74
+ *
75
+ * Severity `'warning'`: every die is still drawn correctly, but the viewer can
76
+ * no longer tell those bins apart by colour — an expected capability is
77
+ * degraded, which is what that level means.
78
+ */
79
+ export declare function binColorWarning(colors: BinColors, plotMode: string | undefined): WaferWarning | null;
80
+ //# sourceMappingURL=binColors.d.ts.map
@@ -0,0 +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}}
@@ -3,11 +3,29 @@ import type { Die, PositionedDie } from '../core/dies.js';
3
3
  import type { Reticle } from '../core/reticle.js';
4
4
  import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
5
5
  import { type Affine } from '../core/transforms.js';
6
+ import { type BinColors } from './binColors.js';
6
7
  import type { TestDef, BinDef, MetadataFieldDef, ReticleConfig } from './buildWaferMap.js';
7
8
  /** Resolve a toolbar activeTest cursor to the canonical test number for getDieTestValue. */
8
9
  export declare function resolveTestNumber(activeTest: number, testDefs?: TestDef[]): {
9
10
  testNumber: number;
10
11
  };
12
+ /**
13
+ * The distinct metadata values a `metadata`-mode view will colour, in the exact
14
+ * order colour indices are assigned to them.
15
+ *
16
+ * The one implementation of that ordering, because two existed and they only
17
+ * agreed by luck: `buildView` derived it per view while `renderWaferGallery`'s
18
+ * lot-wide legend strip derived its own from `String(raw)` over every card —
19
+ * a different value derivation (`metadataCategoricalValue` formats numbers and
20
+ * rejects objects) as well as a different population. A comment asked the two to
21
+ * stay in step; nothing made them.
22
+ *
23
+ * Natural order, so D0, D1, D2, D10 reads correctly instead of the
24
+ * lexicographic D0, D1, D10, D2. Partial and edge-excluded dies are skipped:
25
+ * they never reach the metadata fill branch, so letting them rank values here
26
+ * would shift colours for values no visible die shows.
27
+ */
28
+ export declare function collectMetadataValues(dies: readonly Die[], key: string): string[];
11
29
  /** Find a TestDef by its testNumber. Returns undefined when testDefs is absent or no match. */
12
30
  export declare function findTestDef(testDefs: TestDef[] | undefined, testNumber: number): TestDef | undefined;
13
31
  /** Return sorted unique test numbers present across a set of dies (from testValues keys). */
@@ -106,8 +124,23 @@ export interface View {
106
124
  overlays: ViewOverlay[];
107
125
  /** The plot mode this view was built for. Colours in `rectangles` follow it. */
108
126
  plotMode: PlotMode;
109
- /** Name of the colour scheme used, as registered with `registerColorScheme`. */
110
- colorScheme: string;
127
+ /** Bin palette used for hard/soft-bin modes, as registered with `registerBinColorScheme`. */
128
+ binColorScheme: string;
129
+ /** Value gradient used for value and stacked modes, as registered with `registerValueColorScheme`. */
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;
138
+ /**
139
+ * Every bin's resolved colour, both bin types — the single source for bin
140
+ * colour. Die fills are built from it, and legends, panels and charts must
141
+ * read it rather than re-deriving a colour, or they can disagree with the map.
142
+ */
143
+ binColors: BinColors;
111
144
  /** Wafer-level metadata carried through from the wafer, or `null`. */
112
145
  metadata: WaferMetadata | null;
113
146
  /**
@@ -252,8 +285,30 @@ export interface ViewOptions {
252
285
  showQuadrantBoundaries?: boolean;
253
286
  showXYIndicator?: boolean;
254
287
  dieGap?: number;
255
- /** Named colour scheme — any scheme registered via registerColorScheme(). Default: 'default'. */
256
- colorScheme?: string;
288
+ /** Bin palette for `hardBin`/`softBin` — any name registered via `registerBinColorScheme()`. Default `'default'`. */
289
+ binColorScheme?: string;
290
+ /** Value gradient for `value` and the stacked modes — any name registered via `registerValueColorScheme()`. Default `'default'`. */
291
+ valueColorScheme?: string;
292
+ /**
293
+ * Flip the value gradient so the high end takes its low-end colour. Default
294
+ * false: every built-in reads low = dark, high = light. Set this for a
295
+ * parameter where low is the notable end, or for monochrome print output
296
+ * where more ink should mean more. Applies to the dies, the colorbar and the
297
+ * mapless summary together.
298
+ */
299
+ reverseValueScheme?: boolean;
300
+ /** Honour `BinDef.color` where a bin definition supplies one. Default true. See `resolveBinColors`. */
301
+ useDefinedBinColors?: boolean;
302
+ /**
303
+ * Bin colours resolved over a WIDER population than these dies — a host
304
+ * showing several wafers together passes one `resolveBinColors` result to
305
+ * every map so a bin is the same colour on each (`renderWaferGallery` does
306
+ * this for you). The bin counterpart of `metadataValueOrder`.
307
+ *
308
+ * Ignored, and colours resolved from these dies instead, when it lacks any
309
+ * bin present here: a stale assignment must never leave a real bin uncoloured.
310
+ */
311
+ binColors?: BinColors;
257
312
  highlightBin?: number;
258
313
  /** Dim every die except this metadata value, `'metadata'` mode's analogue of `highlightBin`. */
259
314
  highlightMetadataValue?: string;
@@ -278,6 +333,27 @@ export interface ViewOptions {
278
333
  test: number;
279
334
  range: [number, number];
280
335
  };
336
+ /**
337
+ * The full, ordered list of metadata values to assign colours from, for
338
+ * `metadata` plot mode — the exact counterpart of `valueRange`'s `{ test,
339
+ * range }` form, and applied only when `key` matches `activeMetadataKey`.
340
+ *
341
+ * Without it, each view assigns colours by index into the values present on
342
+ * ITS OWN dies. That is right for a standalone map and wrong for a gallery:
343
+ * a wafer that never exhibited one defect category has a shorter list, so
344
+ * every later value shifts up a colour and the card disagrees with the
345
+ * lot-wide legend beside it about what that colour means. A host showing
346
+ * several wafers together passes the lot-wide order here so every card
347
+ * indexes into the same list.
348
+ *
349
+ * Values present on these dies but absent from the list are appended (in
350
+ * natural order) rather than dropped, so a short or stale list can only cost
351
+ * the shared ordering, never leave real dies uncoloured.
352
+ */
353
+ metadataValueOrder?: {
354
+ key: string;
355
+ values: string[];
356
+ };
281
357
  /**
282
358
  * Controls the default colorbar range when the active testDef has spec limits.
283
359
  * `'spec'` (default when limits present): colorbar spans [limitLow, limitHigh].
@@ -287,8 +363,8 @@ export interface ViewOptions {
287
363
  /**
288
364
  * Which bins count as passing. Default `[1]`.
289
365
  *
290
- * Used only to set `ViewRect.binFail`, never to colour anything — bin colour
291
- * comes from the colour scheme regardless of pass/fail. Must match the
366
+ * Sets `ViewRect.binFail` and decides which bins take the palette's pass
367
+ * colours (see `resolveBinColors`) — never the bin number. Must match the
292
368
  * `passBins` given to `buildWaferMap` / `analyzeWaferMap`, or the map will
293
369
  * disagree with the yield figure beside it.
294
370
  */
@@ -1 +1 @@
1
- import{hasPosition as Zt}from"../core/dies.js";import{getReticleCell as te}from"../core/reticle.js";import{metadataCategoricalValue as ee,metadataDisplayValue as ne}from"../core/metadata.js";import{affineRotation as ht,affineMirror as Rt,affineCompose as at,affinePoint as q,affineVector as mt,affineSwapsAxes as ae}from"../core/transforms.js";import{contrastTextColor as X,SPEC_PASS_FILL as pt,SPEC_FAIL_LOW as ie,SPEC_FAIL_HIGH as yt}from"./colorMap.js";import{getColorScheme as se}from"./colorSchemes.js";import{getDieTestValue as P,getTestPassStatus as Y,isParametricTest as gt}from"./buildWaferMap.js";import{fmt as bt,fmtColorbarAxis as xt,fmtAggregationMethod as Ct}from"./fmt.js";import{metadataValueColor as oe}from"./colorMap.js";import{compareNatural as re,clamp01 as Nt}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 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 le="#d3d6db",Ot="#e8e9ea",ce="#aab0ba",j="#d6d9dd";function it(t,e){return e?ee(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 ue(t,e,a){const{x:i,y:s}=t.center,n=ht(t.orientation,i,s),o=at(Rt(e?.x??!1,e?.y??!1,i,s),ht(t.orientation,i,s)),c=at(Rt(a?.flipX??!1,a?.flipY??!1,i,s),ht(a?.rotation??0,i,s));return{bakedToScreen:c,physicalToScreen:at(c,n),gridToScreen:at(c,o)}}function W(t,e=!1){return{points:[t],closed:e}}function fe(t,e){const{center:a,radius:i,notch:s}=t;let n=a.x+i*Math.cos(e),o=a.y+i*Math.sin(e);if(!s)return{x:n,y:o};const c=Math.sqrt(i**2-(s.length/2)**2),u=s.length/2,y=n-a.x,p=o-a.y;return s.type==="bottom"&&p<-c?(o=a.y-c,n=a.x+Math.max(-u,Math.min(u,y))):s.type==="top"&&p>c?(o=a.y+c,n=a.x+Math.max(-u,Math.min(u,y))):s.type==="left"&&y<-c?(n=a.x-c,o=a.y+Math.max(-u,Math.min(u,p))):s.type==="right"&&y>c&&(n=a.x+c,o=a.y+Math.max(-u,Math.min(u,p))),{x:n,y:o}}function At(t,e){return t.map(e).join(" / ")}function de(t,e=256){const a=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>a[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function he(t,e){const a=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,i*1.8))))}function me(t,e,a){if(e?.length){const i=e.flatMap(s=>{const n=s.testNumber;if(!gt(s)){const u=Y(t,n,s);return u===void 0?[]:[{key:n,label:s.name,value:u?"Pass":"Fail"}]}const o=P(t,n);if(o===void 0)return[];const c=t.testPass?.[n]===!1?" <i>(recorded fail)</i>":"";return[{key:n,label:s.name,value:`${bt(o,s.unit,a)}${c}`}]});if(i.length)return i}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:bt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:i,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:c,lotSize:u,activeTest:y,reticleConfig:p,metadataFields:B}=a,D=s?new Map(s.map(f=>[f.bin,f])):null,T=n?new Map(n.map(f=>[f.bin,f])):null,b=[`Die (${t.x}, ${t.y})`];if(p&&Zt(t)){const f=te(t,p);b.push(`Reticle (${f.column}, ${f.row})`)}if(e==="stackedValues"){const f=P(t,0);if(f!==void 0){const d=i?.[0],h=d?.testNumber,S=d?.name??(h!=null?`Test ${h}`:"Value"),m=c?` (${c})`:"";b.push(`${S}${m}: ${bt(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,S=d!==void 0?h?.get(d)?.name?`${d} \xB7 ${h.get(d).name}`:`Bin ${d}`:"Bin";let m;if(c==="percent")m=`${f.toFixed(0)}%`;else{const I=u?` (${(f/u*100).toFixed(0)}% of lot)`:"";m=`${f}${I}`}const M=c?` [${Ct(c)}]`:"";b.push(`${S}: ${m}${M}`)}}else{const f=me(t,i,o);if(e==="value"&&f.length){const{testNumber:d}=resolveTestNumber(y??0,i),h=findTestDef(i,d),S=P(t,d);let m=f.findIndex(x=>x.key===d);m<0&&(m=0);const M=f[m];let I=`<b>${M.label}: ${M.value}</b>`;if(M.key===d&&h){const x=classifySpec(S,h);(x==="failLow"||x==="failHigh")&&(I+=" <i>(out of spec)</i>")}b.push(I);const N=f.length-1;N>0&&b.push(`<i>+${N} more test${N===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,S=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);d.push(`HBin: ${S}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,S=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);d.push(`SBin: ${S}`)}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 v={...t.metadata??{}};for(const[f,d]of Object.entries(v)){const h=ne(d);if(h===void 0)continue;const S=B?.find(m=>m.key===f)?.label??Ht(f);b.push(`${S}: ${h}`)}return b.join("<br>")}export function buildMapTitle(t,e="engineering",a){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${n} Bin`:o.name?`${n} Bin ${o.bin} \xB7 ${o.name}`:`${n} 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:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const n=t.metadataFields?.find(c=>c.key===t.activeMetadataKey),o=t.activeMetadataKey?Ht(t.activeMetadataKey):"Metadata";return{primary:n?.label??o,secondary:""}}case"stackedValues":{const n=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:c}=xt(o,n?.name,n?.unit,e),u=c||"Value",y=t.aggrMethod?` \xB7 ${Ct(t.aggrMethod)}`:"";return{primary:`${u}${y}`,secondary:i}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:c}=xt(o,n?.name,n?.unit,e),u=c||n?.name;if(t.passFailDisplay!=="off"){const y=n?.testNumber??t.activeTest,p=u?`${u} \xB7 #${y}`:`Test ${y}`,B=t.passFailDisplay==="spec"?"Spec pass/fail":n!==void 0&&!gt(n)?"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:i,colorFns:s,normalize:n,activeTest:o,valueRange:c,testDefs:u,fallbackFormat:y,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:D,allIntegerValues:T}=a,b=findTestDef(u,o),{tickFmt:v}=xt(c[1],b?.name,b?.unit,y),f=T?h=>String(Math.round(h)):v,{testNumber:d}=resolveTestNumber(o,u);return t.flatMap((h,S)=>{let m="",M="#111111";if(i==="value"&&p==="test"){const x=Y(h,d,b);if(x===void 0)return[];m=x?"P":"F",M=X(x?pt:yt)}else if(i==="value"){const x=P(h,d);if(x===void 0)return[];m=At([x],f),M=X(s.forValue(n(x)))}else if(i==="hardBin"||i==="softBin"){const x=i==="softBin"?h.sbin:h.hbin;if(x===void 0)return[];m=String(x),M=X(s.forBin(x))}else if(i==="metadata"){const x=it(h,B);if(x===void 0)return[];m=x,M=X(D?.get(m)??j)}else{const x=P(h,0);if(x===void 0)return[];m=At([x],f),M=X(s.forValue(n(x)))}const I=e?e[S*2]:h.physX,N=e?e[S*2+1]:h.physY;return[{x:I,y:N,text:m,fontSize:he(h,m),color:M,align:"center"}]})}function Et(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function pe(t,e,a=720){const{center:i,radius:s,notch:n}=t,o=v=>q(e,v.x,v.y);if(!(n!==void 0&&t.diameter>150)){const v=[];for(let f=0;f<=a;f++){const d=2*Math.PI*f/a;v.push(o(fe(t,d)))}return[{kind:"wafer-boundary",...W(v,!0),lineColor:"#888888",lineWidth:1}]}const u=1.25,y=n.length,p=Et(n.type),B=Math.atan2(y,s),D=s-u,T=[];let b=!1;for(let v=0;v<=a;v++){const f=2*Math.PI*v/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:i.x+s*Math.cos(p-B),y:i.y+s*Math.sin(p-B)},S={x:i.x+D*Math.cos(p),y:i.y+D*Math.sin(p)},m={x:i.x+s*Math.cos(p+B),y:i.y+s*Math.sin(p+B)};T.push(o(h),o(S),o(m))}continue}T.push(o({x:i.x+s*Math.cos(f),y:i.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...W(T,!0),lineColor:"#888888",lineWidth:1}]}function ye(t,e,a,i=360){const s=[],n=Math.max(1,Math.floor(a));for(let o=1;o<n;o++){const c=t.radius*o/n,u=[];for(let y=0;y<=i;y++){const p=2*Math.PI*y/i,B={x:t.center.x+c*Math.cos(p),y:t.center.y+c*Math.sin(p)};u.push(q(e,B.x,B.y))}s.push({kind:"ring-boundary",...W(u,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function ge(t,e,a,i){const{x:s,y:n}=t.center,o=t.radius,c=(f,d)=>q(e,f,d),u=a-s,y=Math.sqrt(Math.max(0,o*o-u*u)),p=c(a,n-y),B=c(a,n+y),D=i-n,T=Math.sqrt(Math.max(0,o*o-D*D)),b=c(s-T,i),v=c(s+T,i);return[{kind:"quadrant-boundary",...W([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...W([b,v]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function be(t,e){return t.map(a=>{const i=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-i,a.y-s],[a.x+i,a.y-s],[a.x+i,a.y+s],[a.x-i,a.y+s]].map(([o,c])=>q(e,o,c))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function xe(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,i)=>(a.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",...W(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function Me(t,e,a,i,s,n,o,c,u,y,p,B,D,T,b="off",v,f,d){const h=e.width-o,S=e.height-o,m=n?S:h,M=n?h:S,I=k=>s==="softBin"?k.sbin:k.hbin,N=k=>it(k,v);if(e.partial){t.push({x:a,y:i,width:m,height:M,fill:le,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:i,width:m,height:M,fill:ce,type:"stacked",metadata:e.metadata});return}if(u!==void 0&&(s==="hardBin"||s==="softBin")&&I(e)!==u){t.push({x:a,y:i,width:m,height:M,fill:Ot,type:"hardBin",metadata:e.metadata});return}if(d!==void 0&&s==="metadata"&&N(e)!==d){t.push({x:a,y:i,width:m,height:M,fill:Ot,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const Q=Y(e,p,T),ot=Q===void 0?j:Q?pt:yt;t.push({x:a,y:i,width:m,height:M,fill:ot,type:"value",metadata:e.metadata});return}const k=P(e,p),R=classifySpec(k,T);let _,G;k===void 0?_=j:b==="spec"?_=R==="failLow"?ie:R==="failHigh"?yt:pt:(_=c.forValue(y(k)),(R==="failLow"||R==="failHigh")&&(G=R)),t.push({x:a,y:i,width:m,height:M,fill:_,type:"value",specMark:G,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const k=I(e),R=k!=null?c.forBin(k):j,_=k!=null&&!D.has(k)?!0:void 0;t.push({x:a,y:i,width:m,height:M,fill:R,type:s,binFail:_,metadata:e.metadata});return}if(s==="metadata"){const k=N(e),R=k!==void 0?f?.get(k)??j:j;t.push({x:a,y:i,width:m,height:M,fill:R,type:"metadata",metadata:e.metadata});return}const x=P(e,0),st=x!==void 0?c.forValue(y(x)):j;t.push({x:a,y:i,width:m,height:M,fill:st,type:"value",metadata:e.metadata})}function Se(t,e,a){const i=t.radius*.15,s=mt(e,i,0),n=mt(e,0,i),o=s.x+n.x>=0?-1:1,c=s.y+n.y>=0?-1:1,u={x:t.center.x+o*t.radius*.9,y:t.center.y+c*t.radius*.9},y={x:u.x+s.x,y:u.y+s.y},p={x:u.x+n.x,y:u.y+n.y};return a.push({x:y.x+s.x*.35,y:y.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+n.x*.35,y:p.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...W([u,y]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...W([u,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},i){const s=a.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:c=!1,showProbePath:u=!1,ringCount:y=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,dieGap:T=1,colorScheme:b="default",passBins:v=[1],highlightBin:f,highlightMetadataValue:d,interactiveTransform:h,valueRange:S,testDefs:m,activeTest:M=0,activeMetadataKey:I,fallbackFormat:N="engineering",aggregationMethod:x,lotSize:st,dataAxisFlip:k,isLotStack:R=!1,logScale:_,colorbarRangeMode:G="spec",passFailDisplay:Q,showPartialDies:ot=!0}=a,Mt=Q??"off",St=i?.hbinDefs,kt=i?.sbinDefs,$t=i?.metadataFields,_t=$t?.find(l=>l.key===I),wt={x:(k?.x??!1)!==(h?.flipX??!1),y:(k?.y??!1)!==(h?.flipY??!1)},Wt=St?new Map(St.map(l=>[l.bin,l])):null,jt=kt?new Map(kt.map(l=>[l.bin,l])):null,rt=n==="softBin"?jt:Wt,lt=se(b),zt=new Set(v),Bt={forValue:de(lt.forValue),forBin:b==="custom"&&rt?l=>rt.get(l)?.color??lt.forBin(l):lt.forBin};let{testNumber:H}=resolveTestNumber(M,m);if(!m?.length&&n==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((g,V)=>{if(!V.testValues)return g;const $=Object.keys(V.testValues).map(Number),E=$.length?Math.min(...$):void 0;return E!==void 0&&(g===void 0||E<g)?E:g},void 0);r!==void 0&&(H=r)}let U;if(Array.isArray(S))U=S;else if(S){const{testNumber:l}=resolveTestNumber(S.test,m);U=l===H?S.range:void 0}const F=findTestDef(m,H),vt=n==="value"&&F!==void 0&&!gt(F),Kt=F!==void 0&&(F.limitLow!==void 0||F.limitHigh!==void 0);let O="off";n==="value"&&(vt?O="test":Mt==="spec"&&Kt?O="spec":Mt==="test"&&e.some(l=>Y(l,H,F)!==void 0)&&(O="test"));const Tt=O==="spec"?"spec":G,ct=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let C,A,J=!1;if(U){[C,A]=U;let l=!0,r=!1;for(const g of e){const V=ct?P(g,0):P(g,H);if(V!==void 0&&(r=!0,!Number.isInteger(V))){l=!1;break}}J=r&&l}else{const l=!ct&&n==="value"&&Tt==="spec"&&F&&(F.limitLow!==void 0||F.limitHigh!==void 0);let r=1/0,g=-1/0,V=!0;for(const $ of e){const E=ct?P($,0):P($,H);E!==void 0&&(E<r&&(r=E),E>g&&(g=E),Number.isInteger(E)||(V=!1))}isFinite(r)||(V=!1),J=V,l?(C=F.limitLow!==void 0?F.limitLow:isFinite(r)?r:0,A=F.limitHigh!==void 0?F.limitHigh:isFinite(g)?g:1):(C=isFinite(r)?r:0,A=isFinite(g)?g:1)}C===A&&(A=C+1);const It=vt?!1:_??F?.logScale??!1,Ft=It&&C>0&&A>0,qt=Ft;let Z;if(Ft){const l=Math.log10(C),r=Math.log10(A)-l;Z=g=>g<=0?0:Nt((Math.log10(g)-l)/r)}else Z=l=>Nt((l-C)/(A-C));const L=ue(t,k,h);let Vt=null;if(t.notch){const l=Et(t.notch.type),r=mt(L.physicalToScreen,Math.cos(l)*t.radius,Math.sin(l)*t.radius),g=Math.hypot(r.x,r.y);g>0&&(Vt={x:r.x/g,y:r.y/g})}let z=1/0;for(let l=0;l<e.length;l++){const r=e[l];r.width<z&&(z=r.width),r.height<z&&(z=r.height)}const Xt=z!==1/0?Math.min(T,z*.12):T,Lt=[],ut=[],Yt=L.bakedToScreen.a===1&&L.bakedToScreen.b===0&&L.bakedToScreen.c===0&&L.bakedToScreen.d===1&&L.bakedToScreen.e===0&&L.bakedToScreen.f===0;let w=null;if(!Yt){w=new Float64Array(e.length*2);for(let l=0;l<e.length;l++){const r=q(L.bakedToScreen,e[l].physX,e[l].physY);w[l*2]=r.x,w[l*2+1]=r.y}}const Gt=ae(L.gridToScreen),Qt=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",Ut=n==="softBin"||n==="stackedSoftBins";let tt=Qt?new Map:void 0;const ft=n==="value"&&O==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,dt=n==="value"&&O==="test"?{pass:0,fail:0}:void 0,Dt=n==="metadata";let et=Dt?new Map:void 0,nt=null;if(Dt&&I){const l=new Set;for(const g of e){if(g.partial||g.edgeExcluded)continue;const V=it(g,I);V!==void 0&&l.add(V)}const r=[...l].sort(re);nt=new Map(r.map((g,V)=>[g,_t?.values?.find($=>$.value===g)?.color??oe(V)]))}for(let l=0;l<e.length;l++){const r=e[l],g=w?w[l*2]:r.physX,V=w?w[l*2+1]:r.physY;if(ut.push({x:g,y:V}),tt&&!r.partial&&!r.edgeExcluded){const $=Ut?r.sbin:r.hbin;$!=null&&tt.set($,(tt.get($)??0)+1)}if(ft&&!r.partial&&!r.edgeExcluded){const $=classifySpec(P(r,H),F);$&&ft[$]++}if(dt&&!r.partial&&!r.edgeExcluded){const $=Y(r,H,F);$!==void 0&&dt[$?"pass":"fail"]++}if(et&&!r.partial&&!r.edgeExcluded){const $=it(r,I);$!==void 0&&et.set($,(et.get($)??0)+1)}r.partial&&!ot||Me(Lt,r,g,V,n,Gt,Xt,Bt,f,Z,H,rt,zt,F,O,I,nt,d)}const Pt=o?generateTextOverlay(e,w,{plotMode:n,colorFns:Bt,normalize:Z,activeTest:M,valueRange:[C,A],testDefs:m,fallbackFormat:N,passFailDisplay:O,activeMetadataKey:I,metadataColorMap:nt,allIntegerValues:J}):[],K=pe(t,L.physicalToScreen);p&&K.push(...ye(t,L.physicalToScreen,y)),B&&K.push(...ge(t,L.physicalToScreen,t.center.x,t.center.y)),c&&K.push(...be(s,L.gridToScreen)),u&&K.push(...xe(e)),D&&K.push(...Se(t,L.gridToScreen,Pt));const Jt=ut.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:Lt,hoverPoints:ut,texts:Pt,overlays:K,plotMode:n,colorScheme:b,metadata:t.metadata??null,dies:e,valueRange:[C,A],allIntegerValues:J,testDefs:m,activeTest:M,logScale:qt,logScaleRequested:It,aggrMethod:x,lotSize:st,axisFlip:wt,dataAxisFlip:{x:k?.x??!1,y:k?.y??!1},gridToScreen:L.gridToScreen,rotation:((t.orientation+(h?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:R,passFailDisplay:O,colorBySpec:O==="spec",colorbarRangeMode:Tt,waferCenter:t.center,waferRadius:t.radius,notchDir:Vt,binCounts:tt,metadataCounts:et,metadataColorMap:nt,activeMetadataKey:I,metadataFields:$t,specCounts:ft,passFailCounts:dt,dieBounds:Jt}}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 st,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 R,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 pe}from"./colorMap.js";import{compareNatural as ye,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=ot(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",Ot="#e8e9ea",be="#aab0ba";function ot(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=st(Lt(e?.x??!1,e?.y??!1,n,s),mt(t.orientation,n,s)),l=st(Lt(a?.flipX??!1,a?.flipY??!1,n,s),mt(a?.rotation??0,n,s));return{bakedToScreen:l,physicalToScreen:st(l,i),gridToScreen:st(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),f=s.length/2,y=i-a.x,p=o-a.y;return s.type==="bottom"&&p<-l?(o=a.y-l,i=a.x+Math.max(-f,Math.min(f,y))):s.type==="top"&&p>l?(o=a.y+l,i=a.x+Math.max(-f,Math.min(f,y))):s.type==="left"&&y<-l?(i=a.x-l,o=a.y+Math.max(-f,Math.min(f,p))):s.type==="right"&&y>l&&(i=a.x+l,o=a.y+Math.max(-f,Math.min(f,p))),{x:i,y:o}}function Ht(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 f=Q(t,i,s);return f===void 0?[]:[{key:i,label:s.name,value:f?"Pass":"Fail"}]}const o=R(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:f,activeTest:y,reticleConfig:p,metadataFields:$}=a,D=s?new Map(s.map(u=>[u.bin,u])):null,I=i?new Map(i.map(u=>[u.bin,u])):null,g=[`Die (${t.x}, ${t.y})`];if(p&&se(t)){const u=oe(t,p);g.push(`Reticle (${u.column}, ${u.row})`)}if(e==="stackedValues"){const u=R(t,0);if(u!==void 0){const d=n?.[0],h=d?.testNumber,S=d?.name??(h!=null?`Test ${h}`:"Value"),m=l?` (${l})`:"";g.push(`${S}${m}: ${xt(u,d?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const u=R(t,0),d=e==="stackedSoftBins"?t.sbin:t.hbin;if(u!==void 0){const h=e==="stackedSoftBins"?I:D,S=d!==void 0?h?.get(d)?.name?`${d} \xB7 ${h.get(d).name}`:`Bin ${d}`:"Bin";let m;if(l==="percent")m=`${u.toFixed(0)}%`;else{const L=f?` (${(u/f*100).toFixed(0)}% of stacked wafers)`:"";m=`${u}${L}`}const M=l?` [${Pt(l)}]`:"";g.push(`${S}: ${m}${M}`)}}else{const u=ve(t,n,o);if(e==="value"&&u.length){const{testNumber:d}=resolveTestNumber(y??0,n),h=findTestDef(n,d),S=R(t,d);let m=u.findIndex(b=>b.key===d);m<0&&(m=0);const M=u[m];let L=`<b>${M.label}: ${M.value}</b>`;if(M.key===d&&h){const b=classifySpec(S,h);(b==="failLow"||b==="failHigh")&&(L+=" <i>(out of spec)</i>")}g.push(L);const P=u.length-1;P>0&&g.push(`<i>+${P} more test${P===1?"":"s"}</i>`)}else if(u.length){const d=u.length;g.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,S=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);d.push(`HBin: ${S}`)}if(t.sbin!==void 0){const h=I?.get(t.sbin)?.name,S=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);d.push(`SBin: ${S}`)}g.push(d.join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&g.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&g.push(`Site: ${t.siteNum}`),t.partId!==void 0&&g.push(`Part ID: ${t.partId}`),t.partial&&g.push("<i>partial die</i>"),t.probeIndex!==void 0&&g.push(`Probe: #${t.probeIndex}`);const v={...t.metadata??{}};for(const[u,d]of Object.entries(v)){const h=le(d);if(h===void 0)continue;const S=$?.find(m=>m.key===u)?.label??Nt(u);g.push(`${S}: ${h}`)}return g.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),f=l||"Value",y=t.aggrMethod?` \xB7 ${Pt(t.aggrMethod)}`:"";return{primary:`${f}${y}`,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),f=l||i?.name;if(t.passFailDisplay!=="off"){const y=i?.testNumber??t.activeTest,p=f?`${f} \xB7 #${y}`:`Test ${y}`,$=t.passFailDisplay==="spec"?"Spec pass/fail":i!==void 0&&!bt(i)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:$}}return{primary:f??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:n,colorFns:s,normalize:i,activeTest:o,valueRange:l,testDefs:f,fallbackFormat:y,passFailDisplay:p="off",activeMetadataKey:$,metadataColorMap:D,allIntegerValues:I}=a,g=findTestDef(f,o),{tickFmt:v}=Mt(l[1],g?.name,g?.unit,y),u=I?h=>String(Math.round(h)):v,{testNumber:d}=resolveTestNumber(o,f);return t.flatMap((h,S)=>{let m="",M="#111111";if(n==="value"&&p==="test"){const b=Q(h,d,g);if(b===void 0)return[];m=b?"P":"F",M=G(b?yt:gt)}else if(n==="value"){const b=R(h,d);if(b===void 0)return[];m=Ht([b],u),M=G(s.forValue(i(b)))}else if(n==="hardBin"||n==="softBin"){const b=n==="softBin"?h.sbin:h.hbin;if(b===void 0)return[];m=String(b),M=G(s.forBin(b))}else if(n==="metadata"){const b=ot(h,$);if(b===void 0)return[];m=b,M=G(D?.get(m)??j)}else{const b=R(h,0);if(b===void 0)return[];m=Ht([b],u),M=G(s.forValue(i(b)))}const L=e?e[S*2]:h.physX,P=e?e[S*2+1]:h.physY;return[{x:L,y:P,text:m,fontSize:ke(h,m),color:M,align:"center"}]})}function At(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=v=>Y(e,v.x,v.y);if(!(i!==void 0&&t.diameter>150)){const v=[];for(let u=0;u<=a;u++){const d=2*Math.PI*u/a;v.push(o(Me(t,d)))}return[{kind:"wafer-boundary",...z(v,!0),lineColor:"#888888",lineWidth:1}]}const f=1.25,y=i.length,p=At(i.type),$=Math.atan2(y,s),D=s-f,I=[];let g=!1;for(let v=0;v<=a;v++){const u=2*Math.PI*v/a;let d=u-p;for(;d>Math.PI;)d-=2*Math.PI;for(;d<-Math.PI;)d+=2*Math.PI;if(Math.abs(d)<=$){if(!g){g=!0;const h={x:n.x+s*Math.cos(p-$),y:n.y+s*Math.sin(p-$)},S={x:n.x+D*Math.cos(p),y:n.y+D*Math.sin(p)},m={x:n.x+s*Math.cos(p+$),y:n.y+s*Math.sin(p+$)};I.push(o(h),o(S),o(m))}continue}I.push(o({x:n.x+s*Math.cos(u),y:n.y+s*Math.sin(u)}))}return[{kind:"wafer-boundary",...z(I,!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,f=[];for(let y=0;y<=n;y++){const p=2*Math.PI*y/n,$={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};f.push(Y(e,$.x,$.y))}s.push({kind:"ring-boundary",...z(f,!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=(u,d)=>Y(e,u,d),f=a-s,y=Math.sqrt(Math.max(0,o*o-f*f)),p=l(a,i-y),$=l(a,i+y),D=n-i,I=Math.sqrt(Math.max(0,o*o-D*D)),g=l(s-I,n),v=l(s+I,n);return[{kind:"quadrant-boundary",...z([p,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...z([g,v]),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,f,y,p,$,D,I,g="off",v,u,d){const h=e.width-o,S=e.height-o,m=i?S:h,M=i?h:S,L=B=>s==="softBin"?B.sbin:B.hbin,P=B=>ot(B,v);if(e.partial){t.push({x:a,y:n,width:m,height:M,fill:ge,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:n,width:m,height:M,fill:be,type:"stacked",metadata:e.metadata});return}if(f!==void 0&&(s==="hardBin"||s==="softBin")&&L(e)!==f){t.push({x:a,y:n,width:m,height:M,fill:Ot,type:"hardBin",metadata:e.metadata});return}if(d!==void 0&&s==="metadata"&&P(e)!==d){t.push({x:a,y:n,width:m,height:M,fill:Ot,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(g==="test"){const J=Q(e,p,I),K=J===void 0?j:J?yt:gt;t.push({x:a,y:n,width:m,height:M,fill:K,type:"value",metadata:e.metadata});return}const B=R(e,p),T=classifySpec(B,I);let w,U;B===void 0?w=j:g==="spec"?w=T==="failLow"?ue:T==="failHigh"?gt:yt:(w=l.forValue(y(B)),(T==="failLow"||T==="failHigh")&&(U=T)),t.push({x:a,y:n,width:m,height:M,fill:w,type:"value",specMark:U,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const B=L(e),T=B!=null?l.forBin(B):j,w=B!=null&&me(e,D)===!1?!0:void 0;t.push({x:a,y:n,width:m,height:M,fill:T,type:s,binFail:w,metadata:e.metadata});return}if(s==="metadata"){const B=P(e),T=B!==void 0?u?.get(B)??j:j;t.push({x:a,y:n,width:m,height:M,fill:T,type:"metadata",metadata:e.metadata});return}const b=R(e,0),_=b!==void 0?l.forValue(y(b)):j;t.push({x:a,y:n,width:m,height:M,fill:_,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,f={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},y={x:f.x+s.x,y:f.y+s.y},p={x:f.x+i.x,y:f.y+i.y};return a.push({x:y.x+s.x*.35,y:y.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([f,y]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...z([f,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:f=!1,ringCount:y=4,showRingBoundaries:p=!1,showQuadrantBoundaries:$=!1,showXYIndicator:D=!1,dieGap:I=1,binColorScheme:g="default",valueColorScheme:v="default",reverseValueScheme:u=!1,useDefinedBinColors:d=!0,binColors:h,passBins:S=[1],highlightBin:m,highlightMetadataValue:M,interactiveTransform:L,valueRange:P,metadataValueOrder:b,testDefs:_,activeTest:B=0,activeMetadataKey:T,fallbackFormat:w="engineering",aggregationMethod:U,lotSize:J,dataAxisFlip:K,isLotStack:Et=!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===T),Kt={x:(K?.x??!1)!==(L?.flipX??!1),y:(K?.y??!1)!==(L?.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(S),ct=h&&he(h,e)?h:de(e,{passBins:S,binColorScheme:g,hbinDefs:rt,sbinDefs:lt,useDefinedBinColors:d}),Qt=i==="softBin"?ct.soft:ct.hard,vt={forValue:Se(fe(v,u)),forBin:c=>Qt.get(c)??j};let{testNumber:H}=resolveTestNumber(B,_);if(!_?.length&&i==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((x,C)=>{if(!C.testValues)return x;const k=Object.keys(C.testValues).map(Number),N=k.length?Math.min(...k):void 0;return N!==void 0&&(x===void 0||N<x)?N:x},void 0);r!==void 0&&(H=r)}let Z;if(Array.isArray(P))Z=P;else if(P){const{testNumber:c}=resolveTestNumber(P.test,_);Z=c===H?P.range:void 0}const V=findTestDef(_,H),$t=i==="value"&&V!==void 0&&!bt(V),Ut=V!==void 0&&(V.limitLow!==void 0||V.limitHigh!==void 0);let A="off";i==="value"&&($t?A="test":St==="spec"&&Ut?A="spec":St==="test"&&e.some(c=>Q(c,H,V)!==void 0)&&(A="test"));const Bt=A==="spec"?"spec":wt,ut=i==="stackedValues"||i==="stackedBins"||i==="stackedSoftBins";let O,E,tt=!1;if(Z){[O,E]=Z;let c=!0,r=!1;for(const x of e){const C=ut?R(x,0):R(x,H);if(C!==void 0&&(r=!0,!Number.isInteger(C))){c=!1;break}}tt=r&&c}else{const c=!ut&&i==="value"&&Bt==="spec"&&V&&(V.limitLow!==void 0||V.limitHigh!==void 0);let r=1/0,x=-1/0,C=!0;for(const k of e){const N=ut?R(k,0):R(k,H);N!==void 0&&(N<r&&(r=N),N>x&&(x=N),Number.isInteger(N)||(C=!1))}isFinite(r)||(C=!1),tt=C,c?(O=V.limitLow!==void 0?V.limitLow:isFinite(r)?r:0,E=V.limitHigh!==void 0?V.limitHigh:isFinite(x)?x:1):(O=isFinite(r)?r:0,E=isFinite(x)?x:1)}O===E&&(E=O+1);const Tt=$t?!1:_t??V?.logScale??!1,It=Tt&&O>0&&E>0,Jt=It;let et;if(It){const c=Math.log10(O),r=Math.log10(E)-c;et=x=>x<=0?0:Rt((Math.log10(x)-c)/r)}else et=c=>Rt((c-O)/(E-O));const F=xe(t,K,L);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),x=Math.hypot(r.x,r.y);x>0&&(Vt={x:r.x/x,y:r.y/x})}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 Zt=q!==1/0?Math.min(I,q*.12):I,Ct=[],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 nt=ne?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 at=Ft?new Map:void 0,it=null;if(Ft&&T){const c=collectMetadataValues(e,T),r=b?.key===T?b.values:void 0,x=r?[...r,...c.filter(C=>!r.includes(C))]:c;it=new Map(x.map((C,k)=>[C,zt?.values?.find(N=>N.value===C)?.color??pe(k)]))}for(let c=0;c<e.length;c++){const r=e[c],x=W?W[c*2]:r.physX,C=W?W[c*2+1]:r.physY;if(ft.push({x,y:C}),nt&&!r.partial&&!r.edgeExcluded){const k=ae?r.sbin:r.hbin;k!=null&&nt.set(k,(nt.get(k)??0)+1)}if(dt&&!r.partial&&!r.edgeExcluded){const k=classifySpec(R(r,H),V);k&&dt[k]++}if(ht&&!r.partial&&!r.edgeExcluded){const k=Q(r,H,V);k!==void 0&&ht[k?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const k=ot(r,T);k!==void 0&&at.set(k,(at.get(k)??0)+1)}r.partial&&!jt||Ce(Ct,r,x,C,i,ee,Zt,vt,m,et,H,Yt,Gt,V,A,T,it,M)}const Dt=o?generateTextOverlay(e,W,{plotMode:i,colorFns:vt,normalize:et,activeTest:B,valueRange:[O,E],testDefs:_,fallbackFormat:w,passFailDisplay:A,activeMetadataKey:T,metadataColorMap:it,allIntegerValues:tt}):[],X=$e(t,F.physicalToScreen);p&&X.push(...Be(t,F.physicalToScreen,y)),$&&X.push(...Te(t,F.physicalToScreen,t.center.x,t.center.y)),l&&X.push(...Ie(s,F.gridToScreen)),f&&X.push(...Ve(e)),D&&X.push(...Fe(t,F.gridToScreen,Dt));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:Ct,hoverPoints:ft,texts:Dt,overlays:X,plotMode:i,binColorScheme:g,valueColorScheme:v,reverseValueScheme:u,binColors:ct,metadata:t.metadata??null,dies:e,valueRange:[O,E],allIntegerValues:tt,testDefs:_,activeTest:B,logScale:Jt,logScaleRequested:Tt,aggrMethod:U,lotSize:J,axisFlip:Kt,dataAxisFlip:{x:K?.x??!1,y:K?.y??!1},gridToScreen:F.gridToScreen,rotation:((t.orientation+(L?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:Et,passFailDisplay:A,colorBySpec:A==="spec",colorbarRangeMode:Bt,waferCenter:t.center,waferRadius:t.radius,notchDir:Vt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:T,metadataFields:kt,specCounts:dt,passFailCounts:ht,dieBounds:ie}}export{getDieKey}from"../core/dies.js";
@@ -252,8 +252,10 @@ export interface BinDef {
252
252
  /** Human-readable bin name, e.g. `"Pass"` or `"Contact Open"`. */
253
253
  name: string;
254
254
  /**
255
- * Optional CSS color override for this bin.
256
- * When set, overrides the active colour scheme for this bin value.
255
+ * Optional CSS colour for this bin — e.g. a site's standard bin colour sheet.
256
+ * Wins over the bin colour scheme for this bin (unless the viewer turns
257
+ * "Use colours from bin definitions" off); every bin without one still takes
258
+ * a palette colour. See `resolveBinColors`.
257
259
  */
258
260
  color?: string;
259
261
  }
@@ -467,6 +469,10 @@ export interface WaferWarning {
467
469
  * the resolved wafer radius (most likely when the diameter was itself inferred
468
470
  * from sparse/partial data). The excluded band is clamped to the whole wafer
469
471
  * rather than silently producing a smaller, wrong ring.
472
+ * - `'bin-colors-shared'` — raised by the renderers for the bin map on screen:
473
+ * some bins are drawn in the same colour as another bin (more bins than the
474
+ * bin colour scheme has distinct colours, or a `BinDef.color` repeats one).
475
+ * Every die is drawn correctly; colour alone just cannot separate those bins.
470
476
  *
471
477
  * The union is intentionally open to string so future advisory codes can be
472
478
  * added without a breaking change; switch with a `default` branch.