@wafertools/wafermap 0.31.0 → 0.32.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 +12 -12
- package/CHANGELOG.md +172 -0
- package/README.md +3 -3
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/drilldown.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 +17 -4
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
- package/dist/packages/canvas-adapter/toCanvas.js +1 -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 +25 -15
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/dieTable.d.ts +137 -0
- package/dist/packages/core/dieTable.js +1 -0
- package/dist/packages/core/dies.d.ts +22 -0
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/utils.d.ts +14 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +3 -3
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +29 -11
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/columnarInput.d.ts +87 -0
- package/dist/packages/renderer/columnarInput.js +1 -0
- package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/index.d.ts +1 -0
- package/dist/packages/renderer/spec.d.ts +3 -7
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +134 -2
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/boxplot.js +1 -1
- package/dist/packages/stats/capability.d.ts +7 -7
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.js +1 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/filterFindings.d.ts +5 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/math.d.ts +22 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/normalizeInput.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +3 -3
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/regions.d.ts +7 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.js +18 -18
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +5 -0
- package/dist/packages/worker/index.js +1 -1
- package/dist/packages/worker/wafermap.worker.d.ts +3 -0
- package/dist/packages/worker/wafermap.worker.js +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -45,12 +45,11 @@ worth reporting, not a pattern to build on.
|
|
|
45
45
|
pre-multiply. The geometry inputs are `waferConfig` (type `WaferConfig`) and
|
|
46
46
|
`dieConfig` (type `DieConfig`) — both optional, both inferred when omitted.
|
|
47
47
|
- **Bins and test values must be numbers, and verdicts booleans.** Every parser —
|
|
48
|
-
CSV, JSON, a spreadsheet export — hands you `"1"`, and `"1"` is not pass bin 1
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
warnings.
|
|
48
|
+
CSV, JSON, a spreadsheet export — hands you `"1"`, and `"1"` is not pass bin 1.
|
|
49
|
+
`buildWaferMap` never converts them: each is treated as missing and reported as
|
|
50
|
+
`input-values-not-numbers` (severity `'error'`), so those dies have no bin or no value
|
|
51
|
+
and a build that "works" can be mostly empty — read the warnings. Convert with
|
|
52
|
+
`Number()` at parse time.
|
|
54
53
|
- **`passBins` and `ringCount` are set once, on `buildWaferMap`, and travel on the
|
|
55
54
|
result.** Neither is an option on `analyzeWaferMap`, `analyzeWaferLot`,
|
|
56
55
|
`renderWaferMap` or `renderWaferGallery` — passing one there is a type error in
|
|
@@ -137,9 +136,9 @@ worth reporting, not a pattern to build on.
|
|
|
137
136
|
but `'traffic'` and `'jet'` reads low = dark, high = light; if you draw your own
|
|
138
137
|
colorbar or swatch, resolve the colour through `resolveValueColorFn(name, reversed)`
|
|
139
138
|
so it cannot disagree with the dies.
|
|
140
|
-
-
|
|
141
|
-
`result.
|
|
142
|
-
|
|
139
|
+
- What a map was built with is on the result: `result.plotMode`, `result.metadata`,
|
|
140
|
+
`result.isLotStack`, `result.hbinDefs`, `result.sbinDefs`, `result.testDefs`. The renderers
|
|
141
|
+
read them; pass nothing again.
|
|
143
142
|
- Die keys come from `getDieKey(die)`. A hand-rolled `` `${x},${y}` `` breaks
|
|
144
143
|
click-to-highlight silently, because findings carry `dieKeys` in that exact format.
|
|
145
144
|
- `stats.warnings` is `WaferWarning[]` (it was `string[]` before 0.22.0). Read
|
|
@@ -180,9 +179,10 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
180
179
|
`computePerTestStats` is modest; `enableTestValueAnalysis` is the expensive one
|
|
181
180
|
(roughly 10× the base analysis on a large wafer) and exists to find spatial
|
|
182
181
|
patterns automatically — do not enable it by default just because it sounds good.
|
|
183
|
-
- **A Web Worker buys responsiveness, not speed.** `createWafermapWorker`
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
- **A Web Worker buys responsiveness, not speed.** `createWafermapWorker` moves a
|
|
183
|
+
result's test values to the page without copying them, but still copies the dies and the
|
|
184
|
+
input, so total time goes *up*. Use it when a build would otherwise visibly freeze the
|
|
185
|
+
page, not for small datasets.
|
|
186
186
|
- **Capability, pass rates, region yield and the spatial-pattern label come back from
|
|
187
187
|
the analysis** — `stats.capability` (with `computePerTestStats`), `stats.testSpecYield`,
|
|
188
188
|
`stats.testFlagYield`, `stats.functionalYield`, `stats.regionYield` and
|
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,178 @@ under `### Breaking`.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## [0.32.0] — 2026-09-27
|
|
26
|
+
|
|
27
|
+
### Breaking
|
|
28
|
+
|
|
29
|
+
- **Input values of the wrong type are treated as missing.** A bin, site number or test value
|
|
30
|
+
that is not a number, or a pass/fail verdict that is not `true`/`false`, is left out of the
|
|
31
|
+
die — the same rule as a value outside the STDF V4 ranges — and still reported as
|
|
32
|
+
`input-values-not-numbers`. A die whose only bin was text therefore has no verdict rather
|
|
33
|
+
than a fail, which can change `yield` for such input. The input objects are not modified.
|
|
34
|
+
- **Test values on dies from `buildWaferMap` are read-only snapshots.** A map holds test values and
|
|
35
|
+
verdicts as one column per test. `die.testValues` and `die.testPass` on a built die build a frozen
|
|
36
|
+
object from those columns on each read, keeping nothing on the die, so `die.testValues !==
|
|
37
|
+
die.testValues`. Assigning to either field throws a `TypeError`; changing a key of the frozen
|
|
38
|
+
object throws in strict-mode code and is ignored otherwise. Pass the values to `buildWaferMap`,
|
|
39
|
+
or copy the die with your own object (`{ ...die, testValues: mine }`). A spread, `structuredClone` or `JSON.stringify` of a built die
|
|
40
|
+
gives plain objects with the same values. A die no longer shares the input record's
|
|
41
|
+
`testValues`/`testPass` objects, so changing the input after the build does not change the map.
|
|
42
|
+
Dies a host builds itself keep ordinary objects.
|
|
43
|
+
- **Pre-built `dies` get the same input checks as `results`.** Wrong-type and out-of-range
|
|
44
|
+
values are treated as missing, and a die whose coordinates STDF cannot store is kept as an
|
|
45
|
+
unpositioned die.
|
|
46
|
+
- **`WaferMapResult.view` is removed.** It was marked `@internal`: the renderers build their own
|
|
47
|
+
draw list whenever they draw, so a result no longer carries one. Read the result's own fields
|
|
48
|
+
(`plotMode`, `metadata`, `isLotStack`, `hbinDefs`, `sbinDefs`, `testDefs`). This makes each
|
|
49
|
+
result smaller (about 86 bytes less per die) and roughly halves the copy the Web Worker makes of
|
|
50
|
+
a result. The internal `dataAxisFlip` field takes its place.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **The legend filters to several bins or metadata values at once.** Ctrl/Cmd+click on a legend
|
|
55
|
+
entry — the map's legend or the gallery's strip, where Ctrl/Cmd+Enter/Space works from the
|
|
56
|
+
keyboard — adds or removes a value; a plain click shows only that value, or clears the
|
|
57
|
+
filter when it is the only one shown. `highlightBin` accepts `number | number[]` and
|
|
58
|
+
`highlightMetadataValue` accepts `string | string[]`.
|
|
59
|
+
- **A finding and the legend filter agree.** Clicking a finding about a bin filters the legend
|
|
60
|
+
to that bin, and one about yield or a test clears the filter — in a single map and in the
|
|
61
|
+
gallery alike. Changing the legend filter releases the finding, as changing the selection
|
|
62
|
+
does, including a selection made on a gallery card while a lot finding is active.
|
|
63
|
+
- **Lot regional findings are tested on all wafers' data together.** A yield, bin,
|
|
64
|
+
functional pass-rate, limit-fail or test-value difference in a region is combined across
|
|
65
|
+
every wafer (Stouffer's Z over each wafer's own test, weighted by die count) and reported
|
|
66
|
+
with the wafer analysis's gates, redundancy collapse and opposite-region re-test, instead of
|
|
67
|
+
by counting wafers whose own analysis reported it — a pattern present on every wafer but
|
|
68
|
+
too faint on some to pass alone is the lot's pattern. The sentence gives the lot's figure
|
|
69
|
+
and "higher/lower on N/M wafers, all wafers' data combined", N counting the wafers whose
|
|
70
|
+
region differs in that direction; `stats.method` is `'stouffer-z'`. Clusters, edge arcs and
|
|
71
|
+
spatial-pattern labels are still counted by the wafers that report them. Lot findings keep
|
|
72
|
+
absorbed restatements in the list, marked by `absorbedIds`, as wafer findings do.
|
|
73
|
+
- **Selected dies are shown by fading the rest of the map.** Every unselected die is faded
|
|
74
|
+
towards the map background and the selection is outlined, so selected dies keep their full
|
|
75
|
+
colour and it is clear which dies are selected whatever the selection's shape — a block, a
|
|
76
|
+
ring, an edge arc. A finding highlighted from the Summary panel, a gallery lot finding and
|
|
77
|
+
`setSelection` are drawn the same way. The fade and outline sit under the axes and legend.
|
|
78
|
+
- **A map opens in select mode.** A drag draws a selection box; hold Space and drag, use the
|
|
79
|
+
arrow keys, or choose Pan in the toolbar to pan.
|
|
80
|
+
- **Clicking the only selected die again clears the selection.** Clicking a die inside a
|
|
81
|
+
larger selection still selects just that die.
|
|
82
|
+
- **Changing the selection on the map releases an active finding.** A click, box select,
|
|
83
|
+
right-click, Esc or `clearSelection` clears the Summary panel's active finding and its bin
|
|
84
|
+
highlight, so the panel never shows a finding the map no longer highlights.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- **Regional findings report patterns confined to one region.** A bin, limit-fail or
|
|
89
|
+
functional-fail rate that is zero in the rest of the wafer and raised in a region (a bin
|
|
90
|
+
found only at the edge) counts as the largest relative change for the effect gate and for
|
|
91
|
+
severity, for every region family: rings, quadrants, sectors, reticle positions and test
|
|
92
|
+
sites. On a lot whose wafers carry bin 2 only at the edge, the lot finding reads "seen on
|
|
93
|
+
8/8 wafers".
|
|
94
|
+
- **Yield and functional pass-rate findings are judged by their failure rate.** The relative
|
|
95
|
+
effect of a pass rate is measured on its failures: yield 98% → 94% is failures 2% → 6%, and
|
|
96
|
+
reaches the same verdict as the bin rate of the same dies.
|
|
97
|
+
- **A region is not reported as deviating only because another region deviates more.** A
|
|
98
|
+
finding opposite in direction to a stronger finding for the same variable and region family
|
|
99
|
+
must still hold when compared with the rest of the wafer without that region, so an edge
|
|
100
|
+
rich in a bin, a limit fail or high test values no longer makes the inner rings read as low
|
|
101
|
+
in it (or high in yield).
|
|
102
|
+
- **The soft bin that restates yield is absorbed into the yield finding**, as the hard pass bin
|
|
103
|
+
is: the one soft bin every passing die carries and no failing die does.
|
|
104
|
+
- **An edge ring made of scattered fails is classified as an edge ring.** The spatial-pattern
|
|
105
|
+
classifier recognises an edge ring from the failing dies' positions — most fails at the rim
|
|
106
|
+
and spread round at least 60% of it — when no connected group of fails is large enough to
|
|
107
|
+
judge shape by, and before the scratch rule, so a short run along the rim is not a scratch.
|
|
108
|
+
Against WM-811K: detection 86.4%, edge-ring recall 75% (precision 92%), scratch recall 24%.
|
|
109
|
+
- **Lot spatial-pattern rows count related labels together and state the lot's figures.**
|
|
110
|
+
Edge-ring and edge-local wafers count as one edge pattern, and centre and donut as one,
|
|
111
|
+
naming each label's wafer count ("Spatial pattern: edge (edge-ring on 5, edge-local on 2) —
|
|
112
|
+
seen on 7/13 wafers"); the sentence gives the lot's count, not any one wafer's confidence or figures.
|
|
113
|
+
The gallery highlights each counted wafer's failing dies.
|
|
114
|
+
- Findings, their severities and "seen on N wafers" counts can change.
|
|
115
|
+
|
|
116
|
+
### Performance
|
|
117
|
+
|
|
118
|
+
- **Insights no longer blocks while its test statistics are computed.** The Overview's Test Values
|
|
119
|
+
table is built in slices, showing "Computing test statistics…" until it is ready, and when the
|
|
120
|
+
gallery's summary panel is computing the same statistics at the time, the two share the one
|
|
121
|
+
calculation instead of each doing it.
|
|
122
|
+
- **Gallery cards are drawn only when on screen.** A card below the fold keeps its map up to date
|
|
123
|
+
and is drawn as it scrolls into view. Printing and the gallery PNG draw every card first, so
|
|
124
|
+
both still show the whole gallery. On a 25-wafer, 266k-die lot, with 4 cards on screen:
|
|
125
|
+
switching to value mode takes 0.4 s in Chrome (was 1.1 s) and 0.5 s in WebKit (was 2.0 s);
|
|
126
|
+
the gallery opens in 1.8 s and 2.0 s.
|
|
127
|
+
- **The gallery draws about twice as fast in WebKit** (the desktop app on Linux and macOS, and
|
|
128
|
+
Safari). Die fills and outlines are drawn as many small canvas paths rather than one per
|
|
129
|
+
colour, which WebKit rasterises far faster. On a 25-wafer, 266k-die lot the gallery opens in
|
|
130
|
+
4.0 s (was 7.0 s) and a switch to value or stacked mode takes 1.8–2.3 s (was 3.8–4.4 s).
|
|
131
|
+
Chrome draws identical pixels; in WebKit only anti-aliased edge pixels differ.
|
|
132
|
+
- **The lot Summary report opens about five times faster on a large lot.** Its Test Values table
|
|
133
|
+
finds each test's minimum, mean and maximum in one pass instead of sorting every die's value.
|
|
134
|
+
On a 25-wafer, 266k-die lot the "Summary report" button takes 0.4 s in Chrome (was 6.0 s) and
|
|
135
|
+
0.9 s from click to report in WebKit (was 4.7 s). The report is unchanged (compared as HTML).
|
|
136
|
+
Boxplot quartiles and the histogram's outlier range are found the same way as the Summary
|
|
137
|
+
panel's, by selection, with the same figures.
|
|
138
|
+
- **Test-value analysis (`enableTestValueAnalysis`) is two to three times faster on large lots.**
|
|
139
|
+
Spec-limit findings count each region's dies in one pass instead of re-reading every die for
|
|
140
|
+
every region, the test list is read column by column, and per-test statistics find their
|
|
141
|
+
quartiles by selection. Findings are unchanged (compared byte for byte on a 266k-die lot with
|
|
142
|
+
5,289 findings). On that lot: 18.9 s → 7.2 s in Chrome, 25.6 s → 10.7 s in WebKit.
|
|
143
|
+
- **Analysis is about a third faster in Chrome on large lots.** Merging findings in adjacent
|
|
144
|
+
regions, pairing hard and soft bins that cover the same dies, and assigning dies to regions
|
|
145
|
+
now compare die positions as numbers rather than as a text key per die. Findings are
|
|
146
|
+
unchanged (compared byte for byte on a 266k-die lot). On that lot, load-time analysis takes
|
|
147
|
+
2.5 s in Chrome (was 3.7 s).
|
|
148
|
+
- **Stacked modes and mode switches in the gallery are faster.** Stacked cards key die positions by
|
|
149
|
+
number rather than by a string per die, and a map no longer rebuilds a key for every die on
|
|
150
|
+
each redraw unless dies are selected. On a 25-wafer, 266k-die lot in Chrome: switching to
|
|
151
|
+
stacked bins 2.0 s → 1.6 s, to value mode 1.3 s → 1.1 s.
|
|
152
|
+
- **The lot summary panel's test statistics are computed about three times faster.** The
|
|
153
|
+
quartiles of each test's pooled values are found by selection instead of sorting every value
|
|
154
|
+
(the same order statistics, so the same figures). On a 25-wafer, 266k-die lot the gallery
|
|
155
|
+
with its panel opens in 3.1 s in Chrome (was 3.9 s) and about 3.5 s in WebKit (was 4.0 s).
|
|
156
|
+
- **Insights opens several times faster on a large lot** (25 wafers, 266k dies: the Overview
|
|
157
|
+
2.7 s → 0.65 s in Chrome, 0.9 s in WebKit). The Overview's Test Values table reuses the
|
|
158
|
+
pooled statistics the gallery's summary panel has already computed, whether or not the lot
|
|
159
|
+
has functional tests, and the pass-rate chart reads each die's values and recorded verdicts
|
|
160
|
+
in one pass per die.
|
|
161
|
+
Figures are unchanged.
|
|
162
|
+
- **`analyzeWaferMap` is about five times faster on large wafers** (a 25-wafer, 266k-die lot:
|
|
163
|
+
32 s → 6.5 s in Chrome). Region membership, die keys and cluster neighbour lookups are each
|
|
164
|
+
computed once per analysis, not once per finding builder. Findings are unchanged.
|
|
165
|
+
- **Faster building and analysis in WebKit** (the desktop app on Linux and macOS, and Safari).
|
|
166
|
+
Checks that asked whether a die holds any test data read the die's keys one at a time and stop
|
|
167
|
+
at the first; the coverage count checks bins before test data; input checking decides each
|
|
168
|
+
test number's legality once per wafer instead of once per die. On a 266k-die lot in WebKitGTK,
|
|
169
|
+
building fell from 2.5 s to 1.1 s and analysis from 14 s to 7 s. Results are unchanged.
|
|
170
|
+
- **A hidden Summary panel is rendered when it is opened**, not on every mount and option
|
|
171
|
+
change. Every gallery card has one, so a large gallery mounts faster (266k-die lot: 7.3 s →
|
|
172
|
+
5.0 s) and switches plot mode faster (3.4 s → 1.4 s).
|
|
173
|
+
- **Test values are held as one column per test**, not as an object on each die, so a map
|
|
174
|
+
holds a small fraction of the memory it did: on a 266k-die, 51-test lot in Chrome, about 330 MB
|
|
175
|
+
where it was 1.1 GB. A result from `createWafermapWorker` moves its columns to the page without
|
|
176
|
+
copying them.
|
|
177
|
+
- **Die outlines are drawn as merged lines**, one per run of shared edges instead of four sides
|
|
178
|
+
per die. Galleries draw 10–17% faster in WebKit (the desktop app on Linux), where stroking each
|
|
179
|
+
die was most of the drawing time. Every outline is now the same weight in every browser: Chrome
|
|
180
|
+
drew the edge two dies share slightly darker than the wafer's outer edge, WebKit did not.
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
|
|
184
|
+
- **`HighlightWaferTarget.dieKeysByWafer`** — a lot regional finding names its region's dies
|
|
185
|
+
on each counted wafer, keyed by wafer index; the gallery highlights the region on every
|
|
186
|
+
counted card from it.
|
|
187
|
+
|
|
188
|
+
- **`results` can be columns (`DieColumns`).** A host that already holds its results as columns
|
|
189
|
+
(a parser, Arrow, Parquet) passes one array per field and, for test values and verdicts, the
|
|
190
|
+
indices of the records that have one with their values. The map is built with no object per
|
|
191
|
+
record or die. Retests, derived tests, input checks and warnings behave exactly as for rows.
|
|
192
|
+
Missing entries are `NaN` or STDF's missing values (−32768 for coordinates, 65535 for bins and
|
|
193
|
+
sites). Mismatched lengths or out-of-range and repeated indices throw.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
25
197
|
## [0.31.0] — 2026-09-25
|
|
26
198
|
|
|
27
199
|
### Breaking
|
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 ~50 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
|
|
|
@@ -75,7 +75,7 @@ export. Add `analyzeWaferMap` for the findings and summary panel, swap in
|
|
|
75
75
|
|
|
76
76
|
The API reference is long because it documents every option, not because you need
|
|
77
77
|
them: [tsmap](https://github.com/wafertools/tsmap), a complete cross-platform desktop
|
|
78
|
-
application built on this library, imports **
|
|
78
|
+
application built on this library, imports **16** of its ~100 exports. Read the
|
|
79
79
|
[Quick Start](https://wafertools.github.io/wafermap/quickstart/) first and treat the
|
|
80
80
|
[API reference](https://wafertools.github.io/wafermap/api/) as something to search,
|
|
81
81
|
not to read.
|
|
@@ -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{fmt as ne,fmtColorbarAxis as oe}from"../../renderer/fmt.js";import{markedTestLabel as ie,DERIVED_LANE_PAD as re}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:le[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 d=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(d+a)}px)`)};s(),(e.ownerDocument.defaultView??window).requestAnimationFrame(s)}}const le={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 d=s[1].length===3?s[1].split("").map(c=>c+c).join(""):s[1];[t,i,r]=[0,2,4].map(c=>parseInt(d.slice(c,c+2),16)),d.length===8&&(l=parseInt(d.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 se={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||se[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=`${M(e,o)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function M(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-(M(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(M(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}`:ne(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}=oe(e,null,o,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:i}}const ae=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)+ae}}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 ce="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?ce:"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 u=t.querySelector("canvas");u&&saveCanvasPng(u,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 d=t.ownerDocument.createElement("div");Object.assign(d.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(d);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:d,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,d],c)=>{const u=t.createElement("label");Object.assign(u.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(u,"bare");const p=t.createElement("input");p.type="radio",p.name=l,p.value=a,p.checked=a===o,Object.assign(p.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{u.style.background=p.checked?f.bgActive:f.menuBg,u.style.color=p.checked?f.iconActive:f.text,u.dataset.on=p.checked?"true":"false"};s.push(w),w(),p.addEventListener("change",()=>{if(p.checked){for(const v of s)v();n(a)}}),u.append(p,t.createTextNode(d)),r.appendChild(u)}),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:d}=t;let c=o;const u=x=>e.find(O=>O.value===x)?.label??"",p=r.createElement("button");p.type="button",d&&(p.dataset.wmapSelect=d),l&&p.setAttribute("aria-label",l),Object.assign(p.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(p),R(p,!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",p.append(w,v);const E=()=>{w.textContent=e.length===0?s??"(none)":u(c)||s||"(none)"};if(E(),e.length===0)return p.disabled=!0,p.style.opacity="0.6",p.style.cursor="default",Object.defineProperty(p,"value",{get:()=>c,set:x=>{c=x,E()}});let b=null;const B=()=>{b&&(b.remove(),b=null,R(p,!1))},$=()=>{B(),p.focus()};function G(){const x=p.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(p).appendChild(b),R(p,!0),e.length<=W){const h=e.findIndex(y=>y.value===c);F()[h>=0?h:0]?.focus()}}p.addEventListener("click",x=>{if(x.stopPropagation(),b){B();return}G()});const H=x=>{if(!p.isConnected){r.removeEventListener("click",H);return}b&&!b.contains(x.target)&&!p.contains(x.target)&&B()};return r.addEventListener("click",H),Object.defineProperty(p,"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?re:"")+ie(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,d,c=!1){e.innerHTML="",c&&e.appendChild(makeLimitsSelect(l,u=>{l=u,s()},t)),d&&e.appendChild(makeToggle("Axis includes limits",a,u=>{i=u,s()},t)),e.appendChild(makeToggle("Clip outliers",r,u=>{r=u,s()},t))},set(a){const d=a.limits??"both";return a.includeLimits===i&&a.clipOutliers===r&&d===l?!1:(i=a.includeLimits,r=a.clipOutliers,l=d,!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 u=s(c);u!==r&&(r=u,t(u))},{...i,hook:"group-scope"}),d=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,d&&(d.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,d)=>({value:String(d),label:a.label??`#${d}`}))],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 d=makeListSelect(o,n,t,{maxWidth:r,ownerDocument:s,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return a.appendChild(d),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 d=n-i.left+r,c=t-i.top+r;d+s+l>i.width&&(d=n-i.left-r-s),c+a+l>i.height&&(c=t-i.top-r-a),d=Math.max(l,d),c=Math.max(l,c),e.style.left=`${d}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(s=>Number.isFinite(s)));if(n.sort(),n.length<8)return null;const t=s=>{const a=(n.length-1)*s,d=Math.floor(a),c=Math.ceil(a);return d===c?n[d]:n[d]+(n[c]-n[d])*(a-d)},i=t(.25),r=t(.75),l=r-i;return l===0?null:{lo:i-o*l,hi:r+o*l}}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,d=0;if(r&&l?.length){const u=robustFence(l);if(u){const p=Math.max(s,u.lo),w=Math.min(a,u.hi);w>p&&(d=l.filter(v=>Number.isFinite(v)&&(v<p||v>w)).length,s=p,a=w)}}if(i)for(const u of t)s=Math.min(s,u.value),a=Math.max(a,u.value);s===a&&(s-=1,a+=1);const c=[];for(const u of t)u.value<s?c.push({value:u.value,label:u.label,side:"lo"}):u.value>a&&c.push({value:u.value,label:u.label,side:"hi"});return{lo:s,hi:a,offAxis:c,clippedCount:d}}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:d,label:c,side:u}of o){const p=s[u]++*a,w=t==="horizontal"?u==="lo"?"\u2190":"\u2192":u==="lo"?"\u2193":"\u2191",v=u==="lo"?`${w} ${c} ${r(d)}`:`${c} ${r(d)} ${w}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=u==="lo"?"left":"right",fillTextOnHalo(e,v,u==="lo"?n.left+2:n.right-2,n.top+2+p,l)):(e.textBaseline=u==="lo"?"bottom":"top",e.textAlign="left",fillTextOnHalo(e,v,n.left+4,u==="lo"?n.bottom-2-p:n.top+2+p,l))}e.restore()}
|
|
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,13 +1,13 @@
|
|
|
1
|
-
import{hasPosition as
|
|
1
|
+
import{hasPosition as F,isPositionedDie as V}from"../core/dies.js";import{testsPresent as G,testValue as H}from"../core/dieTable.js";import{classifyDie as Y}from"../core/classify.js";import{isParametricTest as _,getTestPassStatus as S}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as U}from"../stats/metadataColumns.js";import{LEADING as N,TRACKING as X,wireControlHover as Q,controlStyle as q,SPACE as J,RADIUS as P,FONT as g,CLR as d,saveTextFile as Z}from"./toolbar.js";import{csvField as k}from"./summaryPanel.js";import{fmt as j}from"../renderer/fmt.js";import{testLabel as E,markedTestLabel as M,derivedFields as ee,derivedKeyText as te,derivedTestSource as ne,isDerivedTest as ie,DERIVED_KEY as le}from"../renderer/testLabel.js";const L=(t,a)=>a?`${t} (${a})`:t,ae=5e4,z=new WeakSet;function re(t){if(z.has(t))return;z.add(t);const a=t.createElement("style");a.textContent=`
|
|
2
2
|
.wmap-dielist-table { width: 100%; border-collapse: collapse; font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
3
3
|
.wmap-dielist-th {
|
|
4
4
|
text-align: left; padding: 4px 8px; position: sticky; top: 0;
|
|
5
|
-
background: ${
|
|
6
|
-
border-bottom: 1px solid ${
|
|
5
|
+
background: ${d.panelBg}; color: ${d.label}; font-weight: 600;
|
|
6
|
+
border-bottom: 1px solid ${d.menuBorder}; white-space: nowrap;
|
|
7
7
|
}
|
|
8
8
|
.wmap-dielist-td {
|
|
9
|
-
padding: 4px 8px; color: ${
|
|
10
|
-
border-bottom: 1px solid ${
|
|
9
|
+
padding: 4px 8px; color: ${d.text};
|
|
10
|
+
border-bottom: 1px solid ${d.menuBorder}; white-space: nowrap;
|
|
11
11
|
}
|
|
12
|
-
`,t.head.appendChild(
|
|
13
|
-
`),"die-list.csv","text/csv",n.onSaveText)}),
|
|
12
|
+
`,t.head.appendChild(a)}function s(t,a,n,i){const r=t.createElement(a);return n&&Object.assign(r.style,n),i!==void 0&&(r.textContent=i),r}function oe(t){return F(t)?String(t.x):""}function se(t){return F(t)?String(t.y):""}function de(t,a){return a!==void 0?a.filter(n=>n.testNumber!==void 0):G(t,"values").map(n=>({testNumber:n,name:E(void 0,n)}))}function ce(t,a,n){if(!a)return;const i=new Map;for(const r of t){if(!V(r))continue;const u=a(r);u&&i.set(r,Y(r,u,{ringCount:n}))}return i.size>0?i:void 0}export function buildDieListSection(t,a,n={}){if(!t.length)return null;const i=n.ownerDocument??document;re(i);const r=de(t,a),u=n.maxRows??ae,C=Math.max(0,t.length-u),K=C>0?t.slice(0,u):t,m=s(i,"div",{display:"flex",flexDirection:"column",gap:J.md,flex:"1",minHeight:"0",minWidth:"0"}),v=s(i,"div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});v.appendChild(s(i,"div",{fontSize:g.meta,fontWeight:"700",letterSpacing:X,textTransform:"uppercase",color:d.label},n.title??`Die list (${t.length})`));const h=s(i,"button",{fontSize:g.body,padding:"3px 8px",borderRadius:P.control,...q("outlined"),background:d.menuBg},"Export CSV");h.type="button",Q(h),v.appendChild(h),m.appendChild(v),n.note&&m.appendChild(s(i,"div",{fontSize:g.body,color:d.label,lineHeight:N.base,flexShrink:"0"},n.note));const w=ce(t,n.getWafer,n.ringCount??4),B=t.some(e=>e.edgeExcluded===!0),O=[...n.extraColumn?[n.extraColumn.label]:[],"X","Y","Ring","Quadrant","Edge excluded","Site","Hard bin","Soft bin",...r.map(e=>L(E(e,e.testNumber),e.unit))],{columns:W,truncatedKeys:x}=U({dies:t,waferMetadata:n.waferMetadata,metadataFields:n.metadataFields,dieKeys:n.metadataColumns,waferKeys:n.waferMetadataColumns==="none"?"none":void 0,waferPlacement:n.waferMetadataColumns??"csv",reservedLabels:O}),y=[...n.extraColumn?[{label:n.extraColumn.label,get:e=>n.extraColumn.get(e)??""}]:[],{label:"X",get:oe},{label:"Y",get:se},...w?[{label:"Ring",get:e=>{const l=w.get(e);return l?String(l.ring):""}},{label:"Quadrant",get:e=>w.get(e)?.quadrant??""}]:[],...B?[{label:"Edge excluded",get:e=>e.edgeExcluded?"Yes":"No"}]:[],{label:"Site",get:e=>e.siteNum!==void 0?String(e.siteNum):""},{label:"Hard bin",get:e=>e.hbin!==void 0?String(e.hbin):""},{label:"Soft bin",get:e=>e.sbin!==void 0?String(e.sbin):""},...r.map(e=>{const l=!_(e);return{label:L(M(e,e.testNumber),e.unit),...ie(e)?{csvLabel:`${L(E(e,e.testNumber),e.unit)} [derived from ${ne(e)??le}]`}:{},get:o=>{if(l){const p=S(o,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const c=H(o,e.testNumber);if(c!==void 0)return j(c,e.unit);const b=S(o,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"},csvGet:o=>{if(l){const p=S(o,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const c=H(o,e.testNumber);if(c!==void 0)return j(c,void 0,"engineering");const b=S(o,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"}}}),...W.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],$=y.filter(e=>!e.csvOnly),A=te(r.map(e=>({label:M(e,e.testNumber),...ee(e)})));A&&m.appendChild(s(i,"div",{fontSize:g.body,color:d.label,lineHeight:N.base,flexShrink:"0"},A));const D=s(i,"div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${d.menuBorder}`,borderRadius:P.control,...n.maxHeight?{maxHeight:n.maxHeight}:{}}),f=i.createElement("table");f.className="wmap-dielist-table",f.setAttribute("aria-label",n.title??`Die list (${t.length} dies)`);const R=s(i,"thead"),T=s(i,"tr");for(const e of $){const l=i.createElement("th");l.className="wmap-dielist-th",l.scope="col",l.textContent=e.label,T.appendChild(l)}R.appendChild(T),f.appendChild(R);const I=s(i,"tbody");for(const e of K){const l=s(i,"tr");for(const o of $){const c=i.createElement("td");c.className="wmap-dielist-td",c.textContent=o.get(e),l.appendChild(c)}I.appendChild(l)}if(f.appendChild(I),D.appendChild(f),m.appendChild(D),C>0||x.length>0){const e=[];C>0&&e.push(`Showing the first ${u.toLocaleString()} of ${t.length.toLocaleString()} dies. The CSV export contains all ${t.length.toLocaleString()}.`),x.length>0&&e.push(`${x.length} further metadata field${x.length===1?"":"s"} not shown.`),m.appendChild(s(i,"div",{fontSize:g.body,color:d.label,lineHeight:N.base,flexShrink:"0"},e.join(" ")))}return h.addEventListener("click",()=>{const e=[y.map(l=>k(l.csvLabel??l.label)).join(",")];for(const l of t)e.push(y.map(o=>k((o.csvGet??o.get)(l))).join(","));Z(e.join(`
|
|
13
|
+
`),"die-list.csv","text/csv",n.onSaveText)}),m}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as b}from"../core/dies.js";import{isParametricTest as y}from"../renderer/buildWaferMap.js";import{buildSweepData as
|
|
1
|
+
import{isYieldEligibleDie as b}from"../core/dies.js";import{testValue as T}from"../core/dieTable.js";import{isParametricTest as y}from"../renderer/buildWaferMap.js";import{buildSweepData as C}from"../stats/sweep.js";import{renderSweepPanel as E}from"./charts/sweep.js";import{renderHistogramPanel as $}from"./charts/histogram.js";import{renderCapabilityPanel as I}from"./charts/capability.js";import{openChartExpandModal as M,populationPhrase as w}from"./charts/chartShell.js";import{buildCheckMenuEl as x,createToolbarHelpers as N,getTooltip as R,menuLayerFor as L,wireMenuA11y as V,CLR as k,FONT as F,SPACE as O}from"./toolbar.js";const P=30,v=e=>e.items.flatMap(t=>t.dies);function A(e){return e.items.map(t=>({...t,dies:t.dies.filter(a=>b(a))}))}function H(e,t,a){return(e.testDefs??[]).filter(y).map(s=>s.testNumber).filter(s=>{let o=0;for(const i of t){const n=T(i,s);if(n!==void 0&&Number.isFinite(n)&&++o>=a)return!0}return!1})}function S(e,t,a,s,o){const n=e.ownerDocument.createElement("div");n.textContent=a,n.dataset.wmapPopulation="1",Object.assign(n.style,{color:k.label,fontSize:F.body,marginBottom:O.sm});const r=e.querySelector("[data-wmap-chart-expand]")?.parentElement;r?.parentElement===e?r.after(n):e.prepend(n),M(e,t,{anchor:s,onClosed:o})}function W(e,t,a,s){const o=v(t);let i=t.notMeasuredReason??null;return!i&&!o.some(n=>b(n))&&(i="These dies are all partial or edge-excluded, which charts leave out"),i||C(o,t.testDefs,e).series.some(r=>r.points.some(d=>d.count>0))||(i="These dies have no values for this sweep\u2019s tests"),{section:"Sweep",label:e.title,unavailable:i,open:()=>{const n=E({spec:e,dies:o,population:t.population,testDefs:t.testDefs,onSaveImage:a.onSaveImage,ownerDocument:s.ownerDocument}),r=o.filter(d=>b(d)).length;M(n.card,`${e.title} \u2014 ${w(r,o.length,t.population)}`,{anchor:s,onClosed:()=>n.destroy()})}}}function B(e,t,a){const s=A(e),o=v(e).length,i=s.flatMap(l=>l.dies),n=w(i.length,o,e.population),r=(e.testDefs??[]).filter(y),d=a.ownerDocument,f=(l,c)=>e.notMeasuredReason?e.notMeasuredReason:i.length===0?"These dies are all partial or edge-excluded, which charts leave out":H(e,i,l).length>0?null:c,u={section:"Distributions",label:"Value histogram",unavailable:f(1,"These dies have no parametric test values"),open:()=>{const l=$({title:"Value histogram",items:s,testDefs:r,selectedTestNumber:e.activeTest,onSaveImage:t.onSaveImage,ownerDocument:d});S(l.card,`Value histogram \u2014 ${n}`,`Population: ${n}`,a,()=>l.destroy())}},h={section:"Distributions",label:"Process capability",unavailable:f(2,"Needs at least 2 of these dies with values for one test"),open:()=>{const l=I({title:"Process capability",items:s,testDefs:r,selectedTestNumber:e.activeTest,onSaveImage:t.onSaveImage,ownerDocument:d}),c=i.length<P?` \xB7 fewer than ${P} dies, so each Ppk is a rough estimate`:"";S(l.card,`Process capability \u2014 ${n}`,`Population: ${n}${c}`,a,()=>l.destroy())}};return[u,h]}function _(e,t,a){return[...B(e,t,a),...(t.sweeps??[]).map(s=>W(s,e,t,a))]}const g=new WeakMap;export function openDrilldownMenu(e,t,a,s){const o=t.ownerDocument,i=o.defaultView??window;g.get(o)?.();const n=_(a,s,t),r=[];let d;for(const p of n)p.section!==d&&(r.push({section:p.section}),d=p.section),r.push({label:p.label,active:!1,action:!0,enabled:p.unavailable===null,disabledHint:p.unavailable??void 0,onClick:()=>{m(),p.open()}});const{makeMenuSection:f}=N(R(o)),u=x(new DOMRect(e.x,e.y-4,0,0),r,{makeMenuSection:f},i),h=v(a).length;u.setAttribute("role","menu"),u.setAttribute("aria-label",`Open a chart of ${w(h,h,a.population)}`),u.dataset.wmapDrilldownMenu="1",L(t).appendChild(u);const l=u.getBoundingClientRect();l.bottom>i.innerHeight-4&&(u.style.top=`${Math.max(4,e.y-l.height)}px`),l.right>i.innerWidth-4&&(u.style.left=`${Math.max(4,i.innerWidth-l.width-4)}px`);let c=!1;function m(){c||(c=!0,u.remove(),o.removeEventListener("pointerdown",D,!0),i.removeEventListener("blur",m),g.get(o)===m&&g.delete(o))}function D(p){u.contains(p.target)||m()}return o.addEventListener("pointerdown",D,!0),i.addEventListener("blur",m),V(u,t,m),g.set(o,m),m}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildFacetTable as He,facetValueOf as Fe,FACET_NONE_VALUE as Ne}from"../stats/facets.js";import{mergeTestDefs as le}from"../stats/mergeTestDefs.js";import{isParametricTest as Ve}from"../renderer/buildWaferMap.js";import{waferDisplayLabel as _e,waferIdentityLabel as Ue}from"../core/waferLabel.js";import{hasDrilldownTargets as de,waferPopulation as ze}from"./chartPopulation.js";import{openDrilldownMenu as qe}from"./drilldown.js";import{INPUT_DEFAULT_PASS_BINS as R,itemPassBins as Qe,passBinsLabel as ce}from"../core/passBins.js";import{NO_DATA_FILL as pe}from"../renderer/colorMap.js";import{describeWaferPopulation as Ke,populationStat as Xe}from"../stats/population.js";import{LEADING as ue,ALPHA as Je,SPACE as C,EDGE_GUTTER as ee,FONT as W,CLR as k,RADIUS as fe,SHADOW as Ze,controlStyle as me,wireControlHover as te,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 be}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 V,makeLabeledSelect as mt,makeLinkedGroupSelect as bt}from"./charts/chartShell.js";import{buildYieldData as _,buildYieldDataCombined as ge}from"../stats/yield.js";import{buildBinParetoData as gt}from"../stats/binPareto.js";import{buildLotTestSection as ht,buildLotFunctionalSection as yt,buildMetadataStripBox as wt}from"./summaryPanel.js";import{buildRegionYieldData as he,buildRingRegions as Ct,buildQuadrantRegions as xt}from"../stats/regions.js";import{renderRegionYieldDiagram as ye}from"./charts/regionYieldDiagram.js";const we=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}],St={key:"sweeps",label:"Sweeps"};export function createInsightsTab(x){const{getItems:Ce,getLotStats:xe,getBinColors:ne,getRingCount:Se,onSaveImage:T,onSaveText:U,openWafer:G,focusTest:ae}=x,ve=x.showMetadataStrip??!0,M=x.contentInset??ee,p=x.ownerDocument??document,I=p.createElement("div");Object.assign(I.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const Te=ee,O=p.createElement("div");Object.assign(O.style,{paddingLeft:Te});const B=p.createElement("div");Object.assign(B.style,{display:"flex",gap:C.xs,alignItems:"center",background:k.menuBg,border:`1px solid ${k.menuBorder}`,borderRadius:fe.container,boxShadow:Ze.panel,padding:`0 ${C.lg}`,marginBottom:C.lg,marginLeft:M,marginRight:M}),B.setAttribute("role","tablist"),B.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from(B.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 j=p.createElement("div");Object.assign(j.style,{display:"flex",flexDirection:"column",gap:C.lg,marginTop:"0",paddingLeft:M,paddingRight:M}),I.appendChild(O),I.appendChild(B),I.appendChild(j);const oe=(x.sweeps?.length??0)>0,ke=oe?[...we,St]:we;let L=x.defaultView==="sweeps"&&!oe?"overview":x.defaultView??"overview",A,$=null,E=null,Y={includeLimits:void 0,clipOutliers:!1,limits:"both"};const z=new Map,se=(e,t)=>{Y=e;for(const a of z.values())for(const s of a)s!==t&&s.setAxisPrefs(e)},q=e=>{E!==e&&(E=e,F())};let H=new Map;function re(){for(const e of H.values())e.destroy();H=new Map}function Q(e,t,a){G?.(e,t,a)}function K(e){if(e.some(t=>de(t.testDefs,x.sweeps)))return(t,a,s)=>{const r=e.find(u=>u.waferIndex===t);if(!r||!de(r.testDefs,x.sweeps))return;s.preventDefault();const i=ze(r.dies,{waferLabel:r.identity,testDefs:r.testDefs,activeTest:a??$??void 0,waferIndex:t});qe({x:s.clientX,y:s.clientY},s.target,i,{sweeps:x.sweeps,onSaveImage:T})}}function De(e){return G?{open:(t,a)=>Q(t,`Wafer ${e.find(s=>s.waferIndex===t)?.label??t}`,a)}:ae&&e.length===1?{open:(t,a)=>ae(a),label:"show this test on the map"}:null}function Be(){return Ce().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:_e(e,t),identity:Ue(e),waferIndex:t,passBins:Qe(e),statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function ie(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:W.body,padding:`${C.sm} ${ee} ${C.md}`,cursor:"pointer",marginBottom:"-1px"})}function $e(e,t){const a=p.createElement("button");a.type="button",a.textContent=t;const s=e===L;return ie(a,s),a.dataset.on=s?"true":"false",te(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",()=>{L!==e&&(L=e,F({keepSections:!0}))}),a}function Ee(e){const t=p.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,ie(t,!1),t.dataset.wmapInsightsBack="1",t.addEventListener("click",e.onBack),t}function Le(e,t,a,s=!0){const r=V(p),i=ne();let u="label";const f=a??"group",g=ce(e.map(l=>l.passBins)),d=xe?.(),m=new Map(d?.lotYieldSeries.map(l=>[l.waferIndex,l.yieldPercent])),h=l=>({...l,label:l.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:m.get(l.waferIndex),key:l.waferIndex}),b=e.map(h),y=t?.map(l=>({key:l.key,items:l.items.map(h)})),S=()=>y?ge(y,R,u):_(b,R,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:l=>(u=l,{data:S()})},barColor:()=>ut,valueLabel:l=>`${l.percent.toFixed(1)}%`,reference:l=>{if(l.length<3)return null;const D=l.map(Z=>Z.percent).sort((Z,Ge)=>Z-Ge),P=D.length%2?D[(D.length-1)/2]:(D[D.length/2-1]+D[D.length/2])/2;return{value:P,label:`median ${P.toFixed(1)}%`}},drill:y?{onOpenGroup:l=>{const D=y.find(P=>P.key===l.label)?.items??[];return{data:_(D,R,u),title:`Yield by wafer \u2014 ${f}: ${l.label} (pass: ${g})`}},onBack:()=>({data:S(),title:`Yield by ${f} (pass: ${g})`}),groupLabelText:f}:void 0,onOpen:G?l=>{typeof l.key=="number"&&Q(l.key,`Wafer ${l.label}`)}:void 0,onWaferContextMenu:K(e),ownerDocument:p};let n=null;if(s&&(n=be(o,T),n.card.style.minHeight="360px",r.appendChild(n.card)),t){const l=ct({title:"Hard bin pareto",groups:t.map(D=>({key:D.key,items:D.items.map(P=>({dies:P.dies,hardBinCounts:P.statsSummary?.stats.hardBinCounts,softBinCounts:P.statsSummary?.stats.softBinCounts}))})),onSaveImage:T,ownerDocument:p});return l.card.style.minHeight="360px",r.appendChild(l.card),{card:r,destroy:()=>{n?.destroy(),l.destroy()}}}let v="hbin";const c=l=>l===void 0?pe:(v==="hbin"?i.hard:i.soft).get(l)??pe,w=e.map(l=>({dies:l.dies,hardBinCounts:l.statsSummary?.stats.hardBinCounts,softBinCounts:l.statsSummary?.stats.softBinCounts})),N=()=>gt(w,v),Ye={title:"Hard bin pareto",data:N(),selfControl:{current:v,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:l=>(v=l,{data:N(),title:`${v==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:l=>c(l.binCode),ownerDocument:p},J=be(Ye,T);return J.card.style.minHeight="360px",r.appendChild(J.card),{card:r,destroy:()=>{n?.destroy(),J.destroy()}}}function X(){const e=p.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...ft(),display:"flex",flexDirection:"column",gap:C.lg,minWidth:"0"}),e}function Pe(e){if(O.innerHTML="",O.style.marginBottom="0",!ve)return;const t=wt(e.map(a=>({metadata:a.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",O.appendChild(t),O.style.marginBottom=C.xs)}function Ie(e,t,a,s){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const r=e.map(c=>c.wafer),i=e.map(c=>c.dies),u=e.flatMap(c=>c.dies),f=e.map(c=>c.statsSummary),g=f.every(c=>c!==void 0)?f:void 0,d=Se?.()??4,m=[],h=[],y=pt({groups:(s??[{key:"",items:e}]).map(c=>({key:c.key,items:c.items.map(w=>({dies:w.dies,testSpecYield:w.statsSummary?.stats.testSpecYield,functionalYield:w.statsSummary?.stats.functionalYield}))})),testDefs:a,onSaveImage:T,ownerDocument:p});y.card.style.minHeight="300px",m.push(y.card),h.push(y.destroy);const S=he(i,r,d,c=>e[c].passBins,Ct);if(S.length){const c=ye({title:"Ring yield",mode:"ring",rows:S,onSaveImage:T,ownerDocument:p});m.push(c.card),h.push(c.destroy)}const o=he(i,r,d,c=>e[c].passBins,xt);if(o.length){const c=ye({title:"Quadrant yield",mode:"quadrant",rows:o,onSaveImage:T,ownerDocument:p});m.push(c.card),h.push(c.destroy)}let n=null;if(t.length){const c=ht(u,t,void 0,g,U,i.map(w=>({dies:w})),void 0,"full");if(c){const w=X();w.appendChild(c),n=w}}let v=null;if(a.length){const c=yt(u,a,g,U);if(c){const w=X();w.appendChild(c),v=w}}return{elements:m,testValuesCard:n,functionalCard:v,destroy:()=>{for(const c of h)c()}}}function Oe(e){const t=ce(e.map(d=>d.passBins)),a=e.length===1,s=e[0],r=X();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:C.md});const i=d=>{const m=d.lastElementChild;return m&&Object.assign(m.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),d};function u(d,m,h){const b=p.createElement("div");Object.assign(b.style,{display:"flex",alignItems:"baseline",gap:C.sm,paddingRight:C.xxl,marginRight:C.md,borderRight:`1px solid ${k.menuBorder}`});const y=p.createElement("div");y.textContent=d,Object.assign(y.style,{fontSize:W.heading,fontWeight:"700",color:k.value,lineHeight:ue.tight});const S=p.createElement("div");if(S.textContent=m,Object.assign(S.style,{fontSize:W.body,color:k.label}),b.append(y,S),h){const o=p.createElement("div");o.textContent=h,Object.assign(o.style,{fontSize:W.meta,color:k.label,opacity:Je.muted}),b.appendChild(o)}return b}if(a){const d=_([{...s,key:s.waferIndex}],R)[0]?.percent;return d!==void 0&&r.appendChild(u(`${d.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=_(e.map(d=>({...d,key:d.waferIndex})),R).map(d=>d.percent).filter(d=>Number.isFinite(d));if(f.length){const d=f.reduce((b,y)=>b+y,0)/f.length,m=ge([{key:"all",items:e.map(b=>({...b,key:b.waferIndex}))}],R)[0]?.percent,h=m!==void 0&&m.toFixed(1)!==d.toFixed(1);r.appendChild(u(`${d.toFixed(1)}%`,`Mean per-wafer yield \xB7 pass: ${t}`,h?`${m.toFixed(1)}% by dies`:void 0))}const g=e.map(d=>d.statsSummary);if(g.every(d=>d!==void 0)){const d=g.reduce((h,b)=>h+b.stats.analyzedDies,0),m=g.reduce((h,b)=>h+b.stats.excludedDies,0);r.appendChild(u(d.toLocaleString(),"Dies analysed",m?`${m.toLocaleString()} excluded`:void 0))}else r.appendChild(u(e.reduce((d,m)=>d+m.dies.length,0).toLocaleString(),"Total dies"));return i(r)}function Ae(e,t,a,s,r){const i=p.createElement("div");Object.assign(i.style,{display:"flex",flexDirection:"column",gap:C.lg});const u=e.length===1&&!s;e.length&&!s&&i.appendChild(Oe(e));const f=Le(e,s,r,!u);i.appendChild(f.card);const g=Ie(e,t,a,s);for(const d of g.elements)f.card.appendChild(d);return g.testValuesCard&&i.appendChild(g.testValuesCard),g.functionalCard&&i.appendChild(g.functionalCard),{card:i,destroy:()=>{f.destroy(),g.destroy()}}}function Re(e,t,a,s){const r=V(p),i=De(e),u=o=>n=>se(n,o()),f=o=>({...o,testStats:o.statsSummary?.stats.perTestStats}),g=e.map(f),d=a?.map(o=>({key:o.key,items:o.items.map(f)})),m=at({title:"Test value distribution",axisPrefs:Y,onAxisPrefsChange:u(()=>m),onTestChange:o=>y(o),selectedTestNumber:$??void 0,items:g,testDefs:t,groups:d,groupLabelText:s,onSaveImage:T,onGroupChange:o=>q(o),onOpen:i?.open,openActionLabel:i?.label,onWaferContextMenu:K(e),ownerDocument:p}),h=it({title:"Value histogram",axisPrefs:Y,onAxisPrefsChange:u(()=>h),onTestChange:o=>y(o),selectedTestNumber:$??void 0,items:e,testDefs:t,groups:a,onGroupChange:o=>q(o),onSaveImage:T,ownerDocument:p}),b=ot({title:"Wafer-to-wafer trend",axisPrefs:Y,onAxisPrefsChange:u(()=>b),onTestChange:o=>y(o),selectedTestNumber:$??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:G?(o,n)=>Q(o,`Wafer ${e.find(v=>v.waferIndex===o)?.label??o}`,n):void 0,onWaferContextMenu:K(e),ownerDocument:p}),y=o=>{$=o,m.setTest(o),h.setTest(o),b.setTest(o),S.setTest(o)};z.set("distribution",[m,h,b]);const S=nt({title:"Process capability",items:e,testDefs:t,onSaveImage:T,selectedTestNumber:$??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 We(e){const t=V(p),a=le(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:C.md,padding:`${C.sm} ${C.md}`,background:k.warnBg,border:`1px solid ${k.warnBorder}`,borderRadius:fe.container,color:k.warnText,fontSize:W.body,lineHeight:ue.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,me("outlined")),i.textContent=s===1?"Remove this sweep":`Remove these ${s} sweeps`,te(i);const u=e.map(f=>f.id);i.addEventListener("click",()=>x.onRemoveSweeps(u)),a.appendChild(i)}return a}function je(e,t,a){const s=V(p),r=dt({title:"Test scatter",items:e,testDefs:t,groups:a,binColors:ne().hard,onSaveImage:T,axisPrefs:Y,onAxisPrefsChange:u=>se(u,r),ownerDocument:p});z.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 F(e){e?.keepSections||re(),j.innerHTML="",B.innerHTML="",x.backTab&&B.appendChild(Ee(x.backTab));for(const n of ke)B.appendChild($e(n.key,n.label));const t=Be();Pe(t);const a=He(t,{facetableOnly:!0}).filter(n=>n.splittable),s=p.createElement("div");Object.assign(s.style,{display:"flex",gap:C.md,alignItems:"center",marginLeft:C.xxl,paddingBottom:C.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})`}))],A??"",n=>{A=n||void 0,E=null,F()},{hook:"group-by",ownerDocument:p})),r=a.find(n=>n.key===A)?.label):A=void 0;let i;if(A){const n=new Map,v=[];for(const c of t){const w=Fe(c.metadata,A)??Ne;n.has(w)||(n.set(w,[]),v.push(w)),n.get(w).push(c)}i=v.map(c=>({key:c,items:n.get(c)}))}E!==null&&!i?.some(n=>n.key===E)&&(E=null),i&&i.length>0&&s.appendChild(bt(i.map(n=>n.key),"Show:",E,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,{...me("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),te(n,"bare"),et(n,"User guide"),n.addEventListener("click",()=>x.onOpenGuide()),Object.assign(n.style,{marginLeft:"auto"}),u=n}B.appendChild(s),u&&B.appendChild(u);const f=E!==null?i?.find(n=>n.key===E):void 0,g=f?.items??t,d=f?void 0:i,m=le(g),h=m.defs,b=h.filter(Ve);$!==null&&!b.some(n=>n.testNumber===$)&&($=null);const y=e?.keepSections?H.get(L):void 0,S=y??(L==="overview"?Ae(g,b,h,d,r):L==="distributions"?Re(g,b,d,r):L==="sweeps"?We(g):je(g,b,d));y||H.set(L,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:W.body,padding:`0 ${M} ${C.sm}`});const v=o.slice(0,6).map(N=>N.testNumber).join(", "),c=o.length>6?`, and ${o.length-6} more`:"",w=o.length===1?"it":"them";n.textContent=`${o.length} test ${o.length===1?"number is":"numbers are"} hidden (${v}${c}) \u2014 the wafers in view describe ${w} differently, so their values cannot be pooled. `+(i&&i.length>0?`Pick one ${r??"group"} under "Show:" to analyse ${w}.`:"Load one test program at a time to analyse them."),n.dataset.wmapWithheldTests=String(o.length),j.appendChild(n)}j.appendChild(S.card)}return{el:I,render:F,destroy:()=>{re(),I.remove()}}}
|
|
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()}}}
|