@wafertools/wafermap 0.30.3 → 0.31.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 (85) hide show
  1. package/AGENTS.md +9 -19
  2. package/CHANGELOG.md +140 -0
  3. package/README.md +2 -2
  4. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  5. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  6. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +89 -6
  7. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  9. package/dist/packages/canvas-adapter/charts/scatter.d.ts +16 -2
  10. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
  13. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  14. package/dist/packages/canvas-adapter/dieList.d.ts +1 -1
  15. package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
  16. package/dist/packages/canvas-adapter/exportName.js +1 -1
  17. package/dist/packages/canvas-adapter/index.d.ts +2 -3
  18. package/dist/packages/canvas-adapter/index.js +1 -1
  19. package/dist/packages/canvas-adapter/insightsTab.d.ts +11 -1
  20. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  21. package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
  22. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
  23. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  24. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +15 -30
  25. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  26. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  27. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  28. package/dist/packages/canvas-adapter/toolbar.js +1 -1
  29. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  30. package/dist/packages/canvas-adapter/userGuideHtml.js +49 -43
  31. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  32. package/dist/packages/canvas-adapter/version.js +1 -1
  33. package/dist/packages/canvas-adapter/warnings.js +1 -1
  34. package/dist/packages/core/aggregates.d.ts +0 -6
  35. package/dist/packages/core/aggregates.js +1 -1
  36. package/dist/packages/core/dies.d.ts +1 -3
  37. package/dist/packages/core/dies.js +1 -1
  38. package/dist/packages/core/index.d.ts +8 -10
  39. package/dist/packages/core/index.js +1 -1
  40. package/dist/packages/core/stdf.d.ts +9 -0
  41. package/dist/packages/core/stdf.js +1 -0
  42. package/dist/packages/core/transforms.d.ts +0 -23
  43. package/dist/packages/core/transforms.js +1 -1
  44. package/dist/packages/renderer/binColors.js +1 -1
  45. package/dist/packages/renderer/buildView.d.ts +1 -9
  46. package/dist/packages/renderer/buildView.js +1 -1
  47. package/dist/packages/renderer/buildWaferMap.d.ts +54 -34
  48. package/dist/packages/renderer/buildWaferMap.js +1 -1
  49. package/dist/packages/renderer/colorMap.d.ts +0 -2
  50. package/dist/packages/renderer/colorMap.js +1 -1
  51. package/dist/packages/renderer/deprecate.d.ts +1 -1
  52. package/dist/packages/renderer/deprecate.js +1 -1
  53. package/dist/packages/renderer/derivedTests/apply.js +1 -1
  54. package/dist/packages/renderer/derivedTests/parser.js +1 -1
  55. package/dist/packages/renderer/index.d.ts +6 -5
  56. package/dist/packages/renderer/index.js +1 -1
  57. package/dist/packages/renderer/spec.d.ts +17 -0
  58. package/dist/packages/renderer/spec.js +1 -1
  59. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  60. package/dist/packages/stats/capability.d.ts +4 -2
  61. package/dist/packages/stats/capability.js +1 -1
  62. package/dist/packages/stats/clusterDetection.d.ts +2 -1
  63. package/dist/packages/stats/correlation.d.ts +1 -1
  64. package/dist/packages/stats/facets.d.ts +1 -1
  65. package/dist/packages/stats/index.d.ts +6 -16
  66. package/dist/packages/stats/index.js +1 -1
  67. package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
  68. package/dist/packages/stats/mergeTestDefs.js +1 -1
  69. package/dist/packages/stats/patternClassification.d.ts +2 -1
  70. package/dist/packages/stats/regions.d.ts +2 -1
  71. package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
  72. package/dist/packages/stats/renderFindingsReport.js +1 -21
  73. package/dist/packages/stats/renderSummaryReport.js +11 -11
  74. package/dist/packages/stats/sweep.d.ts +8 -0
  75. package/dist/packages/stats/sweep.js +1 -1
  76. package/dist/packages/stats/types.d.ts +9 -3
  77. package/package.json +2 -2
  78. package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
  79. package/dist/packages/canvas-adapter/deprecated.js +0 -1
  80. package/dist/packages/core/deprecated.d.ts +0 -60
  81. package/dist/packages/core/deprecated.js +0 -1
  82. package/dist/packages/renderer/deprecated.d.ts +0 -45
  83. package/dist/packages/renderer/deprecated.js +0 -1
  84. package/dist/packages/stats/deprecated.d.ts +0 -85
  85. package/dist/packages/stats/deprecated.js +0 -1
@@ -72,13 +72,13 @@ 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-23T17:22:32.765Z">v0.30.3</span></h1>
75
+ <h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-09-25T14:31:48.441Z">v0.31.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
79
79
  or anyone else who may use a wafer map application \u2014 not for developers integrating the wafermap library.</p>
80
80
  <p>Some features depend on what data the application has loaded (bin names, test
81
- definitions, spec limits, reticle geometry). Where this applies it is noted.</p>
81
+ definitions, test limits, reticle geometry). Where this applies it is noted.</p>
82
82
  <p><strong>Getting oriented.</strong> The wafer map is an interactive viewer. A toolbar is always
83
83
  present at the top-right of the map \u2014 use it to change what the colours represent,
84
84
  toggle overlays, rotate or flip the wafer, zoom, select dies, and open the summary
@@ -112,10 +112,6 @@ the display to match your convention; die coordinates are unaffected.</p>
112
112
  <td>Active plot value \u2014 bin category or test measurement</td>
113
113
  </tr>
114
114
  <tr>
115
- <td>Muted grey at perimeter</td>
116
- <td>Partial die \u2014 falls outside the wafer circle boundary</td>
117
- </tr>
118
- <tr>
119
115
  <td>Neutral grey (interior)</td>
120
116
  <td>No data \u2014 die has no bin or test result in the loaded dataset</td>
121
117
  </tr>
@@ -124,8 +120,7 @@ the display to match your convention; die coordinates are unaffected.</p>
124
120
  <td>Edge-excluded die \u2014 falls within the edge exclusion band configured for this wafer</td>
125
121
  </tr>
126
122
  </tbody></table>
127
- <p>No-data grey and partial-die grey are visually distinct. A no-data die is not a
128
- fail; it simply has no result recorded. Edge-excluded dies are shown dimmed and are
123
+ <p>A no-data die is not a fail; it simply has no result recorded. Edge-excluded dies are shown dimmed and are
129
124
  not counted in yield calculations.</p>
130
125
  <h3 id="14-legend-and-colorbar">1.4 Legend and colorbar</h3>
131
126
  <p><strong>Bin modes</strong> show a discrete colour legend: one swatch per bin, with bin number
@@ -134,20 +129,20 @@ then failing bins from the most dies to the fewest \u2014 the same order as the
134
129
  panel and reports.</p>
135
130
  <p><strong>Value mode</strong> shows a continuous colorbar: the colour scale runs from the minimum
136
131
  to maximum value, with units when available. The colorbar is informational only \u2014
137
- clicking it has no effect. To switch to a pass/fail view, use the <strong>Spec pass/fail</strong>
138
- or <strong>Test pass/fail</strong> option in the Overlays menu (available when spec limits are
132
+ clicking it has no effect. To switch to a pass/fail view, use the <strong>Limit pass/fail</strong>
133
+ or <strong>Test pass/fail</strong> option in the Overlays menu (available when test limits are
139
134
  defined, or a recorded verdict exists, for the active test).</p>
140
- <p><strong>Spec pass/fail mode</strong> replaces the colorbar with a small <strong>spec legend</strong>: Pass,
135
+ <p><strong>Limit pass/fail mode</strong> replaces the colorbar with a small legend: Pass,
141
136
  Fail high, and Fail low swatches (only the categories that apply to the test&#39;s
142
- limits) with a die count beside each. This judges dies against the test&#39;s spec
143
- limits (<code>limitLow</code> / <code>limitHigh</code>).</p>
137
+ limits) with a die count beside each. This judges dies against the test&#39;s lower
138
+ and upper test limits.</p>
144
139
  <p><strong>Test pass/fail mode</strong> replaces the colorbar with a <strong>Pass / Fail legend</strong> and die
145
140
  counts, coloured by the tester&#39;s own <strong>recorded</strong> verdict for that test \u2014 not a
146
- spec-limit judgement. A test with no measured value (a functional, go/no-go test)
141
+ limit judgement. A test with no measured value (a functional, go/no-go test)
147
142
  always displays this way; selecting it switches the map into Test pass/fail
148
143
  automatically, since there is nothing to plot on a gradient.</p>
149
144
  <p>Every map also shows a short <strong>title</strong> by the colorbar or legend naming what is
150
- displayed \u2014 the test name (and number, in spec mode), the bin type, or the stacked
145
+ displayed \u2014 the test name (and number, in limit pass/fail mode), the bin type, or the stacked
151
146
  wafer count.</p>
152
147
  <p>Clicking a bin swatch in the legend filters the display to that bin
153
148
  (see <a href="#3-toolbar-controls" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'3-toolbar-controls\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Highlight bin</a>).</p>
@@ -220,33 +215,33 @@ application has loaded.</p>
220
215
  <p>Stacked modes are only available on lot-stack maps (multiple wafers combined into
221
216
  one display). The panel identifies how many wafers were stacked and which
222
217
  aggregation method is active.</p>
223
- <h3 id="test-value-mode-colorbar-range-and-spec-limits">Test Value mode \u2014 colorbar range and spec limits</h3>
224
- <p>When spec limits are defined for the active test, two additional display options
218
+ <h3 id="test-value-mode-colorbar-range-and-test-limits">Test Value mode \u2014 colorbar range and test limits</h3>
219
+ <p>When test limits are defined for the active test, two additional display options
225
220
  become available:</p>
226
221
  <ul>
227
- <li><strong>Colorbar range \u2014 Data / Spec</strong>: switches the colorbar scale between the
228
- actual data extent and the spec limit bounds. This affects only the colorbar&#39;s
229
- range, never how out-of-spec dies are shown. In <strong>both</strong> ranges every die is
230
- coloured by the gradient so you can read the value distribution, and out-of-spec
231
- dies are marked with a triangle \u2014 pointing <strong>down (\u25BD) for below LSL</strong>, <strong>up (\u25B3)
232
- for above USL</strong> \u2014 so they stand out without leaving the distribution. The
222
+ <li><strong>Colorbar range \u2014 Data / Limits</strong>: switches the colorbar scale between the
223
+ actual data extent and the test limits. This affects only the colorbar&#39;s
224
+ range, never how failing dies are shown. In <strong>both</strong> ranges every die is
225
+ coloured by the gradient so you can read the value distribution, and dies
226
+ outside the test limits are marked with a triangle \u2014 pointing <strong>down (\u25BD) for below the low limit</strong>, <strong>up (\u25B3)
227
+ for above the high limit</strong> \u2014 so they stand out without leaving the distribution. The
233
228
  triangle is drawn black or white per die for contrast against its own colour, so
234
229
  it stays visible under any colour scheme, and its shape (not colour) carries the
235
230
  below/above-limit meaning.</li>
236
- <li><strong>Spec pass/fail</strong>: when active, dies within spec are shown in a pass colour
237
- and dies outside spec are highlighted \u2014 <strong>blue for below the Lower Spec Limit
238
- (LSL)</strong>, <strong>red for above the Upper Spec Limit (USL)</strong>. Both flags apply
239
- independently; a die can be flagged on either or both limits. A spec legend
231
+ <li><strong>Limit pass/fail</strong>: when active, dies within the test limits are shown in
232
+ a pass colour and dies outside them are highlighted \u2014 <strong>blue for below the
233
+ low limit</strong>, <strong>red for above the high limit</strong>. Both flags apply
234
+ independently; a die can be flagged on either or both limits. A Pass/Fail legend
240
235
  replaces the colorbar, listing each applicable category with its die count.</li>
241
236
  </ul>
242
237
  <p>Some tests have no measured value at all \u2014 a continuity check or any other
243
238
  go/no-go test, where the only result is a recorded pass or fail. Selecting one
244
239
  of these <strong>functional tests</strong> as the active test switches the map into
245
240
  <strong>Test pass/fail</strong> automatically: a Pass/Fail legend by die count, coloured by
246
- the tester&#39;s own recorded verdict rather than a spec-limit judgement (there is
241
+ the tester&#39;s own recorded verdict rather than a limit judgement (there is
247
242
  no gradient to fall back to). <strong>Test pass/fail</strong> is also available as an
248
243
  Overlays option on an ordinary parametric test when it carries recorded
249
- verdicts, as an alternative to spec-limit judgement.</p>
244
+ verdicts, as an alternative to limit judgement.</p>
250
245
  <div data-wmap-demo="value-heatmap" class="wmap-demo"></div><div data-wmap-demo="spec-passfail" class="wmap-demo"></div><hr>
