@wafertools/wafermap 0.32.0 → 0.33.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 +1 -1
- package/CHANGELOG.md +162 -1
- package/README.md +2 -2
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +2 -12
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +30 -0
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +22 -3
- package/dist/packages/canvas-adapter/summaryPanel.js +4 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +54 -1
- 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 +76 -13
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/aggregates.d.ts +6 -0
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/compact.d.ts +205 -0
- package/dist/packages/core/compact.js +3 -0
- package/dist/packages/core/passBins.d.ts +6 -0
- package/dist/packages/core/passBins.js +1 -1
- package/dist/packages/renderer/axisTicks.d.ts +14 -0
- package/dist/packages/renderer/axisTicks.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +26 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/compactView.d.ts +22 -0
- package/dist/packages/renderer/compactView.js +1 -0
- package/dist/packages/renderer/xyIndicator.d.ts +27 -0
- package/dist/packages/renderer/xyIndicator.js +1 -0
- package/dist/packages/stats/analyzeWaferLot.d.ts +28 -0
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +36 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binRows.d.ts +38 -0
- package/dist/packages/stats/binRows.js +1 -0
- package/dist/packages/stats/filterFindings.d.ts +25 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingText.d.ts +9 -0
- package/dist/packages/stats/findingText.js +2 -0
- package/dist/packages/stats/lotDrift.d.ts +16 -0
- package/dist/packages/stats/lotDrift.js +1 -0
- package/dist/packages/stats/lotPattern.d.ts +33 -0
- package/dist/packages/stats/lotPattern.js +1 -0
- package/dist/packages/stats/math.d.ts +51 -0
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +31 -0
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/presentation.d.ts +42 -0
- package/dist/packages/stats/presentation.js +1 -0
- package/dist/packages/stats/regions.d.ts +9 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +15 -2
- package/dist/packages/stats/renderSummaryReport.js +32 -27
- package/dist/packages/stats/reportHtml.d.ts +54 -9
- package/dist/packages/stats/reportHtml.js +130 -47
- package/dist/packages/stats/synthesis.d.ts +66 -0
- package/dist/packages/stats/synthesis.js +2 -0
- package/dist/packages/stats/types.d.ts +1 -1
- package/dist/packages/stats/yieldRows.d.ts +31 -0
- package/dist/packages/stats/yieldRows.js +1 -0
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/dist/packages/stats/findingsNarrative.d.ts +0 -3
- package/dist/packages/stats/findingsNarrative.js +0 -1
package/AGENTS.md
CHANGED
|
@@ -241,7 +241,7 @@ compatibility shims for them.
|
|
|
241
241
|
- [API reference](https://wafertools.github.io/wafermap/api/) — every type, option and return value
|
|
242
242
|
- [Developer guide](https://wafertools.github.io/wafermap/guide/) — worked walkthroughs
|
|
243
243
|
- [Troubleshooting](https://wafertools.github.io/wafermap/troubleshooting/)
|
|
244
|
-
- [Examples](https://wafertools.github.io/wafermap/examples/) —
|
|
244
|
+
- [Examples](https://wafertools.github.io/wafermap/examples/) — 23 runnable pages, also
|
|
245
245
|
[downloadable](https://wafertools.github.io/wafermap/wafermap-examples.zip) to run offline
|
|
246
246
|
|
|
247
247
|
When a rule here and the API reference disagree, the API reference wins — tell the
|
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,167 @@ under `### Breaking`.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## [0.33.0] — 2026-10-02
|
|
26
|
+
|
|
27
|
+
### Breaking
|
|
28
|
+
|
|
29
|
+
- **`sectorCount` is 4, 8 or 16.** The compass names cover 16 bearings, so 32 has no names to give its sectors;
|
|
30
|
+
it is corrected to the default with an `'analysis-option-corrected'` warning, like any other unusable value.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **An expanded map's window widens for the Summary panel.** Opening the panel in a map's Expand modal, or in a wafer
|
|
35
|
+
opened from a gallery, makes the window wider by the room the panel needs, so the map keeps its size instead of
|
|
36
|
+
giving up more than half its width; closing the panel narrows it again. The window stays within the screen (a
|
|
37
|
+
floating window moves left if it would run off the edge), does nothing when maximised, and leaves a width the user
|
|
38
|
+
has dragged it to. A wafer opened in a separate browser window is resized the same way where the browser allows it.
|
|
39
|
+
- **The report builders load when a report is opened.** The Summary panel's report button and the gallery's load
|
|
40
|
+
`renderSummaryReport` with a dynamic `import()` instead of with the map, which takes about 10 KB gzipped out of what
|
|
41
|
+
rendering a map downloads (core is ~143 KB with this release's additions); the first report opens a moment later while it loads.
|
|
42
|
+
`renderWaferReportHtml` and `renderLotReportHtml` are unchanged public exports.
|
|
43
|
+
- **The Summary panel leads into the report, and the report acts on the map.** "What stands out" ends with a **Full
|
|
44
|
+
report** link that opens the same report the Summary report button does, which begins with that same section. In
|
|
45
|
+
a report opened from the panel, a click on a finding's row closes it and shows that finding on the map, as its
|
|
46
|
+
row in the panel does (on a lot report that is split into several groups, a finding the panel does not hold is
|
|
47
|
+
ignored). Opened on its own, the report is a plain page and its rows do nothing.
|
|
48
|
+
- **The reports and the Summary panel count and order bins, findings and statistics the same way.** One count of dies
|
|
49
|
+
per bin (partial and edge-excluded dies left out) and one order (pass bins first, then fail bins by count) feed the
|
|
50
|
+
panel's bars and the reports' tables, under one title that states the population ("Hard Bin Breakdown — % of dies
|
|
51
|
+
(N=…)"); the reports show the soft-bin breakdown as well when the data has soft bins, in the same columns for a
|
|
52
|
+
wafer and a lot. The reports' findings table is arranged as the panel's list is: each spatial pattern with the
|
|
53
|
+
findings it explains beneath it, then the rest by region, most severe first. The wafer report's Test Values table
|
|
54
|
+
has the panel's full columns (N, min, quartiles, median, mean, max, σ) and the share of dies inside each test's
|
|
55
|
+
limits; a lot's has N, min, mean, max, σ (pooled exactly) and limit yield, and says why it has no quartiles. The
|
|
56
|
+
lot overview names the unweighted mean of wafer yields "Mean per-wafer yield" everywhere.
|
|
57
|
+
- **The Summary panel and the HTML reports read yields the same way.** One computation gives each wafer's
|
|
58
|
+
difference from the lot median (with the lot's outlier rule naming the outliers) and each ring or quadrant's
|
|
59
|
+
difference from the wafer or lot it divides. The panel's wafer and region rows print that difference in points
|
|
60
|
+
and tint a shortfall of 1, 2 or 4 or more points, as the report's tables do; the report's wafer table says
|
|
61
|
+
"low outlier" in words, as the panel always has. The panel's findings mark severity as three dots filled by level
|
|
62
|
+
rather than a colour-only dot, as the reports do. The reports draw bin bars in the colour the map gives that bin
|
|
63
|
+
(the live colours when opened from a panel, the default palette otherwise).
|
|
64
|
+
- **The Summary panel opens with "What stands out"**, the same synthesis the HTML reports open with (a headline, up to
|
|
65
|
+
three items ranked by the dies each costs, an "also" line, and what was compared), in place of the findings
|
|
66
|
+
narrative above the findings list. It is a section of its own, so a clean wafer or lot says "Nothing stands out"
|
|
67
|
+
where the findings list is absent. The names in it (a region, a bin) select that finding on the map; the Findings
|
|
68
|
+
section's Detail button opens the same sentences above the full list. Where the wafers in a lot were judged by
|
|
69
|
+
different pass bins, the items rest on yield alone and name no pass bins.
|
|
70
|
+
- **The weakest finding severity is called "Minor" where it is shown** (the Summary panel's severity filter and the
|
|
71
|
+
reports); the value in `StatsFinding.severity` is unchanged.
|
|
72
|
+
- **A merged region on a wafer is graded with its constituents' multiple-testing correction.** The merged finding's
|
|
73
|
+
p-value is multiplied by the weakest constituent's adjusted-to-raw ratio before severity is assigned, the same rule the
|
|
74
|
+
lot merge applies, and `stats.adjustedPValue` is set. A merge can no longer grade more severe than the correction its
|
|
75
|
+
parts carry allows, so a few merged findings show a lower severity.
|
|
76
|
+
- **A lot stack by standard deviation or count carries no test limits.** Its values are spreads and tallies, not
|
|
77
|
+
measurements of the test, so `result.testDefs` has the limits removed and nothing is judged against them: no
|
|
78
|
+
limit fail findings, limit yield, capability index or out-of-spec colouring. Stacks by mean, median, minimum
|
|
79
|
+
and maximum keep them.
|
|
80
|
+
- **A sector run and a quadrant over the same part of the wafer are one finding.** "Sectors W–S" and
|
|
81
|
+
"Quadrant SW" make one statement, so the one with the smaller p-value is shown and the other is listed
|
|
82
|
+
under it (`absorbedIds`). They must be the same comparison (metric, variable and direction) and cover
|
|
83
|
+
angles that overlap by at least three fifths of their union, by geometry. A single sector inside a
|
|
84
|
+
quadrant, and a mean against a limit fail rate, stay separate. Wafer and lot findings follow the same rule.
|
|
85
|
+
- **Lot findings merge adjacent regions.** A run of adjacent sectors, quadrants or rings that carries one
|
|
86
|
+
signal is one lot finding ("Sectors E–N", "Rings 1–2"), as on a single wafer. Each wafer's regions are
|
|
87
|
+
added up, the rest being the wafer's other regions, and those per-wafer figures are combined across the
|
|
88
|
+
wafers as the single regions are (Stouffer's Z, the same gates). The run is adjusted with the
|
|
89
|
+
Benjamini–Hochberg multiplier of its weakest region and is reported only if it passes on its own;
|
|
90
|
+
otherwise its separate rows stay. "N/M wafers" counts the wafers where the merged region itself differs at
|
|
91
|
+
the analysis's significance level.
|
|
92
|
+
- **A merged run is compared with the rest of its region family.** As for each region on its own: sectors
|
|
93
|
+
leave out the centre dies, so a merged sector run's "rest of the map" is the other sectors' dies.
|
|
94
|
+
- **Adjacent regions merge per metric.** A test's mean finding and its limit fail rate are kept apart when
|
|
95
|
+
adjacent sectors, quadrants and rings are merged, so a run such as sectors E, NE and N reads as one
|
|
96
|
+
finding ("Sectors E–N"), and a quadrant with both a mean and a limit finding lists both. A run of limit
|
|
97
|
+
fail rates merges too, recomputed over the union of its regions. A run of quadrants is named in order
|
|
98
|
+
round the wafer.
|
|
99
|
+
- **A lot's spatial pattern is decided from the lot.** `analyzeWaferLot` stacks the lot's wafers by die
|
|
100
|
+
position, finds the positions where failures recur (an exact binomial test on each position's 3×3
|
|
101
|
+
neighbourhood, Benjamini–Hochberg across positions), classifies that area with the same classifier and
|
|
102
|
+
thresholds as a single wafer, and reports the pattern once, on every wafer whose failures are concentrated
|
|
103
|
+
in it. A pattern visible on every wafer is reported on every wafer under one label, with the ring, edge-arc
|
|
104
|
+
and cluster findings it explains listed under it. Wafers on a different grid, and lots too small for
|
|
105
|
+
failures to recur significantly, keep the per-wafer count. The wafer-level classifier and its thresholds
|
|
106
|
+
are unchanged (WM-811K: 64.1% overall).
|
|
107
|
+
- **Outlier wafers have one rule**, read by both the lot findings and the Summary panel's
|
|
108
|
+
Wafer Yield list, so they always name the same wafers. From 3 to 7 wafers it is Dixon's Q
|
|
109
|
+
test on the lowest and highest wafer (95% → `notable`, 99% → `unusual`); from 8 wafers,
|
|
110
|
+
Tukey's fences over per-wafer yield (1.5 × IQR → `notable`, 3 × IQR → `unusual`). Either
|
|
111
|
+
way the wafer must also be at least 3 yield points from the median. The
|
|
112
|
+
finding's `stats.method` is `'dixon-q'` or `'tukey-fence'`, and `effect.effectSize` is
|
|
113
|
+
that test's statistic (Q, or the distance from the median in IQR units). The Wafer Yield
|
|
114
|
+
list labels high outliers as well as low ones.
|
|
115
|
+
|
|
116
|
+
### Added
|
|
117
|
+
|
|
118
|
+
- **Compact layout for multi-project wafers.** A **Compact layout** row in the Overlays menu, on a single map and on the
|
|
119
|
+
gallery, draws the dies on a grid with the empty rows and columns removed and each group of dies outlined, so a
|
|
120
|
+
sparse layout fills the map instead of leaving each die a few pixels wide. It is offered when the occupied columns
|
|
121
|
+
and rows repeat at a regular pitch, or at the `reticleConfig` width and height, or a multiple of it (a product on
|
|
122
|
+
every second reticle), when every wafer supplies the same ones; `viewOptions: { compact: true }` applies it regardless. Only the layout changes:
|
|
123
|
+
every die is still drawn and counted, so legends, yield and statistics are the same, and hover text and axis
|
|
124
|
+
labels give original die coordinates. A die with no bin keeps its place. The notch marker follows rotation and
|
|
125
|
+
flips, and so does the XY indicator, which sits in a margin beside the grid. A gallery builds one layout from every
|
|
126
|
+
wafer shown, so its cards stay comparable. The wafer outline, ring, quadrant and reticle overlays describe the
|
|
127
|
+
physical wafer and are not drawn in this layout.
|
|
128
|
+
- **Rotating or flipping a map switches the XY indicator on.** The first Rotate or Flip from the Orientation menu, on a
|
|
129
|
+
single map or a gallery, turns on the +X/+Y arrows in whatever layout is showing, so the reader can see which way
|
|
130
|
+
the die coordinates run. They stay on until switched off in the Overlays menu, and **Reset orientation** does not
|
|
131
|
+
change them.
|
|
132
|
+
- **An Axis labels row in the Overlays menu.** The die-coordinate labels along the bottom and left edges appear when the
|
|
133
|
+
map is zoomed, as before; the row shows them always, or hides them, and **Clear overlays** returns it to that default.
|
|
134
|
+
`viewOptions: { showAxes }` is the same choice for a host. In the compact layout the labels name the first column
|
|
135
|
+
or row of each group of dies, which are the reticle boundaries, and every die once the cells are wide enough to
|
|
136
|
+
label them all.
|
|
137
|
+
- **Gallery cards fit the compact layout.** With the compact layout on, a card is as tall as its map needs and as wide
|
|
138
|
+
as that layout calls for, rather than sized for the wafer it replaces, so more cards fit on screen. A quarter turn
|
|
139
|
+
swaps the card's width and height. Turning the layout off restores the square card.
|
|
140
|
+
- **A multi-project wafer example and guide section.** The new Multi-project wafers example page shows the compact
|
|
141
|
+
layout on a synthetic wafer where two products share a reticle, and the guide's "Compact layout for multi-project
|
|
142
|
+
wafers" section covers the option, when it is offered, galleries, axis labels and the diagnostics.
|
|
143
|
+
- **Layout diagnostics.** A **Layout diagnostics** row beside it shows what the compact layout's detector saw: counts,
|
|
144
|
+
periods and scores only, with no die positions, bins, test values or wafer identity. **Copy** and **Save as file**
|
|
145
|
+
let someone report why a layout was or was not recognised without sharing their data.
|
|
146
|
+
- **A lot's yield or a test's mean can be reported as drifting across the wafers.** `analyzeWaferLot` runs a
|
|
147
|
+
Mann–Kendall trend test (with the Theil–Sen slope to size it) on the per-wafer yield, and on each test's per-wafer
|
|
148
|
+
mean when per-test statistics were computed, for lots of five or more wafers. The p-values are corrected together
|
|
149
|
+
(Benjamini–Hochberg), and a trend must also move far enough to matter: two points of yield, or half a within-wafer
|
|
150
|
+
σ of a test. A finding has id `drift:yield` or `drift:test:<number>`, level `'inter-wafer'`, family `'wafer'`,
|
|
151
|
+
method `'mann-kendall'`, and its summary says "(input order)": the wafers' order is a physical one only when the host
|
|
152
|
+
says so. The reports and the Summary panel list the strongest as a **Watch** line under "What stands out", with up to
|
|
153
|
+
one more for a test whose Ppk against its limits is under 1.0 and that costs no dies of its own. Watch is kept apart
|
|
154
|
+
from the items, which are ranked by dies lost.
|
|
155
|
+
- **Both HTML reports open with a "What stands out" section.** A headline (the yield, and for a lot its
|
|
156
|
+
spread across wafers), up to three items ranked by the dies each costs, and a line saying what was
|
|
157
|
+
compared. An item is a region whose pass rate fell, a fail bin that rose, a wafer well below the lot, a
|
|
158
|
+
parametric test outside its limits on a share of the dies, or a functional test that fails;
|
|
159
|
+
findings standing on the same dies are one item, a repeated spatial pattern leads its item, and the fail
|
|
160
|
+
bins that make up a region's shortfall are named with their share. An item's impact (High, Moderate or Low) is the
|
|
161
|
+
higher of its share of the lot's dies (4% and 2%) and its share of the dies that fail (40% and 15%), so a small area that
|
|
162
|
+
is most of a good lot's loss reads as high impact. Only an item costing at least one
|
|
163
|
+
yield point of the analysed dies is listed; otherwise the section says nothing stands out. Each figure links
|
|
164
|
+
to its row in the Findings table, which now carries an anchor per row (a test item names the test and
|
|
165
|
+
has no row to link to).
|
|
166
|
+
- **Both HTML reports have a new layout.** Severity is three dots and a word (Unusual, Notable, Minor) in place of
|
|
167
|
+
the coloured badge, so it reads in a black-and-white print and for colour-blind readers; findings rows and the
|
|
168
|
+
items in "What stands out" carry a light tint and a left edge. Yields, regions, wafers and bins are drawn with a
|
|
169
|
+
bar behind the figure, and a wafer or region is tinted only when it is 1, 2 or 4 or more points below its
|
|
170
|
+
reference, so a lot with nothing wrong has no tint. The key figures are tiles, pass bins are written in grey, and
|
|
171
|
+
a contents line leads the page (not printed). Items over the three shown go on one "Also over a yield point" line.
|
|
172
|
+
|
|
173
|
+
### Fixed
|
|
174
|
+
|
|
175
|
+
- An outlier wafer's finding reports its yield difference as a fraction in `effect.absoluteDelta`, like every
|
|
176
|
+
other yield finding, so the findings table shows "−4.3 pp" for a wafer 4.3 points below the lot median.
|
|
177
|
+
- The spatial-pattern classifier calls a failing region a scratch only when it reaches beyond
|
|
178
|
+
the central zone (35% of the radius). An elongated cluster wholly inside it is a centre
|
|
179
|
+
cluster. On the WM-811K benchmark (25,519 wafers): centre recall 59.9% → 60.5%, scratch
|
|
180
|
+
precision 33.3% → 35.7%, scratch recall 24.3% → 24.1%, overall 64.0% → 64.1%.
|
|
181
|
+
- A lot's repeated spatial pattern lists, as its `relatedIds`, the repeated findings that
|
|
182
|
+
each wafer's own pattern claimed on every wafer they appear on — so an edge arc that is part
|
|
183
|
+
of the edge ring on each of its wafers is shown under the lot's edge pattern, as at wafer
|
|
184
|
+
level, not as a separate lot finding.
|
|
185
|
+
|
|
25
186
|
## [0.32.0] — 2026-09-27
|
|
26
187
|
|
|
27
188
|
### Breaking
|
|
@@ -1543,7 +1704,7 @@ export's notice names its replacement; the old names are still removed in 0.31.0
|
|
|
1543
1704
|
placements take a fixed 180px band with `overflowY: hidden`, which most wafers' content
|
|
1544
1705
|
exceeds, so the last visible row was cut mid-line with nothing indicating more existed. All
|
|
1545
1706
|
placements now scroll, the way `'right'`/`'left'` already did. (Whether this content suits a
|
|
1546
|
-
wide-short band at all is a separate open question
|
|
1707
|
+
wide-short band at all is a separate open question.)
|
|
1547
1708
|
- **Closing and reopening Insights no longer discards the shared axis toggles.** `axisPrefs`
|
|
1548
1709
|
("axis includes limits" / "clip outliers") lived inside `renderDistributionsSection`, which
|
|
1549
1710
|
`render()` rebuilds — so a scope change, or simply toggling Insights off and on, silently
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/wafertools/wafermap/actions/workflows/deploy.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/@wafertools/wafermap)
|
|
7
7
|

|
|
8
|
-

|
|
9
9
|
[](./LICENSE)
|
|
10
10
|
|
|
11
11
|
<img src="docs/images/hero-test-values.png" alt="wafermap demo" style="max-width:640px; display:block; margin:8px 0;" />
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
Browser-first wafer map visualization for semiconductor test data.
|
|
14
14
|
|
|
15
15
|
**Zero runtime dependencies.** Pure ES modules with TypeScript types — works in React,
|
|
16
|
-
Svelte, Vue, plain HTML, or a Web Worker. The DOM-free data-and-stats layer is ~
|
|
16
|
+
Svelte, Vue, plain HTML, or a Web Worker. The DOM-free data-and-stats layer is ~60 kB
|
|
17
17
|
min+gz; the interactive renderer is larger, and its chart suite and in-app guide are
|
|
18
18
|
loaded on demand rather than shipped up front — [measured sizes](docs/performance.md).
|
|
19
19
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type SaveImageHandler } from '../toolbar.js';
|
|
2
|
+
import { robustFence } from '../../stats/math.js';
|
|
2
3
|
export type { SaveImageHandler };
|
|
3
4
|
/**
|
|
4
5
|
* The card's own expand button is hidden for the modal's duration and shown
|
|
@@ -571,18 +572,7 @@ export declare function fillTextOnHalo(ctx: CanvasRenderingContext2D, text: stri
|
|
|
571
572
|
export declare function strokeLimitLine(ctx: CanvasRenderingContext2D, line: LimitLine, color: string, x0: number, y0: number, x1: number, y1: number): void;
|
|
572
573
|
/** The "Limits:" dropdown, offered only where a test has both kinds. */
|
|
573
574
|
export declare function makeLimitsSelect(current: LimitsShown, onChange: (v: LimitsShown) => void, ownerDocument?: Document): HTMLLabelElement;
|
|
574
|
-
|
|
575
|
-
*
|
|
576
|
-
* Deliberately NOT mean ± 3σ. σ is computed FROM the data including the
|
|
577
|
-
* outlier, so a single reading of 1e30 inflates σ far enough that the fence no
|
|
578
|
-
* longer excludes it — the classic masking failure, and it fails hardest exactly
|
|
579
|
-
* when the outlier is worst. Quartiles are unmoved by the extreme tail.
|
|
580
|
-
*
|
|
581
|
-
* Returns null when there are too few values for quartiles to mean anything. */
|
|
582
|
-
export declare function robustFence(values: number[], k?: number): {
|
|
583
|
-
lo: number;
|
|
584
|
-
hi: number;
|
|
585
|
-
} | null;
|
|
575
|
+
export { robustFence };
|
|
586
576
|
/**
|
|
587
577
|
* Whether a panel should include the spec limits in its axis BY DEFAULT.
|
|
588
578
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SHADOW as D,LEADING as j,wireControlHover as L,controlStyle as I,SPACE as m,RADIUS as C,fontPx as k,FONT as S,CLR as f,Z_BASE as X,menuLayerFor as K,wireListNavigation as Y,MENU_SEARCH_THRESHOLD as W,makeMenuSearchBox as Z,markMenuTrigger as R,saveImageBlob as J,openReparentedModal as Q}from"../toolbar.js";import{ICONS as _}from"../icons.js";import{minOf as ee,maxOf as te}from"../../core/utils.js";import{fiveNumberSummary as ne}from"../../stats/math.js";import{fmt as oe,fmtColorbarAxis as ie}from"../../renderer/fmt.js";import{markedTestLabel as re,DERIVED_LANE_PAD as le}from"../../renderer/testLabel.js";export function openChartExpandModal(e,o,n={}){for(const s of e.querySelectorAll('div[style*="position: absolute"]'))s.style.display==="block"&&s.style.pointerEvents==="none"&&(s.style.display="none");const t=e.querySelector("[data-wmap-chart-expand]"),i=e.getAttribute("style")??"",r=e.dataset.wmapChartGrow??"plot";Object.assign(e.style,{flex:"1",minHeight:"0",alignSelf:"stretch",border:"none",borderRadius:"0"}),e.dataset.wmapExpanded="1";const l=Q([e],{title:o,anchor:n.anchor,ownerDocument:e.ownerDocument,boxSize:se[r],onClosed:()=>{delete e.dataset.wmapExpanded,e.setAttribute("style",i),t&&(t.style.display="flex"),n.onClosed?.()}});if(!l){delete e.dataset.wmapExpanded,e.setAttribute("style",i);return}if(t&&(t.style.display="none"),r==="rows"){const s=()=>{const a=Number(e.dataset.wmapChartFullH);if(!a)return;const p=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(p+a)}px)`)};s(),(e.ownerDocument.defaultView??window).requestAnimationFrame(s)}}const se={plot:{width:"min(94vw, 1100px)",height:"min(90vh, 760px)"},rows:{width:"min(94vw, 960px)",height:"min(90vh, 700px)"},square:{width:"min(94vw, 820px)",height:"min(90vh, 860px)"}};export const GRID_ALPHA=.4;export function withAlpha(e,o){const n=e.trim();let t,i,r,l=1;const s=n.match(/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i),a=n.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/i);if(s){const p=s[1].length===3?s[1].split("").map(c=>c+c).join(""):s[1];[t,i,r]=[0,2,4].map(c=>parseInt(p.slice(c,c+2),16)),p.length===8&&(l=parseInt(p.slice(6,8),16)/255)}else if(a)[t,i,r]=[a[1],a[2],a[3]].map(Number),a[4]!==void 0&&(l=a[4].endsWith("%")?parseFloat(a[4])/100:Number(a[4]));else return e;return`rgba(${t}, ${i}, ${r}, ${+(l*o).toFixed(3)})`}const ae={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},P={text:"text",textMuted:"text-muted",border:"border",bg:"surface",bgHover:"bg-hover",track:"panel-bg",warnBorder:"warn-border",accent:"icon-active",limitLine:"warn-text"};export function resolveChartCanvasColors(e){const o=getComputedStyle(e),n={};for(const t of Object.keys(P)){const i=o.getPropertyValue(`--wmap-${P[t]}`).trim();n[t]=i||ae[t]}return n.grid=withAlpha(n.border,GRID_ALPHA),n}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,o){const n=e.ownerDocument.defaultView??window;let t=0;const i=new n.ResizeObserver(()=>{t||(t=n.requestAnimationFrame(()=>{t=0,o()}))});return i.observe(e),{disconnect:()=>{t&&n.cancelAnimationFrame(t),t=0,i.disconnect()}}}export function growCardToFitContent(e,o,n){const t=`${H(e,o)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function H(e,o){const n=e.ownerDocument.defaultView??window,t=n.getComputedStyle(e);let i=0;for(const r of Array.from(e.children)){if(r===o)continue;const l=r,s=n.getComputedStyle(l);s.display==="none"||s.position==="absolute"||s.position==="fixed"||(i+=l.offsetHeight+(parseFloat(s.marginTop)||0)+(parseFloat(s.marginBottom)||0))}return t.boxSizing==="border-box"&&(i+=(parseFloat(t.paddingTop)||0)+(parseFloat(t.paddingBottom)||0)+(parseFloat(t.borderTopWidth)||0)+(parseFloat(t.borderBottomWidth)||0)),i}function z(e,o){const n=e.ownerDocument.defaultView??window;let t=0;for(const i of Array.from(e.children)){if(i===o)continue;const r=i,l=n.getComputedStyle(r);l.display==="none"||l.position==="absolute"||l.position==="fixed"||(t+=r.offsetHeight+(parseFloat(l.marginTop)||0)+(parseFloat(l.marginBottom)||0))}return t}export const WAFER_MENU_HINT="<br><em>right-click to chart this wafer</em>";export function populationPhrase(e,o,n){const t=e.toLocaleString(),i=e===1?"die":"dies";return n?e===o?`${t} ${i} ${n}`:`${t} of ${o.toLocaleString()} dies ${n} (partial and edge-excluded dies left out)`:`${t} ${i}`}export function setChartGrow(e,o){e.dataset.wmapChartGrow=o}export function isExpandedCard(e){return e.dataset.wmapExpanded==="1"}export function bodyRoom(e,o,n){const i=(e.ownerDocument.defaultView??window).getComputedStyle(e),r=e.clientHeight-(parseFloat(i.paddingTop)||0)-(parseFloat(i.paddingBottom)||0),l=i.boxSizing==="border-box"?(parseFloat(i.paddingTop)||0)+(parseFloat(i.paddingBottom)||0)+(parseFloat(i.borderTopWidth)||0)+(parseFloat(i.borderBottomWidth)||0):0;return r-(H(e,o)-l)-(n?z(o,n):0)}export function fitRowsHeight(e,o,n,t,i=0){if(growCardToFitContent(e,o,i+n),e.dataset.wmapChartFullH=String(Math.ceil(H(e,o)+i+t)),!isExpandedCard(e))return n;const r=bodyRoom(e,o)-i;return Math.max(n,Math.min(t,Math.floor(r)))}export function chartFillHeight(e,o,n,t){const i=z(o,n);return growCardToFitContent(e,o,i+t),Math.max(t,o.clientHeight-i)}export function chartDpr(e){return(e.ownerDocument.defaultView??window).devicePixelRatio||1}export function prepareCanvas(e,o,n,t){const i=chartDpr(e);e.width=Math.max(1,Math.floor(n*i)),e.height=Math.max(1,Math.floor(t*i)),e.style.width=`${n}px`,e.style.height=`${t}px`;const r=e.getContext("2d");return r?(r.setTransform(i,0,0,i,0,0),r.clearRect(0,0,n,t),r.font=`${k(-1)}px system-ui, sans-serif`,r.textBaseline="middle",{ctx:r,theme:resolveChartCanvasColors(o)}):null}export function applyCanvasFlow(e,o=0){let n;if(typeof o=="number")n=o;else{const t=(o.ownerDocument.defaultView??window).getComputedStyle(o);n=o.offsetTop+o.offsetHeight+(parseFloat(t.marginBottom)||0)}e.style.position="absolute",e.style.left="0",e.style.right="0",e.style.top=`${n}px`,e.style.bottom="0"}export function formatValue(e){return Number.isFinite(e)?Number.isInteger(e)?`${e}`:oe(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,o,n){if(!o&&n===void 0)return{tick:formatValue,tickWithUnit:formatValue,unitLabel:""};const{tickFmt:t,axisLabel:i,scaledUnit:r}=ie(e,null,o,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:i}}const ce=8;export const VERTICAL_TICK_SPACING_PX=28;export function horizontalTickSpacing(e,o,n){return(t,i)=>{const r=makeAxisFormat(o,n,t).tick;return i.reduce((l,s)=>Math.max(l,e.measureText(r(s)).width),0)+ce}}export function drawAxisUnit(e,o,n,t,i){const r={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font=`${k(-1)}px system-ui, sans-serif`,e.textAlign="left",e.textBaseline="middle",e.fillStyle=i,e.fillText(`(${o})`,n,t),Object.assign(e,r)}export function saveCanvasPng(e,o,n){const t=e.ownerDocument.createElement("canvas");t.width=e.width,t.height=e.height;const i=t.getContext("2d");i.fillStyle="#ffffff",i.fillRect(0,0,t.width,t.height),i.drawImage(e,0,0),t.toBlob(r=>{r&&J(r,o,n)},"image/png")}const V="10px";export function chartSwatchCss(e,o="solid"){return["display:inline-block",`width:${V}`,`height:${V}`,`border-radius:${C.pill}`,`background:${o==="outline"?"transparent":e}`,`border:1px ${o==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const de="0.35";export function makeSeriesLegendItem(e,o,n=document){const t=n.createElement("button");t.type="button",Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:m.xs,padding:`${m.xs} ${m.md}`,borderRadius:C.pill,border:`1px solid ${f.menuBorder}`,background:"none",cursor:"pointer",fontSize:S.body,color:f.text,whiteSpace:"nowrap"}),L(t,"bare");const i=n.createElement("span");return i.style.cssText=chartSwatchCss(o),t.append(i,n.createTextNode(e)),{el:t,setState({selected:r,dimmed:l}){t.style.borderColor=r?f.iconActive:f.menuBorder,t.style.fontWeight=r?"600":"400",t.style.opacity=l?de:"1",t.setAttribute("aria-pressed",r?"true":"false")}}}export function cardFrameStyle(){return{background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:C.container,padding:m.xl,fontVariantNumeric:"tabular-nums"}}export function cardShell(e,o,n=document){const t=n.createElement("div");t.dataset.wmapChartCard="1",t.dataset.wmapChartTitle=e,Object.assign(t.style,{display:"flex",flexDirection:"column",...cardFrameStyle(),minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const i=t.ownerDocument.createElement("div");Object.assign(i.style,{display:"flex",alignItems:"center",gap:m.sm,paddingBottom:"6px",marginBottom:m.md,borderBottom:`1px solid ${f.menuBorder}`});const r=t.ownerDocument.createElement("div");r.textContent=e,Object.assign(r.style,{color:f.value,fontSize:S.heading,fontWeight:"600",flex:"1"}),i.appendChild(r);const l=makeTooltip(t),s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Save as PNG"),s.innerHTML=_.download,Object.assign(s.style,{...I("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(s),attachChartTip(s,t,l,"Save as PNG"),s.addEventListener("click",()=>{const d=t.querySelector("canvas");d&&saveCanvasPng(d,e,o)}),i.appendChild(s);const a=t.ownerDocument.createElement("button");a.type="button",a.setAttribute("aria-label","Expand"),a.innerHTML=_.expand,Object.assign(a.style,{...I("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(a),attachChartTip(a,t,l,"Expand"),a.dataset.wmapChartExpand="1",a.addEventListener("click",()=>openChartExpandModal(t,r.textContent??e)),i.appendChild(a),t.appendChild(i);const p=t.ownerDocument.createElement("div");Object.assign(p.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(p);const c=t.ownerDocument.createElement("div");return Object.assign(c.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),t.appendChild(c),{card:t,heading:r,controlsRow:p,body:c}}export function makeSegmented(e,o,n,t=document,i=!1){const r=t.createElement("div");r.setAttribute("role","radiogroup"),Object.assign(r.style,{display:"inline-flex",border:`1px solid ${f.menuBorder}`,borderRadius:C.control,overflow:"hidden"});const l=`seg-${Math.random().toString(36).slice(2,9)}`,s=[];return e.forEach(([a,p],c)=>{const d=t.createElement("label");Object.assign(d.style,{display:"inline-flex",alignItems:"center",fontSize:S.body,padding:i?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:c>0?`1px solid ${f.menuBorder}`:"none"}),L(d,"bare");const u=t.createElement("input");u.type="radio",u.name=l,u.value=a,u.checked=a===o,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{d.style.background=u.checked?f.bgActive:f.menuBg,d.style.color=u.checked?f.iconActive:f.text,d.dataset.on=u.checked?"true":"false"};s.push(w),w(),u.addEventListener("change",()=>{if(u.checked){for(const v of s)v();n(a)}}),d.append(u,t.createTextNode(p)),r.appendChild(d)}),r}export function makeBackButton(e,o=document){const n=o.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...I("outlined"),background:f.menuBg,color:f.label}),L(n),n.addEventListener("click",e),n}export function renderEmptyState(e,o,n){const t=e.ownerDocument.createElement("div");t.textContent=o,Object.assign(t.style,{color:f.label,fontSize:S.body,padding:"8px 0"}),n&&Object.assign(t.style,n),e.appendChild(t)}export function makeListSelect(e,o,n,t={}){const{maxWidth:i="200px",ownerDocument:r=document,ariaLabel:l,emptyText:s,searchPlaceholder:a="Filter\u2026",hook:p}=t;let c=o;const d=x=>e.find(O=>O.value===x)?.label??"",u=r.createElement("button");u.type="button",p&&(u.dataset.wmapSelect=p),l&&u.setAttribute("aria-label",l),Object.assign(u.style,{fontSize:S.body,padding:`${m.xxs} ${m.sm}`,background:f.menuBg,color:f.text,border:`1px solid ${f.menuBorder}`,borderRadius:C.control,maxWidth:i,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:m.sm,justifyContent:"space-between"}),L(u),R(u,!1);const w=r.createElement("span");Object.assign(w.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const v=r.createElement("span");v.textContent="\u25BE",v.style.flex="0 0 auto",u.append(w,v);const E=()=>{w.textContent=e.length===0?s??"(none)":d(c)||s||"(none)"};if(E(),e.length===0)return u.disabled=!0,u.style.opacity="0.6",u.style.cursor="default",Object.defineProperty(u,"value",{get:()=>c,set:x=>{c=x,E()}});let b=null;const B=()=>{b&&(b.remove(),b=null,R(u,!1))},$=()=>{B(),u.focus()};function G(){const x=u.getBoundingClientRect(),O=r.defaultView??window,N=Math.max(x.width,220),q=Math.min(x.left,Math.max(4,(O.innerWidth??1/0)-N-4));b=r.createElement("div"),b.setAttribute("role","listbox"),b.setAttribute("aria-label",l??"Options"),Object.assign(b.style,{position:"fixed",top:`${x.bottom+4}px`,left:`${q}px`,background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:C.control,boxShadow:D.menu,zIndex:X,minWidth:`${N}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const T=[],F=()=>T.filter(h=>h.row.style.display!=="none").map(h=>h.row);if(e.length>W){const h=Z(y=>{for(const g of T)g.row.style.display=g.label.includes(y)?"":"none"},a,r);Object.assign(h.style,{position:"sticky",top:"0",zIndex:"1",background:f.menuBg}),h.addEventListener("keydown",y=>{if(y.key==="Escape"){$();return}y.key==="ArrowDown"&&(y.preventDefault(),F()[0]?.focus())}),b.appendChild(h)}for(const h of e){const y=h.value===c,g=r.createElement("div");g.textContent=h.label,g.setAttribute("role","option"),g.setAttribute("aria-selected",y?"true":"false"),g.tabIndex=-1,Object.assign(g.style,{padding:`${m.sm} ${m.xl}`,fontSize:S.body,cursor:"pointer",color:y?f.iconActive:f.text,fontWeight:y?"700":"400",background:y?f.menuActive:"transparent",whiteSpace:"nowrap"}),g.addEventListener("mouseenter",()=>{y||(g.style.background=f.menuHover)}),g.addEventListener("mouseleave",()=>{y||(g.style.background="transparent")}),g.addEventListener("click",U=>{U.stopPropagation(),c=h.value,E(),$(),n(h.value)}),b.appendChild(g),T.push({row:g,label:h.label.toLowerCase()})}if(Y(b,F,$),K(u).appendChild(b),R(u,!0),e.length<=W){const h=e.findIndex(y=>y.value===c);F()[h>=0?h:0]?.focus()}}u.addEventListener("click",x=>{if(x.stopPropagation(),b){B();return}G()});const M=x=>{if(!u.isConnected){r.removeEventListener("click",M);return}b&&!b.contains(x.target)&&!u.contains(x.target)&&B()};return r.addEventListener("click",M),Object.defineProperty(u,"value",{get:()=>c,set:x=>{c=x,E()}})}export function testOptionLabels(e){const o=e.some(n=>n.derived);return e.map(n=>(o&&!n.derived?le:"")+re(n,n.testNumber))}export function makeTestSelect(e,o,n,t={}){const{maxWidth:i="200px",emptyText:r="No parametric tests",ownerDocument:l=document}=t;return makeListSelect(testOptionLabels(e).map((s,a)=>({value:String(e[a].testNumber),label:s})),o!==null?String(o):"",s=>n(Number(s)),{maxWidth:i,ownerDocument:l,ariaLabel:"Test",emptyText:r,searchPlaceholder:"Filter tests\u2026"})}export function makeLinkedAxisPrefs(e,o,n,t=document){let i=o?.includeLimits,r=o?.clipOutliers??!1,l=o?.limits??"both";const s=()=>n({includeLimits:i,clipOutliers:r,limits:l});return{get:()=>({includeLimits:i,clipOutliers:r,limits:l}),sync(a,p,c=!1){e.innerHTML="",c&&e.appendChild(makeLimitsSelect(l,d=>{l=d,s()},t)),p&&e.appendChild(makeToggle("Axis includes limits",a,d=>{i=d,s()},t)),e.appendChild(makeToggle("Clip outliers",r,d=>{r=d,s()},t))},set(a){const p=a.limits??"both";return a.includeLimits===i&&a.clipOutliers===r&&p===l?!1:(i=a.includeLimits,r=a.clipOutliers,l=p,!0)}}}export function makeLinkedTestSelect(e,o,n,t={}){let i=o;const r=makeTestSelect(e,i,l=>{l!==i&&(i=l,n(l))},t);return{el:r,get:()=>i,set(l){return i===l||!e.some(s=>s.testNumber===l)?!1:(i=l,r.value=String(l),!0)}}}export const ALL_GROUPS="\0all";export function makeLinkedGroupSelect(e,o,n,t,i={}){let r=n;const l=c=>c??ALL_GROUPS,s=c=>c===ALL_GROUPS?null:c,a=makeLabeledSelect(o,[{value:ALL_GROUPS,label:"All groups"},...e.map(c=>({value:c,label:c}))],l(r),c=>{const d=s(c);d!==r&&(r=d,t(d))},{...i,hook:"group-scope"}),p=a.querySelector('[data-wmap-select="group-scope"]');return{el:a,get:()=>r,set(c){return r===c||c!==null&&!e.includes(c)?!1:(r=c,p&&(p.value=l(c)),!0)}}}export function makeWaferSelect(e,o,n,t={}){const{maxWidth:i="160px",allLabel:r="All wafers",ownerDocument:l=document}=t,s="\0all";return makeListSelect([{value:s,label:r},...e.map((a,p)=>({value:String(p),label:a.label??`#${p}`}))],o===null?s:String(o),a=>n(a===s?null:Number(a)),{maxWidth:i,ownerDocument:l,ariaLabel:r,searchPlaceholder:"Filter wafers\u2026"})}export function makeToggle(e,o,n,t=document){const i=t.createElement("label");Object.assign(i.style,{display:"inline-flex",alignItems:"center",gap:m.xs,fontSize:S.body,color:f.label,cursor:"pointer",userSelect:"none"}),L(i,"bare");const r=t.createElement("input");return r.type="checkbox",r.checked=o,r.style.cssText="margin:0;cursor:pointer;",r.addEventListener("change",()=>n(r.checked)),i.append(r,t.createTextNode(e)),i}export function makeLabeledSelect(e,o,n,t,i={}){const{maxWidth:r="160px",hook:l,ownerDocument:s=document}=i,a=s.createElement("label");a.textContent=e,Object.assign(a.style,{color:f.label,fontSize:S.body,display:"flex",alignItems:"center",gap:m.xs});const p=makeListSelect(o,n,t,{maxWidth:r,ownerDocument:s,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return a.appendChild(p),a}export function makeChartGridWrap(e=document){const o=e.createElement("div");return o.dataset.wmapChartGrid="1",Object.assign(o.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:m.lg,flex:"0 0 auto"}),o}export function makeTooltip(e){const o=e.ownerDocument.createElement("div");return Object.assign(o.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:"rgba(30, 32, 40, 0.93)",color:"#f0f0f2",border:"1px solid rgba(255,255,255,0.10)",borderRadius:C.control,padding:`${m.sm} ${m.xl}`,fontSize:S.sub,fontFamily:S.family,maxWidth:"320px",whiteSpace:"normal",overflowWrap:"break-word",boxShadow:D.menu}),e.appendChild(o),o}export function attachChartTip(e,o,n,t){e.removeAttribute("title"),e.textContent?.trim()||(e.ariaLabel=t),e.addEventListener("mouseenter",i=>{n.textContent=t,n.style.display="block",positionChartTooltip(n,o,i.clientX,i.clientY)}),e.addEventListener("mousemove",i=>{n.style.display==="block"&&positionChartTooltip(n,o,i.clientX,i.clientY)}),e.addEventListener("mouseleave",()=>{n.style.display="none"}),e.addEventListener("click",()=>{n.style.display="none"}),e.addEventListener("focus",()=>{n.textContent=t,n.style.display="block";const i=e.getBoundingClientRect();positionChartTooltip(n,o,i.left+i.width/2,i.bottom)}),e.addEventListener("blur",()=>{n.style.display="none"})}export function positionChartTooltip(e,o,n,t){const i=o.getBoundingClientRect(),r=14,l=4;e.style.left="0",e.style.top="0";const s=e.offsetWidth,a=e.offsetHeight;let p=n-i.left+r,c=t-i.top+r;p+s+l>i.width&&(p=n-i.left-r-s),c+a+l>i.height&&(c=t-i.top-r-a),p=Math.max(l,p),c=Math.max(l,c),e.style.left=`${p}px`,e.style.top=`${c}px`}export const LIMIT_LABEL={test:{lo:"Lo limit",hi:"Hi limit"},spec:{lo:"LSL",hi:"USL"}},LIMIT_DASH={test:[3,3],spec:[10,4]},LIMITS_SHOWN_OPTIONS=[{value:"both",label:"Test + spec"},{value:"test",label:"Test limits"},{value:"spec",label:"Spec limits (LSL/USL)"},{value:"none",label:"None"}];function A(e,o){const[n,t]=o==="test"?[e?.limitLow,e?.limitHigh]:[e?.specLow,e?.specHigh],i=[];return n!==void 0&&Number.isFinite(n)&&i.push({value:n,kind:o,end:"lo",label:LIMIT_LABEL[o].lo}),t!==void 0&&Number.isFinite(t)&&i.push({value:t,kind:o,end:"hi",label:LIMIT_LABEL[o].hi}),i}export function hasBothLimitKinds(e){return A(e,"test").length>0&&A(e,"spec").length>0}export function limitLines(e,o="both"){if(o==="none")return[];const n=A(e,"test"),t=A(e,"spec"),i=o==="test"?n:o==="spec"?t:[...n,...t];return i.length?i:[...n,...t]}export function limitExtent(e){if(!e.length)return{};const o=e.map(n=>n.value);return{lo:ee(o),hi:te(o)}}export function stackLabelRows(e,o=4){const n=e.map((r,l)=>({...r,i:l})).sort((r,l)=>r.start-l.start),t=[],i=new Array(e.length).fill(0);for(const r of n){let l=t.findIndex(s=>r.start>=s+o);l===-1?(l=t.length,t.push(r.end)):t[l]=r.end,i[r.i]=l}return i}export function fillTextOnHalo(e,o,n,t,i){if(i){const r=e.measureText(o),l=2,s=e.fillStyle;e.fillStyle=i,e.fillRect(n-r.actualBoundingBoxLeft-l,t-r.actualBoundingBoxAscent-l,r.actualBoundingBoxLeft+r.actualBoundingBoxRight+2*l,r.actualBoundingBoxAscent+r.actualBoundingBoxDescent+2*l),e.fillStyle=s}e.fillText(o,n,t)}export function strokeLimitLine(e,o,n,t,i,r,l){e.save(),e.strokeStyle=n,e.lineWidth=1,e.setLineDash(LIMIT_DASH[o.kind]),e.beginPath(),e.moveTo(t,i),e.lineTo(r,l),e.stroke(),e.restore()}export function makeLimitsSelect(e,o,n=document){return makeLabeledSelect("Limits:",LIMITS_SHOWN_OPTIONS,e,t=>o(t),{maxWidth:"170px",hook:"limits-shown",ownerDocument:n})}export function robustFence(e,o=1.5){const n=Float64Array.from(e.filter(l=>Number.isFinite(l)));if(n.length<8)return null;const{q1:t,q3:i}=ne(n),r=i-t;return r===0?null:{lo:t-o*r,hi:i+o*r}}export function shouldIncludeLimitsByDefault(e,o,n,t,i=1/3){if(n===void 0&&t===void 0||!Number.isFinite(e)||!Number.isFinite(o))return!1;const r=Math.min(e,n??e),s=Math.max(o,t??o)-r;if(s<=0)return!0;const a=o-e;return a===0?!0:a/s>=i}export function resolveAxisRange(e){const{dataMin:o,dataMax:n,limits:t=[],includeLimits:i,clipOutliers:r,values:l}=e;let s=o,a=n,p=0;if(r&&l?.length){const d=robustFence(l);if(d){const u=Math.max(s,d.lo),w=Math.min(a,d.hi);w>u&&(p=l.filter(v=>Number.isFinite(v)&&(v<u||v>w)).length,s=u,a=w)}}if(i)for(const d of t)s=Math.min(s,d.value),a=Math.max(a,d.value);s===a&&(s-=1,a+=1);const c=[];for(const d of t)d.value<s?c.push({value:d.value,label:d.label,side:"lo"}):d.value>a&&c.push({value:d.value,label:d.label,side:"hi"});return{lo:s,hi:a,offAxis:c,clippedCount:p}}export function limitLabelSide(e,o,n,t,i,r=3){const l=i?-1:1;return(l<0?e-r-o>=n:e+r+o<=t)?l:-l}export function drawOffAxisLimits(e,o,n,t,i,r,l){if(!o.length)return;e.save(),e.fillStyle=i,e.font=`${k(-1)}px system-ui, sans-serif`;const s={lo:0,hi:0},a=k(-1)+2;for(const{value:p,label:c,side:d}of o){const u=s[d]++*a,w=t==="horizontal"?d==="lo"?"\u2190":"\u2192":d==="lo"?"\u2193":"\u2191",v=d==="lo"?`${w} ${c} ${r(p)}`:`${c} ${r(p)} ${w}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=d==="lo"?"left":"right",fillTextOnHalo(e,v,d==="lo"?n.left+2:n.right-2,n.top+2+u,l)):(e.textBaseline=d==="lo"?"bottom":"top",e.textAlign="left",fillTextOnHalo(e,v,n.left+4,d==="lo"?n.bottom-2-u:n.top+2+u,l))}e.restore()}
|
|
1
|
+
import{SHADOW as N,LEADING as j,wireControlHover as L,controlStyle as F,SPACE as m,RADIUS as C,fontPx as k,FONT as S,CLR as f,Z_BASE as K,menuLayerFor as Y,wireListNavigation as Z,MENU_SEARCH_THRESHOLD as W,makeMenuSearchBox as J,markMenuTrigger as R,saveImageBlob as Q,openReparentedModal as ee}from"../toolbar.js";import{ICONS as _}from"../icons.js";import{minOf as te,maxOf as ne}from"../../core/utils.js";import{robustFence as P}from"../../stats/math.js";import{fmt as oe,fmtColorbarAxis as ie}from"../../renderer/fmt.js";import{markedTestLabel as re,DERIVED_LANE_PAD as le}from"../../renderer/testLabel.js";export function openChartExpandModal(e,o,n={}){for(const s of e.querySelectorAll('div[style*="position: absolute"]'))s.style.display==="block"&&s.style.pointerEvents==="none"&&(s.style.display="none");const t=e.querySelector("[data-wmap-chart-expand]"),i=e.getAttribute("style")??"",r=e.dataset.wmapChartGrow??"plot";Object.assign(e.style,{flex:"1",minHeight:"0",alignSelf:"stretch",border:"none",borderRadius:"0"}),e.dataset.wmapExpanded="1";const l=ee([e],{title:o,anchor:n.anchor,ownerDocument:e.ownerDocument,boxSize:se[r],onClosed:()=>{delete e.dataset.wmapExpanded,e.setAttribute("style",i),t&&(t.style.display="flex"),n.onClosed?.()}});if(!l){delete e.dataset.wmapExpanded,e.setAttribute("style",i);return}if(t&&(t.style.display="none"),r==="rows"){const s=()=>{const a=Number(e.dataset.wmapChartFullH);if(!a)return;const p=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(p+a)}px)`)};s(),(e.ownerDocument.defaultView??window).requestAnimationFrame(s)}}const se={plot:{width:"min(94vw, 1100px)",height:"min(90vh, 760px)"},rows:{width:"min(94vw, 960px)",height:"min(90vh, 700px)"},square:{width:"min(94vw, 820px)",height:"min(90vh, 860px)"}};export const GRID_ALPHA=.4;export function withAlpha(e,o){const n=e.trim();let t,i,r,l=1;const s=n.match(/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i),a=n.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/i);if(s){const p=s[1].length===3?s[1].split("").map(c=>c+c).join(""):s[1];[t,i,r]=[0,2,4].map(c=>parseInt(p.slice(c,c+2),16)),p.length===8&&(l=parseInt(p.slice(6,8),16)/255)}else if(a)[t,i,r]=[a[1],a[2],a[3]].map(Number),a[4]!==void 0&&(l=a[4].endsWith("%")?parseFloat(a[4])/100:Number(a[4]));else return e;return`rgba(${t}, ${i}, ${r}, ${+(l*o).toFixed(3)})`}const ae={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},z={text:"text",textMuted:"text-muted",border:"border",bg:"surface",bgHover:"bg-hover",track:"panel-bg",warnBorder:"warn-border",accent:"icon-active",limitLine:"warn-text"};export function resolveChartCanvasColors(e){const o=getComputedStyle(e),n={};for(const t of Object.keys(z)){const i=o.getPropertyValue(`--wmap-${z[t]}`).trim();n[t]=i||ae[t]}return n.grid=withAlpha(n.border,GRID_ALPHA),n}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,o){const n=e.ownerDocument.defaultView??window;let t=0;const i=new n.ResizeObserver(()=>{t||(t=n.requestAnimationFrame(()=>{t=0,o()}))});return i.observe(e),{disconnect:()=>{t&&n.cancelAnimationFrame(t),t=0,i.disconnect()}}}export function growCardToFitContent(e,o,n){const t=`${H(e,o)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function H(e,o){const n=e.ownerDocument.defaultView??window,t=n.getComputedStyle(e);let i=0;for(const r of Array.from(e.children)){if(r===o)continue;const l=r,s=n.getComputedStyle(l);s.display==="none"||s.position==="absolute"||s.position==="fixed"||(i+=l.offsetHeight+(parseFloat(s.marginTop)||0)+(parseFloat(s.marginBottom)||0))}return t.boxSizing==="border-box"&&(i+=(parseFloat(t.paddingTop)||0)+(parseFloat(t.paddingBottom)||0)+(parseFloat(t.borderTopWidth)||0)+(parseFloat(t.borderBottomWidth)||0)),i}function V(e,o){const n=e.ownerDocument.defaultView??window;let t=0;for(const i of Array.from(e.children)){if(i===o)continue;const r=i,l=n.getComputedStyle(r);l.display==="none"||l.position==="absolute"||l.position==="fixed"||(t+=r.offsetHeight+(parseFloat(l.marginTop)||0)+(parseFloat(l.marginBottom)||0))}return t}export const WAFER_MENU_HINT="<br><em>right-click to chart this wafer</em>";export function populationPhrase(e,o,n){const t=e.toLocaleString(),i=e===1?"die":"dies";return n?e===o?`${t} ${i} ${n}`:`${t} of ${o.toLocaleString()} dies ${n} (partial and edge-excluded dies left out)`:`${t} ${i}`}export function setChartGrow(e,o){e.dataset.wmapChartGrow=o}export function isExpandedCard(e){return e.dataset.wmapExpanded==="1"}export function bodyRoom(e,o,n){const i=(e.ownerDocument.defaultView??window).getComputedStyle(e),r=e.clientHeight-(parseFloat(i.paddingTop)||0)-(parseFloat(i.paddingBottom)||0),l=i.boxSizing==="border-box"?(parseFloat(i.paddingTop)||0)+(parseFloat(i.paddingBottom)||0)+(parseFloat(i.borderTopWidth)||0)+(parseFloat(i.borderBottomWidth)||0):0;return r-(H(e,o)-l)-(n?V(o,n):0)}export function fitRowsHeight(e,o,n,t,i=0){if(growCardToFitContent(e,o,i+n),e.dataset.wmapChartFullH=String(Math.ceil(H(e,o)+i+t)),!isExpandedCard(e))return n;const r=bodyRoom(e,o)-i;return Math.max(n,Math.min(t,Math.floor(r)))}export function chartFillHeight(e,o,n,t){const i=V(o,n);return growCardToFitContent(e,o,i+t),Math.max(t,o.clientHeight-i)}export function chartDpr(e){return(e.ownerDocument.defaultView??window).devicePixelRatio||1}export function prepareCanvas(e,o,n,t){const i=chartDpr(e);e.width=Math.max(1,Math.floor(n*i)),e.height=Math.max(1,Math.floor(t*i)),e.style.width=`${n}px`,e.style.height=`${t}px`;const r=e.getContext("2d");return r?(r.setTransform(i,0,0,i,0,0),r.clearRect(0,0,n,t),r.font=`${k(-1)}px system-ui, sans-serif`,r.textBaseline="middle",{ctx:r,theme:resolveChartCanvasColors(o)}):null}export function applyCanvasFlow(e,o=0){let n;if(typeof o=="number")n=o;else{const t=(o.ownerDocument.defaultView??window).getComputedStyle(o);n=o.offsetTop+o.offsetHeight+(parseFloat(t.marginBottom)||0)}e.style.position="absolute",e.style.left="0",e.style.right="0",e.style.top=`${n}px`,e.style.bottom="0"}export function formatValue(e){return Number.isFinite(e)?Number.isInteger(e)?`${e}`:oe(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,o,n){if(!o&&n===void 0)return{tick:formatValue,tickWithUnit:formatValue,unitLabel:""};const{tickFmt:t,axisLabel:i,scaledUnit:r}=ie(e,null,o,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:i}}const ce=8;export const VERTICAL_TICK_SPACING_PX=28;export function horizontalTickSpacing(e,o,n){return(t,i)=>{const r=makeAxisFormat(o,n,t).tick;return i.reduce((l,s)=>Math.max(l,e.measureText(r(s)).width),0)+ce}}export function drawAxisUnit(e,o,n,t,i){const r={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font=`${k(-1)}px system-ui, sans-serif`,e.textAlign="left",e.textBaseline="middle",e.fillStyle=i,e.fillText(`(${o})`,n,t),Object.assign(e,r)}export function saveCanvasPng(e,o,n){const t=e.ownerDocument.createElement("canvas");t.width=e.width,t.height=e.height;const i=t.getContext("2d");i.fillStyle="#ffffff",i.fillRect(0,0,t.width,t.height),i.drawImage(e,0,0),t.toBlob(r=>{r&&Q(r,o,n)},"image/png")}const G="10px";export function chartSwatchCss(e,o="solid"){return["display:inline-block",`width:${G}`,`height:${G}`,`border-radius:${C.pill}`,`background:${o==="outline"?"transparent":e}`,`border:1px ${o==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const de="0.35";export function makeSeriesLegendItem(e,o,n=document){const t=n.createElement("button");t.type="button",Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:m.xs,padding:`${m.xs} ${m.md}`,borderRadius:C.pill,border:`1px solid ${f.menuBorder}`,background:"none",cursor:"pointer",fontSize:S.body,color:f.text,whiteSpace:"nowrap"}),L(t,"bare");const i=n.createElement("span");return i.style.cssText=chartSwatchCss(o),t.append(i,n.createTextNode(e)),{el:t,setState({selected:r,dimmed:l}){t.style.borderColor=r?f.iconActive:f.menuBorder,t.style.fontWeight=r?"600":"400",t.style.opacity=l?de:"1",t.setAttribute("aria-pressed",r?"true":"false")}}}export function cardFrameStyle(){return{background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:C.container,padding:m.xl,fontVariantNumeric:"tabular-nums"}}export function cardShell(e,o,n=document){const t=n.createElement("div");t.dataset.wmapChartCard="1",t.dataset.wmapChartTitle=e,Object.assign(t.style,{display:"flex",flexDirection:"column",...cardFrameStyle(),minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const i=t.ownerDocument.createElement("div");Object.assign(i.style,{display:"flex",alignItems:"center",gap:m.sm,paddingBottom:"6px",marginBottom:m.md,borderBottom:`1px solid ${f.menuBorder}`});const r=t.ownerDocument.createElement("div");r.textContent=e,Object.assign(r.style,{color:f.value,fontSize:S.heading,fontWeight:"600",flex:"1"}),i.appendChild(r);const l=makeTooltip(t),s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Save as PNG"),s.innerHTML=_.download,Object.assign(s.style,{...F("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(s),attachChartTip(s,t,l,"Save as PNG"),s.addEventListener("click",()=>{const d=t.querySelector("canvas");d&&saveCanvasPng(d,e,o)}),i.appendChild(s);const a=t.ownerDocument.createElement("button");a.type="button",a.setAttribute("aria-label","Expand"),a.innerHTML=_.expand,Object.assign(a.style,{...F("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(a),attachChartTip(a,t,l,"Expand"),a.dataset.wmapChartExpand="1",a.addEventListener("click",()=>openChartExpandModal(t,r.textContent??e)),i.appendChild(a),t.appendChild(i);const p=t.ownerDocument.createElement("div");Object.assign(p.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(p);const c=t.ownerDocument.createElement("div");return Object.assign(c.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),t.appendChild(c),{card:t,heading:r,controlsRow:p,body:c}}export function makeSegmented(e,o,n,t=document,i=!1){const r=t.createElement("div");r.setAttribute("role","radiogroup"),Object.assign(r.style,{display:"inline-flex",border:`1px solid ${f.menuBorder}`,borderRadius:C.control,overflow:"hidden"});const l=`seg-${Math.random().toString(36).slice(2,9)}`,s=[];return e.forEach(([a,p],c)=>{const d=t.createElement("label");Object.assign(d.style,{display:"inline-flex",alignItems:"center",fontSize:S.body,padding:i?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:c>0?`1px solid ${f.menuBorder}`:"none"}),L(d,"bare");const u=t.createElement("input");u.type="radio",u.name=l,u.value=a,u.checked=a===o,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{d.style.background=u.checked?f.bgActive:f.menuBg,d.style.color=u.checked?f.iconActive:f.text,d.dataset.on=u.checked?"true":"false"};s.push(w),w(),u.addEventListener("change",()=>{if(u.checked){for(const v of s)v();n(a)}}),d.append(u,t.createTextNode(p)),r.appendChild(d)}),r}export function makeBackButton(e,o=document){const n=o.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...F("outlined"),background:f.menuBg,color:f.label}),L(n),n.addEventListener("click",e),n}export function renderEmptyState(e,o,n){const t=e.ownerDocument.createElement("div");t.textContent=o,Object.assign(t.style,{color:f.label,fontSize:S.body,padding:"8px 0"}),n&&Object.assign(t.style,n),e.appendChild(t)}export function makeListSelect(e,o,n,t={}){const{maxWidth:i="200px",ownerDocument:r=document,ariaLabel:l,emptyText:s,searchPlaceholder:a="Filter\u2026",hook:p}=t;let c=o;const d=x=>e.find(O=>O.value===x)?.label??"",u=r.createElement("button");u.type="button",p&&(u.dataset.wmapSelect=p),l&&u.setAttribute("aria-label",l),Object.assign(u.style,{fontSize:S.body,padding:`${m.xxs} ${m.sm}`,background:f.menuBg,color:f.text,border:`1px solid ${f.menuBorder}`,borderRadius:C.control,maxWidth:i,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:m.sm,justifyContent:"space-between"}),L(u),R(u,!1);const w=r.createElement("span");Object.assign(w.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const v=r.createElement("span");v.textContent="\u25BE",v.style.flex="0 0 auto",u.append(w,v);const E=()=>{w.textContent=e.length===0?s??"(none)":d(c)||s||"(none)"};if(E(),e.length===0)return u.disabled=!0,u.style.opacity="0.6",u.style.cursor="default",Object.defineProperty(u,"value",{get:()=>c,set:x=>{c=x,E()}});let b=null;const B=()=>{b&&(b.remove(),b=null,R(u,!1))},$=()=>{B(),u.focus()};function U(){const x=u.getBoundingClientRect(),O=r.defaultView??window,D=Math.max(x.width,220),q=Math.min(x.left,Math.max(4,(O.innerWidth??1/0)-D-4));b=r.createElement("div"),b.setAttribute("role","listbox"),b.setAttribute("aria-label",l??"Options"),Object.assign(b.style,{position:"fixed",top:`${x.bottom+4}px`,left:`${q}px`,background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:C.control,boxShadow:N.menu,zIndex:K,minWidth:`${D}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const T=[],I=()=>T.filter(h=>h.row.style.display!=="none").map(h=>h.row);if(e.length>W){const h=J(y=>{for(const g of T)g.row.style.display=g.label.includes(y)?"":"none"},a,r);Object.assign(h.style,{position:"sticky",top:"0",zIndex:"1",background:f.menuBg}),h.addEventListener("keydown",y=>{if(y.key==="Escape"){$();return}y.key==="ArrowDown"&&(y.preventDefault(),I()[0]?.focus())}),b.appendChild(h)}for(const h of e){const y=h.value===c,g=r.createElement("div");g.textContent=h.label,g.setAttribute("role","option"),g.setAttribute("aria-selected",y?"true":"false"),g.tabIndex=-1,Object.assign(g.style,{padding:`${m.sm} ${m.xl}`,fontSize:S.body,cursor:"pointer",color:y?f.iconActive:f.text,fontWeight:y?"700":"400",background:y?f.menuActive:"transparent",whiteSpace:"nowrap"}),g.addEventListener("mouseenter",()=>{y||(g.style.background=f.menuHover)}),g.addEventListener("mouseleave",()=>{y||(g.style.background="transparent")}),g.addEventListener("click",X=>{X.stopPropagation(),c=h.value,E(),$(),n(h.value)}),b.appendChild(g),T.push({row:g,label:h.label.toLowerCase()})}if(Z(b,I,$),Y(u).appendChild(b),R(u,!0),e.length<=W){const h=e.findIndex(y=>y.value===c);I()[h>=0?h:0]?.focus()}}u.addEventListener("click",x=>{if(x.stopPropagation(),b){B();return}U()});const M=x=>{if(!u.isConnected){r.removeEventListener("click",M);return}b&&!b.contains(x.target)&&!u.contains(x.target)&&B()};return r.addEventListener("click",M),Object.defineProperty(u,"value",{get:()=>c,set:x=>{c=x,E()}})}export function testOptionLabels(e){const o=e.some(n=>n.derived);return e.map(n=>(o&&!n.derived?le:"")+re(n,n.testNumber))}export function makeTestSelect(e,o,n,t={}){const{maxWidth:i="200px",emptyText:r="No parametric tests",ownerDocument:l=document}=t;return makeListSelect(testOptionLabels(e).map((s,a)=>({value:String(e[a].testNumber),label:s})),o!==null?String(o):"",s=>n(Number(s)),{maxWidth:i,ownerDocument:l,ariaLabel:"Test",emptyText:r,searchPlaceholder:"Filter tests\u2026"})}export function makeLinkedAxisPrefs(e,o,n,t=document){let i=o?.includeLimits,r=o?.clipOutliers??!1,l=o?.limits??"both";const s=()=>n({includeLimits:i,clipOutliers:r,limits:l});return{get:()=>({includeLimits:i,clipOutliers:r,limits:l}),sync(a,p,c=!1){e.innerHTML="",c&&e.appendChild(makeLimitsSelect(l,d=>{l=d,s()},t)),p&&e.appendChild(makeToggle("Axis includes limits",a,d=>{i=d,s()},t)),e.appendChild(makeToggle("Clip outliers",r,d=>{r=d,s()},t))},set(a){const p=a.limits??"both";return a.includeLimits===i&&a.clipOutliers===r&&p===l?!1:(i=a.includeLimits,r=a.clipOutliers,l=p,!0)}}}export function makeLinkedTestSelect(e,o,n,t={}){let i=o;const r=makeTestSelect(e,i,l=>{l!==i&&(i=l,n(l))},t);return{el:r,get:()=>i,set(l){return i===l||!e.some(s=>s.testNumber===l)?!1:(i=l,r.value=String(l),!0)}}}export const ALL_GROUPS="\0all";export function makeLinkedGroupSelect(e,o,n,t,i={}){let r=n;const l=c=>c??ALL_GROUPS,s=c=>c===ALL_GROUPS?null:c,a=makeLabeledSelect(o,[{value:ALL_GROUPS,label:"All groups"},...e.map(c=>({value:c,label:c}))],l(r),c=>{const d=s(c);d!==r&&(r=d,t(d))},{...i,hook:"group-scope"}),p=a.querySelector('[data-wmap-select="group-scope"]');return{el:a,get:()=>r,set(c){return r===c||c!==null&&!e.includes(c)?!1:(r=c,p&&(p.value=l(c)),!0)}}}export function makeWaferSelect(e,o,n,t={}){const{maxWidth:i="160px",allLabel:r="All wafers",ownerDocument:l=document}=t,s="\0all";return makeListSelect([{value:s,label:r},...e.map((a,p)=>({value:String(p),label:a.label??`#${p}`}))],o===null?s:String(o),a=>n(a===s?null:Number(a)),{maxWidth:i,ownerDocument:l,ariaLabel:r,searchPlaceholder:"Filter wafers\u2026"})}export function makeToggle(e,o,n,t=document){const i=t.createElement("label");Object.assign(i.style,{display:"inline-flex",alignItems:"center",gap:m.xs,fontSize:S.body,color:f.label,cursor:"pointer",userSelect:"none"}),L(i,"bare");const r=t.createElement("input");return r.type="checkbox",r.checked=o,r.style.cssText="margin:0;cursor:pointer;",r.addEventListener("change",()=>n(r.checked)),i.append(r,t.createTextNode(e)),i}export function makeLabeledSelect(e,o,n,t,i={}){const{maxWidth:r="160px",hook:l,ownerDocument:s=document}=i,a=s.createElement("label");a.textContent=e,Object.assign(a.style,{color:f.label,fontSize:S.body,display:"flex",alignItems:"center",gap:m.xs});const p=makeListSelect(o,n,t,{maxWidth:r,ownerDocument:s,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return a.appendChild(p),a}export function makeChartGridWrap(e=document){const o=e.createElement("div");return o.dataset.wmapChartGrid="1",Object.assign(o.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:m.lg,flex:"0 0 auto"}),o}export function makeTooltip(e){const o=e.ownerDocument.createElement("div");return Object.assign(o.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:"rgba(30, 32, 40, 0.93)",color:"#f0f0f2",border:"1px solid rgba(255,255,255,0.10)",borderRadius:C.control,padding:`${m.sm} ${m.xl}`,fontSize:S.sub,fontFamily:S.family,maxWidth:"320px",whiteSpace:"normal",overflowWrap:"break-word",boxShadow:N.menu}),e.appendChild(o),o}export function attachChartTip(e,o,n,t){e.removeAttribute("title"),e.textContent?.trim()||(e.ariaLabel=t),e.addEventListener("mouseenter",i=>{n.textContent=t,n.style.display="block",positionChartTooltip(n,o,i.clientX,i.clientY)}),e.addEventListener("mousemove",i=>{n.style.display==="block"&&positionChartTooltip(n,o,i.clientX,i.clientY)}),e.addEventListener("mouseleave",()=>{n.style.display="none"}),e.addEventListener("click",()=>{n.style.display="none"}),e.addEventListener("focus",()=>{n.textContent=t,n.style.display="block";const i=e.getBoundingClientRect();positionChartTooltip(n,o,i.left+i.width/2,i.bottom)}),e.addEventListener("blur",()=>{n.style.display="none"})}export function positionChartTooltip(e,o,n,t){const i=o.getBoundingClientRect(),r=14,l=4;e.style.left="0",e.style.top="0";const s=e.offsetWidth,a=e.offsetHeight;let p=n-i.left+r,c=t-i.top+r;p+s+l>i.width&&(p=n-i.left-r-s),c+a+l>i.height&&(c=t-i.top-r-a),p=Math.max(l,p),c=Math.max(l,c),e.style.left=`${p}px`,e.style.top=`${c}px`}export const LIMIT_LABEL={test:{lo:"Lo limit",hi:"Hi limit"},spec:{lo:"LSL",hi:"USL"}},LIMIT_DASH={test:[3,3],spec:[10,4]},LIMITS_SHOWN_OPTIONS=[{value:"both",label:"Test + spec"},{value:"test",label:"Test limits"},{value:"spec",label:"Spec limits (LSL/USL)"},{value:"none",label:"None"}];function A(e,o){const[n,t]=o==="test"?[e?.limitLow,e?.limitHigh]:[e?.specLow,e?.specHigh],i=[];return n!==void 0&&Number.isFinite(n)&&i.push({value:n,kind:o,end:"lo",label:LIMIT_LABEL[o].lo}),t!==void 0&&Number.isFinite(t)&&i.push({value:t,kind:o,end:"hi",label:LIMIT_LABEL[o].hi}),i}export function hasBothLimitKinds(e){return A(e,"test").length>0&&A(e,"spec").length>0}export function limitLines(e,o="both"){if(o==="none")return[];const n=A(e,"test"),t=A(e,"spec"),i=o==="test"?n:o==="spec"?t:[...n,...t];return i.length?i:[...n,...t]}export function limitExtent(e){if(!e.length)return{};const o=e.map(n=>n.value);return{lo:te(o),hi:ne(o)}}export function stackLabelRows(e,o=4){const n=e.map((r,l)=>({...r,i:l})).sort((r,l)=>r.start-l.start),t=[],i=new Array(e.length).fill(0);for(const r of n){let l=t.findIndex(s=>r.start>=s+o);l===-1?(l=t.length,t.push(r.end)):t[l]=r.end,i[r.i]=l}return i}export function fillTextOnHalo(e,o,n,t,i){if(i){const r=e.measureText(o),l=2,s=e.fillStyle;e.fillStyle=i,e.fillRect(n-r.actualBoundingBoxLeft-l,t-r.actualBoundingBoxAscent-l,r.actualBoundingBoxLeft+r.actualBoundingBoxRight+2*l,r.actualBoundingBoxAscent+r.actualBoundingBoxDescent+2*l),e.fillStyle=s}e.fillText(o,n,t)}export function strokeLimitLine(e,o,n,t,i,r,l){e.save(),e.strokeStyle=n,e.lineWidth=1,e.setLineDash(LIMIT_DASH[o.kind]),e.beginPath(),e.moveTo(t,i),e.lineTo(r,l),e.stroke(),e.restore()}export function makeLimitsSelect(e,o,n=document){return makeLabeledSelect("Limits:",LIMITS_SHOWN_OPTIONS,e,t=>o(t),{maxWidth:"170px",hook:"limits-shown",ownerDocument:n})}export{P as robustFence};export function shouldIncludeLimitsByDefault(e,o,n,t,i=1/3){if(n===void 0&&t===void 0||!Number.isFinite(e)||!Number.isFinite(o))return!1;const r=Math.min(e,n??e),s=Math.max(o,t??o)-r;if(s<=0)return!0;const a=o-e;return a===0?!0:a/s>=i}export function resolveAxisRange(e){const{dataMin:o,dataMax:n,limits:t=[],includeLimits:i,clipOutliers:r,values:l}=e;let s=o,a=n,p=0;if(r&&l?.length){const d=P(l);if(d){const u=Math.max(s,d.lo),w=Math.min(a,d.hi);w>u&&(p=l.filter(v=>Number.isFinite(v)&&(v<u||v>w)).length,s=u,a=w)}}if(i)for(const d of t)s=Math.min(s,d.value),a=Math.max(a,d.value);s===a&&(s-=1,a+=1);const c=[];for(const d of t)d.value<s?c.push({value:d.value,label:d.label,side:"lo"}):d.value>a&&c.push({value:d.value,label:d.label,side:"hi"});return{lo:s,hi:a,offAxis:c,clippedCount:p}}export function limitLabelSide(e,o,n,t,i,r=3){const l=i?-1:1;return(l<0?e-r-o>=n:e+r+o<=t)?l:-l}export function drawOffAxisLimits(e,o,n,t,i,r,l){if(!o.length)return;e.save(),e.fillStyle=i,e.font=`${k(-1)}px system-ui, sans-serif`;const s={lo:0,hi:0},a=k(-1)+2;for(const{value:p,label:c,side:d}of o){const u=s[d]++*a,w=t==="horizontal"?d==="lo"?"\u2190":"\u2192":d==="lo"?"\u2193":"\u2191",v=d==="lo"?`${w} ${c} ${r(p)}`:`${c} ${r(p)} ${w}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=d==="lo"?"left":"right",fillTextOnHalo(e,v,d==="lo"?n.left+2:n.right-2,n.top+2+u,l)):(e.textBaseline=d==="lo"?"bottom":"top",e.textAlign="left",fillTextOnHalo(e,v,n.left+4,d==="lo"?n.bottom-2-u:n.top+2+u,l))}e.restore()}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildFacetTable as He,facetValueOf as Fe,FACET_NONE_VALUE as Ne}from"../stats/facets.js";import{mergeTestDefs as de}from"../stats/mergeTestDefs.js";import{isParametricTest as Ve}from"../renderer/buildWaferMap.js";import{waferDisplayLabel as _e,waferIdentityLabel as ze}from"../core/waferLabel.js";import{hasDrilldownTargets as ce,waferPopulation as Ue}from"./chartPopulation.js";import{openDrilldownMenu as qe}from"./drilldown.js";import{INPUT_DEFAULT_PASS_BINS as M,itemPassBins as Qe,passBinsLabel as pe}from"../core/passBins.js";import{NO_DATA_FILL as ue}from"../renderer/colorMap.js";import{describeWaferPopulation as Ke,populationStat as Xe}from"../stats/population.js";import{LEADING as fe,ALPHA as Je,SPACE as w,EDGE_GUTTER as te,FONT as A,CLR as k,RADIUS as me,SHADOW as Ze,controlStyle as be,wireControlHover as ne,wireTooltip as et}from"./toolbar.js";import{ICONS as tt}from"./icons.js";import{renderCapabilityPanel as nt}from"./charts/capability.js";import{renderBoxplotPanel as at}from"./charts/boxplot.js";import{renderTrendPanel as ot}from"./charts/trend.js";import{renderSweepPanel as st}from"./charts/sweep.js";import{sweepAppliesTo as rt}from"../stats/sweep.js";import{renderHistogramPanel as it}from"./charts/histogram.js";import{renderCorrelationPanel as lt}from"./charts/correlation.js";import{renderScatterPanel as dt}from"./charts/scatter.js";import{renderBarPanel as ge}from"./charts/barPanel.js";import{renderBinClusterPanel as ct}from"./charts/binCluster.js";import{renderTestPassRatePanel as pt}from"./charts/testPassRate.js";import{QUANTITY as ut}from"./charts/palette.js";import{cardFrameStyle as ft,makeChartGridWrap as _,makeLabeledSelect as mt,makeLinkedGroupSelect as bt}from"./charts/chartShell.js";import{buildYieldData as z,buildYieldDataCombined as he}from"../stats/yield.js";import{buildBinParetoData as gt}from"../stats/binPareto.js";import{buildLotTestSectionSteps as ht,buildLotFunctionalSection as yt,buildMetadataStripBox as wt}from"./summaryPanel.js";import{runChunked as Ct}from"./chunked.js";import{buildRegionYieldData as ye,buildRingRegions as xt,buildQuadrantRegions as St}from"../stats/regions.js";import{renderRegionYieldDiagram as we}from"./charts/regionYieldDiagram.js";const Ce=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}],vt={key:"sweeps",label:"Sweeps"};export function createInsightsTab(x){const{getItems:xe,getLotStats:Se,getBinColors:ae,getRingCount:ve,onSaveImage:T,onSaveText:U,openWafer:F,focusTest:oe}=x,Te=x.showMetadataStrip??!0,Y=x.contentInset??te,p=x.ownerDocument??document,R=p.createElement("div");Object.assign(R.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const ke=te,W=p.createElement("div");Object.assign(W.style,{paddingLeft:ke});const $=p.createElement("div");Object.assign($.style,{display:"flex",gap:w.xs,alignItems:"center",background:k.menuBg,border:`1px solid ${k.menuBorder}`,borderRadius:me.container,boxShadow:Ze.panel,padding:`0 ${w.lg}`,marginBottom:w.lg,marginLeft:Y,marginRight:Y}),$.setAttribute("role","tablist"),$.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from($.querySelectorAll('[role="tab"]')),a=t.indexOf(p.activeElement);if(a===-1)return;e.preventDefault();const s=t[(a+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];s.focus(),s.click()});const G=p.createElement("div");Object.assign(G.style,{display:"flex",flexDirection:"column",gap:w.lg,marginTop:"0",paddingLeft:Y,paddingRight:Y}),R.appendChild(W),R.appendChild($),R.appendChild(G);const se=(x.sweeps?.length??0)>0,De=se?[...Ce,vt]:Ce;let P=x.defaultView==="sweeps"&&!se?"overview":x.defaultView??"overview",j,E=null,L=null,H={includeLimits:void 0,clipOutliers:!1,limits:"both"};const q=new Map,re=(e,t)=>{H=e;for(const a of q.values())for(const s of a)s!==t&&s.setAxisPrefs(e)},Q=e=>{L!==e&&(L=e,V())};let N=new Map;function ie(){for(const e of N.values())e.destroy();N=new Map}function K(e,t,a){F?.(e,t,a)}function X(e){if(e.some(t=>ce(t.testDefs,x.sweeps)))return(t,a,s)=>{const r=e.find(u=>u.waferIndex===t);if(!r||!ce(r.testDefs,x.sweeps))return;s.preventDefault();const i=Ue(r.dies,{waferLabel:r.identity,testDefs:r.testDefs,activeTest:a??E??void 0,waferIndex:t});qe({x:s.clientX,y:s.clientY},s.target,i,{sweeps:x.sweeps,onSaveImage:T})}}function Be(e){return F?{open:(t,a)=>K(t,`Wafer ${e.find(s=>s.waferIndex===t)?.label??t}`,a)}:oe&&e.length===1?{open:(t,a)=>oe(a),label:"show this test on the map"}:null}function $e(){return xe().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:_e(e,t),identity:ze(e),waferIndex:t,passBins:Qe(e),statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function le(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${k.iconActive}`:"2px solid transparent",color:t?k.iconActive:k.label,fontWeight:t?"700":"500",fontSize:A.body,padding:`${w.sm} ${te} ${w.md}`,cursor:"pointer",marginBottom:"-1px"})}function Ee(e,t){const a=p.createElement("button");a.type="button",a.textContent=t;const s=e===P;return le(a,s),a.dataset.on=s?"true":"false",ne(a,"bare"),a.dataset.wmapInsightsTab=e,a.setAttribute("role","tab"),a.setAttribute("aria-selected",s?"true":"false"),a.tabIndex=s?0:-1,a.addEventListener("click",()=>{P!==e&&(P=e,V({keepSections:!0}))}),a}function Le(e){const t=p.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,le(t,!1),t.dataset.wmapInsightsBack="1",t.addEventListener("click",e.onBack),t}function Pe(e,t,a,s=!0){const r=_(p),i=ae();let u="label";const f=a??"group",g=pe(e.map(d=>d.passBins)),c=Se?.(),m=new Map(c?.lotYieldSeries.map(d=>[d.waferIndex,d.yieldPercent])),h=d=>({...d,label:d.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:m.get(d.waferIndex),key:d.waferIndex}),b=e.map(h),y=t?.map(d=>({key:d.key,items:d.items.map(h)})),S=()=>y?he(y,M,u):z(b,M,u),o={title:t?`Yield by ${f} (pass: ${g})`:`Yield by wafer (pass: ${g})`,data:S(),selfControl:{current:u,options:[["yield","Sort: yield"],["label",t?`Sort: ${f}`:"Sort: wafer ID"]],onChange:d=>(u=d,{data:S()})},barColor:()=>ut,valueLabel:d=>`${d.percent.toFixed(1)}%`,reference:d=>{if(d.length<3)return null;const D=d.map(ee=>ee.percent).sort((ee,Ge)=>ee-Ge),I=D.length%2?D[(D.length-1)/2]:(D[D.length/2-1]+D[D.length/2])/2;return{value:I,label:`median ${I.toFixed(1)}%`}},drill:y?{onOpenGroup:d=>{const D=y.find(I=>I.key===d.label)?.items??[];return{data:z(D,M,u),title:`Yield by wafer \u2014 ${f}: ${d.label} (pass: ${g})`}},onBack:()=>({data:S(),title:`Yield by ${f} (pass: ${g})`}),groupLabelText:f}:void 0,onOpen:F?d=>{typeof d.key=="number"&&K(d.key,`Wafer ${d.label}`)}:void 0,onWaferContextMenu:X(e),ownerDocument:p};let n=null;if(s&&(n=ge(o,T),n.card.style.minHeight="360px",r.appendChild(n.card)),t){const d=ct({title:"Hard bin pareto",groups:t.map(D=>({key:D.key,items:D.items.map(I=>({dies:I.dies,hardBinCounts:I.statsSummary?.stats.hardBinCounts,softBinCounts:I.statsSummary?.stats.softBinCounts}))})),onSaveImage:T,ownerDocument:p});return d.card.style.minHeight="360px",r.appendChild(d.card),{card:r,destroy:()=>{n?.destroy(),d.destroy()}}}let v="hbin";const l=d=>d===void 0?ue:(v==="hbin"?i.hard:i.soft).get(d)??ue,C=e.map(d=>({dies:d.dies,hardBinCounts:d.statsSummary?.stats.hardBinCounts,softBinCounts:d.statsSummary?.stats.softBinCounts})),O=()=>gt(C,v),B={title:"Hard bin pareto",data:O(),selfControl:{current:v,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:d=>(v=d,{data:O(),title:`${v==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:d=>l(d.binCode),ownerDocument:p},Z=ge(B,T);return Z.card.style.minHeight="360px",r.appendChild(Z.card),{card:r,destroy:()=>{n?.destroy(),Z.destroy()}}}function J(){const e=p.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...ft(),display:"flex",flexDirection:"column",gap:w.lg,minWidth:"0"}),e}function Ie(e){if(W.innerHTML="",W.style.marginBottom="0",!Te)return;const t=wt(e.map(a=>({metadata:a.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",W.appendChild(t),W.style.marginBottom=w.xs)}function Oe(e,t,a,s){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const r=e.map(l=>l.wafer),i=e.map(l=>l.dies),u=e.flatMap(l=>l.dies),f=e.map(l=>l.statsSummary),g=f.every(l=>l!==void 0)?f:void 0,c=ve?.()??4,m=[],h=[],y=pt({groups:(s??[{key:"",items:e}]).map(l=>({key:l.key,items:l.items.map(C=>({dies:C.dies,testSpecYield:C.statsSummary?.stats.testSpecYield,functionalYield:C.statsSummary?.stats.functionalYield}))})),testDefs:a,onSaveImage:T,ownerDocument:p});y.card.style.minHeight="300px",m.push(y.card),h.push(y.destroy);const S=ye(i,r,c,l=>e[l].passBins,xt);if(S.length){const l=we({title:"Ring yield",mode:"ring",rows:S,onSaveImage:T,ownerDocument:p});m.push(l.card),h.push(l.destroy)}const o=ye(i,r,c,l=>e[l].passBins,St);if(o.length){const l=we({title:"Quadrant yield",mode:"quadrant",rows:o,onSaveImage:T,ownerDocument:p});m.push(l.card),h.push(l.destroy)}let n=null;if(t.length){const l=J();let C=null;const O=Ct(ht(u,t,void 0,g,U,i.map(B=>({dies:B})),void 0,"full"),{onDone:B=>{C=B,l.removeAttribute("aria-busy"),B?l.replaceChildren(B):l.remove()}});if(O.done)C&&(n=l);else{const B=p.createElement("div");B.textContent="Computing test statistics\u2026",Object.assign(B.style,{color:k.label,fontSize:A.body}),l.setAttribute("aria-busy","true"),l.appendChild(B),n=l,h.push(()=>O.cancel())}}let v=null;if(a.length){const l=yt(u,a,g,U);if(l){const C=J();C.appendChild(l),v=C}}return{elements:m,testValuesCard:n,functionalCard:v,destroy:()=>{for(const l of h)l()}}}function Ae(e){const t=pe(e.map(c=>c.passBins)),a=e.length===1,s=e[0],r=J();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:w.md});const i=c=>{const m=c.lastElementChild;return m&&Object.assign(m.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),c};function u(c,m,h){const b=p.createElement("div");Object.assign(b.style,{display:"flex",alignItems:"baseline",gap:w.sm,paddingRight:w.xxl,marginRight:w.md,borderRight:`1px solid ${k.menuBorder}`});const y=p.createElement("div");y.textContent=c,Object.assign(y.style,{fontSize:A.heading,fontWeight:"700",color:k.value,lineHeight:fe.tight});const S=p.createElement("div");if(S.textContent=m,Object.assign(S.style,{fontSize:A.body,color:k.label}),b.append(y,S),h){const o=p.createElement("div");o.textContent=h,Object.assign(o.style,{fontSize:A.meta,color:k.label,opacity:Je.muted}),b.appendChild(o)}return b}if(a){const c=z([{...s,key:s.waferIndex}],M)[0]?.percent;return c!==void 0&&r.appendChild(u(`${c.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),r.appendChild(u(String(s.dies.length),"Total dies")),i(r)}r.appendChild(u(String(e.length),"Wafers"));const f=z(e.map(c=>({...c,key:c.waferIndex})),M).map(c=>c.percent).filter(c=>Number.isFinite(c));if(f.length){const c=f.reduce((b,y)=>b+y,0)/f.length,m=he([{key:"all",items:e.map(b=>({...b,key:b.waferIndex}))}],M)[0]?.percent,h=m!==void 0&&m.toFixed(1)!==c.toFixed(1);r.appendChild(u(`${c.toFixed(1)}%`,`Mean per-wafer yield \xB7 pass: ${t}`,h?`${m.toFixed(1)}% by dies`:void 0))}const g=e.map(c=>c.statsSummary);if(g.every(c=>c!==void 0)){const c=g.reduce((h,b)=>h+b.stats.analyzedDies,0),m=g.reduce((h,b)=>h+b.stats.excludedDies,0);r.appendChild(u(c.toLocaleString(),"Dies analysed",m?`${m.toLocaleString()} excluded`:void 0))}else r.appendChild(u(e.reduce((c,m)=>c+m.dies.length,0).toLocaleString(),"Total dies"));return i(r)}function Re(e,t,a,s,r){const i=p.createElement("div");Object.assign(i.style,{display:"flex",flexDirection:"column",gap:w.lg});const u=e.length===1&&!s;e.length&&!s&&i.appendChild(Ae(e));const f=Pe(e,s,r,!u);i.appendChild(f.card);const g=Oe(e,t,a,s);for(const c of g.elements)f.card.appendChild(c);return g.testValuesCard&&i.appendChild(g.testValuesCard),g.functionalCard&&i.appendChild(g.functionalCard),{card:i,destroy:()=>{f.destroy(),g.destroy()}}}function We(e,t,a,s){const r=_(p),i=Be(e),u=o=>n=>re(n,o()),f=o=>({...o,testStats:o.statsSummary?.stats.perTestStats}),g=e.map(f),c=a?.map(o=>({key:o.key,items:o.items.map(f)})),m=at({title:"Test value distribution",axisPrefs:H,onAxisPrefsChange:u(()=>m),onTestChange:o=>y(o),selectedTestNumber:E??void 0,items:g,testDefs:t,groups:c,groupLabelText:s,onSaveImage:T,onGroupChange:o=>Q(o),onOpen:i?.open,openActionLabel:i?.label,onWaferContextMenu:X(e),ownerDocument:p}),h=it({title:"Value histogram",axisPrefs:H,onAxisPrefsChange:u(()=>h),onTestChange:o=>y(o),selectedTestNumber:E??void 0,items:e,testDefs:t,groups:a,onGroupChange:o=>Q(o),onSaveImage:T,ownerDocument:p}),b=ot({title:"Wafer-to-wafer trend",axisPrefs:H,onAxisPrefsChange:u(()=>b),onTestChange:o=>y(o),selectedTestNumber:E??void 0,items:e.map(o=>({label:o.label,key:o.waferIndex,dies:o.dies,testStats:o.statsSummary?.stats.perTestStats})),testDefs:t,onSaveImage:T,centreLabel:Xe(Ke(e.map(o=>o.wafer.metadata)),"mean"),onOpen:F?(o,n)=>K(o,`Wafer ${e.find(v=>v.waferIndex===o)?.label??o}`,n):void 0,onWaferContextMenu:X(e),ownerDocument:p}),y=o=>{E=o,m.setTest(o),h.setTest(o),b.setTest(o),S.setTest(o)};q.set("distribution",[m,h,b]);const S=nt({title:"Process capability",items:e,testDefs:t,onSaveImage:T,selectedTestNumber:E??void 0,onSelectTest:o=>y(o),ownerDocument:p});return r.append(S.card,m.card,h.card,b.card),{card:r,destroy:()=>{S.destroy(),m.destroy(),h.destroy(),b.destroy()}}}function je(e){const t=_(p),a=de(e).defs,s=e.flatMap(f=>f.dies),r=x.sweeps??[],i=r.filter(f=>!rt(f,a));i.length>0&&t.appendChild(Me(i,r.length));const u=r.map(f=>st({spec:f,dies:s,testDefs:a,onSaveImage:T,ownerDocument:p}));for(const f of u)t.appendChild(f.card);return{card:t,destroy:()=>{for(const f of u)f.destroy()}}}function Me(e,t){const a=p.createElement("div");a.setAttribute("role","status"),Object.assign(a.style,{gridColumn:"1 / -1",display:"flex",flexWrap:"wrap",alignItems:"center",gap:w.md,padding:`${w.sm} ${w.md}`,background:k.warnBg,border:`1px solid ${k.warnBorder}`,borderRadius:me.container,color:k.warnText,fontSize:A.body,lineHeight:fe.base});const s=e.length,r=p.createElement("span");if(r.style.flex="1 1 24ch",r.textContent=(s===t?s===1?"This sweep names no test in this data":"None of these sweeps names a test in this data":`${s} of ${t} sweeps name no test in this data: ${e.map(i=>i.title).join(", ")}`)+` \u2014 ${s===1?"it":"they"} may belong to another test program.`,a.appendChild(r),x.onRemoveSweeps){const i=p.createElement("button");i.type="button",Object.assign(i.style,be("outlined")),i.textContent=s===1?"Remove this sweep":`Remove these ${s} sweeps`,ne(i);const u=e.map(f=>f.id);i.addEventListener("click",()=>x.onRemoveSweeps(u)),a.appendChild(i)}return a}function Ye(e,t,a){const s=_(p),r=dt({title:"Test scatter",items:e,testDefs:t,groups:a,binColors:ae().hard,onSaveImage:T,axisPrefs:H,onAxisPrefsChange:u=>re(u,r),ownerDocument:p});q.set("correlation",[r]);const i=lt({title:"Test correlation matrix",items:e,testDefs:t,onSaveImage:T,onSaveText:U,onSelectPair:(u,f)=>r.setXY(u,f),ownerDocument:p});return s.append(i.card,r.card),{card:s,destroy:()=>{i.destroy(),r.destroy()}}}function V(e){e?.keepSections||ie(),G.innerHTML="",$.innerHTML="",x.backTab&&$.appendChild(Le(x.backTab));for(const n of De)$.appendChild(Ee(n.key,n.label));const t=$e();Ie(t);const a=He(t,{facetableOnly:!0}).filter(n=>n.splittable),s=p.createElement("div");Object.assign(s.style,{display:"flex",gap:w.md,alignItems:"center",marginLeft:w.xxl,paddingBottom:w.xs});let r;a.length>0?(s.appendChild(mt("Group by:",[{value:"",label:"None"},...a.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],j??"",n=>{j=n||void 0,L=null,V()},{hook:"group-by",ownerDocument:p})),r=a.find(n=>n.key===j)?.label):j=void 0;let i;if(j){const n=new Map,v=[];for(const l of t){const C=Fe(l.metadata,j)??Ne;n.has(C)||(n.set(C,[]),v.push(C)),n.get(C).push(l)}i=v.map(l=>({key:l,items:n.get(l)}))}L!==null&&!i?.some(n=>n.key===L)&&(L=null),i&&i.length>0&&s.appendChild(bt(i.map(n=>n.key),"Show:",L,n=>Q(n),{ownerDocument:p}).el);let u=null;if(x.onOpenGuide){const n=p.createElement("button");n.type="button",n.innerHTML=tt.help,n.setAttribute("aria-label","User guide"),Object.assign(n.style,{...be("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),ne(n,"bare"),et(n,"User guide"),n.addEventListener("click",()=>x.onOpenGuide()),Object.assign(n.style,{marginLeft:"auto"}),u=n}$.appendChild(s),u&&$.appendChild(u);const f=L!==null?i?.find(n=>n.key===L):void 0,g=f?.items??t,c=f?void 0:i,m=de(g),h=m.defs,b=h.filter(Ve);E!==null&&!b.some(n=>n.testNumber===E)&&(E=null);const y=e?.keepSections?N.get(P):void 0,S=y??(P==="overview"?Re(g,b,h,c,r):P==="distributions"?We(g,b,c,r):P==="sweeps"?je(g):Ye(g,b,c));y||N.set(P,S);const o=m.conflicts.filter(n=>n.excluded);if(o.length>0){const n=p.createElement("div");Object.assign(n.style,{color:k.label,fontSize:A.body,padding:`0 ${Y} ${w.sm}`});const v=o.slice(0,6).map(O=>O.testNumber).join(", "),l=o.length>6?`, and ${o.length-6} more`:"",C=o.length===1?"it":"them";n.textContent=`${o.length} test ${o.length===1?"number is":"numbers are"} hidden (${v}${l}) \u2014 the wafers in view describe ${C} differently, so their values cannot be pooled. `+(i&&i.length>0?`Pick one ${r??"group"} under "Show:" to analyse ${C}.`:"Load one test program at a time to analyse them."),n.dataset.wmapWithheldTests=String(o.length),G.appendChild(n)}G.appendChild(S.card)}return{el:R,render:V,destroy:()=>{ie(),R.remove()}}}
|
|
1
|
+
import{MEAN_WAFER_YIELD_LABEL as He}from"../stats/presentation.js";import{buildFacetTable as Fe,facetValueOf as Ne,FACET_NONE_VALUE as _e}from"../stats/facets.js";import{mergeTestDefs as de}from"../stats/mergeTestDefs.js";import{isParametricTest as Ve}from"../renderer/buildWaferMap.js";import{waferDisplayLabel as ze,waferIdentityLabel as Ue}from"../core/waferLabel.js";import{hasDrilldownTargets as ce,waferPopulation as qe}from"./chartPopulation.js";import{openDrilldownMenu as Qe}from"./drilldown.js";import{INPUT_DEFAULT_PASS_BINS as M,itemPassBins as Ke,passBinsLabel as pe}from"../core/passBins.js";import{NO_DATA_FILL as ue}from"../renderer/colorMap.js";import{describeWaferPopulation as Xe,populationStat as Je}from"../stats/population.js";import{LEADING as fe,ALPHA as Ze,SPACE as w,EDGE_GUTTER as te,FONT as O,CLR as k,RADIUS as me,SHADOW as et,controlStyle as be,wireControlHover as ne,wireTooltip as tt}from"./toolbar.js";import{ICONS as nt}from"./icons.js";import{renderCapabilityPanel as ot}from"./charts/capability.js";import{renderBoxplotPanel as at}from"./charts/boxplot.js";import{renderTrendPanel as st}from"./charts/trend.js";import{renderSweepPanel as rt}from"./charts/sweep.js";import{sweepAppliesTo as it}from"../stats/sweep.js";import{renderHistogramPanel as lt}from"./charts/histogram.js";import{renderCorrelationPanel as dt}from"./charts/correlation.js";import{renderScatterPanel as ct}from"./charts/scatter.js";import{renderBarPanel as ge}from"./charts/barPanel.js";import{renderBinClusterPanel as pt}from"./charts/binCluster.js";import{renderTestPassRatePanel as ut}from"./charts/testPassRate.js";import{QUANTITY as ft}from"./charts/palette.js";import{cardFrameStyle as mt,makeChartGridWrap as V,makeLabeledSelect as bt,makeLinkedGroupSelect as gt}from"./charts/chartShell.js";import{buildYieldData as z,buildYieldDataCombined as he}from"../stats/yield.js";import{buildBinParetoData as ht}from"../stats/binPareto.js";import{buildLotTestSectionSteps as yt,buildLotFunctionalSection as wt,buildMetadataStripBox as Ct}from"./summaryPanel.js";import{runChunked as xt}from"./chunked.js";import{buildRegionYieldData as ye,buildRingRegions as St,buildQuadrantRegions as vt}from"../stats/regions.js";import{renderRegionYieldDiagram as we}from"./charts/regionYieldDiagram.js";const Ce=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}],Tt={key:"sweeps",label:"Sweeps"};export function createInsightsTab(x){const{getItems:xe,getLotStats:Se,getBinColors:oe,getRingCount:ve,onSaveImage:T,onSaveText:U,openWafer:F,focusTest:ae}=x,Te=x.showMetadataStrip??!0,Y=x.contentInset??te,p=x.ownerDocument??document,R=p.createElement("div");Object.assign(R.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const ke=te,W=p.createElement("div");Object.assign(W.style,{paddingLeft:ke});const $=p.createElement("div");Object.assign($.style,{display:"flex",gap:w.xs,alignItems:"center",background:k.menuBg,border:`1px solid ${k.menuBorder}`,borderRadius:me.container,boxShadow:et.panel,padding:`0 ${w.lg}`,marginBottom:w.lg,marginLeft:Y,marginRight:Y}),$.setAttribute("role","tablist"),$.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from($.querySelectorAll('[role="tab"]')),o=t.indexOf(p.activeElement);if(o===-1)return;e.preventDefault();const s=t[(o+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];s.focus(),s.click()});const G=p.createElement("div");Object.assign(G.style,{display:"flex",flexDirection:"column",gap:w.lg,marginTop:"0",paddingLeft:Y,paddingRight:Y}),R.appendChild(W),R.appendChild($),R.appendChild(G);const se=(x.sweeps?.length??0)>0,De=se?[...Ce,Tt]:Ce;let P=x.defaultView==="sweeps"&&!se?"overview":x.defaultView??"overview",j,E=null,L=null,H={includeLimits:void 0,clipOutliers:!1,limits:"both"};const q=new Map,re=(e,t)=>{H=e;for(const o of q.values())for(const s of o)s!==t&&s.setAxisPrefs(e)},Q=e=>{L!==e&&(L=e,_())};let N=new Map;function ie(){for(const e of N.values())e.destroy();N=new Map}function K(e,t,o){F?.(e,t,o)}function X(e){if(e.some(t=>ce(t.testDefs,x.sweeps)))return(t,o,s)=>{const r=e.find(u=>u.waferIndex===t);if(!r||!ce(r.testDefs,x.sweeps))return;s.preventDefault();const i=qe(r.dies,{waferLabel:r.identity,testDefs:r.testDefs,activeTest:o??E??void 0,waferIndex:t});Qe({x:s.clientX,y:s.clientY},s.target,i,{sweeps:x.sweeps,onSaveImage:T})}}function Be(e){return F?{open:(t,o)=>K(t,`Wafer ${e.find(s=>s.waferIndex===t)?.label??t}`,o)}:ae&&e.length===1?{open:(t,o)=>ae(o),label:"show this test on the map"}:null}function $e(){return xe().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:ze(e,t),identity:Ue(e),waferIndex:t,passBins:Ke(e),statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function le(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${k.iconActive}`:"2px solid transparent",color:t?k.iconActive:k.label,fontWeight:t?"700":"500",fontSize:O.body,padding:`${w.sm} ${te} ${w.md}`,cursor:"pointer",marginBottom:"-1px"})}function Ee(e,t){const o=p.createElement("button");o.type="button",o.textContent=t;const s=e===P;return le(o,s),o.dataset.on=s?"true":"false",ne(o,"bare"),o.dataset.wmapInsightsTab=e,o.setAttribute("role","tab"),o.setAttribute("aria-selected",s?"true":"false"),o.tabIndex=s?0:-1,o.addEventListener("click",()=>{P!==e&&(P=e,_({keepSections:!0}))}),o}function Le(e){const t=p.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,le(t,!1),t.dataset.wmapInsightsBack="1",t.addEventListener("click",e.onBack),t}function Pe(e,t,o,s=!0){const r=V(p),i=oe();let u="label";const f=o??"group",g=pe(e.map(d=>d.passBins)),c=Se?.(),m=new Map(c?.lotYieldSeries.map(d=>[d.waferIndex,d.yieldPercent])),h=d=>({...d,label:d.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:m.get(d.waferIndex),key:d.waferIndex}),b=e.map(h),y=t?.map(d=>({key:d.key,items:d.items.map(h)})),S=()=>y?he(y,M,u):z(b,M,u),a={title:t?`Yield by ${f} (pass: ${g})`:`Yield by wafer (pass: ${g})`,data:S(),selfControl:{current:u,options:[["yield","Sort: yield"],["label",t?`Sort: ${f}`:"Sort: wafer ID"]],onChange:d=>(u=d,{data:S()})},barColor:()=>ft,valueLabel:d=>`${d.percent.toFixed(1)}%`,reference:d=>{if(d.length<3)return null;const D=d.map(ee=>ee.percent).sort((ee,Ge)=>ee-Ge),A=D.length%2?D[(D.length-1)/2]:(D[D.length/2-1]+D[D.length/2])/2;return{value:A,label:`median ${A.toFixed(1)}%`}},drill:y?{onOpenGroup:d=>{const D=y.find(A=>A.key===d.label)?.items??[];return{data:z(D,M,u),title:`Yield by wafer \u2014 ${f}: ${d.label} (pass: ${g})`}},onBack:()=>({data:S(),title:`Yield by ${f} (pass: ${g})`}),groupLabelText:f}:void 0,onOpen:F?d=>{typeof d.key=="number"&&K(d.key,`Wafer ${d.label}`)}:void 0,onWaferContextMenu:X(e),ownerDocument:p};let n=null;if(s&&(n=ge(a,T),n.card.style.minHeight="360px",r.appendChild(n.card)),t){const d=pt({title:"Hard bin pareto",groups:t.map(D=>({key:D.key,items:D.items.map(A=>({dies:A.dies,hardBinCounts:A.statsSummary?.stats.hardBinCounts,softBinCounts:A.statsSummary?.stats.softBinCounts}))})),onSaveImage:T,ownerDocument:p});return d.card.style.minHeight="360px",r.appendChild(d.card),{card:r,destroy:()=>{n?.destroy(),d.destroy()}}}let v="hbin";const l=d=>d===void 0?ue:(v==="hbin"?i.hard:i.soft).get(d)??ue,C=e.map(d=>({dies:d.dies,hardBinCounts:d.statsSummary?.stats.hardBinCounts,softBinCounts:d.statsSummary?.stats.softBinCounts})),I=()=>ht(C,v),B={title:"Hard bin pareto",data:I(),selfControl:{current:v,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:d=>(v=d,{data:I(),title:`${v==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:d=>l(d.binCode),ownerDocument:p},Z=ge(B,T);return Z.card.style.minHeight="360px",r.appendChild(Z.card),{card:r,destroy:()=>{n?.destroy(),Z.destroy()}}}function J(){const e=p.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...mt(),display:"flex",flexDirection:"column",gap:w.lg,minWidth:"0"}),e}function Ae(e){if(W.innerHTML="",W.style.marginBottom="0",!Te)return;const t=Ct(e.map(o=>({metadata:o.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",W.appendChild(t),W.style.marginBottom=w.xs)}function Ie(e,t,o,s){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const r=e.map(l=>l.wafer),i=e.map(l=>l.dies),u=e.flatMap(l=>l.dies),f=e.map(l=>l.statsSummary),g=f.every(l=>l!==void 0)?f:void 0,c=ve?.()??4,m=[],h=[],y=ut({groups:(s??[{key:"",items:e}]).map(l=>({key:l.key,items:l.items.map(C=>({dies:C.dies,testSpecYield:C.statsSummary?.stats.testSpecYield,functionalYield:C.statsSummary?.stats.functionalYield}))})),testDefs:o,onSaveImage:T,ownerDocument:p});y.card.style.minHeight="300px",m.push(y.card),h.push(y.destroy);const S=ye(i,r,c,l=>e[l].passBins,St);if(S.length){const l=we({title:"Ring yield",mode:"ring",rows:S,onSaveImage:T,ownerDocument:p});m.push(l.card),h.push(l.destroy)}const a=ye(i,r,c,l=>e[l].passBins,vt);if(a.length){const l=we({title:"Quadrant yield",mode:"quadrant",rows:a,onSaveImage:T,ownerDocument:p});m.push(l.card),h.push(l.destroy)}let n=null;if(t.length){const l=J();let C=null;const I=xt(yt(u,t,void 0,g,U,i.map(B=>({dies:B})),void 0,"full"),{onDone:B=>{C=B,l.removeAttribute("aria-busy"),B?l.replaceChildren(B):l.remove()}});if(I.done)C&&(n=l);else{const B=p.createElement("div");B.textContent="Computing test statistics\u2026",Object.assign(B.style,{color:k.label,fontSize:O.body}),l.setAttribute("aria-busy","true"),l.appendChild(B),n=l,h.push(()=>I.cancel())}}let v=null;if(o.length){const l=wt(u,o,g,U);if(l){const C=J();C.appendChild(l),v=C}}return{elements:m,testValuesCard:n,functionalCard:v,destroy:()=>{for(const l of h)l()}}}function Oe(e){const t=pe(e.map(c=>c.passBins)),o=e.length===1,s=e[0],r=J();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:w.md});const i=c=>{const m=c.lastElementChild;return m&&Object.assign(m.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),c};function u(c,m,h){const b=p.createElement("div");Object.assign(b.style,{display:"flex",alignItems:"baseline",gap:w.sm,paddingRight:w.xxl,marginRight:w.md,borderRight:`1px solid ${k.menuBorder}`});const y=p.createElement("div");y.textContent=c,Object.assign(y.style,{fontSize:O.heading,fontWeight:"700",color:k.value,lineHeight:fe.tight});const S=p.createElement("div");if(S.textContent=m,Object.assign(S.style,{fontSize:O.body,color:k.label}),b.append(y,S),h){const a=p.createElement("div");a.textContent=h,Object.assign(a.style,{fontSize:O.meta,color:k.label,opacity:Ze.muted}),b.appendChild(a)}return b}if(o){const c=z([{...s,key:s.waferIndex}],M)[0]?.percent;return c!==void 0&&r.appendChild(u(`${c.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),r.appendChild(u(String(s.dies.length),"Total dies")),i(r)}r.appendChild(u(String(e.length),"Wafers"));const f=z(e.map(c=>({...c,key:c.waferIndex})),M).map(c=>c.percent).filter(c=>Number.isFinite(c));if(f.length){const c=f.reduce((b,y)=>b+y,0)/f.length,m=he([{key:"all",items:e.map(b=>({...b,key:b.waferIndex}))}],M)[0]?.percent,h=m!==void 0&&m.toFixed(1)!==c.toFixed(1);r.appendChild(u(`${c.toFixed(1)}%`,`${He} \xB7 pass: ${t}`,h?`${m.toFixed(1)}% by dies`:void 0))}const g=e.map(c=>c.statsSummary);if(g.every(c=>c!==void 0)){const c=g.reduce((h,b)=>h+b.stats.analyzedDies,0),m=g.reduce((h,b)=>h+b.stats.excludedDies,0);r.appendChild(u(c.toLocaleString(),"Dies analysed",m?`${m.toLocaleString()} excluded`:void 0))}else r.appendChild(u(e.reduce((c,m)=>c+m.dies.length,0).toLocaleString(),"Total dies"));return i(r)}function Re(e,t,o,s,r){const i=p.createElement("div");Object.assign(i.style,{display:"flex",flexDirection:"column",gap:w.lg});const u=e.length===1&&!s;e.length&&!s&&i.appendChild(Oe(e));const f=Pe(e,s,r,!u);i.appendChild(f.card);const g=Ie(e,t,o,s);for(const c of g.elements)f.card.appendChild(c);return g.testValuesCard&&i.appendChild(g.testValuesCard),g.functionalCard&&i.appendChild(g.functionalCard),{card:i,destroy:()=>{f.destroy(),g.destroy()}}}function We(e,t,o,s){const r=V(p),i=Be(e),u=a=>n=>re(n,a()),f=a=>({...a,testStats:a.statsSummary?.stats.perTestStats}),g=e.map(f),c=o?.map(a=>({key:a.key,items:a.items.map(f)})),m=at({title:"Test value distribution",axisPrefs:H,onAxisPrefsChange:u(()=>m),onTestChange:a=>y(a),selectedTestNumber:E??void 0,items:g,testDefs:t,groups:c,groupLabelText:s,onSaveImage:T,onGroupChange:a=>Q(a),onOpen:i?.open,openActionLabel:i?.label,onWaferContextMenu:X(e),ownerDocument:p}),h=lt({title:"Value histogram",axisPrefs:H,onAxisPrefsChange:u(()=>h),onTestChange:a=>y(a),selectedTestNumber:E??void 0,items:e,testDefs:t,groups:o,onGroupChange:a=>Q(a),onSaveImage:T,ownerDocument:p}),b=st({title:"Wafer-to-wafer trend",axisPrefs:H,onAxisPrefsChange:u(()=>b),onTestChange:a=>y(a),selectedTestNumber:E??void 0,items:e.map(a=>({label:a.label,key:a.waferIndex,dies:a.dies,testStats:a.statsSummary?.stats.perTestStats})),testDefs:t,onSaveImage:T,centreLabel:Je(Xe(e.map(a=>a.wafer.metadata)),"mean"),onOpen:F?(a,n)=>K(a,`Wafer ${e.find(v=>v.waferIndex===a)?.label??a}`,n):void 0,onWaferContextMenu:X(e),ownerDocument:p}),y=a=>{E=a,m.setTest(a),h.setTest(a),b.setTest(a),S.setTest(a)};q.set("distribution",[m,h,b]);const S=ot({title:"Process capability",items:e,testDefs:t,onSaveImage:T,selectedTestNumber:E??void 0,onSelectTest:a=>y(a),ownerDocument:p});return r.append(S.card,m.card,h.card,b.card),{card:r,destroy:()=>{S.destroy(),m.destroy(),h.destroy(),b.destroy()}}}function je(e){const t=V(p),o=de(e).defs,s=e.flatMap(f=>f.dies),r=x.sweeps??[],i=r.filter(f=>!it(f,o));i.length>0&&t.appendChild(Me(i,r.length));const u=r.map(f=>rt({spec:f,dies:s,testDefs:o,onSaveImage:T,ownerDocument:p}));for(const f of u)t.appendChild(f.card);return{card:t,destroy:()=>{for(const f of u)f.destroy()}}}function Me(e,t){const o=p.createElement("div");o.setAttribute("role","status"),Object.assign(o.style,{gridColumn:"1 / -1",display:"flex",flexWrap:"wrap",alignItems:"center",gap:w.md,padding:`${w.sm} ${w.md}`,background:k.warnBg,border:`1px solid ${k.warnBorder}`,borderRadius:me.container,color:k.warnText,fontSize:O.body,lineHeight:fe.base});const s=e.length,r=p.createElement("span");if(r.style.flex="1 1 24ch",r.textContent=(s===t?s===1?"This sweep names no test in this data":"None of these sweeps names a test in this data":`${s} of ${t} sweeps name no test in this data: ${e.map(i=>i.title).join(", ")}`)+` \u2014 ${s===1?"it":"they"} may belong to another test program.`,o.appendChild(r),x.onRemoveSweeps){const i=p.createElement("button");i.type="button",Object.assign(i.style,be("outlined")),i.textContent=s===1?"Remove this sweep":`Remove these ${s} sweeps`,ne(i);const u=e.map(f=>f.id);i.addEventListener("click",()=>x.onRemoveSweeps(u)),o.appendChild(i)}return o}function Ye(e,t,o){const s=V(p),r=ct({title:"Test scatter",items:e,testDefs:t,groups:o,binColors:oe().hard,onSaveImage:T,axisPrefs:H,onAxisPrefsChange:u=>re(u,r),ownerDocument:p});q.set("correlation",[r]);const i=dt({title:"Test correlation matrix",items:e,testDefs:t,onSaveImage:T,onSaveText:U,onSelectPair:(u,f)=>r.setXY(u,f),ownerDocument:p});return s.append(i.card,r.card),{card:s,destroy:()=>{i.destroy(),r.destroy()}}}function _(e){e?.keepSections||ie(),G.innerHTML="",$.innerHTML="",x.backTab&&$.appendChild(Le(x.backTab));for(const n of De)$.appendChild(Ee(n.key,n.label));const t=$e();Ae(t);const o=Fe(t,{facetableOnly:!0}).filter(n=>n.splittable),s=p.createElement("div");Object.assign(s.style,{display:"flex",gap:w.md,alignItems:"center",marginLeft:w.xxl,paddingBottom:w.xs});let r;o.length>0?(s.appendChild(bt("Group by:",[{value:"",label:"None"},...o.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],j??"",n=>{j=n||void 0,L=null,_()},{hook:"group-by",ownerDocument:p})),r=o.find(n=>n.key===j)?.label):j=void 0;let i;if(j){const n=new Map,v=[];for(const l of t){const C=Ne(l.metadata,j)??_e;n.has(C)||(n.set(C,[]),v.push(C)),n.get(C).push(l)}i=v.map(l=>({key:l,items:n.get(l)}))}L!==null&&!i?.some(n=>n.key===L)&&(L=null),i&&i.length>0&&s.appendChild(gt(i.map(n=>n.key),"Show:",L,n=>Q(n),{ownerDocument:p}).el);let u=null;if(x.onOpenGuide){const n=p.createElement("button");n.type="button",n.innerHTML=nt.help,n.setAttribute("aria-label","User guide"),Object.assign(n.style,{...be("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),ne(n,"bare"),tt(n,"User guide"),n.addEventListener("click",()=>x.onOpenGuide()),Object.assign(n.style,{marginLeft:"auto"}),u=n}$.appendChild(s),u&&$.appendChild(u);const f=L!==null?i?.find(n=>n.key===L):void 0,g=f?.items??t,c=f?void 0:i,m=de(g),h=m.defs,b=h.filter(Ve);E!==null&&!b.some(n=>n.testNumber===E)&&(E=null);const y=e?.keepSections?N.get(P):void 0,S=y??(P==="overview"?Re(g,b,h,c,r):P==="distributions"?We(g,b,c,r):P==="sweeps"?je(g):Ye(g,b,c));y||N.set(P,S);const a=m.conflicts.filter(n=>n.excluded);if(a.length>0){const n=p.createElement("div");Object.assign(n.style,{color:k.label,fontSize:O.body,padding:`0 ${Y} ${w.sm}`});const v=a.slice(0,6).map(I=>I.testNumber).join(", "),l=a.length>6?`, and ${a.length-6} more`:"",C=a.length===1?"it":"them";n.textContent=`${a.length} test ${a.length===1?"number is":"numbers are"} hidden (${v}${l}) \u2014 the wafers in view describe ${C} differently, so their values cannot be pooled. `+(i&&i.length>0?`Pick one ${r??"group"} under "Show:" to analyse ${C}.`:"Load one test program at a time to analyse them."),n.dataset.wmapWithheldTests=String(a.length),G.appendChild(n)}G.appendChild(S.card)}return{el:R,render:_,destroy:()=>{ie(),R.remove()}}}
|