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