251
246
  <h3 id="derived-tests">Derived tests (\u2020)</h3>
252
247
  <p>A test name marked <strong>\u2020</strong> in front is a <em>derived</em> test: its value was calculated
@@ -284,7 +279,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
284
279
  <tr>
285
280
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yIDEyczMuNi03IDEwLTcgMTAgNyAxMCA3LTMuNiA3LTEwIDctMTAtNy0xMC03eiIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjMiLz48L3N2Zz4K" width="20" height="20"></td>
286
281
  <td>Overlays</td>
287
- <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>
282
+ <td>Check-menu of optional display layers: XY axis indicator, ring boundaries, quadrant lines, die coordinate labels, reticle grid (when geometry is configured), Limit 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>
288
283
  </tr>
289
284
  <tr>
290
285
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Im0xNC42MjIgMTcuODk3LTEwLjY4LTIuOTEzIi8+PHBhdGggZD0iTTE4LjM3NiAyLjYyMmExIDEgMCAxIDEgMy4wMDIgMy4wMDJMMTcuMzYgOS42NDNhLjUuNSAwIDAgMCAwIC43MDdsLjk0NC45NDRhMi40MSAyLjQxIDAgMCAxIDAgMy40MDhsLS45NDQuOTQ0YS41LjUgMCAwIDEtLjcwNyAwTDguMzU0IDcuMzQ4YS41LjUgMCAwIDEgMC0uNzA3bC45NDQtLjk0NGEyLjQxIDIuNDEgMCAwIDEgMy40MDggMGwuOTQ0Ljk0NGEuNS41IDAgMCAwIC43MDcgMHoiLz48cGF0aCBkPSJNOSA4Yy0xLjgwNCAyLjcxLTMuOTcgMy40Ni02LjU4MyAzLjk0OGEuNTA3LjUwNyAwIDAgMC0uMzAyLjgxOWw3LjMyIDguODgzYTEgMSAwIDAgMCAxLjE4NS4yMDRDMTIuNzM1IDIwLjQwNSAxNiAxNi43OTIgMTYgMTUiLz48L3N2Zz4K" width="20" height="20"></td>
@@ -299,7 +294,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
299
294
  <tr>
300
295
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDQgTDUgMjAiLz48cGF0aCBkPSJNNSA0IEw4IDQiLz48cGF0aCBkPSJNNSAyMCBMOCAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxOSAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxNiA0Ii8+PHBhdGggZD0iTTE5IDIwIEwxNiAyMCIvPjxwYXRoIGQ9Ik01IDEyIEwxOSAxMiIgc3Ryb2tlLWRhc2hhcnJheT0iMyAyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
301
296
  <td>Colorbar range</td>
302
- <td>Test Value mode with spec limits only. Toggles the colorbar between the <strong>spec-limit range</strong> (default \u2014 the colours mean the same thing on every wafer, so maps are comparable) and the <strong>data range</strong> (stretches the scale to the values actually present, which shows more contrast but is not comparable between wafers).</td>
297
+ <td>Test Value mode with test limits only. Toggles the colorbar between the <strong>limit range</strong> (default \u2014 the colours mean the same thing on every wafer, so maps are comparable) and the <strong>data range</strong> (stretches the scale to the values actually present, which shows more contrast but is not comparable between wafers).</td>
303
298
  </tr>
304
299
  <tr>
305
300
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjciIHg9IjMiIHk9IjMiIHJ4PSIxIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iNyIgeD0iMyIgeT0iMTQiIHJ4PSIxIi8+PHBhdGggZD0iTTE0IDRoNyIvPjxwYXRoIGQ9Ik0xNCA5aDciLz48cGF0aCBkPSJNMTQgMTVoNyIvPjxwYXRoIGQ9Ik0xNCAyMGg3Ii8+PC9zdmc+Cg==" width="20" height="20"></td>
@@ -404,7 +399,7 @@ highlighting one bin at a time.</p>
404
399
  <li><strong>Quadrant lines</strong> \u2014 divides the wafer into N, S, E, W quadrants</li>
405
400
  <li><strong>Die coordinate labels</strong> \u2014 draws the (x, y) grid position inside each die (useful at high zoom)</li>
406
401
  <li><strong>Reticle grid</strong> \u2014 stepper field grid (only shown when reticle geometry is configured)</li>
407
- <li><strong>Spec pass/fail</strong> \u2014 pass/fail colouring for Test Value mode, judged against spec limits, when the active test defines them</li>
402
+ <li><strong>Limit pass/fail</strong> \u2014 pass/fail colouring for Test Value mode, judged against the test limits, when the active test defines them</li>
408
403
  <li><strong>Test pass/fail</strong> \u2014 pass/fail colouring for Test Value mode, coloured by the tester&#39;s recorded verdict; always on for a functional (no measured value) active test</li>
409
404
  </ul>
410
405
  <div data-wmap-demo="overlays" class="wmap-demo"></div><p><em>Ring boundaries, quadrant lines, and XY indicator all active.</em></p>
@@ -515,7 +510,7 @@ highlighting one bin at a time.</p>
515
510
  <tr>
516
511
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik01IDQgTDUgMjAiLz48cGF0aCBkPSJNNSA0IEw4IDQiLz48cGF0aCBkPSJNNSAyMCBMOCAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxOSAyMCIvPjxwYXRoIGQ9Ik0xOSA0IEwxNiA0Ii8+PHBhdGggZD0iTTE5IDIwIEwxNiAyMCIvPjxwYXRoIGQ9Ik01IDEyIEwxOSAxMiIgc3Ryb2tlLWRhc2hhcnJheT0iMyAyIi8+PC9zdmc+Cg==" width="20" height="20"></td>
517
512
  <td>Colorbar range</td>
518
- <td>Test Value mode with spec limits only. Toggles all cards between the spec-limit range and the data range. Leave it on spec limits when comparing wafers \u2014 the data range rescales per view.</td>
513
+ <td>Test Value mode with test limits only. Toggles all cards between the limit range and the data range. Leave it on the test limits when comparing wafers \u2014 the data range rescales per view.</td>
519
514
  </tr>
520
515
  <tr>
521
516
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNOCAydjQiLz48cGF0aCBkPSJNMTIgMnY0Ii8+PHBhdGggZD0iTTE2IDJ2NCIvPjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxOCIgeD0iNCIgeT0iNCIgcng9IjIiLz48cGF0aCBkPSJNOCAxMGg2Ii8+PHBhdGggZD0iTTggMTRoOCIvPjxwYXRoIGQ9Ik04IDE4aDUiLz48L3N2Zz4K" width="20" height="20"></td>
@@ -556,7 +551,7 @@ The rest depends on the active plot mode:</p>
556
551
  <li><strong>Bin modes</strong> \u2014 the die&#39;s bin verdict (number and name, if named), plus a
557
552
  note of how many test values are recorded for the die</li>
558
553
  <li><strong>Test Value mode</strong> \u2014 the active test&#39;s value in bold, flagged when it is
559
- out of spec, with the remaining tests summarised as &quot;+N more tests&quot;</li>
554
+ outside the test limits, with the remaining tests summarised as &quot;+N more tests&quot;</li>
560
555
  <li><strong>Stacked modes</strong> \u2014 the single aggregated value or count at that position</li>
561
556
  </ul>
562
557
  <h3 id="42-zoom-and-pan">4.2 Zoom and pan</h3>
@@ -594,7 +589,8 @@ wafer otherwise.</p>
594
589
  <ul>
595
590
  <li><strong>Value histogram</strong> \u2014 one test&#39;s distribution, opening on the test the map
596
591
  is showing.</li>
597
- <li><strong>Process capability</strong> \u2014 every test normalised to its spec limits, with its
592
+ <li><strong>Process capability</strong> \u2014 every test normalised to its spec limits, or its
593
+ test limits when it has none, with its
598
594
  Ppk. Below 30 dies the chart says each Ppk is a rough estimate: a Ppk from a
599
595
  handful of dies can be far from the process&#39;s real capability.</li>
600
596
  <li><strong>Sweeps</strong> your application has defined (see <a href="#sweep-cards" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'sweep-cards\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Sweep cards</a>).</li>
@@ -712,10 +708,10 @@ with a significance test behind it.</li>
712
708
  marked. Wafers are in slot order by default, since that is what makes a
713
709
  slot-correlated pattern visible; a <strong>Slot / Yield</strong> selector re-sorts. Wafers
714
710
  far below the rest are labelled &quot;low outlier&quot;.</li>
715
- <li><strong>Test values</strong> \u2014 per test: mean, <strong>Ppk</strong>, and spec yield. Ppk (not Cpk)
711
+ <li><strong>Test values</strong> \u2014 per test: mean, <strong>Ppk</strong>, and limit yield. Ppk (not Cpk)
716
712
  because it measures against the <em>overall</em> spread, including wafer-to-wafer
717
713
  variation, which is what the dies actually ship against. The full descriptive
718
- statistics \u2014 min, quartiles, median, max, \u03C3, both spec limits, and all four
714
+ statistics \u2014 min, quartiles, median, max, \u03C3, both test limits, and all four
719
715
  capability indices \u2014 are in the summary report and the CSV export.</li>
720
716
  <li><strong>Functional tests</strong> \u2014 pass/fail counts and pass rate per functional test.</li>
721
717
  </ul>
@@ -846,21 +842,31 @@ actually in use and marked with the median, a hard/soft bin pareto, and
846
842
  ring/quadrant regional yield plus the full per-test statistics table.</p>
847
843
  </li>
848
844
  <li><p><strong>Distributions</strong> \u2014 process capability (Cp/Cpk/Pp/Ppk for tests with both a
849
- lower and upper spec limit; tests missing a spec still appear, normalized
845
+ lower and upper limit \u2014 spec limits where given, otherwise test limits; tests
846
+ without both still appear, normalized
850
847
  onto their own range and sorted by variability), a test-value box plot, a
851
848
  value histogram, and a <strong>wafer-to-wafer trend</strong> \u2014 one point per wafer at its
852
849
  mean, \xB11\u03C3 whiskers, the mean of all wafers dashed across (labelled the &quot;lot
853
- mean&quot; when they all come from one lot), and spec limits where the
854
- test has them. The trend is always in slot order, deliberately: drift across
850
+ mean&quot; when they all come from one lot), and the test&#39;s limits. The trend is always in slot order, deliberately: drift across
855
851
  a cassette only reads in the physical sequence, so there is no sort control
856
852
  to destroy it. Clicking a capability box drives the box plot, histogram and
857
853
  trend onto that same test automatically.</p>
854
+ <p>The box plot, histogram, trend and the Correlation tab&#39;s scatter draw the
855
+ test&#39;s limits as dashed lines. A test can have two kinds: <strong>test limits</strong>
856
+ (labelled <em>Lo limit</em> / <em>Hi limit</em>, short dashes) \u2014 what each die was judged
857
+ pass/fail by \u2014 and <strong>spec limits</strong> (labelled <em>LSL</em> / <em>USL</em>, long dashes) \u2014
858
+ the process specification that capability is measured against. When a test
859
+ has both, a <strong>Limits</strong> choice appears \u2014 <em>Test + spec</em> (the default), <em>Test
860
+ limits</em>, <em>Spec limits</em> or <em>None</em> \u2014 and it applies to all four charts at
861
+ once. A test that lacks the kind you chose shows the kind it has, so it
862
+ never looks as if it had no limits; a limit that falls outside the plotted
863
+ range is marked at the edge of the chart with an arrow.</p>
858
864
  <p>The box plot, histogram and trend share one row of axis controls, kept in
859
865
  sync across all three so switching between them never re-reads the same
860
866
  data on a different scale: <strong>Axis includes limits</strong> widens the axis to cover
861
- LSL/USL even where the data sits well clear of them (on by default only when
867
+ every limit shown even where the data sits well clear of them (on by default only when
862
868
  doing so still leaves the data at least a third of the axis \u2014 otherwise a
863
- generous spec window would squash a perfectly capable distribution into a
869
+ generous limit window would squash a perfectly capable distribution into a
864
870
  sliver), and <strong>Clip outliers</strong> narrows the axis to a robust range (the
865
871
  Tukey fence, 1.5\xD7 IQR beyond Q1/Q3) so a handful of extreme values don&#39;t
866
872
  stretch the axis until the rest of the distribution reads as a flat line.
@@ -896,7 +902,7 @@ yield/box-plot rows.</em></p>
896
902
  open that wafer&#39;s map), hard/soft bin pareto, ring and quadrant yield, and
897
903
  pooled per-test statistics. The &quot;\u2039 Gallery&quot; tab returns to the card grid.</em></p>
898
904
  <p><em>Distributions sub-tab: process capability (coloured by Ppk \u2014 green capable,
899
- orange marginal, red poor; tests without spec limits are muted and dashed), a
905
+ orange marginal, red poor; tests without limits are muted and dashed), a
900
906
  per-wafer test-value box plot, and a value histogram. Clicking a capability
901
907
  box drives the box plot and histogram onto that test.</em></p>
902
908
  <p><em>Correlation sub-tab: test-to-test Pearson correlation matrix (blue =
@@ -1,3 +1,3 @@
1
- export declare const WMAP_VERSION = "0.30.3";
2
- export declare const WMAP_BUILD_TIME = "2026-09-23T17:24:44.719Z";
1
+ export declare const WMAP_VERSION = "0.31.0";
2
+ export declare const WMAP_BUILD_TIME = "2026-09-25T14:33:27.298Z";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- export const WMAP_VERSION="0.30.3",WMAP_BUILD_TIME="2026-09-23T17:24:44.719Z";
1
+ export const WMAP_VERSION="0.31.0",WMAP_BUILD_TIME="2026-09-25T14:33:27.298Z";
@@ -1 +1 @@
1
- import{SHADOW as S,TRACKING as O,SPACE as s,RADIUS as v,FONT as y,CLR as d,Z_BASE as B}from"./toolbar.js";const E={error:0,warning:1,info:2};export function severityOf(t){return t.severity??"warning"}export function collectWarnings(t){const o=[],i=new Set,n=e=>{if(!e?.message)return;const r=`${e.code}\0${e.message}`;i.has(r)||(i.add(r),o.push(e))};for(const e of t.result?.warnings??[])n(e);for(const e of t.statsSummary?.stats.warnings??[])n(e);for(const e of t.lotStatsSummary?.perWafer??[])for(const r of e.summary.stats.warnings??[])n(r);for(const e of t.extra??[])n(e);return o.sort((e,r)=>E[severityOf(e)]-E[severityOf(r)])}function $(t){return t==="error"?{bg:d.errBg,border:d.errBorder,text:d.errText}:{bg:d.warnBg,border:d.warnBorder,text:d.warnText}}function w(t){return t==="error"?"\u26D4":t==="info"?"\u2139":"\u26A0"}export const SHORT_LABEL={"partial-coverage":"Partial wafer coverage","geometry-conflict":"Geometry conflict","non-standard-diameter":"Non-standard wafer diameter","diameter-exceeds-die-extent":"Diameter exceeds die extent","edge-exclusion-exceeds-radius":"Edge exclusion exceeds radius","test-count-capped":"Test analysis skipped","test-def-collision":"Test definitions disagree","analysis-option-corrected":"Analysis option corrected","bin-colors-shared":"Bins share a colour","pass-bins-mixed":"Pass bins differ by wafer","ring-count-mixed":"Ring counts differ by wafer","input-field-removed":"Removed input name ignored","input-values-not-numbers":"Bins or test values are text","derived-test-invalid":"Derived test not applied"};function C(t){const o=t.code?SHORT_LABEL[t.code]:void 0;if(o)return o;const i=t.message.split(/(?<=\.)\s/)[0]??t.message;return i.length>60?`${i.slice(0,57)}\u2026`:i}export function buildWarningsBanner(t,o=document){const i=o.createElement("div");Object.assign(i.style,{marginBottom:s.lg});for(const n of t){const e=severityOf(n),r=$(e),m=o.createElement("div");Object.assign(m.style,{background:r.bg,border:`1px solid ${r.border}`,borderRadius:v.control,marginBottom:s.sm,fontSize:y.body,color:r.text,lineHeight:"1.5"}),m.setAttribute("role","status");const a=o.createElement("button");a.type="button",Object.assign(a.style,{display:"flex",alignItems:"center",gap:s.sm,width:"100%",background:"none",border:"none",font:"inherit",color:"inherit",textAlign:"left",padding:`${s.sm} ${s.lg}`,cursor:"pointer"});const p=o.createElement("span");Object.assign(p.style,{flex:"1",minWidth:"0"}),p.textContent=`${w(e)} ${C(n)}`;const l=o.createElement("span");Object.assign(l.style,{flexShrink:"0"}),a.append(p,l);const g=o.createElement("div");Object.assign(g.style,{padding:`0 ${s.lg} ${s.sm}`}),g.textContent=n.message;let c=!1;const f=()=>{l.textContent=c?"\u25B4":"\u25BE",g.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,g),i.appendChild(m)}return i}export function buildWarningsMenuEl(t,o,i=window){const n=i.document,e=n.createElement("div"),r=280,a=t.left+r<=(i.innerWidth??1/0)?t.left:Math.max(4,t.right-r);Object.assign(e.style,{position:"fixed",top:`${t.bottom+4}px`,left:`${a}px`,background:d.menuBg,border:`1px solid ${d.menuBorder}`,borderRadius:v.control,boxShadow:S.menu,zIndex:B,width:`${r}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),e.setAttribute("role","dialog"),e.setAttribute("aria-label","Data warnings");const p=n.createElement("div");Object.assign(p.style,{padding:`${s.sm} ${s.xl} ${s.xs}`,fontSize:y.body,fontWeight:"700",letterSpacing:O,textTransform:"uppercase",color:d.label}),p.textContent=o.length===1?"1 warning":`${o.length} warnings`,e.appendChild(p);for(const l of o){const g=severityOf(l),c=$(g),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(g),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 C,TRACKING as O,SPACE as r,RADIUS as v,FONT as y,CLR as d,Z_BASE as B}from"./toolbar.js";const E={error:0,warning:1,info:2};export function severityOf(t){return t.severity??"warning"}export function collectWarnings(t){const s=[],i=new Set,n=e=>{if(!e?.message)return;const o=`${e.code}\0${e.message}`;i.has(o)||(i.add(o),s.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 o of e.summary.stats.warnings??[])n(o);for(const e of t.extra??[])n(e);return s.sort((e,o)=>E[severityOf(e)]-E[severityOf(o)])}function $(t){return t==="error"?{bg:d.errBg,border:d.errBorder,text:d.errText}:{bg:d.warnBg,border:d.warnBorder,text:d.warnText}}function S(t){return t==="error"?"\u26D4":t==="info"?"\u2139":"\u26A0"}export const SHORT_LABEL={"partial-coverage":"Partial wafer coverage","geometry-conflict":"Geometry conflict","non-standard-diameter":"Non-standard wafer diameter","diameter-exceeds-die-extent":"Diameter exceeds die extent","edge-exclusion-exceeds-radius":"Edge exclusion exceeds radius","test-count-capped":"Test analysis skipped","test-def-collision":"Test definitions disagree","analysis-option-corrected":"Analysis option corrected","bin-colors-shared":"Bins share a colour","pass-bins-mixed":"Pass bins differ by wafer","ring-count-mixed":"Ring counts differ by wafer","input-field-removed":"Removed input name ignored","input-values-not-numbers":"Bins or test values are text","input-values-outside-stdf":"Values outside STDF ranges","retests-by-part-id":"Retests matched by part ID","derived-test-invalid":"Derived test not applied"};function w(t){const s=t.code?SHORT_LABEL[t.code]:void 0;if(s)return s;const i=t.message.split(/(?<=\.)\s/)[0]??t.message;return i.length>60?`${i.slice(0,57)}\u2026`:i}export function buildWarningsBanner(t,s=document){const i=s.createElement("div");Object.assign(i.style,{marginBottom:r.lg});for(const n of t){const e=severityOf(n),o=$(e),m=s.createElement("div");Object.assign(m.style,{background:o.bg,border:`1px solid ${o.border}`,borderRadius:v.control,marginBottom:r.sm,fontSize:y.body,color:o.text,lineHeight:"1.5"}),m.setAttribute("role","status");const a=s.createElement("button");a.type="button",Object.assign(a.style,{display:"flex",alignItems:"center",gap:r.sm,width:"100%",background:"none",border:"none",font:"inherit",color:"inherit",textAlign:"left",padding:`${r.sm} ${r.lg}`,cursor:"pointer"});const p=s.createElement("span");Object.assign(p.style,{flex:"1",minWidth:"0"}),p.textContent=`${S(e)} ${w(n)}`;const l=s.createElement("span");Object.assign(l.style,{flexShrink:"0"}),a.append(p,l);const g=s.createElement("div");Object.assign(g.style,{padding:`0 ${r.lg} ${r.sm}`}),g.textContent=n.message;let c=!1;const f=()=>{l.textContent=c?"\u25B4":"\u25BE",g.style.display=c?"block":"none",a.setAttribute("aria-expanded",String(c)),a.setAttribute("aria-label",`${w(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,g),i.appendChild(m)}return i}export function buildWarningsMenuEl(t,s,i=window){const n=i.document,e=n.createElement("div"),o=280,a=t.left+o<=(i.innerWidth??1/0)?t.left:Math.max(4,t.right-o);Object.assign(e.style,{position:"fixed",top:`${t.bottom+4}px`,left:`${a}px`,background:d.menuBg,border:`1px solid ${d.menuBorder}`,borderRadius:v.control,boxShadow:C.menu,zIndex:B,width:`${o}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),e.setAttribute("role","dialog"),e.setAttribute("aria-label","Data warnings");const p=n.createElement("div");Object.assign(p.style,{padding:`${r.sm} ${r.xl} ${r.xs}`,fontSize:y.body,fontWeight:"700",letterSpacing:O,textTransform:"uppercase",color:d.label}),p.textContent=s.length===1?"1 warning":`${s.length} warnings`,e.appendChild(p);for(const l of s){const g=severityOf(l),c=$(g),f=n.createElement("div");Object.assign(f.style,{display:"flex",gap:r.md,padding:`${r.md} ${r.xl}`,fontSize:y.body,lineHeight:"1.5",color:d.text,borderTop:`1px solid ${d.menuBorder}`,alignItems:"flex-start"});const u=n.createElement("span");Object.assign(u.style,{color:c.text,flexShrink:"0"}),u.textContent=S(g),f.appendChild(u);const x=n.createElement("div"),b=n.createElement("div");Object.assign(b.style,{fontWeight:"700",color:c.text,marginBottom:r.xxs}),b.textContent=l.code,x.appendChild(b);const h=n.createElement("div");h.textContent=l.message,x.appendChild(h),f.appendChild(x),e.appendChild(f)}return e}
@@ -1,4 +1,3 @@
1
- import type { Die } from './dies.js';
2
1
  export type AggregationMethod = 'mean' | 'median' | 'stddev' | 'min' | 'max' | 'count';
3
2
  /** Minimal die shape required by aggregation functions — satisfied by both Die and DieResult. */
4
3
  export interface DieLike {
@@ -29,11 +28,6 @@ export interface DieLike {
29
28
  * ```
30
29
  */
31
30
  export declare function aggregateValues(diesByWafer: DieLike[][], method: AggregationMethod, paramIndex?: number): DieLike[];
32
- /**
33
- * Return all unique bin values present in dies, sorted ascending.
34
- * `binSpace` selects whether to inspect `die.hbin` (default) or `die.sbin`.
35
- */
36
- export declare function getUniqueBins(dies: Die[], binSpace?: 'hard' | 'soft'): number[];
37
31
  /**
38
32
  * Aggregate multiple wafers by counting, per die position, how many wafers
39
33
  * had a specific bin value.
@@ -1 +1 @@
1
- import{minOf as p,maxOf as m}from"./utils.js";import{getDieKey as g}from"./dies.js";export function aggregateValues(a,s,l=0){if(!a.length)return[];const n=new Map,t=new Map;for(const d of a)for(const o of d){const e=g(o),f=o.testValues?.[l];f!==void 0?(n.has(e)||(n.set(e,[]),t.set(e,o)),n.get(e).push(f)):t.has(e)||t.set(e,o)}const r=[];for(const[d,o]of t){const e=n.get(d);if(!e?.length){r.push({...o,testValues:void 0,testPass:void 0});continue}let f;if(s==="mean")f=e.reduce((i,u)=>i+u,0)/e.length;else if(s==="median"){const i=[...e].sort((c,h)=>c-h),u=Math.floor(i.length/2);f=i.length%2?i[u]:(i[u-1]+i[u])/2}else if(s==="stddev"){if(e.length<2){r.push({...o,testValues:{0:0},testPass:void 0});continue}const i=e.reduce((u,c)=>u+c,0)/e.length;f=Math.sqrt(e.reduce((u,c)=>u+(c-i)**2,0)/(e.length-1))}else s==="min"?f=p(e):s==="max"?f=m(e):f=e.length;r.push({...o,testValues:{0:f},testPass:void 0})}return r}export function getUniqueBins(a,s="hard"){const l=new Set;for(const n of a){const t=s==="soft"?n.sbin:n.hbin;t!==void 0&&l.add(t)}return[...l].sort((n,t)=>n-t)}export function aggregateBinCounts(a,s,l="hard"){if(!a.length)return[];const n=new Map;for(const t of a)for(const r of t)if((l==="soft"?r.sbin:r.hbin)===s){const o=g(r);n.set(o,(n.get(o)??0)+1)}return(a[0]??[]).map(t=>({...t,testValues:{0:n.get(g(t))??0},testPass:void 0,...l==="soft"?{sbin:s}:{hbin:s}}))}
1
+ import{minOf as p,maxOf as m}from"./utils.js";import{getDieKey as h}from"./dies.js";export function aggregateValues(l,n,c=0){if(!l.length)return[];const a=new Map,o=new Map;for(const g of l)for(const t of g){const e=h(t),s=t.testValues?.[c];s!==void 0?(a.has(e)||(a.set(e,[]),o.set(e,t)),a.get(e).push(s)):o.has(e)||o.set(e,t)}const u=[];for(const[g,t]of o){const e=a.get(g);if(!e?.length){u.push({...t,testValues:void 0,testPass:void 0});continue}let s;if(n==="mean")s=e.reduce((f,i)=>f+i,0)/e.length;else if(n==="median"){const f=[...e].sort((r,d)=>r-d),i=Math.floor(f.length/2);s=f.length%2?f[i]:(f[i-1]+f[i])/2}else if(n==="stddev"){if(e.length<2){u.push({...t,testValues:{0:0},testPass:void 0});continue}const f=e.reduce((i,r)=>i+r,0)/e.length;s=Math.sqrt(e.reduce((i,r)=>i+(r-f)**2,0)/(e.length-1))}else n==="min"?s=p(e):n==="max"?s=m(e):s=e.length;u.push({...t,testValues:{0:s},testPass:void 0})}return u}export function aggregateBinCounts(l,n,c="hard"){if(!l.length)return[];const a=new Map;for(const o of l)for(const u of o)if((c==="soft"?u.sbin:u.hbin)===n){const t=h(u);a.set(t,(a.get(t)??0)+1)}return(l[0]??[]).map(o=>({...o,testValues:{0:a.get(h(o))??0},testPass:void 0,...c==="soft"?{sbin:n}:{hbin:n}}))}
@@ -51,11 +51,9 @@ export interface Die {
51
51
  /** STDF site_num — which parallel test site tested this die. Only meaningful when > 1 distinct value appears per wafer. */
52
52
  siteNum?: number;
53
53
  /** STDF pir.part_id — tester-assigned identifier for this tested unit. Encodes probe sequence at most fabs. */
54
- partId?: number;
54
+ partId?: number | string;
55
55
  }
56
56
  export interface DieEligibilityOptions {
57
- /** Include dies that straddle the wafer boundary (`die.partial`). Default: excluded. */
58
- includePartial?: boolean;
59
57
  /** Include dies inside the edge-exclusion zone (`die.edgeExcluded`). Default: excluded. */
60
58
  includeEdgeExcluded?: boolean;
61
59
  }
@@ -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,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}
1
+ export function hasPosition(n){return n.x!=null&&n.y!=null}export function isPositionedDie(n){return n.x!=null&&n.y!=null}export function getDieKey(n){return hasPosition(n)?`${n.x},${n.y}`:`id:${n.id}`}export function isYieldEligibleDie(n,t={}){return!(n.partial||!t.includeEdgeExcluded&&n.edgeExcluded)}export function diePassStatus(n,t){const e=n.hbin??n.sbin;return e===void 0?void 0:t.has(e)}export function generateDies(n,t){const{width:e,height:o,offset:s={x:0,y:0}}=t,u=t.gridSize??Math.ceil(n.radius/Math.min(e,o))+1,l=[];for(let i=-u;i<=u;i++)for(let r=-u;r<=u;r++){const c=n.center.x+r*e+s.x,x=n.center.y+i*o+s.y;l.push({id:`${r}_${i}`,x:r,y:i,physX:c,physY:x,width:e,height:o})}return l}
@@ -1,11 +1,9 @@
1
- export * from './aggregates.js';
2
- export * from './classify.js';
3
- export * from './dies.js';
4
- export * from './metadata.js';
5
- export * from './probe.js';
6
- export * from './reticle.js';
7
- export type { DataRow, MapOptions, TransformOptions, Affine, CoordFrame } from './transforms.js';
8
- export * from './wafer.js';
9
- export type { PitchResult } from './inference/pitch.js';
10
- export { createWafer, generateDies, isPositionedDie, clipDiesToWafer, applyOrientation, transformDies, mapDataToDies, isInsideWafer, affineIdentity, affineRotation, affineMirror, affineCompose, affineInvert, affinePoint, affineVector, affineSwapsAxes, applyProbeSequence, generateReticleGrid, classifyDie, getRingLabel, aggregateValues, aggregateBinCounts, getUniqueBins, metadataCategoricalValue, resolveGridPitch, } from './deprecated.js';
1
+ export { hasPosition, getDieKey, isYieldEligibleDie, diePassStatus } from './dies.js';
2
+ export type { Die, DieEligibilityOptions } from './dies.js';
3
+ export { metadataDisplayValue } from './metadata.js';
4
+ export type { DieMetadata, WaferMetadata } from './metadata.js';
5
+ export { getReticleCell } from './reticle.js';
6
+ export type { Reticle } from './reticle.js';
7
+ export type { Affine, CoordFrame } from './transforms.js';
8
+ export type { Wafer } from './wafer.js';
11
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- export*from"./aggregates.js";export*from"./classify.js";export*from"./dies.js";export*from"./metadata.js";export*from"./probe.js";export*from"./reticle.js";export*from"./wafer.js";export{createWafer,generateDies,isPositionedDie,clipDiesToWafer,applyOrientation,transformDies,mapDataToDies,isInsideWafer,affineIdentity,affineRotation,affineMirror,affineCompose,affineInvert,affinePoint,affineVector,affineSwapsAxes,applyProbeSequence,generateReticleGrid,classifyDie,getRingLabel,aggregateValues,aggregateBinCounts,getUniqueBins,metadataCategoricalValue,resolveGridPitch}from"./deprecated.js";
1
+ export{hasPosition,getDieKey,isYieldEligibleDie,diePassStatus}from"./dies.js";export{metadataDisplayValue}from"./metadata.js";export{getReticleCell}from"./reticle.js";
@@ -0,0 +1,9 @@
1
+ export declare const STDF_BIN_MAX = 32767;
2
+ export declare const STDF_COORD_MAX = 32767;
3
+ export declare const STDF_TEST_NUM_MAX = 4294967295;
4
+ export declare const STDF_SITE_MAX = 255;
5
+ export declare const isStdfBin: (v: number) => boolean;
6
+ export declare const isStdfCoord: (v: number) => boolean;
7
+ export declare const isStdfTestNumber: (v: number) => boolean;
8
+ export declare const isStdfSite: (v: number) => boolean;
9
+ //# sourceMappingURL=stdf.d.ts.map
@@ -0,0 +1 @@
1
+ export const STDF_BIN_MAX=32767,STDF_COORD_MAX=32767,STDF_TEST_NUM_MAX=4294967295,STDF_SITE_MAX=255,isStdfBin=t=>Number.isInteger(t)&&t>=0&&t<=32767,isStdfCoord=t=>Number.isInteger(t)&&Math.abs(t)<=32767,isStdfTestNumber=t=>Number.isInteger(t)&&t>=0&&t<=4294967295,isStdfSite=t=>Number.isInteger(t)&&t>=0&&t<=255;
@@ -1,17 +1,5 @@
1
1
  import type { Wafer } from './wafer.js';
2
2
  import type { Die, DieSpec, PositionedDie } from './dies.js';
3
- export interface DataRow {
4
- [key: string]: string | number;
5
- }
6
- export interface MapOptions {
7
- xField?: string;
8
- yField?: string;
9
- iField?: string;
10
- jField?: string;
11
- valueField: string;
12
- /** 'xy' matches by wafer coordinates; 'ij' matches by grid indices (default: 'xy') */
13
- matchBy?: 'xy' | 'ij';
14
- }
15
3
  export interface TransformOptions {
16
4
  /** Additional rotation in degrees, applied on top of existing die coordinates. */
17
5
  rotation?: number;
@@ -65,8 +53,6 @@ export interface Affine<From extends CoordFrame = CoordFrame, To extends CoordFr
65
53
  * `View.hoverPoints` holds and what hit-testing compares against.
66
54
  */
67
55
  export type CoordFrame = 'physical' | 'grid' | 'baked' | 'screen';
68
- /** The identity transform. */
69
- export declare function affineIdentity<F extends CoordFrame>(): Affine<F, F>;
70
56
  /**
71
57
  * Clockwise rotation by `angleDegrees` about (cx, cy) — the same convention as
72
58
  * {@link rotatePoint}, which this reproduces exactly.
@@ -130,13 +116,4 @@ export declare function transformDies(dies: PositionedDie[], options: TransformO
130
116
  x: number;
131
117
  y: number;
132
118
  }): PositionedDie[];
133
- /**
134
- * Attach data values to dies.
135
- * matchBy='xy' — matches by wafer coordinates (v0.1 behaviour)
136
- * matchBy='ij' — matches by grid indices
137
- *
138
- * Note: duplicate rows with the same coordinate produce last-wins behaviour.
139
- * Deduplicate `data` upstream if needed.
140
- */
141
- export declare function mapDataToDies(dies: Die[], data: DataRow[], options: MapOptions): Die[];
142
119
  //# sourceMappingURL=transforms.d.ts.map
@@ -1 +1 @@
1
- import{getDieKey as l}from"./dies.js";export function rotatePoint(t,n,e,o=0,s=0){const c=e*Math.PI/180,i=Math.cos(c),f=Math.sin(c),a=t-o,r=n-s;return{x:o+a*i+r*f,y:s-a*f+r*i}}export function affineIdentity(){return{a:1,b:0,c:0,d:1,e:0,f:0}}export function affineRotation(t,n=0,e=0){const o=t*Math.PI/180,s=Math.cos(o),c=Math.sin(o);return{a:s,c,b:-c,d:s,e:n-(s*n+c*e),f:e-(-c*n+s*e)}}export function affineMirror(t,n,e=0,o=0){return{a:t?-1:1,c:0,b:0,d:n?-1:1,e:t?2*e:0,f:n?2*o:0}}export function affineCompose(t,n){return{a:t.a*n.a+t.c*n.b,b:t.b*n.a+t.d*n.b,c:t.a*n.c+t.c*n.d,d:t.b*n.c+t.d*n.d,e:t.a*n.e+t.c*n.f+t.e,f:t.b*n.e+t.d*n.f+t.f}}export function affineInvert(t){const n=t.a*t.d-t.b*t.c,e=t.d/n,o=-t.b/n,s=-t.c/n,c=t.a/n;return{a:e,b:o,c:s,d:c,e:-(e*t.e+s*t.f),f:-(o*t.e+c*t.f)}}export function affinePoint(t,n,e){return{x:t.a*n+t.c*e+t.e,y:t.b*n+t.d*e+t.f}}export function affineVector(t,n,e){return{x:t.a*n+t.c*e,y:t.b*n+t.d*e}}export function affineSwapsAxes(t){return Math.abs(t.a)<1e-9&&Math.abs(t.d)<1e-9&&Math.abs(t.b)>1e-9}function d(t,n){return Math.sqrt(t*t-(n/2)**2)}export function isInsideWafer(t,n,e){const o=t-e.center.x,s=n-e.center.y;if(o*o+s*s>e.radius*e.radius)return!1;if(e.notch){const c=d(e.radius,e.notch.length);if(e.notch.type==="bottom"&&s<-c||e.notch.type==="top"&&s>c||e.notch.type==="left"&&o<-c||e.notch.type==="right"&&o>c)return!1}return!0}export function clipDiesToWafer(t,n,e){const o=[];for(const s of t){const c=s.physX,i=s.physY,f=isInsideWafer(c,i,n);if(!e){f&&o.push({...s,insideWafer:!0,partial:!1});continue}const a=e.width/2,r=e.height/2,p=[[c-a,i-r],[c+a,i-r],[c+a,i+r],[c-a,i+r]].filter(([h,y])=>isInsideWafer(h,y,n)).length;!f&&p===0||o.push({...s,insideWafer:!0,partial:p<4})}return o}export function applyOrientation(t,n){if(n.orientation===0)return t;const e=affineRotation(n.orientation,n.center.x,n.center.y);return t.map(o=>{const s=affinePoint(e,o.physX,o.physY);return{...o,physX:s.x,physY:s.y}})}export function transformDies(t,n,e={x:0,y:0}){const{rotation:o=0,flipX:s=!1,flipY:c=!1}=n;if(o===0&&!s&&!c)return t;const i=affineCompose(affineMirror(s,c,e.x,e.y),affineRotation(o,e.x,e.y));return t.map(f=>{const a=affinePoint(i,f.physX,f.physY);return{...f,physX:a.x,physY:a.y}})}export function mapDataToDies(t,n,e){const{valueField:o,matchBy:s="xy"}=e,c=new Map,i=r=>{const u=c.get(l(r));if(u===void 0)return{...r};const p=r.testValues??{};return{...r,testValues:{...p,[Object.keys(p).length]:u}}};if(s==="ij"){const r=e.iField??"x",u=e.jField??"y";for(const p of n)c.set(`${+p[r]},${+p[u]}`,+p[o]);return t.map(i)}const f=e.xField??"x",a=e.yField??"y";for(const r of n)c.set(`${+r[f]},${+r[a]}`,+r[o]);return t.map(i)}
1
+ export function rotatePoint(t,n,o,s=0,c=0){const e=o*Math.PI/180,r=Math.cos(e),i=Math.sin(e),a=t-s,f=n-c;return{x:s+a*r+f*i,y:c-a*i+f*r}}export function affineRotation(t,n=0,o=0){const s=t*Math.PI/180,c=Math.cos(s),e=Math.sin(s);return{a:c,c:e,b:-e,d:c,e:n-(c*n+e*o),f:o-(-e*n+c*o)}}export function affineMirror(t,n,o=0,s=0){return{a:t?-1:1,c:0,b:0,d:n?-1:1,e:t?2*o:0,f:n?2*s:0}}export function affineCompose(t,n){return{a:t.a*n.a+t.c*n.b,b:t.b*n.a+t.d*n.b,c:t.a*n.c+t.c*n.d,d:t.b*n.c+t.d*n.d,e:t.a*n.e+t.c*n.f+t.e,f:t.b*n.e+t.d*n.f+t.f}}export function affineInvert(t){const n=t.a*t.d-t.b*t.c,o=t.d/n,s=-t.b/n,c=-t.c/n,e=t.a/n;return{a:o,b:s,c,d:e,e:-(o*t.e+c*t.f),f:-(s*t.e+e*t.f)}}export function affinePoint(t,n,o){return{x:t.a*n+t.c*o+t.e,y:t.b*n+t.d*o+t.f}}export function affineVector(t,n,o){return{x:t.a*n+t.c*o,y:t.b*n+t.d*o}}export function affineSwapsAxes(t){return Math.abs(t.a)<1e-9&&Math.abs(t.d)<1e-9&&Math.abs(t.b)>1e-9}function y(t,n){return Math.sqrt(t*t-(n/2)**2)}export function isInsideWafer(t,n,o){const s=t-o.center.x,c=n-o.center.y;if(s*s+c*c>o.radius*o.radius)return!1;if(o.notch){const e=y(o.radius,o.notch.length);if(o.notch.type==="bottom"&&c<-e||o.notch.type==="top"&&c>e||o.notch.type==="left"&&s<-e||o.notch.type==="right"&&s>e)return!1}return!0}export function clipDiesToWafer(t,n,o){const s=[];for(const c of t){const e=c.physX,r=c.physY,i=isInsideWafer(e,r,n);if(!o){i&&s.push({...c,insideWafer:!0,partial:!1});continue}const a=o.width/2,f=o.height/2,p=[[e-a,r-f],[e+a,r-f],[e+a,r+f],[e-a,r+f]].filter(([h,u])=>isInsideWafer(h,u,n)).length;!i&&p===0||s.push({...c,insideWafer:!0,partial:p<4})}return s}export function applyOrientation(t,n){if(n.orientation===0)return t;const o=affineRotation(n.orientation,n.center.x,n.center.y);return t.map(s=>{const c=affinePoint(o,s.physX,s.physY);return{...s,physX:c.x,physY:c.y}})}export function transformDies(t,n,o={x:0,y:0}){const{rotation:s=0,flipX:c=!1,flipY:e=!1}=n;if(s===0&&!c&&!e)return t;const r=affineCompose(affineMirror(c,e,o.x,o.y),affineRotation(s,o.x,o.y));return t.map(i=>{const a=affinePoint(r,i.physX,i.physY);return{...i,physX:a.x,physY:a.y}})}
@@ -1 +1 @@
1
- import{diePassStatus as w}from"../core/dies.js";import{INPUT_DEFAULT_PASS_BINS as S,itemPassBins as x}from"../core/passBins.js";import{getBinColorScheme as y}from"./colorSchemes.js";import{arrayEqual as u,mapEqual as p,setEqual as b}from"../core/utils.js";export function resolveBinColors(s,n={}){return resolveBinColorsByWafer([{dies:s,passBins:n.passBins}],n).colors}export function binColorsForMaps(s,n={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],n).colors}export function resolveBinColorsForMaps(s,n={}){return resolveBinColorsByWafer(s.map(e=>({dies:e.dies,passBins:x(e)})),{...n,hbinDefs:mergeBinDefs(s.map(e=>e.hbinDefs)),sbinDefs:mergeBinDefs(s.map(e=>e.sbinDefs))})}export function mergeBinDefs(s){const n=new Set,e=[];for(const a of s)for(const r of a??[])n.has(r.bin)||(n.add(r.bin),e.push(r));return e}export function resolveBinColorsByWafer(s,n={}){const e=y(n.binColorScheme),a=n.useDefinedBinColors??!0,r=d(s),f=B(r.hardBins,i=>r.pass.hard.has(i),e,a?n.hbinDefs:void 0,!1),t=B(r.softBins,i=>r.pass.soft.has(i),e,a?n.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:r.pass},mixedHardBins:r.mixedHardBins}}export function binPassSets(s,n){return d([{dies:s,passBins:n}]).pass}export function binPassSetsByWafer(s){return d(s).pass}function d(s){const n=new Map,e=new Map,a=new Set;for(const t of s){const i=new Set(t.passBins??S);for(const l of t.dies){const o=w(l,i)===!0;l.hbin!=null&&(n.set(l.hbin,(n.get(l.hbin)??!0)&&o),o&&a.add(l.hbin)),l.sbin!=null&&e.set(l.sbin,(e.get(l.sbin)??!0)&&o)}}const r=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:r(n),soft:r(e)};return{hardBins:[...n.keys()],softBins:[...e.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function g(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const n=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${n?"bin":"bins"} ${g(s)} ${n?"passes":"pass"} on some wafers and ${n?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${n?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,n){if(!s.pass)return!1;for(const e of n)if(e.hbin!=null&&!s.hard.has(e.hbin)||e.sbin!=null&&!s.soft.has(e.sbin))return!1;return!0}export function binColorWarning(s,n){const e=n==="softBin"?"soft":n==="hardBin"?"hard":null;if(!e)return null;const a=s.shared[e];if(!a.length)return null;const r=g(a);return{code:"bin-colors-shared",severity:"warning",message:`${e==="hard"?"Hard":"Soft"} bins ${r} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const C=1,P=2;function m(s,n,e,a){const r=s.length,f=a?Math.floor(r/2):0;return s[((n-e+f)%r+r)%r]}function B(s,n,e,a,r){const f=new Map;for(const o of a??[])o.color&&f.set(o.bin,o.color);const t=new Map;for(const o of s)t.set(o,f.get(o)??(n(o)?m(e.pass,o,C,r):m(e.fail,o,P,r)));const i=new Map;for(const[o,h]of t){const c=h.toLowerCase();i.set(c,[...i.get(c)??[],o])}const l=[...i.values()].filter(o=>o.length>1).flat().sort((o,h)=>o-h);return{colors:t,shared:l}}export function binColorsEqual(s,n){return s===n?!0:!s||!n?!1:p(s.hard,n.hard)&&p(s.soft,n.soft)&&u(s.shared.hard,n.shared.hard)&&u(s.shared.soft,n.shared.soft)&&b(s.pass.hard,n.pass.hard)&&b(s.pass.soft,n.pass.soft)}
1
+ import{diePassStatus as w}from"../core/dies.js";import{INPUT_DEFAULT_PASS_BINS as S,itemPassBins as x}from"../core/passBins.js";import{getBinColorScheme as y}from"./colorSchemes.js";import{arrayEqual as u,mapEqual as p,setEqual as b}from"../core/utils.js";export function resolveBinColors(s,n={}){return resolveBinColorsByWafer([{dies:s,passBins:n.passBins}],n).colors}export function binColorsForMaps(s,n={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],n).colors}export function resolveBinColorsForMaps(s,n={}){return resolveBinColorsByWafer(s.map(e=>({dies:e.dies,passBins:x(e)})),{...n,hbinDefs:mergeBinDefs(s.map(e=>e.hbinDefs)),sbinDefs:mergeBinDefs(s.map(e=>e.sbinDefs))})}export function mergeBinDefs(s){const n=new Set,e=[];for(const a of s)for(const r of a??[])n.has(r.bin)||(n.add(r.bin),e.push(r));return e}export function resolveBinColorsByWafer(s,n={}){const e=y(n.binColorScheme),a=n.useDefinedBinColors??!0,r=d(s),f=B(r.hardBins,i=>r.pass.hard.has(i),e,a?n.hbinDefs:void 0,!1),t=B(r.softBins,i=>r.pass.soft.has(i),e,a?n.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:r.pass},mixedHardBins:r.mixedHardBins}}export function binPassSets(s,n){return d([{dies:s,passBins:n}]).pass}export function binPassSetsByWafer(s){return d(s).pass}function d(s){const n=new Map,e=new Map,a=new Set;for(const t of s){const i=new Set(t.passBins??S);for(const l of t.dies){const o=w(l,i)===!0;l.hbin!=null&&(n.set(l.hbin,(n.get(l.hbin)??!0)&&o),o&&a.add(l.hbin)),l.sbin!=null&&e.set(l.sbin,(e.get(l.sbin)??!0)&&o)}}const r=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:r(n),soft:r(e)};return{hardBins:[...n.keys()],softBins:[...e.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function g(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const n=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${n?"bin":"bins"} ${g(s)} ${n?"passes":"pass"} on some wafers and ${n?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${n?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,n){if(!s.pass)return!1;for(const e of n)if(e.hbin!=null&&!s.hard.has(e.hbin)||e.sbin!=null&&!s.soft.has(e.sbin))return!1;return!0}export function binColorWarning(s,n){const e=n==="softBin"?"soft":n==="hardBin"?"hard":null;if(!e)return null;const a=s.shared[e];if(!a.length)return null;const r=g(a);return{code:"bin-colors-shared",severity:"warning",message:`${e==="hard"?"Hard":"Soft"} bins ${r} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const C=1,P=2;function m(s,n,e,a){const r=s.length,f=a?Math.floor(r/2):0;return s[((Math.floor(n)-e+f)%r+r)%r]}function B(s,n,e,a,r){const f=new Map;for(const o of a??[])o.color&&f.set(o.bin,o.color);const t=new Map;for(const o of s)t.set(o,f.get(o)??(n(o)?m(e.pass,o,C,r):m(e.fail,o,P,r)));const i=new Map;for(const[o,h]of t){const c=h.toLowerCase();i.set(c,[...i.get(c)??[],o])}const l=[...i.values()].filter(o=>o.length>1).flat().sort((o,h)=>o-h);return{colors:t,shared:l}}export function binColorsEqual(s,n){return s===n?!0:!s||!n?!1:p(s.hard,n.hard)&&p(s.soft,n.soft)&&u(s.shared.hard,n.shared.hard)&&u(s.shared.soft,n.shared.soft)&&b(s.pass.hard,n.pass.hard)&&b(s.pass.soft,n.pass.soft)}
@@ -117,8 +117,7 @@ export interface View {
117
117
  /**
118
118
  * Transformed centre of each die, index-parallel with {@link dies}. Separate
119
119
  * from `rectangles` because a die always has a centre for hit-testing and
120
- * viewport fitting even when it is not drawn (a partial die with
121
- * `showPartialDies: false`).
120
+ * viewport fitting, however many rectangles it draws as.
122
121
  */
123
122
  hoverPoints: ViewHoverPoint[];
124
123
  /** Text to draw — die labels and any axis/indicator strings the view generated. */
@@ -438,13 +437,6 @@ export interface ViewOptions {
438
437
  * regardless of this option — it has no value to plot.
439
438
  */
440
439
  passFailDisplay?: 'off' | 'spec' | 'test';
441
- /**
442
- * When true (default), partial (edge) dies — positions that only partially
443
- * overlap the wafer circle — are rendered in a muted grey.
444
- * Set to false to hide them entirely, matching real prober behaviour where
445
- * edge positions are never tested.
446
- */
447
- showPartialDies?: boolean;
448
440
  }
449
441
  interface ColorFns {
450
442
  forValue: (t: number) => string;
@@ -1 +1 @@
1
- import{hasPosition as de}from"../core/dies.js";import{getReticleCell as he}from"../core/reticle.js";import{metadataCategoricalValue as me,metadataDisplayValue as pe}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ht,affineCompose as st,affinePoint as G,affineVector as xt,affineSwapsAxes as ye}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ge,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as be}from"./colorSchemes.js";import{resolveBinColors as xe,binColorsCover as Me}from"./binColors.js";import{NO_DATA_FILL as z}from"./colorMap.js";import{diePassStatus as Se}from"../core/dies.js";import{getDieTestValue as P,getTestPassStatus as U,isParametricTest as vt}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as kt,fmtColorbarAxis as $t,fmtAggregationMethod as _t}from"./fmt.js";import{metadataValueColor as ve}from"./colorMap.js";import{clamp01 as Kt,compareNatural as ke,escHtml as L,minOf as $e}from"../core/utils.js";import{prettyKey as Wt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as jt,isDerivedTest as Be,DERIVED_MARK as Te,DERIVED_KEY as Ie}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const i=new Set;for(const a of t){if(a.partial||a.edgeExcluded)continue;const s=ct(a,e);s!==void 0&&i.add(s)}return[...i].sort(ke)}export function findTestDef(t,e){return t?.find(i=>i.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,i)=>e-i)}const De="#d3d6db",wt="#e8e9ea",Ve="#aab0ba";function ct(t,e){return e?me(t.metadata?.[e]):void 0}function Ce(t,e,i){const{x:a,y:s}=t.center,n=bt(t.orientation,a,s),o=st(Ht(e?.x??!1,e?.y??!1,a,s),bt(t.orientation,a,s)),l=st(Ht(i?.flipX??!1,i?.flipY??!1,a,s),bt(i?.rotation??0,a,s));return{bakedToScreen:l,physicalToScreen:st(l,n),gridToScreen:st(l,o)}}function q(t,e=!1){return{points:[t],closed:e}}function Fe(t,e){const{center:i,radius:a,notch:s}=t;let n=i.x+a*Math.cos(e),o=i.y+a*Math.sin(e);if(!s)return{x:n,y:o};const l=Math.sqrt(a**2-(s.length/2)**2),d=s.length/2,g=n-i.x,p=o-i.y;return s.type==="bottom"&&p<-l?(o=i.y-l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="top"&&p>l?(o=i.y+l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="left"&&g<-l?(n=i.x-l,o=i.y+Math.max(-d,Math.min(d,p))):s.type==="right"&&g>l&&(n=i.x+l,o=i.y+Math.max(-d,Math.min(d,p))),{x:n,y:o}}function zt(t,e){return t.map(e).join(" / ")}function Pe(t,e=256){const i=Array.from({length:e},(a,s)=>t(s/(e-1)));return a=>i[Math.min(e-1,Math.max(0,Math.round(a*(e-1))))]}function Le(t,e){const i=Math.max(1,Math.min(t.width,t.height)),a=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(i*.55,a*1.8))))}function Re(t,e,i){if(e?.length){const a=e.flatMap(s=>{const n=s.testNumber;if(!vt(s)){const l=U(t,n,s);return l===void 0?[]:[{key:n,label:lt(s,n),value:l?"Pass":"Fail"}]}const o=P(t,n);return o===void 0?[]:[{key:n,label:lt(s,n),value:kt(o,s.unit,i),recordedFail:t.testPass?.[n]===!1}]});if(a.length)return a}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:kt(s,void 0,i)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,i={}){const{testDefs:a,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:l,lotSize:d,activeTest:g,reticleConfig:p,metadataFields:B}=i,F=s?new Map(s.map(f=>[f.bin,f])):null,T=n?new Map(n.map(f=>[f.bin,f])):null,b=[L(`Die (${t.x}, ${t.y})`)];if(p&&de(t)){const f=he(t,p);b.push(L(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=P(t,0);if(f!==void 0){const u=a?.[0],h=u?lt(u,u.testNumber):"Value",v=l?` (${l})`:"";b.push(L(`${h}${v}: ${kt(f,u?.unit,o)}`));const m=jt(u);m&&b.push(`<i>${L(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=P(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:F,v=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(l==="percent")m=`${f.toFixed(0)}%`;else{const V=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${V}`}const x=l?` [${_t(l)}]`:"";b.push(L(`${v}: ${m}${x}`))}}else{const f=Re(t,a,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(g??0,a),h=findTestDef(a,u),v=P(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let V=`<b>${L(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(v,h);(A==="failLow"||A==="failHigh")&&(V+=" <i>(out of spec)</i>")}b.push(V);const E=jt(findTestDef(a,x.key));E&&b.push(`<i>${L(E)}</i>`);const y=f.length-1;y>0&&b.push(`<i>+${y} more test${y===1?"":"s"}</i>`)}else if(f.length){const u=f.length;b.push(`<i>${u} test result${u===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const u=[];if(t.hbin!==void 0){const h=F?.get(t.hbin)?.name,v=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${v}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,v=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${v}`)}b.push(u.map(L).join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&b.push(L(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(L(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(L(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(L(`Probe: #${t.probeIndex}`));const $={...t.metadata??{}};for(const[f,u]of Object.entries($)){const h=pe(u);if(h===void 0)continue;const v=B?.find(m=>m.key===f)?.label??Wt(f);b.push(L(`${v}: ${h}`))}return b.join("<br>")}function Bt(t){return t?.name?lt(t,t.testNumber):void 0}function Tt(t){return Be(t)?{note:`${Te} ${Ie}`}:{}}export function buildMapTitle(t,e="engineering",i){const a=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=i?.length===1?i[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:a};case"stackedSoftBins":return{primary:s("Soft"),secondary:a};case"metadata":{const n=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Wt(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:l}=$t(o,Bt(n),n?.unit,e),d=l||"Value",g=t.aggrMethod?` \xB7 ${_t(t.aggrMethod)}`:"";return{primary:`${d}${g}`,secondary:a,...Tt(n)}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||Bt(n);if(t.passFailDisplay!=="off"){const g=n?.testNumber??t.activeTest,p=d?`${d} \xB7 #${g}`:rt(void 0,g),B=t.passFailDisplay==="spec"?"Spec pass/fail":n!==void 0&&!vt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B,...Tt(n)}}return{primary:d??rt(void 0,t.activeTest),secondary:"",...Tt(n)}}}}export function generateTextOverlay(t,e,i){const{plotMode:a,colorFns:s,normalize:n,activeTest:o,valueRange:l,testDefs:d,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:F,allIntegerValues:T}=i,b=findTestDef(d,o),{tickFmt:$}=$t(l[1],b?.name,b?.unit,g),f=T?h=>String(Math.round(h)):$,{testNumber:u}=resolveTestNumber(o,d);return t.flatMap((h,v)=>{let m="",x="#111111";if(a==="value"&&p==="test"){const y=U(h,u,b);if(y===void 0)return[];m=y?"P":"F",x=Q(y?Mt:St)}else if(a==="value"){const y=P(h,u);if(y===void 0)return[];m=zt([y],f),x=Q(s.forValue(n(y)))}else if(a==="hardBin"||a==="softBin"){const y=a==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=Q(s.forBin(y))}else if(a==="metadata"){const y=ct(h,B);if(y===void 0)return[];m=y,x=Q(F?.get(m)??z)}else{const y=P(h,0);if(y===void 0)return[];m=zt([y],f),x=Q(s.forValue(n(y)))}const V=e?e[v*2]:h.physX,E=e?e[v*2+1]:h.physY;return[{x:V,y:E,text:m,fontSize:Le(h,m),color:x,align:"center"}]})}function qt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Ne(t,e,i=720){const{center:a,radius:s,notch:n}=t,o=$=>G(e,$.x,$.y);if(!(n!==void 0&&t.diameter>150)){const $=[];for(let f=0;f<=i;f++){const u=2*Math.PI*f/i;$.push(o(Fe(t,u)))}return[{kind:"wafer-boundary",...q($,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,g=n.length,p=qt(n.type),B=Math.atan2(g,s),F=s-d,T=[];let b=!1;for(let $=0;$<=i;$++){const f=2*Math.PI*$/i;let u=f-p;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=B){if(!b){b=!0;const h={x:a.x+s*Math.cos(p-B),y:a.y+s*Math.sin(p-B)},v={x:a.x+F*Math.cos(p),y:a.y+F*Math.sin(p)},m={x:a.x+s*Math.cos(p+B),y:a.y+s*Math.sin(p+B)};T.push(o(h),o(v),o(m))}continue}T.push(o({x:a.x+s*Math.cos(f),y:a.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...q(T,!0),lineColor:"#888888",lineWidth:1}]}function Oe(t,e,i,a=360){const s=[],n=Math.max(1,Math.floor(i));for(let o=1;o<n;o++){const l=t.radius*o/n,d=[];for(let g=0;g<=a;g++){const p=2*Math.PI*g/a,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...q(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Ee(t,e,i,a){const{x:s,y:n}=t.center,o=t.radius,l=(f,u)=>G(e,f,u),d=i-s,g=Math.sqrt(Math.max(0,o*o-d*d)),p=l(i,n-g),B=l(i,n+g),F=a-n,T=Math.sqrt(Math.max(0,o*o-F*F)),b=l(s-T,a),$=l(s+T,a);return[{kind:"quadrant-boundary",...q([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...q([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ae(t,e){return t.map(i=>{const a=i.width/2,s=i.height/2;return{kind:"reticle",points:[[[i.x-a,i.y-s],[i.x+a,i.y-s],[i.x+a,i.y+s],[i.x-a,i.y+s]].map(([o,l])=>G(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function He(t){const e=t.filter(i=>i.probeIndex!==void 0).sort((i,a)=>(i.probeIndex??0)-(a.probeIndex??0));return e.length?[{kind:"probe-path",...q(e.map(i=>({x:i.physX,y:i.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function _e(t,e,i,a,s,n,o,l,d,g,p,B,F,T,b="off",$,f,u){const h=e.width-o,v=e.height-o,m=n?v:h,x=n?h:v,V=S=>s==="softBin"?S.sbin:S.hbin,E=S=>ct(S,$);if(e.partial){t.push({x:i,y:a,width:m,height:x,fill:De,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:i,y:a,width:m,height:x,fill:Ve,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&V(e)!==d){t.push({x:i,y:a,width:m,height:x,fill:wt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&E(e)!==u){t.push({x:i,y:a,width:m,height:x,fill:wt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const j=U(e,p,T),ut=j===void 0?z:j?Mt:St;t.push({x:i,y:a,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const S=P(e,p),N=ot(S,T);let W,J;S===void 0?W=z:b==="spec"?W=N==="failLow"?ge:N==="failHigh"?St:Mt:(W=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(J=N)),t.push({x:i,y:a,width:m,height:x,fill:W,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=V(e),N=S!=null?l.forBin(S):z,W=S!=null&&Se(e,F)===!1?!0:void 0;t.push({x:i,y:a,width:m,height:x,fill:N,type:s,binFail:W,metadata:e.metadata});return}if(s==="metadata"){const S=E(e),N=S!==void 0?f?.get(S)??z:z;t.push({x:i,y:a,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=P(e,0),A=y!==void 0?l.forValue(g(y)):z;t.push({x:i,y:a,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function Ke(t,e,i){const a=t.radius*.15,s=xt(e,a,0),n=xt(e,0,a),o=s.x+n.x>=0?-1:1,l=s.y+n.y>=0?-1:1,d={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:d.x+s.x,y:d.y+s.y},p={x:d.x+n.x,y:d.y+n.y};return i.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+n.x*.35,y:p.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...q([d,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...q([d,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,i={},a){const s=i.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:d=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:F=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:$=!1,useDefinedBinColors:f=!0,binColors:u,passBins:h=[1],highlightBin:v,highlightMetadataValue:m,interactiveTransform:x,valueRange:V,metadataValueOrder:E,testDefs:y,activeTest:A=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:W,lotSize:J,dataAxisFlip:j,isLotStack:ut=!1,logScale:Xt,colorbarRangeMode:Yt="spec",passFailDisplay:Gt,showPartialDies:Qt=!0}=i,It=Gt??"off",ft=a?.hbinDefs,dt=a?.sbinDefs,Dt=a?.metadataFields,Ut=Dt?.find(c=>c.key===S),Jt={x:(j?.x??!1)!==(x?.flipX??!1),y:(j?.y??!1)!==(x?.flipY??!1)},Zt=ft?new Map(ft.map(c=>[c.bin,c])):null,te=dt?new Map(dt.map(c=>[c.bin,c])):null,ee=n==="softBin"?te:Zt,ne=new Set(h),ht=u&&Me(u,e)?u:xe(e,{passBins:h,binColorScheme:T,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),ae=n==="softBin"?ht.soft:ht.hard,Vt={forValue:Pe(be(b,$)),forBin:c=>ae.get(c)??z};let{testNumber:H}=resolveTestNumber(A,y);if(!y?.length&&n==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((M,D)=>{if(!D.testValues)return M;const k=Object.keys(D.testValues).map(Number),R=k.length?$e(k):void 0;return R!==void 0&&(M===void 0||R<M)?R:M},void 0);r!==void 0&&(H=r)}let Z;if(Array.isArray(V))Z=V;else if(V){const{testNumber:c}=resolveTestNumber(V.test,y);Z=c===H?V.range:void 0}const I=findTestDef(y,H),Ct=n==="value"&&I!==void 0&&!vt(I),ie=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let K="off";n==="value"&&(Ct?K="test":It==="spec"&&ie?K="spec":It==="test"&&e.some(c=>U(c,H,I)!==void 0)&&(K="test"));const Ft=K==="spec"?"spec":Yt,mt=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let O,_,tt=!1;if(Z){[O,_]=Z;let c=!0,r=!1;for(const M of e){const D=mt?P(M,0):P(M,H);if(D!==void 0&&(r=!0,!Number.isInteger(D))){c=!1;break}}tt=r&&c}else{const c=!mt&&n==="value"&&Ft==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,D=!0;for(const k of e){const R=mt?P(k,0):P(k,H);R!==void 0&&(R<r&&(r=R),R>M&&(M=R),Number.isInteger(R)||(D=!1))}isFinite(r)||(D=!1),tt=D,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,_=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,_=isFinite(M)?M:1)}O===_&&(_=O+1);const Pt=Ct?!1:Xt??I?.logScale??!1,Lt=Pt&&O>0&&_>0,se=Lt;let et;if(Lt){const c=Math.log10(O),r=Math.log10(_)-c;et=M=>M<=0?0:Kt((Math.log10(M)-c)/r)}else et=c=>Kt((c-O)/(_-O));const C=Ce(t,j,x);let Rt=null;if(t.notch){const c=qt(t.notch.type),r=xt(C.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Rt={x:r.x/M,y:r.y/M})}let X=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<X&&(X=r.width),r.height<X&&(X=r.height)}const Nt=1,oe=X!==1/0?Math.min(Nt,X*.12):Nt,Ot=[],pt=[],re=C.bakedToScreen.a===1&&C.bakedToScreen.b===0&&C.bakedToScreen.c===0&&C.bakedToScreen.d===1&&C.bakedToScreen.e===0&&C.bakedToScreen.f===0;let w=null;if(!re){w=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=G(C.bakedToScreen,e[c].physX,e[c].physY);w[c*2]=r.x,w[c*2+1]=r.y}}const le=ye(C.gridToScreen),ce=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",ue=n==="softBin"||n==="stackedSoftBins";let nt=ce?new Map:void 0;const yt=n==="value"&&K==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,gt=n==="value"&&K==="test"?{pass:0,fail:0}:void 0,Et=n==="metadata";let at=Et?new Map:void 0,it=null;if(Et&&S){const c=collectMetadataValues(e,S),r=E?.key===S?E.values:void 0,M=r?[...r,...c.filter(D=>!r.includes(D))]:c;it=new Map(M.map((D,k)=>[D,Ut?.values?.find(R=>R.value===D)?.color??ve(k)]))}for(let c=0;c<e.length;c++){const r=e[c],M=w?w[c*2]:r.physX,D=w?w[c*2+1]:r.physY;if(pt.push({x:M,y:D}),nt&&!r.partial&&!r.edgeExcluded){const k=ue?r.sbin:r.hbin;k!=null&&nt.set(k,(nt.get(k)??0)+1)}if(yt&&!r.partial&&!r.edgeExcluded){const k=ot(P(r,H),I);k&&yt[k]++}if(gt&&!r.partial&&!r.edgeExcluded){const k=U(r,H,I);k!==void 0&&gt[k?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const k=ct(r,S);k!==void 0&&at.set(k,(at.get(k)??0)+1)}r.partial&&!Qt||_e(Ot,r,M,D,n,le,oe,Vt,v,et,H,ee,ne,I,K,S,it,m)}const At=o?generateTextOverlay(e,w,{plotMode:n,colorFns:Vt,normalize:et,activeTest:A,valueRange:[O,_],testDefs:y,fallbackFormat:N,passFailDisplay:K,activeMetadataKey:S,metadataColorMap:it,allIntegerValues:tt}):[],Y=Ne(t,C.physicalToScreen);p&&Y.push(...Oe(t,C.physicalToScreen,g)),B&&Y.push(...Ee(t,C.physicalToScreen,t.center.x,t.center.y)),l&&Y.push(...Ae(s,C.gridToScreen)),d&&Y.push(...He(e)),F&&Y.push(...Ke(t,C.gridToScreen,At));const fe=pt.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:Ot,hoverPoints:pt,texts:At,overlays:Y,plotMode:n,binColorScheme:T,valueColorScheme:b,reverseValueScheme:$,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[O,_],allIntegerValues:tt,testDefs:y,activeTest:A,logScale:se,logScaleRequested:Pt,aggrMethod:W,lotSize:J,axisFlip:Jt,dataAxisFlip:{x:j?.x??!1,y:j?.y??!1},gridToScreen:C.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:K,colorbarRangeMode:Ft,waferCenter:t.center,waferRadius:t.radius,notchDir:Rt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:S,metadataFields:Dt,specCounts:yt,passFailCounts:gt,dieBounds:fe}}export{getDieKey}from"../core/dies.js";
1
+ import{hasPosition as fe}from"../core/dies.js";import{getReticleCell as de}from"../core/reticle.js";import{metadataCategoricalValue as he,metadataDisplayValue as me}from"../core/metadata.js";import{affineRotation as bt,affineMirror as Ht,affineCompose as st,affinePoint as G,affineVector as xt,affineSwapsAxes as pe}from"../core/transforms.js";import{contrastTextColor as Q,SPEC_PASS_FILL as Mt,SPEC_FAIL_LOW as ye,SPEC_FAIL_HIGH as St}from"./colorMap.js";import{resolveValueColorFn as ge}from"./colorSchemes.js";import{resolveBinColors as be,binColorsCover as xe}from"./binColors.js";import{NO_DATA_FILL as w}from"./colorMap.js";import{diePassStatus as Me}from"../core/dies.js";import{getDieTestValue as L,getTestPassStatus as U,isParametricTest as vt}from"./buildWaferMap.js";import{classifySpec as ot}from"./spec.js";export{ot as classifySpec};import{fmt as kt,fmtColorbarAxis as $t,fmtAggregationMethod as _t}from"./fmt.js";import{metadataValueColor as Se}from"./colorMap.js";import{clamp01 as Kt,compareNatural as ve,escHtml as P,minOf as ke}from"../core/utils.js";import{prettyKey as Wt}from"../core/utils.js";import{testLabel as rt,markedTestLabel as lt,derivedTestNote as jt,isDerivedTest as $e,DERIVED_MARK as Be,DERIVED_KEY as Te}from"./testLabel.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const i=new Set;for(const a of t){if(a.partial||a.edgeExcluded)continue;const s=ct(a,e);s!==void 0&&i.add(s)}return[...i].sort(ve)}export function findTestDef(t,e){return t?.find(i=>i.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,i)=>e-i)}const Ie="#d3d6db",zt="#e8e9ea",De="#aab0ba";function ct(t,e){return e?he(t.metadata?.[e]):void 0}function Ve(t,e,i){const{x:a,y:s}=t.center,n=bt(t.orientation,a,s),o=st(Ht(e?.x??!1,e?.y??!1,a,s),bt(t.orientation,a,s)),l=st(Ht(i?.flipX??!1,i?.flipY??!1,a,s),bt(i?.rotation??0,a,s));return{bakedToScreen:l,physicalToScreen:st(l,n),gridToScreen:st(l,o)}}function q(t,e=!1){return{points:[t],closed:e}}function Ce(t,e){const{center:i,radius:a,notch:s}=t;let n=i.x+a*Math.cos(e),o=i.y+a*Math.sin(e);if(!s)return{x:n,y:o};const l=Math.sqrt(a**2-(s.length/2)**2),d=s.length/2,g=n-i.x,p=o-i.y;return s.type==="bottom"&&p<-l?(o=i.y-l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="top"&&p>l?(o=i.y+l,n=i.x+Math.max(-d,Math.min(d,g))):s.type==="left"&&g<-l?(n=i.x-l,o=i.y+Math.max(-d,Math.min(d,p))):s.type==="right"&&g>l&&(n=i.x+l,o=i.y+Math.max(-d,Math.min(d,p))),{x:n,y:o}}function wt(t,e){return t.map(e).join(" / ")}function Fe(t,e=256){const i=Array.from({length:e},(a,s)=>t(s/(e-1)));return a=>i[Math.min(e-1,Math.max(0,Math.round(a*(e-1))))]}function Le(t,e){const i=Math.max(1,Math.min(t.width,t.height)),a=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(i*.55,a*1.8))))}function Pe(t,e,i){if(e?.length){const a=e.flatMap(s=>{const n=s.testNumber;if(!vt(s)){const l=U(t,n,s);return l===void 0?[]:[{key:n,label:lt(s,n),value:l?"Pass":"Fail"}]}const o=L(t,n);return o===void 0?[]:[{key:n,label:lt(s,n),value:kt(o,s.unit,i),recordedFail:t.testPass?.[n]===!1}]});if(a.length)return a}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:kt(s,void 0,i)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([a,s])=>({key:Number(a),label:rt(void 0,Number(a)),value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,i={}){const{testDefs:a,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:l,lotSize:d,activeTest:g,reticleConfig:p,metadataFields:B}=i,F=s?new Map(s.map(f=>[f.bin,f])):null,T=n?new Map(n.map(f=>[f.bin,f])):null,b=[P(`Die (${t.x}, ${t.y})`)];if(p&&fe(t)){const f=de(t,p);b.push(P(`Reticle (${f.column}, ${f.row})`))}if(e==="stackedValues"){const f=L(t,0);if(f!==void 0){const u=a?.[0],h=u?lt(u,u.testNumber):"Value",v=l?` (${l})`:"";b.push(P(`${h}${v}: ${kt(f,u?.unit,o)}`));const m=jt(u);m&&b.push(`<i>${P(m)}</i>`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=L(t,0),u=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:F,v=u!==void 0?h?.get(u)?.name?`${u} \xB7 ${h.get(u).name}`:`Bin ${u}`:"Bin";let m;if(l==="percent")m=`${f.toFixed(0)}%`;else{const V=d?` (${(f/d*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${V}`}const x=l?` [${_t(l)}]`:"";b.push(P(`${v}: ${m}${x}`))}}else{const f=Pe(t,a,o);if(e==="value"&&f.length){const{testNumber:u}=resolveTestNumber(g??0,a),h=findTestDef(a,u),v=L(t,u);let m=f.findIndex(A=>A.key===u);m<0&&(m=0);const x=f[m];let V=`<b>${P(`${x.label}: ${x.value}`)}${x.recordedFail?" <i>(recorded fail)</i>":""}</b>`;if(x.key===u&&h){const A=ot(v,h);(A==="failLow"||A==="failHigh")&&(V+=" <i>(outside test limits)</i>")}b.push(V);const E=jt(findTestDef(a,x.key));E&&b.push(`<i>${P(E)}</i>`);const y=f.length-1;y>0&&b.push(`<i>+${y} more test${y===1?"":"s"}</i>`)}else if(f.length){const u=f.length;b.push(`<i>${u} test result${u===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const u=[];if(t.hbin!==void 0){const h=F?.get(t.hbin)?.name,v=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);u.push(`HBin: ${v}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,v=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);u.push(`SBin: ${v}`)}b.push(u.map(P).join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&b.push(P(`Retests: ${t.retestCount}`)),t.siteNum!==void 0&&b.push(P(`Site: ${t.siteNum}`)),t.partId!==void 0&&b.push(P(`Part ID: ${t.partId}`)),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(P(`Probe: #${t.probeIndex}`));const $={...t.metadata??{}};for(const[f,u]of Object.entries($)){const h=me(u);if(h===void 0)continue;const v=B?.find(m=>m.key===f)?.label??Wt(f);b.push(P(`${v}: ${h}`))}return b.join("<br>")}function Bt(t){return t?.name?lt(t,t.testNumber):void 0}function Tt(t){return $e(t)?{note:`${Be} ${Te}`}:{}}export function buildMapTitle(t,e="engineering",i){const a=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=i?.length===1?i[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:a};case"stackedSoftBins":return{primary:s("Soft"),secondary:a};case"metadata":{const n=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Wt(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:l}=$t(o,Bt(n),n?.unit,e),d=l||"Value",g=t.aggrMethod?` \xB7 ${_t(t.aggrMethod)}`:"";return{primary:`${d}${g}`,secondary:a,...Tt(n)}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=$t(o,Bt(n),n?.unit,e),d=l||Bt(n);if(t.passFailDisplay!=="off"){const g=n?.testNumber??t.activeTest,p=d?`${d} \xB7 #${g}`:rt(void 0,g),B=t.passFailDisplay==="spec"?"Limit pass/fail":n!==void 0&&!vt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B,...Tt(n)}}return{primary:d??rt(void 0,t.activeTest),secondary:"",...Tt(n)}}}}export function generateTextOverlay(t,e,i){const{plotMode:a,colorFns:s,normalize:n,activeTest:o,valueRange:l,testDefs:d,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:F,allIntegerValues:T}=i,b=findTestDef(d,o),{tickFmt:$}=$t(l[1],b?.name,b?.unit,g),f=T?h=>String(Math.round(h)):$,{testNumber:u}=resolveTestNumber(o,d);return t.flatMap((h,v)=>{let m="",x="#111111";if(a==="value"&&p==="test"){const y=U(h,u,b);if(y===void 0)return[];m=y?"P":"F",x=Q(y?Mt:St)}else if(a==="value"){const y=L(h,u);if(y===void 0)return[];m=wt([y],f),x=Q(s.forValue(n(y)))}else if(a==="hardBin"||a==="softBin"){const y=a==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=Q(s.forBin(y))}else if(a==="metadata"){const y=ct(h,B);if(y===void 0)return[];m=y,x=Q(F?.get(m)??w)}else{const y=L(h,0);if(y===void 0)return[];m=wt([y],f),x=Q(s.forValue(n(y)))}const V=e?e[v*2]:h.physX,E=e?e[v*2+1]:h.physY;return[{x:V,y:E,text:m,fontSize:Le(h,m),color:x,align:"center"}]})}function qt(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function Re(t,e,i=720){const{center:a,radius:s,notch:n}=t,o=$=>G(e,$.x,$.y);if(!(n!==void 0&&t.diameter>150)){const $=[];for(let f=0;f<=i;f++){const u=2*Math.PI*f/i;$.push(o(Ce(t,u)))}return[{kind:"wafer-boundary",...q($,!0),lineColor:"#888888",lineWidth:1}]}const d=1.25,g=n.length,p=qt(n.type),B=Math.atan2(g,s),F=s-d,T=[];let b=!1;for(let $=0;$<=i;$++){const f=2*Math.PI*$/i;let u=f-p;for(;u>Math.PI;)u-=2*Math.PI;for(;u<-Math.PI;)u+=2*Math.PI;if(Math.abs(u)<=B){if(!b){b=!0;const h={x:a.x+s*Math.cos(p-B),y:a.y+s*Math.sin(p-B)},v={x:a.x+F*Math.cos(p),y:a.y+F*Math.sin(p)},m={x:a.x+s*Math.cos(p+B),y:a.y+s*Math.sin(p+B)};T.push(o(h),o(v),o(m))}continue}T.push(o({x:a.x+s*Math.cos(f),y:a.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...q(T,!0),lineColor:"#888888",lineWidth:1}]}function Ne(t,e,i,a=360){const s=[],n=Math.max(1,Math.floor(i));for(let o=1;o<n;o++){const l=t.radius*o/n,d=[];for(let g=0;g<=a;g++){const p=2*Math.PI*g/a,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};d.push(G(e,B.x,B.y))}s.push({kind:"ring-boundary",...q(d,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Oe(t,e,i,a){const{x:s,y:n}=t.center,o=t.radius,l=(f,u)=>G(e,f,u),d=i-s,g=Math.sqrt(Math.max(0,o*o-d*d)),p=l(i,n-g),B=l(i,n+g),F=a-n,T=Math.sqrt(Math.max(0,o*o-F*F)),b=l(s-T,a),$=l(s+T,a);return[{kind:"quadrant-boundary",...q([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...q([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ee(t,e){return t.map(i=>{const a=i.width/2,s=i.height/2;return{kind:"reticle",points:[[[i.x-a,i.y-s],[i.x+a,i.y-s],[i.x+a,i.y+s],[i.x-a,i.y+s]].map(([o,l])=>G(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Ae(t){const e=t.filter(i=>i.probeIndex!==void 0).sort((i,a)=>(i.probeIndex??0)-(a.probeIndex??0));return e.length?[{kind:"probe-path",...q(e.map(i=>({x:i.physX,y:i.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function He(t,e,i,a,s,n,o,l,d,g,p,B,F,T,b="off",$,f,u){const h=e.width-o,v=e.height-o,m=n?v:h,x=n?h:v,V=S=>s==="softBin"?S.sbin:S.hbin,E=S=>ct(S,$);if(e.partial){t.push({x:i,y:a,width:m,height:x,fill:Ie,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:i,y:a,width:m,height:x,fill:De,type:"stacked",metadata:e.metadata});return}if(d!==void 0&&(s==="hardBin"||s==="softBin")&&V(e)!==d){t.push({x:i,y:a,width:m,height:x,fill:zt,type:"hardBin",metadata:e.metadata});return}if(u!==void 0&&s==="metadata"&&E(e)!==u){t.push({x:i,y:a,width:m,height:x,fill:zt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const j=U(e,p,T),ut=j===void 0?w:j?Mt:St;t.push({x:i,y:a,width:m,height:x,fill:ut,type:"value",metadata:e.metadata});return}const S=L(e,p),N=ot(S,T);let W,J;S===void 0?W=w:b==="spec"?W=N==="failLow"?ye:N==="failHigh"?St:Mt:(W=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(J=N)),t.push({x:i,y:a,width:m,height:x,fill:W,type:"value",specMark:J,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=V(e),N=S!=null?l.forBin(S):w,W=S!=null&&Me(e,F)===!1?!0:void 0;t.push({x:i,y:a,width:m,height:x,fill:N,type:s,binFail:W,metadata:e.metadata});return}if(s==="metadata"){const S=E(e),N=S!==void 0?f?.get(S)??w:w;t.push({x:i,y:a,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=L(e,0),A=y!==void 0?l.forValue(g(y)):w;t.push({x:i,y:a,width:m,height:x,fill:A,type:"value",metadata:e.metadata})}function _e(t,e,i){const a=t.radius*.15,s=xt(e,a,0),n=xt(e,0,a),o=s.x+n.x>=0?-1:1,l=s.y+n.y>=0?-1:1,d={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:d.x+s.x,y:d.y+s.y},p={x:d.x+n.x,y:d.y+n.y};return i.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+n.x*.35,y:p.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...q([d,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...q([d,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,i={},a){const s=i.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:d=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:F=!1,binColorScheme:T="default",valueColorScheme:b="default",reverseValueScheme:$=!1,useDefinedBinColors:f=!0,binColors:u,passBins:h=[1],highlightBin:v,highlightMetadataValue:m,interactiveTransform:x,valueRange:V,metadataValueOrder:E,testDefs:y,activeTest:A=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:W,lotSize:J,dataAxisFlip:j,isLotStack:ut=!1,logScale:Xt,colorbarRangeMode:Yt="spec",passFailDisplay:Gt}=i,It=Gt??"off",ft=a?.hbinDefs,dt=a?.sbinDefs,Dt=a?.metadataFields,Qt=Dt?.find(c=>c.key===S),Ut={x:(j?.x??!1)!==(x?.flipX??!1),y:(j?.y??!1)!==(x?.flipY??!1)},Jt=ft?new Map(ft.map(c=>[c.bin,c])):null,Zt=dt?new Map(dt.map(c=>[c.bin,c])):null,te=n==="softBin"?Zt:Jt,ee=new Set(h),ht=u&&xe(u,e)?u:be(e,{passBins:h,binColorScheme:T,hbinDefs:ft,sbinDefs:dt,useDefinedBinColors:f}),ne=n==="softBin"?ht.soft:ht.hard,Vt={forValue:Fe(ge(b,$)),forBin:c=>ne.get(c)??w};let{testNumber:H}=resolveTestNumber(A,y);if(!y?.length&&n==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((M,D)=>{if(!D.testValues)return M;const k=Object.keys(D.testValues).map(Number),R=k.length?ke(k):void 0;return R!==void 0&&(M===void 0||R<M)?R:M},void 0);r!==void 0&&(H=r)}let Z;if(Array.isArray(V))Z=V;else if(V){const{testNumber:c}=resolveTestNumber(V.test,y);Z=c===H?V.range:void 0}const I=findTestDef(y,H),Ct=n==="value"&&I!==void 0&&!vt(I),ae=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let K="off";n==="value"&&(Ct?K="test":It==="spec"&&ae?K="spec":It==="test"&&e.some(c=>U(c,H,I)!==void 0)&&(K="test"));const Ft=K==="spec"?"spec":Yt,mt=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let O,_,tt=!1;if(Z){[O,_]=Z;let c=!0,r=!1;for(const M of e){const D=mt?L(M,0):L(M,H);if(D!==void 0&&(r=!0,!Number.isInteger(D))){c=!1;break}}tt=r&&c}else{const c=!mt&&n==="value"&&Ft==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,D=!0;for(const k of e){const R=mt?L(k,0):L(k,H);R!==void 0&&(R<r&&(r=R),R>M&&(M=R),Number.isInteger(R)||(D=!1))}isFinite(r)||(D=!1),tt=D,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,_=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,_=isFinite(M)?M:1)}O===_&&(_=O+1);const Lt=Ct?!1:Xt??I?.logScale??!1,Pt=Lt&&O>0&&_>0,ie=Pt;let et;if(Pt){const c=Math.log10(O),r=Math.log10(_)-c;et=M=>M<=0?0:Kt((Math.log10(M)-c)/r)}else et=c=>Kt((c-O)/(_-O));const C=Ve(t,j,x);let Rt=null;if(t.notch){const c=qt(t.notch.type),r=xt(C.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Rt={x:r.x/M,y:r.y/M})}let X=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<X&&(X=r.width),r.height<X&&(X=r.height)}const Nt=1,se=X!==1/0?Math.min(Nt,X*.12):Nt,Ot=[],pt=[],oe=C.bakedToScreen.a===1&&C.bakedToScreen.b===0&&C.bakedToScreen.c===0&&C.bakedToScreen.d===1&&C.bakedToScreen.e===0&&C.bakedToScreen.f===0;let z=null;if(!oe){z=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=G(C.bakedToScreen,e[c].physX,e[c].physY);z[c*2]=r.x,z[c*2+1]=r.y}}const re=pe(C.gridToScreen),le=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",ce=n==="softBin"||n==="stackedSoftBins";let nt=le?new Map:void 0;const yt=n==="value"&&K==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,gt=n==="value"&&K==="test"?{pass:0,fail:0}:void 0,Et=n==="metadata";let at=Et?new Map:void 0,it=null;if(Et&&S){const c=collectMetadataValues(e,S),r=E?.key===S?E.values:void 0,M=r?[...r,...c.filter(D=>!r.includes(D))]:c;it=new Map(M.map((D,k)=>[D,Qt?.values?.find(R=>R.value===D)?.color??Se(k)]))}for(let c=0;c<e.length;c++){const r=e[c],M=z?z[c*2]:r.physX,D=z?z[c*2+1]:r.physY;if(pt.push({x:M,y:D}),nt&&!r.partial&&!r.edgeExcluded){const k=ce?r.sbin:r.hbin;k!=null&&nt.set(k,(nt.get(k)??0)+1)}if(yt&&!r.partial&&!r.edgeExcluded){const k=ot(L(r,H),I);k&&yt[k]++}if(gt&&!r.partial&&!r.edgeExcluded){const k=U(r,H,I);k!==void 0&&gt[k?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const k=ct(r,S);k!==void 0&&at.set(k,(at.get(k)??0)+1)}He(Ot,r,M,D,n,re,se,Vt,v,et,H,te,ee,I,K,S,it,m)}const At=o?generateTextOverlay(e,z,{plotMode:n,colorFns:Vt,normalize:et,activeTest:A,valueRange:[O,_],testDefs:y,fallbackFormat:N,passFailDisplay:K,activeMetadataKey:S,metadataColorMap:it,allIntegerValues:tt}):[],Y=Re(t,C.physicalToScreen);p&&Y.push(...Ne(t,C.physicalToScreen,g)),B&&Y.push(...Oe(t,C.physicalToScreen,t.center.x,t.center.y)),l&&Y.push(...Ee(s,C.gridToScreen)),d&&Y.push(...Ae(e)),F&&Y.push(..._e(t,C.gridToScreen,At));const ue=pt.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:Ot,hoverPoints:pt,texts:At,overlays:Y,plotMode:n,binColorScheme:T,valueColorScheme:b,reverseValueScheme:$,binColors:ht,metadata:t.metadata??null,dies:e,valueRange:[O,_],allIntegerValues:tt,testDefs:y,activeTest:A,logScale:ie,logScaleRequested:Lt,aggrMethod:W,lotSize:J,axisFlip:Ut,dataAxisFlip:{x:j?.x??!1,y:j?.y??!1},gridToScreen:C.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ut,passFailDisplay:K,colorbarRangeMode:Ft,waferCenter:t.center,waferRadius:t.radius,notchDir:Rt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:S,metadataFields:Dt,specCounts:yt,passFailCounts:gt,dieBounds:ue}}export{getDieKey}from"../core/dies.js";