@wafertools/wafermap 0.29.0 → 0.30.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 +13 -14
- package/CHANGELOG.md +235 -0
- package/README.md +5 -5
- package/dist/packages/canvas-adapter/deprecated.d.ts +10 -0
- package/dist/packages/canvas-adapter/deprecated.js +1 -0
- package/dist/packages/canvas-adapter/dieList.d.ts +1 -3
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/index.d.ts +1 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +0 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +1 -40
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +74 -104
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +11 -8
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +20 -12
- 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 +12 -8
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/deprecated.d.ts +64 -0
- package/dist/packages/core/deprecated.js +1 -0
- package/dist/packages/core/index.d.ts +1 -3
- package/dist/packages/core/index.js +1 -1
- package/dist/packages/core/passBins.d.ts +33 -0
- package/dist/packages/core/passBins.js +1 -0
- package/dist/packages/core/waferLabel.d.ts +25 -0
- package/dist/packages/core/waferLabel.js +1 -0
- package/dist/packages/renderer/binColors.d.ts +67 -13
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +8 -9
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +47 -13
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +4 -3
- package/dist/packages/renderer/colorSchemes.d.ts +8 -6
- package/dist/packages/renderer/deprecate.d.ts +24 -0
- package/dist/packages/renderer/deprecate.js +1 -0
- package/dist/packages/renderer/deprecated.d.ts +45 -0
- package/dist/packages/renderer/deprecated.js +1 -0
- package/dist/packages/renderer/index.d.ts +2 -3
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +6 -1
- package/dist/packages/stats/clusterDetection.d.ts +0 -2
- package/dist/packages/stats/deprecated.d.ts +87 -0
- package/dist/packages/stats/deprecated.js +1 -0
- package/dist/packages/stats/index.d.ts +3 -14
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/regions.d.ts +7 -2
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +3 -0
- package/dist/packages/stats/renderSummaryReport.js +34 -34
- package/dist/packages/stats/types.d.ts +0 -43
- package/dist/packages/stats/yield.d.ts +12 -6
- package/dist/packages/stats/yield.js +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -20,14 +20,13 @@ loudly over guessing.
|
|
|
20
20
|
### Entry points
|
|
21
21
|
|
|
22
22
|
- `@wafertools/wafermap` — `buildWaferMap()`, geometry, `registerBinColorScheme()` / `registerValueColorScheme()`. Pure, no DOM, server-safe.
|
|
23
|
-
- `@wafertools/wafermap/render` — `renderWaferMap()`, `renderWaferGallery()
|
|
23
|
+
- `@wafertools/wafermap/render` — `renderWaferMap()`, `renderWaferGallery()`. Needs the DOM.
|
|
24
24
|
- `@wafertools/wafermap/stats` — `analyzeWaferMap()`, `analyzeWaferLot()`. Pure analysis.
|
|
25
25
|
- `@wafertools/wafermap/worker` — `createWafermapWorker()` for off-main-thread builds.
|
|
26
26
|
|
|
27
27
|
Default path: `buildWaferMap()` once when data loads, then `renderWaferMap()` for a
|
|
28
|
-
single wafer or `renderWaferGallery()` for several.
|
|
29
|
-
|
|
30
|
-
correctness guarantee that comes with it.
|
|
28
|
+
single wafer or `renderWaferGallery()` for several. Do not use `toCanvas()`, `buildView()`
|
|
29
|
+
or the other low-level pipeline functions: they are deprecated and removed in 0.31.0.
|
|
31
30
|
|
|
32
31
|
### Traps that produce silently wrong maps
|
|
33
32
|
|
|
@@ -65,7 +64,6 @@ correctness guarantee that comes with it.
|
|
|
65
64
|
the wrong place. The renderers surface these themselves in a toolbar indicator, so
|
|
66
65
|
do NOT hand-roll a second display — pass
|
|
67
66
|
`warnings: { display: false, onWarning }` if the app has its own notification UI.
|
|
68
|
-
(`result.inference.warnings` is a deprecated string mirror; do not use it.)
|
|
69
67
|
- **Give the container a resolved height.** `renderWaferMap` fills its container.
|
|
70
68
|
A bare block-flow `<div>` is fine — it grows to the canvas. The real failure is a
|
|
71
69
|
flex/grid child whose ancestors never resolve a height: it stays 0-tall and the
|
|
@@ -82,11 +80,10 @@ correctness guarantee that comes with it.
|
|
|
82
80
|
- `retestPolicy: 'best'`/`'worst'` is pass/fail-aware via `passBins`; bin number only
|
|
83
81
|
breaks ties within a category.
|
|
84
82
|
- Hard bins (`hbin`) and soft bins (`sbin`) are independent number spaces. Never merge them.
|
|
85
|
-
- **
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
will name colours the map is not drawing. Bin maps and value maps have separate
|
|
83
|
+
- **Do not colour bins yourself.** The maps give each bin one colour from its number
|
|
84
|
+
and its pass/fail verdict (pass bins, per `passBins`, take green pass colours), so a
|
|
85
|
+
bin is the same colour in every lot. To choose colours, set `BinDef.color` or register
|
|
86
|
+
a palette with `registerBinColorScheme`. Bin maps and value maps have separate
|
|
90
87
|
schemes: `binColorScheme` and `valueColorScheme`.
|
|
91
88
|
- Build once, render many: `buildWaferMap()` handles data + geometry; re-render UI
|
|
92
89
|
changes through the controller's `setOptions()`, not by rebuilding.
|
|
@@ -146,6 +143,8 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
146
143
|
| `DieResult.values` / `Die.values` | `testValues` (keyed by test number) |
|
|
147
144
|
| `TestDef.index` | `TestDef.testNumber` (required) |
|
|
148
145
|
| `ViewOptions.colorBySpec` | `passFailDisplay: 'spec'` |
|
|
146
|
+
| `View.colorBySpec` | `view.passFailDisplay` |
|
|
147
|
+
| `WaferMapResult.inference.warnings` | `WaferMapResult.warnings` (structured, with a `code`) |
|
|
149
148
|
| `ViewOptions.testIndex` | `activeTest` |
|
|
150
149
|
| `mountWaferCanvas` | `renderWaferMap` |
|
|
151
150
|
| `HARD_BIN_COLORS` / `SOFT_BIN_COLORS` | `BIN_PALETTE` |
|
|
@@ -153,15 +152,15 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
153
152
|
| `MountOptions` | `RenderOptions` |
|
|
154
153
|
| `WaferCanvasController` | `WaferMapController` |
|
|
155
154
|
| `CanvasHitTarget` | `HitTarget` |
|
|
156
|
-
| `buildScene` / `BuildSceneOptions` / `SceneOptions` | `
|
|
155
|
+
| `buildScene` / `BuildSceneOptions` / `SceneOptions` | `renderWaferMap` |
|
|
157
156
|
| `WaferFlat`, field `flat` | `WaferNotch`, field `notch` |
|
|
158
|
-
| `isInsideWaferWithFlat` | `
|
|
157
|
+
| `isInsideWaferWithFlat` | nothing — `buildWaferMap` resolves the geometry |
|
|
159
158
|
| `DieSample` / `WaferMapPoint` | `DieResult` |
|
|
160
159
|
| `colorScheme` / `WaferViewOptions.colorScheme` | `binColorScheme` (bin maps) and `valueColorScheme` (value and stacked maps) |
|
|
161
160
|
| `registerColorScheme` / `getColorScheme` / `listColorSchemes` | `registerBinColorScheme` / `registerValueColorScheme` and their `get` / `list` pairs |
|
|
162
|
-
| `hardBinColor` / `softBinColor` / `hardBinGreyscale` | `
|
|
161
|
+
| `hardBinColor` / `softBinColor` / `hardBinGreyscale` | `BinDef.color`, or `registerBinColorScheme` |
|
|
163
162
|
| `plotMode: 'specLimit'` | `passFailDisplay: 'spec'` |
|
|
164
|
-
| standalone `getDieAtPoint` | `
|
|
163
|
+
| standalone `getDieAtPoint` | `onHover` / `onClick` on `renderWaferMap` |
|
|
165
164
|
| `RenderOptions.tooltipTestLimit` | (was a no-op; nothing replaces it) |
|
|
166
165
|
|
|
167
166
|
Passing a removed option is a type error, and is ignored at runtime. Do not add
|
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,241 @@ under `### Breaking`.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## [0.30.0] — 2026-09-15
|
|
26
|
+
|
|
27
|
+
### Breaking
|
|
28
|
+
|
|
29
|
+
- **Bin colour is keyed by bin number again, not by die count.** Since 0.28.0,
|
|
30
|
+
`resolveBinColors` ranked bins by die count and handed out palette colours in that order, so
|
|
31
|
+
a colour meant "the biggest fail bin in this view" rather than any particular bin: two lots
|
|
32
|
+
of one program drew hard bin 7 red in one and brown in the other, and filtering a gallery
|
|
33
|
+
could recolour bins. Industry wafer-map tools key colour on the bin number so engineers can
|
|
34
|
+
learn a program's colours and compare screenshots; wmap now does the same. A pass bin takes
|
|
35
|
+
`pass[(bin − 1) mod n]` and a fail bin `fail[(bin − 2) mod n]`, so bin 1 is the first green and
|
|
36
|
+
bin 2 is red in the default palette. Still pass/fail-aware (a failing bin 1 is never green),
|
|
37
|
+
still honours `BinDef.color`. The slot comes from the number alone, so changing `passBins`
|
|
38
|
+
recolours only bins whose verdict changed.
|
|
39
|
+
- **Soft bins read the palette shifted by half its length**, so hard bin *n* and soft bin *n*
|
|
40
|
+
are different colours by default.
|
|
41
|
+
- **`shared` now reports fixed pairs:** bins whose numbers are a palette-length apart (fail
|
|
42
|
+
bins 2 and 21 in `'default'`, 2 and 16 in `'accessible'`) when both are present, rather than
|
|
43
|
+
"more bins than colours".
|
|
44
|
+
- Same function, same types, different colours for the same input, which is why this is
|
|
45
|
+
breaking. Any host screenshot or test that pinned a rank-derived colour will change.
|
|
46
|
+
- **The `passBins` options on `analyzeWaferMap`, `analyzeWaferLot`, `renderWaferMap` and
|
|
47
|
+
`renderWaferGallery` are removed.** Pass bins are set once, on `buildWaferMap`'s input, and
|
|
48
|
+
carried on the result as `WaferMapResult.passBins` (new), which all four read — per wafer in a
|
|
49
|
+
gallery or lot. A second place to set them is how every surface came to judge pass/fail by `[1]`
|
|
50
|
+
(see Fixed), and an analysis override produced a summary whose findings and yield figure used
|
|
51
|
+
different pass bins. A map not built by `buildWaferMap` states them in that same `passBins`
|
|
52
|
+
field. Passing a removed option is a type error; at runtime `analyzeWaferMap` and
|
|
53
|
+
`analyzeWaferLot` ignore it and say so with an `analysis-option-corrected` warning, as they do
|
|
54
|
+
for a stale `ringCount`. To analyse or render with different pass bins, rebuild.
|
|
55
|
+
- **Options and methods removed as unused, duplicated or dead.** Found by measuring what tsmap,
|
|
56
|
+
the examples and the library itself actually use; each was either never set by anything, a
|
|
57
|
+
second way to set something that already had one, or accepted and ignored.
|
|
58
|
+
- `RenderOptions`: `minZoom`/`maxZoom` (fixed at 0.4× and 20× of the fitted view),
|
|
59
|
+
`renderTooltip`, `maxSize`, `toolbarControls` (its `'view-only'` value was never used),
|
|
60
|
+
`showPlotModeSelector`, and the top-level `legendPosition` and `fallbackFormat` — set both
|
|
61
|
+
through `viewOptions`.
|
|
62
|
+
- `GalleryOptions`: `cardPadding`, `maxSize` (the density-derived card size cap always
|
|
63
|
+
applies), `showPlotModeSelector`, `legendPosition` and `fallbackFormat` (through `viewOptions`).
|
|
64
|
+
- `WaferMapController`: `setDies` (it replaced dies but not the wafer geometry, so switching
|
|
65
|
+
wafers with it kept the previous wafer's outline — use `setResult`), `setFallbackFormat` (use
|
|
66
|
+
`setOptions({ fallbackFormat })`), `setHelpButtonVisible`, `setTooltipParent`.
|
|
67
|
+
`GalleryController`: `setFallbackFormat`, `setColumns`.
|
|
68
|
+
- `ViewOptions.dieGap` (fixed at 1 mm), `ToCanvasOptions.topClearance` (always passed 0),
|
|
69
|
+
`DieListDisplayOptions.csvFilename`.
|
|
70
|
+
- `AnalyzeWaferMapOptions.includePartial` / `includeEdgeExcluded`: analysing dies that yield
|
|
71
|
+
excludes could only make the findings describe a different population from the yield figure
|
|
72
|
+
beside them. `isYieldEligibleDie`'s own options are unchanged.
|
|
73
|
+
- `fallbackFormat` is now a view preference (`WaferPreferences.fallbackFormat`), reported
|
|
74
|
+
through `onViewOptionsChange` with the others.
|
|
75
|
+
- **Gallery-to-card plumbing is off the public API.** `WaferDisplayState` loses `binColors`,
|
|
76
|
+
`metadataValueOrder` and `lotSize`; `RenderOptions` loses `chromeInset` and `onExpand`;
|
|
77
|
+
`WaferMapController` loses `setSummaryVisible` and `setViewControlsVisible`. The gallery set
|
|
78
|
+
all of them on its own cards, and on a host's map they read as settings a host must supply to
|
|
79
|
+
get a valid map — a lone map resolves its bin colours, metadata order and lot size itself.
|
|
80
|
+
`renderWaferGallery`'s `getOptions()` and `onViewOptionsChange` no longer carry the three
|
|
81
|
+
fields either (`binColors` held `Map`s, which a host persisting its options could not
|
|
82
|
+
serialise). `ViewOptions.binColors`, `.metadataValueOrder` and `.lotSize` for `buildView` are
|
|
83
|
+
unchanged.
|
|
84
|
+
- **Ring count is set once, on `buildWaferMap`.** `WaferMapInput.ringCount` (default 4) is
|
|
85
|
+
carried as `WaferMapResult.ringCount`, and the ring overlay, ring findings, Summary panel,
|
|
86
|
+
report and Insights all read it. `AnalyzeWaferMapOptions.ringCount` and the view preference
|
|
87
|
+
`ringCount` are removed: they were two places that had to match, and when they did not, the
|
|
88
|
+
ring boundaries on the map described different rings from the ring findings beside them.
|
|
89
|
+
Validation moved with it — a bad value is corrected and reported in `result.warnings` as
|
|
90
|
+
`analysis-option-corrected`, still with no upper bound. A gallery whose wafers were built with
|
|
91
|
+
different ring counts raises the new `ring-count-mixed` warning; each card and each wafer's
|
|
92
|
+
findings use their own. `ViewOptions.ringCount` (for `buildView`) and `classifyDie`'s option
|
|
93
|
+
are unchanged.
|
|
94
|
+
- **`BinColors` has a new required field, `pass: { hard: Set<number>; soft: Set<number> }`** —
|
|
95
|
+
the bins that pass, per type, as `resolveBinColors` judged them. A host that builds a
|
|
96
|
+
`BinColors` by hand must add it; an object without it passed as `ViewOptions.binColors` is
|
|
97
|
+
now ignored and resolved afresh rather than trusted. `binPassSets(dies, passBins)` gives the
|
|
98
|
+
same sets without colours.
|
|
99
|
+
- **`WaferMapResult.inference.warnings` is removed.** It has been a deprecated string mirror since
|
|
100
|
+
0.13.5, when `WaferMapResult.warnings` replaced it with the same messages plus a stable `code` and
|
|
101
|
+
a severity. Read `result.warnings`. Internally each geometry advisory now records its code where it
|
|
102
|
+
is detected, rather than having it recovered by matching phrases in the message.
|
|
103
|
+
- **`View.colorBySpec` is removed.** Its input option went in 0.21.0; the output stayed, only ever
|
|
104
|
+
equal to `passFailDisplay === 'spec'`. Read `view.passFailDisplay`.
|
|
105
|
+
- **`ToCanvasOptions.activeBin`, `.hoverBin` and `.minRightReserve` are removed.** They carry the
|
|
106
|
+
interactive map's own legend highlight, pointer and mode-switch layout state, which a direct
|
|
107
|
+
`toCanvas` caller has none of — the same reason `RenderOptions` already dropped them.
|
|
108
|
+
- **`AnalyzeWaferMapOptions` loses its per-analysis switches:** `enableYieldAnalysis`, `enableHardBinAnalysis`, `enableSoftBinAnalysis`, `enableReticlePositionAnalysis`, `enableTestSiteAnalysis`, `enableClusterAnalysis`, `enableAngularAnalysis` and `enablePatternClassification`.
|
|
109
|
+
Each was cheap and on by default; every analysis now runs, and a caller wanting fewer findings
|
|
110
|
+
filters them (`filterFindings`). Passing one from untyped JavaScript is reported as
|
|
111
|
+
`analysis-option-corrected`. The two that cost real time stay: `enableTestValueAnalysis`, and
|
|
112
|
+
`computePerTestStats`, which triples analysis time on a 7,843-die, 50-test wafer (94 ms → 298 ms)
|
|
113
|
+
and so cannot simply always run.
|
|
114
|
+
- **`WaferMapController.setExpandVisible`, `setIdentityVisible`, `closeSummaryPanel` and
|
|
115
|
+
`getActiveLegend` are removed.** The first two duplicated the `showExpandButton` and `showIdentity`
|
|
116
|
+
options, and nothing used the other two. The gallery keeps expand-button control internally.
|
|
117
|
+
|
|
118
|
+
### Fixed
|
|
119
|
+
|
|
120
|
+
- **A wafer with no die positions covered the Insights view.** In `renderWaferMap`, a
|
|
121
|
+
coordinate-less wafer's "No die position data" summary stayed on top of the chart suite once
|
|
122
|
+
Insights was opened, hiding most of it. A mixed wafer's "+N dies without position data" footer
|
|
123
|
+
did the same. Their z-index beat the Insights layer's. The map view is now hidden outright while
|
|
124
|
+
Insights is open, so nothing inside it can show through, and its controls leave the tab order.
|
|
125
|
+
|
|
126
|
+
- **Input names removed in earlier releases vanished without a trace.** A plain-JavaScript caller
|
|
127
|
+
still passing `data` instead of `results` got an empty map, and `values` instead of `testValues`
|
|
128
|
+
a map with no test data — no error, no warning, nothing that looked wrong. `buildWaferMap` now
|
|
129
|
+
reports every removed name it finds (`data`, `die`, `stack`, `values`, `TestDef.index`,
|
|
130
|
+
`dieConfig.origin`, `waferConfig.flat`, `reticleConfig.anchor`, `lotStack.aggr`) as a new
|
|
131
|
+
`input-field-removed` warning naming each replacement, in `result.warnings` and on the console.
|
|
132
|
+
It still does not honour them: rename and rebuild.
|
|
133
|
+
- **Pass bins given to `buildWaferMap` were used for `result.yield` and nothing else.** The result
|
|
134
|
+
had nowhere to keep them, so every later surface fell back to `[1]` unless the caller repeated
|
|
135
|
+
them: `analyzeWaferMap`'s findings and yield statistics, bin colours and legend order, the
|
|
136
|
+
failing-die hatch, the Summary panel and report, region yield, the Insights yield charts, the
|
|
137
|
+
gallery strip's Yield, and the map's own internal view. A program whose bins 1 and 2 both pass
|
|
138
|
+
therefore showed bin 2 as a failure everywhere except the one yield figure — and the docs
|
|
139
|
+
already claimed `analyzeWaferMap` inferred them from the result. tsmap hit this on every file
|
|
140
|
+
whose pass bins are not just bin 1: it gives each wafer's pass bins to `buildWaferMap` and to
|
|
141
|
+
nothing else. `[1]` is now a default only at `buildWaferMap`'s input; `WaferMapResult.passBins`
|
|
142
|
+
carries the value everywhere after.
|
|
143
|
+
- **A gallery judges each wafer by its own pass bins**, so a lot mixing test programs is
|
|
144
|
+
correct per wafer in cards, strip yield, lot panel, report, region yield and Insights. Hard
|
|
145
|
+
bins that pass on one wafer and fail on another get one colour and one legend row, so the
|
|
146
|
+
gallery raises `pass-bins-mixed` naming them. Yield labels say "per wafer: bin 1 · bins 1, 2"
|
|
147
|
+
when wafers disagree, rather than naming one wafer's set.
|
|
148
|
+
- The report's lot region-yield table and the gallery strip's yield were separate copies of
|
|
149
|
+
the per-die pass rule; both now use the shared ones (`buildRegionYieldData`, `diePassStatus`).
|
|
150
|
+
- **A map's bin legend drew its title over the first row when there were many bins.** Rows
|
|
151
|
+
were fitted to the full canvas height and the "Soft Bin"/"Hard Bin" title placed above them
|
|
152
|
+
afterwards, clamped below the toolbar, so a legend tall enough to fill the height had its
|
|
153
|
+
title printed across row one. The title row (and a floating legend's padding and heading) is
|
|
154
|
+
now reserved before rows are fitted; overflow moves into "+ N more" one row sooner.
|
|
155
|
+
- **Bin legends list pass bins first, then failing bins by die count**, the order the Summary
|
|
156
|
+
panel, report and Insights pareto already used (`sortBinsForDisplay`). The per-map canvas
|
|
157
|
+
legend and the gallery's legend strip both sorted by bin number, so one lot was listed two
|
|
158
|
+
ways on one screen, and a program with dozens of bins buried its biggest failures.
|
|
159
|
+
- **Soft bins were ordered, and the gallery's soft-bin yield was totalled, as if hard pass bin
|
|
160
|
+
numbers applied to them.** The Summary panel, both report bin tables and the gallery strip
|
|
161
|
+
judged a soft bin "pass" by looking it up in `passBins`, which holds hard-bin numbers: soft bin
|
|
162
|
+
1 sorted as a pass and soft bin 100 as a fail whatever their dies did, and the gallery strip's
|
|
163
|
+
Yield read about 0% in soft-bin mode for any program whose passing soft bins are numbered
|
|
164
|
+
differently. They now use the soft bin's own verdict (`BinColors.pass.soft`).
|
|
165
|
+
- **A gallery card had no title unless the host passed `label`, and every other surface named
|
|
166
|
+
that wafer differently.** Without `label` the card header was blank, so an engineer could not
|
|
167
|
+
tell which wafer a card showed. The findings list, the lot report, the yield list and the die
|
|
168
|
+
list's Wafer column called it `W3`, a position that reads like a wafer ID and need not match
|
|
169
|
+
the real one; a detached window called it "Wafer map"; and the Insights tab used the wafer ID.
|
|
170
|
+
The wafer ID was on every item. One internal rule, `waferDisplayLabel` (`core/waferLabel.ts`),
|
|
171
|
+
now names a wafer everywhere: the host's `label`, else `wafer.metadata.waferId`, else
|
|
172
|
+
"Wafer 3 (no ID)". Hosts that pass `label` see no change.
|
|
173
|
+
- **The gallery's Legend style menu offered per-card legend positions as if they applied to
|
|
174
|
+
a legend nobody could see.** Per-card legends are off by default in `renderWaferGallery`
|
|
175
|
+
(the lot legend strip stands in for them), yet the menu listed the six positions first,
|
|
176
|
+
live, with **Default (right)** ticked, and the **Legend on each map** toggle last. It read
|
|
177
|
+
as though the positions moved the lot strip. The toggle now comes first, and the positions
|
|
178
|
+
follow under **Position on each map**, greyed and unticked, with the reason as a tooltip,
|
|
179
|
+
until per-card legends are on. The single-map menu, which has no toggle, is unchanged.
|
|
180
|
+
The rule is in `makeLegendStyleBtn` (`toolbar.ts`).
|
|
181
|
+
|
|
182
|
+
### Changed
|
|
183
|
+
|
|
184
|
+
- **Segments of the gallery legend's population bar are now separated by a 1px gap.**
|
|
185
|
+
Segments are ordered by die count, so any two palette colours can sit side by side, and at
|
|
186
|
+
8px tall the darker ones (black, indigo, dark teal, brown) and the pass greens ran together,
|
|
187
|
+
so the bar read as fewer, wider bins than it held. The gap is transparent, so
|
|
188
|
+
it shows the strip's own background in either theme, and it adds to each segment's 2px
|
|
189
|
+
minimum width rather than eating into it.
|
|
190
|
+
|
|
191
|
+
### Deprecated
|
|
192
|
+
|
|
193
|
+
- **`valueToViridis`, `valueToGreyscale` and `getValueColorScheme`**, to be removed in
|
|
194
|
+
0.31.0. Each still works, logs one console notice on first use, and is struck
|
|
195
|
+
through in editors. Use `resolveValueColorFn(name, reversed)` instead:
|
|
196
|
+
`resolveValueColorFn('default')` and `resolveValueColorFn('greyscale')` return exactly the
|
|
197
|
+
colours the first two did. None of the three applies `reverseValueScheme`, so a legend, chart
|
|
198
|
+
or export built on them could show a reading in a different colour from the map.
|
|
199
|
+
`listValueColorSchemes()` gives scheme names and labels.
|
|
200
|
+
- **The chart-data builders**, to be removed in 0.31.0: `buildYieldData`,
|
|
201
|
+
`buildYieldDataCombined`, `buildBinParetoData`, `buildBinClusterData`, `buildCapabilityData`,
|
|
202
|
+
`buildTestBoxplotData`, `buildTestTrendData`, `trendCentre`, `buildTestPassRateData`,
|
|
203
|
+
`hasJudgeableTests`, `buildTestHistogramData`, `buildTestHistogramSeries`,
|
|
204
|
+
`buildCorrelationMatrix`, `filterCorrelationMatrix`, `buildScatterData` and
|
|
205
|
+
`buildScatterDataGrouped`. They were made public so a host could draw the Insights charts
|
|
206
|
+
itself, before the Insights tab (`insights: { enabled: true }`) drew them; neither tsmap nor
|
|
207
|
+
any example calls them. Each still works and logs one console notice on first use. There is
|
|
208
|
+
no replacement: if you depend on one, say so at https://github.com/wafertools/wafermap/issues. Their types stay until the
|
|
209
|
+
functions go. `buildFacetTable`, `facetValueOf` and `mergeTestDefs` are not
|
|
210
|
+
deprecated.
|
|
211
|
+
- **The low-level drawing pipeline**, to be removed in 0.31.0: `buildView`, `toCanvas`, `createWafer`,
|
|
212
|
+
`generateDies`, `clipDiesToWafer`, `applyOrientation`, `transformDies`, `applyProbeSequence`,
|
|
213
|
+
`generateReticleGrid`, `mapDataToDies`, `isInsideWafer`, `getReticleCell`, `resolveGridPitch`,
|
|
214
|
+
`classifyDie`, `getRingLabel`, `aggregateValues`, `aggregateBinCounts`, `getUniqueBins`,
|
|
215
|
+
`buildHoverText`, `buildMapTitle` and the eight `affine*` helpers. It let a host draw a map without
|
|
216
|
+
`renderWaferMap`; nothing known does — tsmap draws every map through the renderers — and only the
|
|
217
|
+
pipeline example used it, which is removed. The types only the pipeline needs (`ViewOptions`,
|
|
218
|
+
`ToCanvasOptions` and the rest) go with it.
|
|
219
|
+
- **Helpers exported by accident**, to be removed in 0.31.0: the region builders (`buildRingRegions`,
|
|
220
|
+
`buildQuadrantRegions`, `buildSectorRegions`, `buildReticlePositionRegions`, `buildTestSiteRegions`,
|
|
221
|
+
`buildRegionYieldData`, `areQuadrantsAdjacent`, `parseRegionKey`, `sectorCompassNames`),
|
|
222
|
+
`classifyPattern`, `visibleFindings`, `computeFunctionalYield`, `resolveMetadataColumns`,
|
|
223
|
+
`discoverDieMetadataKeys`, the report builders (`renderSummaryReportHtml`,
|
|
224
|
+
`renderLotSummaryReportHtml`, `renderFindingsReportHtml`, `openHtmlReport`, `openReportModal`),
|
|
225
|
+
`resolveBinColors`, `getBinColorScheme`, `contrastTextColor`, `getDieTestValue`, `dieHasTestData`,
|
|
226
|
+
`isParametricTest`, `isPositionedDie`, `metadataDisplayValue`, `metadataCategoricalValue`,
|
|
227
|
+
`buildDieListSection`, `DEFAULT_FACET_CURATION` and `STANDARD_WAFER_DIAMETERS_MM`. The library
|
|
228
|
+
applies each itself. Staying, because each carries a rule or a hook a host needs: `getDieKey`,
|
|
229
|
+
`getTestPassStatus`, `diePassStatus`, `isYieldEligibleDie`, `hasPosition`, `filterFindings`,
|
|
230
|
+
`resolveValueColorFn`, `FACET_NONE_VALUE`, `setReportOpener` and `setDetachWindowOpener`. The two
|
|
231
|
+
constants cannot log a notice; the functions do.
|
|
232
|
+
- **The 0.31.0 removal is enforced**, not just announced: a test fails once the changelog or
|
|
233
|
+
`package.json` reaches 0.31.0 while any of these exports is still there.
|
|
234
|
+
|
|
235
|
+
### Docs
|
|
236
|
+
|
|
237
|
+
- **New example, "Bin colours across a full program"** (`docs/examples/bin-colours.html`).
|
|
238
|
+
A four-wafer gallery over a new generated dataset, `showcase-bin-rich`: 15 hard bins with two
|
|
239
|
+
pass grades and gaps in the numbering, and 32 soft bins numbered under their hard bin. Each
|
|
240
|
+
failure mode has its own spatial pattern. Every other bundled dataset has 2–6 bins, which is
|
|
241
|
+
too few to show how bin colour behaves across a real program. The soft bins deliberately
|
|
242
|
+
outnumber the palette, so the example also shows the `bin-colors-shared` warning. The data
|
|
243
|
+
comes from `scripts/gen-showcase-csvs.mjs` like the other showcase files.
|
|
244
|
+
|
|
245
|
+
### Internal
|
|
246
|
+
|
|
247
|
+
- **CI runs once per push to `main`, not twice.** `ci.yml` had its own `push` trigger while
|
|
248
|
+
`deploy.yml`, which also runs on every push, calls it as its gating job, so the same suite
|
|
249
|
+
ran twice in parallel on each commit. `ci.yml` now triggers only on pull requests (and as
|
|
250
|
+
a reusable workflow). `deploy.yml` is therefore the only thing testing pushes to `main`,
|
|
251
|
+
and says so. The README badge now points at `deploy.yml`.
|
|
252
|
+
- **The perf suite's ratio tests use the fastest of N runs instead of the median**
|
|
253
|
+
(`fastest()` in `tests/perf.test.mjs`). The two paths in a ratio are timed one after the
|
|
254
|
+
other, so background load inflated them unequally. The value-vs-hardBin check failed an
|
|
255
|
+
`npm publish` at 2.86× on a loaded machine where both paths had slowed about 7×. Absolute
|
|
256
|
+
budget tests keep the median, where a minimum would weaken the guard. The value-mode limit
|
|
257
|
+
stays at 2.5×: measured, the steadier statistic does not buy back enough headroom under
|
|
258
|
+
CPU saturation to tighten it.
|
|
259
|
+
|
|
25
260
|
## [0.29.0] — 2026-09-12
|
|
26
261
|
|
|
27
262
|
### Breaking
|
package/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
<img src="docs/images/wafermap-readme-header-256.png" width="64" height="64" alt="wafermap icon">
|
|
4
4
|
|
|
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 ~49 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
|
|
|
@@ -77,8 +77,8 @@ application built on this library, imports **14** of its ~100 exports. Read the
|
|
|
77
77
|
[API reference](https://wafertools.github.io/wafermap/api/) as something to search,
|
|
78
78
|
not to read.
|
|
79
79
|
|
|
80
|
-
Note the two import paths. The renderers (`renderWaferMap`,
|
|
81
|
-
`
|
|
80
|
+
Note the two import paths. The renderers (`renderWaferMap`,
|
|
81
|
+
`renderWaferGallery`) live **only** at `@wafertools/wafermap/render`, so importing them from the
|
|
82
82
|
root package will fail. That keeps the root entry DOM-free — usable in Node for a
|
|
83
83
|
build-and-analyse pipeline, and tree-shakeable when you only need the geometry, data
|
|
84
84
|
and stats layers.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { toCanvas as toCanvasImpl } from './toCanvas.js';
|
|
2
|
+
import { buildDieListSection as buildDieListSectionImpl } from './dieList.js';
|
|
3
|
+
import { openReportModal as openReportModalImpl } from './toolbar.js';
|
|
4
|
+
/** @deprecated Removed in 0.31.0. It belongs to the low-level drawing pipeline, which is being withdrawn: build with `buildWaferMap` and draw with `renderWaferMap` or `renderWaferGallery`. */
|
|
5
|
+
export declare const toCanvas: typeof toCanvasImpl;
|
|
6
|
+
/** @deprecated Removed in 0.31.0. `renderWaferMap` and `renderWaferGallery` show the die list themselves (the `dieList` option). */
|
|
7
|
+
export declare const buildDieListSection: typeof buildDieListSectionImpl;
|
|
8
|
+
/** @deprecated Removed in 0.31.0. Reports open from the Summary panel's report button; route them into your host with `setReportOpener`. */
|
|
9
|
+
export declare const openReportModal: typeof openReportModalImpl;
|
|
10
|
+
//# sourceMappingURL=deprecated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{deprecated as e}from"../renderer/deprecate.js";import{toCanvas as t}from"./toCanvas.js";import{buildDieListSection as r}from"./dieList.js";import{openReportModal as i}from"./toolbar.js";const o="If you depend on it, say so at https://github.com/wafertools/wafermap/issues.",a="It belongs to the low-level drawing pipeline, which is being withdrawn: build with buildWaferMap and draw with renderWaferMap or renderWaferGallery.",n="renderWaferMap and renderWaferGallery show the die list themselves (the dieList option).",s="Reports open from the Summary panel's report button; route them into your host with setReportOpener.";export const toCanvas=e(t,"toCanvas",`${a} ${o}`),buildDieListSection=e(r,"buildDieListSection",`${n} ${o}`),openReportModal=e(i,"openReportModal",`${s} ${o}`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Die } from '../core/dies.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { Wafer } from '../core/index.js';
|
|
3
3
|
import { type MetadataFieldDef, type TestDef } from '../renderer/buildWaferMap.js';
|
|
4
4
|
import type { WaferMetadata } from '../core/metadata.js';
|
|
5
5
|
import { type MetadataKeySelection } from '../stats/metadataColumns.js';
|
|
@@ -76,8 +76,6 @@ export interface DieListDisplayOptions {
|
|
|
76
76
|
* directly into a long document).
|
|
77
77
|
*/
|
|
78
78
|
maxHeight?: string;
|
|
79
|
-
/** CSV filename, including extension. Default: `'dies.csv'`. */
|
|
80
|
-
csvFilename?: string;
|
|
81
79
|
}
|
|
82
80
|
export interface DieListOptions extends DieListDisplayOptions {
|
|
83
81
|
/** Heading text. Default: `Die list (N)`. */
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{hasPosition as R,isPositionedDie as O}from"../core/dies.js";import{classifyDie as z}from"../core/
|
|
1
|
+
import{hasPosition as R,isPositionedDie as O}from"../core/dies.js";import{classifyDie as z}from"../core/classify.js";import{isParametricTest as B,getTestPassStatus as x}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as V}from"../stats/metadataColumns.js";import{LEADING as D,TRACKING as W,wireControlHover as G,controlStyle as K,SPACE as X,RADIUS as I,FONT as C,CLR as c,saveTextFile as Y}from"./toolbar.js";import{csvField as T}from"./summaryPanel.js";import{fmt as k}from"../renderer/fmt.js";const _=5e4,F=new WeakSet;function Q(t){if(F.has(t))return;F.add(t);const o=t.createElement("style");o.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: ${c.panelBg}; color: ${c.label}; font-weight: 600;
|
|
6
|
+
border-bottom: 1px solid ${c.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: ${c.text};
|
|
10
|
+
border-bottom: 1px solid ${c.menuBorder}; white-space: nowrap;
|
|
11
11
|
}
|
|
12
|
-
`,
|
|
13
|
-
`),
|
|
12
|
+
`,t.head.appendChild(o)}function d(t,o,n,a){const i=t.createElement(o);return n&&Object.assign(i.style,n),a!==void 0&&(i.textContent=a),i}function U(t){return R(t)?String(t.x):""}function q(t){return R(t)?String(t.y):""}function J(t,o){if(o!==void 0)return o.filter(a=>a.testNumber!==void 0);const n=new Map;for(const a of t)for(const i of Object.keys(a.testValues??{})){const s=Number(i);n.has(s)||n.set(s,{testNumber:s,name:`Test ${s}`})}return[...n.values()].sort((a,i)=>a.testNumber-i.testNumber)}function Z(t,o,n){if(!o)return;const a=new Map;for(const i of t){if(!O(i))continue;const s=o(i);s&&a.set(i,z(i,s,{ringCount:n}))}return a.size>0?a:void 0}export function buildDieListSection(t,o,n={}){if(!t.length)return null;const a=n.ownerDocument??document;Q(a);const i=J(t,o),s=n.maxRows??_,S=Math.max(0,t.length-s),H=S>0?t.slice(0,s):t,f=d(a,"div",{display:"flex",flexDirection:"column",gap:X.md,flex:"1",minHeight:"0",minWidth:"0"}),w=d(a,"div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});w.appendChild(d(a,"div",{fontSize:C.meta,fontWeight:"700",letterSpacing:W,textTransform:"uppercase",color:c.label},n.title??`Die list (${t.length})`));const g=d(a,"button",{fontSize:C.body,padding:"3px 8px",borderRadius:I.control,...K("outlined"),background:c.menuBg},"Export CSV");g.type="button",G(g),w.appendChild(g),f.appendChild(w),n.note&&f.appendChild(d(a,"div",{fontSize:C.body,color:c.label,lineHeight:D.base,flexShrink:"0"},n.note));const v=Z(t,n.getWafer,n.ringCount??4),P=t.some(e=>e.edgeExcluded===!0),j=[...n.extraColumn?[n.extraColumn.label]:[],"X","Y","Ring","Quadrant","Edge excluded","Site","Hard bin","Soft bin",...i.map(e=>e.unit?`${e.name} (${e.unit})`:e.name)],{columns:M,truncatedKeys:h}=V({dies:t,waferMetadata:n.waferMetadata,metadataFields:n.metadataFields,dieKeys:n.metadataColumns,waferKeys:n.waferMetadataColumns==="none"?"none":void 0,waferPlacement:n.waferMetadataColumns??"csv",reservedLabels:j}),y=[...n.extraColumn?[{label:n.extraColumn.label,get:e=>n.extraColumn.get(e)??""}]:[],{label:"X",get:U},{label:"Y",get:q},...v?[{label:"Ring",get:e=>{const l=v.get(e);return l?String(l.ring):""}},{label:"Quadrant",get:e=>v.get(e)?.quadrant??""}]:[],...P?[{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):""},...i.map(e=>{const l=!B(e);return{label:e.unit?`${e.name} (${e.unit})`:e.name,get:r=>{if(l){const p=x(r,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const u=r.testValues?.[e.testNumber];if(u!==void 0)return k(u,e.unit);const b=x(r,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"},csvGet:r=>{if(l){const p=x(r,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const u=r.testValues?.[e.testNumber];if(u!==void 0)return k(u,void 0,"engineering");const b=x(r,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"}}}),...M.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],N=y.filter(e=>!e.csvOnly),$=d(a,"div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${c.menuBorder}`,borderRadius:I.control,...n.maxHeight?{maxHeight:n.maxHeight}:{}}),m=a.createElement("table");m.className="wmap-dielist-table",m.setAttribute("aria-label",n.title??`Die list (${t.length} dies)`);const E=d(a,"thead"),A=d(a,"tr");for(const e of N){const l=a.createElement("th");l.className="wmap-dielist-th",l.scope="col",l.textContent=e.label,A.appendChild(l)}E.appendChild(A),m.appendChild(E);const L=d(a,"tbody");for(const e of H){const l=d(a,"tr");for(const r of N){const u=a.createElement("td");u.className="wmap-dielist-td",u.textContent=r.get(e),l.appendChild(u)}L.appendChild(l)}if(m.appendChild(L),$.appendChild(m),f.appendChild($),S>0||h.length>0){const e=[];S>0&&e.push(`Showing the first ${s.toLocaleString()} of ${t.length.toLocaleString()} dies. The CSV export contains all ${t.length.toLocaleString()}.`),h.length>0&&e.push(`${h.length} further metadata field${h.length===1?"":"s"} not shown.`),f.appendChild(d(a,"div",{fontSize:C.body,color:c.label,lineHeight:D.base,flexShrink:"0"},e.join(" ")))}return g.addEventListener("click",()=>{const e=[y.map(l=>T(l.label)).join(",")];for(const l of t)e.push(y.map(r=>T((r.csvGet??r.get)(l))).join(","));Y(e.join(`
|
|
13
|
+
`),"dies.csv","text/csv",n.onSaveText)}),f}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
export { WMAP_VERSION, WMAP_BUILD_TIME } from './version.js';
|
|
2
|
-
export { toCanvas } from './toCanvas.js';
|
|
3
2
|
export type { ToCanvasOptions, ToCanvasResult, HitTarget, ViewportTransform } from './toCanvas.js';
|
|
4
3
|
export { renderWaferMap } from './renderWaferMap.js';
|
|
5
4
|
export type { RenderOptions, WaferMapController, WaferViewOptions, WaferPreferences, WaferDisplayState } from './renderWaferMap.js';
|
|
6
5
|
export { renderWaferGallery } from './renderWaferGallery.js';
|
|
7
6
|
export type { WaferMapDisplayItem, WaferMapDisplayItemFactory, GalleryOptions, GalleryController } from './renderWaferGallery.js';
|
|
8
7
|
export type { SummaryPanelOptions, FindingsNotice } from './summaryPanel.js';
|
|
9
|
-
export { buildDieListSection } from './dieList.js';
|
|
10
8
|
export type { DieListOptions, DieListDisplayOptions } from './dieList.js';
|
|
11
9
|
export { collectWarnings, severityOf } from './warnings.js';
|
|
12
10
|
export type { WarningsOptions } from './warnings.js';
|
|
@@ -16,6 +14,6 @@ export type { DetachWindowOpener } from './toolbar.js';
|
|
|
16
14
|
export type { UserGuideExtension } from './toolbar.js';
|
|
17
15
|
export { openWaferMapGuide } from './toolbar.js';
|
|
18
16
|
export { ICONS } from './icons.js';
|
|
19
|
-
export { openReportModal } from './toolbar.js';
|
|
20
17
|
export type { OverlayHandle } from './toolbar.js';
|
|
18
|
+
export { toCanvas, buildDieListSection, openReportModal, } from './deprecated.js';
|
|
21
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{WMAP_VERSION,WMAP_BUILD_TIME}from"./version.js";export{
|
|
1
|
+
export{WMAP_VERSION,WMAP_BUILD_TIME}from"./version.js";export{renderWaferMap}from"./renderWaferMap.js";export{renderWaferGallery}from"./renderWaferGallery.js";export{collectWarnings,severityOf}from"./warnings.js";export{setDetachWindowOpener}from"./toolbar.js";export{openWaferMapGuide}from"./toolbar.js";export{ICONS}from"./icons.js";export{toCanvas,buildDieListSection,openReportModal}from"./deprecated.js";
|
|
@@ -38,7 +38,6 @@ export interface InsightsTabDeps {
|
|
|
38
38
|
* picked up, and so a bin is the same colour in a chart as on the map.
|
|
39
39
|
*/
|
|
40
40
|
getBinColors: () => BinColors;
|
|
41
|
-
passBins: number[];
|
|
42
41
|
/** Read fresh each render — used by the Overview tab's ring/quadrant regional yield cards. Default 4. */
|
|
43
42
|
getRingCount?: () => number;
|
|
44
43
|
onSaveImage?: SaveImageHandler;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildFacetTable as $e,facetValueOf as Be,FACET_NONE_VALUE as De}from"../stats/facets.js";import{mergeTestDefs as Le}from"../stats/mergeTestDefs.js";import{isParametricTest as Ie}from"../renderer/buildWaferMap.js";import{NO_DATA_FILL as ne}from"../renderer/colorMap.js";import{describeWaferPopulation as Oe,populationStat as Ee}from"../stats/population.js";import{LEADING as Pe,ALPHA as Ae,SPACE as w,EDGE_GUTTER as K,FONT as G,CLR as B,RADIUS as Re,SHADOW as je,controlStyle as We,wireControlHover as ae,wireTooltip as Ge}from"./toolbar.js";import{ICONS as He}from"./icons.js";import{renderCapabilityPanel as Ye}from"./charts/capability.js";import{renderBoxplotPanel as Fe}from"./charts/boxplot.js";import{renderTrendPanel as Me}from"./charts/trend.js";import{renderHistogramPanel as Ne}from"./charts/histogram.js";import{renderCorrelationPanel as Ve}from"./charts/correlation.js";import{renderScatterPanel as _e}from"./charts/scatter.js";import{renderBarPanel as oe}from"./charts/barPanel.js";import{renderBinClusterPanel as ze}from"./charts/binCluster.js";import{renderTestPassRatePanel as Ue}from"./charts/testPassRate.js";import{QUANTITY as qe}from"./charts/palette.js";import{cardFrameStyle as Qe,makeChartGridWrap as X,makeLabeledSelect as Ke,makeLinkedGroupSelect as Xe}from"./charts/chartShell.js";import{buildYieldData as N,buildYieldDataCombined as re}from"../stats/yield.js";import{buildBinParetoData as Je}from"../stats/binPareto.js";import{buildLotTestSection as Ze,buildLotFunctionalSection as et,buildMetadataStripBox as tt}from"./summaryPanel.js";import{buildRegionYieldData as ie,buildRingRegions as nt,buildQuadrantRegions as at}from"../stats/regions.js";import{renderRegionYieldDiagram as se}from"./charts/regionYieldDiagram.js";const ot=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}];export function createInsightsTab(D){const{getItems:le,getLotStats:de,getBinColors:J,passBins:T,getRingCount:ce,onSaveImage:v,onSaveText:V,openWafer:H,focusTest:Z}=D,pe=D.showMetadataStrip??!0,R=D.contentInset??K,d=D.ownerDocument??document,E=d.createElement("div");Object.assign(E.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const ue=K,P=d.createElement("div");Object.assign(P.style,{paddingLeft:ue});const $=d.createElement("div");Object.assign($.style,{display:"flex",gap:w.xs,alignItems:"center",background:B.menuBg,border:`1px solid ${B.menuBorder}`,borderRadius:Re.container,boxShadow:je.panel,padding:`0 ${w.lg}`,marginBottom:w.lg,marginLeft:R,marginRight:R}),$.setAttribute("role","tablist"),$.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const n=Array.from($.querySelectorAll('[role="tab"]')),o=n.indexOf(d.activeElement);if(o===-1)return;e.preventDefault();const c=n[(o+(e.key==="ArrowRight"?1:-1)+n.length)%n.length];c.focus(),c.click()});const j=d.createElement("div");Object.assign(j.style,{display:"flex",flexDirection:"column",gap:w.lg,marginTop:"0",paddingLeft:R,paddingRight:R}),E.appendChild(P),E.appendChild($),E.appendChild(j);let W=D.defaultView??"overview",A,I=null,L=null,Y={includeLimits:void 0,clipOutliers:!1};const _=e=>{L!==e&&(L=e,M())};let F=[];function z(e,n,o){H?.(e,n,o)}function fe(e){return H?{open:(n,o)=>z(n,`Wafer ${e.find(c=>c.waferIndex===n)?.label??n}`,o)}:Z&&e.length===1?{open:(n,o)=>Z(o),label:"show this test on the map"}:null}function me(){return le().map((e,n)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:e.label??String(e.wafer.metadata?.waferId??""),waferIndex:n,statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function ee(e,n){Object.assign(e.style,{background:"none",border:"none",borderBottom:n?`2px solid ${B.iconActive}`:"2px solid transparent",color:n?B.iconActive:B.label,fontWeight:n?"700":"500",fontSize:G.body,padding:`${w.sm} ${K} ${w.md}`,cursor:"pointer",marginBottom:"-1px"})}function be(e,n){const o=d.createElement("button");o.type="button",o.textContent=n;const c=e===W;return ee(o,c),o.dataset.on=c?"true":"false",ae(o,"bare"),o.dataset.wmapInsightsTab=e,o.setAttribute("role","tab"),o.setAttribute("aria-selected",c?"true":"false"),o.tabIndex=c?0:-1,o.addEventListener("click",()=>{W!==e&&(W=e,M())}),o}function ge(e){const n=d.createElement("button");return n.type="button",n.textContent=`\u2039 ${e.label}`,ee(n,!1),n.dataset.wmapInsightsBack="1",n.addEventListener("click",e.onBack),n}function ye(e,n,o,c=!0){const s=X(d),f=J();let y="label";const u=o??"group",m=T.length===1?`bin ${T[0]}`:`bins ${T.join(", ")}`,i=de?.(),b=new Map(i?.lotYieldSeries.map(r=>[r.waferIndex,r.yieldPercent])),h=r=>({...r,label:r.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:b.get(r.waferIndex),key:r.waferIndex}),g=e.map(h),p=n?.map(r=>({key:r.key,items:r.items.map(h)})),t=()=>p?re(p,T,y):N(g,T,y),x={title:n?`Yield by ${u} (pass: ${m})`:`Yield by wafer (pass: ${m})`,data:t(),selfControl:{current:y,options:[["yield","Sort: yield"],["label",n?`Sort: ${u}`:"Sort: wafer ID"]],onChange:r=>(y=r,{data:t()})},barColor:()=>qe,valueLabel:r=>`${r.percent.toFixed(1)}%`,reference:r=>{if(r.length<3)return null;const k=r.map(Q=>Q.percent).sort((Q,ke)=>Q-ke),O=k.length%2?k[(k.length-1)/2]:(k[k.length/2-1]+k[k.length/2])/2;return{value:O,label:`median ${O.toFixed(1)}%`}},drill:p?{onOpenGroup:r=>{const k=p.find(O=>O.key===r.label)?.items??[];return{data:N(k,T,y),title:`Yield by wafer \u2014 ${u}: ${r.label} (pass: ${m})`}},onBack:()=>({data:t(),title:`Yield by ${u} (pass: ${m})`}),groupLabelText:u}:void 0,onOpen:H?r=>{typeof r.key=="number"&&z(r.key,`Wafer ${r.label}`)}:void 0,ownerDocument:d};let C=null;if(c&&(C=oe(x,v),C.card.style.minHeight="360px",s.appendChild(C.card)),n){const r=ze({title:"Hard bin pareto",groups:n.map(k=>({key:k.key,items:k.items.map(O=>({dies:O.dies,hardBinCounts:O.statsSummary?.stats.hardBinCounts,softBinCounts:O.statsSummary?.stats.softBinCounts}))})),onSaveImage:v,ownerDocument:d});return r.card.style.minHeight="360px",s.appendChild(r.card),{card:s,destroy:()=>{C?.destroy(),r.destroy()}}}let a="hbin";const l=r=>r===void 0?ne:(a==="hbin"?f.hard:f.soft).get(r)??ne,S=e.map(r=>({dies:r.dies,hardBinCounts:r.statsSummary?.stats.hardBinCounts,softBinCounts:r.statsSummary?.stats.softBinCounts})),te=()=>Je(S,a),ve={title:"Hard bin pareto",data:te(),selfControl:{current:a,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:r=>(a=r,{data:te(),title:`${a==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:r=>l(r.binCode),ownerDocument:d},q=oe(ve,v);return q.card.style.minHeight="360px",s.appendChild(q.card),{card:s,destroy:()=>{C?.destroy(),q.destroy()}}}function U(){const e=d.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...Qe(),display:"flex",flexDirection:"column",gap:w.lg,minWidth:"0"}),e}function he(e){if(P.innerHTML="",P.style.marginBottom="0",!pe)return;const n=tt(e.map(o=>({metadata:o.metadata})),void 0,{facetableOnly:!1});n&&(n.style.marginBottom="0",P.appendChild(n),P.style.marginBottom=w.xs)}function Ce(e,n,o,c){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const s=e.map(l=>l.wafer),f=e.map(l=>l.dies),y=e.flatMap(l=>l.dies),u=e.map(l=>l.statsSummary),m=u.every(l=>l!==void 0)?u:void 0,i=ce?.()??4,b=[],h=[],p=Ue({groups:(c??[{key:"",items:e}]).map(l=>({key:l.key,items:l.items.map(S=>({dies:S.dies,testSpecYield:S.statsSummary?.stats.testSpecYield,functionalYield:S.statsSummary?.stats.functionalYield}))})),testDefs:o,onSaveImage:v,ownerDocument:d});p.card.style.minHeight="300px",b.push(p.card),h.push(p.destroy);const t=ie(f,s,i,T,nt);if(t.length){const l=se({title:"Ring yield",mode:"ring",rows:t,onSaveImage:v,ownerDocument:d});b.push(l.card),h.push(l.destroy)}const x=ie(f,s,i,T,at);if(x.length){const l=se({title:"Quadrant yield",mode:"quadrant",rows:x,onSaveImage:v,ownerDocument:d});b.push(l.card),h.push(l.destroy)}let C=null;if(n.length){const l=Ze(y,n,void 0,m,V,f.map(S=>({dies:S})),void 0,"full");if(l){const S=U();S.appendChild(l),C=S}}let a=null;if(o.length){const l=et(y,o,m,V);if(l){const S=U();S.appendChild(l),a=S}}return{elements:b,testValuesCard:C,functionalCard:a,destroy:()=>{for(const l of h)l()}}}function xe(e){const n=T.length===1?`bin ${T[0]}`:`bins ${T.join(", ")}`,o=e.length===1,c=e[0],s=U();Object.assign(s.style,{flexDirection:"row",flexWrap:"wrap",gap:w.md});const f=i=>{const b=i.lastElementChild;return b&&Object.assign(b.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),i};function y(i,b,h){const g=d.createElement("div");Object.assign(g.style,{display:"flex",alignItems:"baseline",gap:w.sm,paddingRight:w.xxl,marginRight:w.md,borderRight:`1px solid ${B.menuBorder}`});const p=d.createElement("div");p.textContent=i,Object.assign(p.style,{fontSize:G.heading,fontWeight:"700",color:B.value,lineHeight:Pe.tight});const t=d.createElement("div");if(t.textContent=b,Object.assign(t.style,{fontSize:G.body,color:B.label}),g.append(p,t),h){const x=d.createElement("div");x.textContent=h,Object.assign(x.style,{fontSize:G.meta,color:B.label,opacity:Ae.muted}),g.appendChild(x)}return g}if(o){const i=N([{...c,key:c.waferIndex}],T)[0]?.percent;return i!==void 0&&s.appendChild(y(`${i.toFixed(1)}%`,`Yield \xB7 pass: ${n}`)),s.appendChild(y(String(c.dies.length),"Total dies")),f(s)}s.appendChild(y(String(e.length),"Wafers"));const u=N(e.map(i=>({...i,key:i.waferIndex})),T).map(i=>i.percent).filter(i=>Number.isFinite(i));if(u.length){const i=u.reduce((g,p)=>g+p,0)/u.length,b=re([{key:"all",items:e.map(g=>({...g,key:g.waferIndex}))}],T)[0]?.percent,h=b!==void 0&&b.toFixed(1)!==i.toFixed(1);s.appendChild(y(`${i.toFixed(1)}%`,`Mean per-wafer yield \xB7 pass: ${n}`,h?`${b.toFixed(1)}% by dies`:void 0))}const m=e.map(i=>i.statsSummary);if(m.every(i=>i!==void 0)){const i=m.reduce((h,g)=>h+g.stats.analyzedDies,0),b=m.reduce((h,g)=>h+g.stats.excludedDies,0);s.appendChild(y(i.toLocaleString(),"Dies analysed",b?`${b.toLocaleString()} excluded`:void 0))}else s.appendChild(y(e.reduce((i,b)=>i+b.dies.length,0).toLocaleString(),"Total dies"));return f(s)}function we(e,n,o,c,s){const f=d.createElement("div");Object.assign(f.style,{display:"flex",flexDirection:"column",gap:w.lg});const y=e.length===1&&!c;e.length&&!c&&f.appendChild(xe(e));const u=ye(e,c,s,!y);f.appendChild(u.card);const m=Ce(e,n,o,c);for(const i of m.elements)u.card.appendChild(i);return m.testValuesCard&&f.appendChild(m.testValuesCard),m.functionalCard&&f.appendChild(m.functionalCard),{card:f,destroy:()=>{u.destroy(),m.destroy()}}}function Se(e,n,o,c){const s=X(d),f=fe(e),y=a=>{Y=a;for(const l of[g,p,t])l!==u&&l?.setAxisPrefs(a)};let u=null;const m=a=>l=>{u=a(),y(l),u=null},i=a=>({...a,testStats:a.statsSummary?.stats.perTestStats}),b=e.map(i),h=o?.map(a=>({key:a.key,items:a.items.map(i)})),g=Fe({title:"Test value distribution",axisPrefs:Y,onAxisPrefsChange:m(()=>g),onTestChange:a=>x(a),selectedTestNumber:I??void 0,items:b,testDefs:n,groups:h,groupLabelText:c,onSaveImage:v,onGroupChange:a=>_(a),onOpen:f?.open,openActionLabel:f?.label,ownerDocument:d}),p=Ne({title:"Value histogram",axisPrefs:Y,onAxisPrefsChange:m(()=>p),onTestChange:a=>x(a),selectedTestNumber:I??void 0,items:e,testDefs:n,groups:o,onGroupChange:a=>_(a),onSaveImage:v,ownerDocument:d}),t=Me({title:"Wafer-to-wafer trend",axisPrefs:Y,onAxisPrefsChange:m(()=>t),onTestChange:a=>x(a),selectedTestNumber:I??void 0,items:e.map(a=>({label:a.label,key:a.waferIndex,dies:a.dies,testStats:a.statsSummary?.stats.perTestStats})),testDefs:n,onSaveImage:v,centreLabel:Ee(Oe(e.map(a=>a.wafer.metadata)),"mean"),onOpen:H?(a,l)=>z(a,`Wafer ${e.find(S=>S.waferIndex===a)?.label??a}`,l):void 0,ownerDocument:d}),x=a=>{I=a,g.setTest(a),p.setTest(a),t.setTest(a),C.setTest(a)},C=Ye({title:"Process capability",items:e,testDefs:n,onSaveImage:v,selectedTestNumber:I??void 0,onSelectTest:a=>x(a),ownerDocument:d});return s.append(C.card,g.card,p.card,t.card),{card:s,destroy:()=>{C.destroy(),g.destroy(),p.destroy(),t.destroy()}}}function Te(e,n,o){const c=X(d),s=_e({title:"Test scatter",items:e,testDefs:n,groups:o,binColors:J().hard,onSaveImage:v,ownerDocument:d}),f=Ve({title:"Test correlation matrix",items:e,testDefs:n,onSaveImage:v,onSaveText:V,onSelectPair:(y,u)=>s.setXY(y,u),ownerDocument:d});return c.append(f.card,s.card),{card:c,destroy:()=>{f.destroy(),s.destroy()}}}function M(){for(const t of F)t.destroy();F=[],j.innerHTML="",$.innerHTML="",D.backTab&&$.appendChild(ge(D.backTab));for(const t of ot)$.appendChild(be(t.key,t.label));const e=me();he(e);const n=$e(e,{facetableOnly:!0}).filter(t=>t.splittable),o=d.createElement("div");Object.assign(o.style,{display:"flex",gap:w.md,alignItems:"center",marginLeft:w.xxl,paddingBottom:w.xs});let c;n.length>0?(o.appendChild(Ke("Group by:",[{value:"",label:"None"},...n.map(t=>({value:t.key,label:`${t.label} (${t.values.length})`}))],A??"",t=>{A=t||void 0,L=null,M()},{hook:"group-by",ownerDocument:d})),c=n.find(t=>t.key===A)?.label):A=void 0;let s;if(A){const t=new Map,x=[];for(const C of e){const a=Be(C.metadata,A)??De;t.has(a)||(t.set(a,[]),x.push(a)),t.get(a).push(C)}s=x.map(C=>({key:C,items:t.get(C)}))}L!==null&&!s?.some(t=>t.key===L)&&(L=null),s&&s.length>0&&o.appendChild(Xe(s.map(t=>t.key),"Show:",L,t=>_(t),{ownerDocument:d}).el);let f=null;if(D.onOpenGuide){const t=d.createElement("button");t.type="button",t.innerHTML=He.help,t.setAttribute("aria-label","User guide"),Object.assign(t.style,{...We("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),ae(t,"bare"),Ge(t,"User guide"),t.addEventListener("click",()=>D.onOpenGuide()),Object.assign(t.style,{marginLeft:"auto"}),f=t}$.appendChild(o),f&&$.appendChild(f);const y=L!==null?s?.find(t=>t.key===L):void 0,u=y?.items??e,m=y?void 0:s,i=Le(u),b=i.defs,h=b.filter(Ie);I!==null&&!h.some(t=>t.testNumber===I)&&(I=null);const g=W==="overview"?we(u,h,b,m,c):W==="distributions"?Se(u,h,m,c):Te(u,h,m),p=i.conflicts.filter(t=>t.excluded);if(p.length>0){const t=d.createElement("div");Object.assign(t.style,{color:B.label,fontSize:G.body,padding:`0 ${R} ${w.sm}`});const x=p.slice(0,6).map(l=>l.testNumber).join(", "),C=p.length>6?`, and ${p.length-6} more`:"",a=p.length===1?"it":"them";t.textContent=`${p.length} test ${p.length===1?"number is":"numbers are"} hidden (${x}${C}) \u2014 the wafers in view describe ${a} differently, so their values cannot be pooled. `+(s&&s.length>0?`Pick one ${c??"group"} under "Show:" to analyse ${a}.`:"Load one test program at a time to analyse them."),t.dataset.wmapWithheldTests=String(p.length),j.appendChild(t)}F.push(g),j.appendChild(g.card)}return{el:E,render:M,destroy:()=>{for(const e of F)e.destroy();E.remove()}}}
|
|
1
|
+
import{buildFacetTable as De,facetValueOf as $e,FACET_NONE_VALUE as Le}from"../stats/facets.js";import{mergeTestDefs as Ee}from"../stats/mergeTestDefs.js";import{isParametricTest as Ie}from"../renderer/buildWaferMap.js";import{waferDisplayLabel as Oe}from"../core/waferLabel.js";import{INPUT_DEFAULT_PASS_BINS as A,itemPassBins as Pe,passBinsLabel as ne}from"../core/passBins.js";import{NO_DATA_FILL as ae}from"../renderer/colorMap.js";import{describeWaferPopulation as Ae,populationStat as Re}from"../stats/population.js";import{LEADING as We,ALPHA as je,SPACE as S,EDGE_GUTTER as K,FONT as G,CLR as k,RADIUS as Ge,SHADOW as He,controlStyle as Ye,wireControlHover as oe,wireTooltip as Fe}from"./toolbar.js";import{ICONS as Ne}from"./icons.js";import{renderCapabilityPanel as Me}from"./charts/capability.js";import{renderBoxplotPanel as Ve}from"./charts/boxplot.js";import{renderTrendPanel as _e}from"./charts/trend.js";import{renderHistogramPanel as Ue}from"./charts/histogram.js";import{renderCorrelationPanel as ze}from"./charts/correlation.js";import{renderScatterPanel as qe}from"./charts/scatter.js";import{renderBarPanel as re}from"./charts/barPanel.js";import{renderBinClusterPanel as Qe}from"./charts/binCluster.js";import{renderTestPassRatePanel as Ke}from"./charts/testPassRate.js";import{QUANTITY as Xe}from"./charts/palette.js";import{cardFrameStyle as Je,makeChartGridWrap as X,makeLabeledSelect as Ze,makeLinkedGroupSelect as et}from"./charts/chartShell.js";import{buildYieldData as M,buildYieldDataCombined as se}from"../stats/yield.js";import{buildBinParetoData as tt}from"../stats/binPareto.js";import{buildLotTestSection as nt,buildLotFunctionalSection as at,buildMetadataStripBox as ot}from"./summaryPanel.js";import{buildRegionYieldData as ie,buildRingRegions as rt,buildQuadrantRegions as st}from"../stats/regions.js";import{renderRegionYieldDiagram as le}from"./charts/regionYieldDiagram.js";const it=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}];export function createInsightsTab(D){const{getItems:de,getLotStats:ce,getBinColors:J,getRingCount:pe,onSaveImage:T,onSaveText:V,openWafer:H,focusTest:Z}=D,ue=D.showMetadataStrip??!0,R=D.contentInset??K,d=D.ownerDocument??document,I=d.createElement("div");Object.assign(I.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const fe=K,O=d.createElement("div");Object.assign(O.style,{paddingLeft:fe});const B=d.createElement("div");Object.assign(B.style,{display:"flex",gap:S.xs,alignItems:"center",background:k.menuBg,border:`1px solid ${k.menuBorder}`,borderRadius:Ge.container,boxShadow:He.panel,padding:`0 ${S.lg}`,marginBottom:S.lg,marginLeft:R,marginRight:R}),B.setAttribute("role","tablist"),B.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const n=Array.from(B.querySelectorAll('[role="tab"]')),o=n.indexOf(d.activeElement);if(o===-1)return;e.preventDefault();const c=n[(o+(e.key==="ArrowRight"?1:-1)+n.length)%n.length];c.focus(),c.click()});const W=d.createElement("div");Object.assign(W.style,{display:"flex",flexDirection:"column",gap:S.lg,marginTop:"0",paddingLeft:R,paddingRight:R}),I.appendChild(O),I.appendChild(B),I.appendChild(W);let j=D.defaultView??"overview",P,L=null,$=null,Y={includeLimits:void 0,clipOutliers:!1};const _=e=>{$!==e&&($=e,N())};let F=[];function U(e,n,o){H?.(e,n,o)}function me(e){return H?{open:(n,o)=>U(n,`Wafer ${e.find(c=>c.waferIndex===n)?.label??n}`,o)}:Z&&e.length===1?{open:(n,o)=>Z(o),label:"show this test on the map"}:null}function be(){return de().map((e,n)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:Oe(e,n),waferIndex:n,passBins:Pe(e),statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function ee(e,n){Object.assign(e.style,{background:"none",border:"none",borderBottom:n?`2px solid ${k.iconActive}`:"2px solid transparent",color:n?k.iconActive:k.label,fontWeight:n?"700":"500",fontSize:G.body,padding:`${S.sm} ${K} ${S.md}`,cursor:"pointer",marginBottom:"-1px"})}function ge(e,n){const o=d.createElement("button");o.type="button",o.textContent=n;const c=e===j;return ee(o,c),o.dataset.on=c?"true":"false",oe(o,"bare"),o.dataset.wmapInsightsTab=e,o.setAttribute("role","tab"),o.setAttribute("aria-selected",c?"true":"false"),o.tabIndex=c?0:-1,o.addEventListener("click",()=>{j!==e&&(j=e,N())}),o}function ye(e){const n=d.createElement("button");return n.type="button",n.textContent=`\u2039 ${e.label}`,ee(n,!1),n.dataset.wmapInsightsBack="1",n.addEventListener("click",e.onBack),n}function he(e,n,o,c=!0){const l=X(d),f=J();let y="label";const u=o??"group",m=ne(e.map(s=>s.passBins)),i=ce?.(),b=new Map(i?.lotYieldSeries.map(s=>[s.waferIndex,s.yieldPercent])),h=s=>({...s,label:s.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:b.get(s.waferIndex),key:s.waferIndex}),g=e.map(h),p=n?.map(s=>({key:s.key,items:s.items.map(h)})),t=()=>p?se(p,A,y):M(g,A,y),x={title:n?`Yield by ${u} (pass: ${m})`:`Yield by wafer (pass: ${m})`,data:t(),selfControl:{current:y,options:[["yield","Sort: yield"],["label",n?`Sort: ${u}`:"Sort: wafer ID"]],onChange:s=>(y=s,{data:t()})},barColor:()=>Xe,valueLabel:s=>`${s.percent.toFixed(1)}%`,reference:s=>{if(s.length<3)return null;const v=s.map(Q=>Q.percent).sort((Q,ke)=>Q-ke),E=v.length%2?v[(v.length-1)/2]:(v[v.length/2-1]+v[v.length/2])/2;return{value:E,label:`median ${E.toFixed(1)}%`}},drill:p?{onOpenGroup:s=>{const v=p.find(E=>E.key===s.label)?.items??[];return{data:M(v,A,y),title:`Yield by wafer \u2014 ${u}: ${s.label} (pass: ${m})`}},onBack:()=>({data:t(),title:`Yield by ${u} (pass: ${m})`}),groupLabelText:u}:void 0,onOpen:H?s=>{typeof s.key=="number"&&U(s.key,`Wafer ${s.label}`)}:void 0,ownerDocument:d};let C=null;if(c&&(C=re(x,T),C.card.style.minHeight="360px",l.appendChild(C.card)),n){const s=Qe({title:"Hard bin pareto",groups:n.map(v=>({key:v.key,items:v.items.map(E=>({dies:E.dies,hardBinCounts:E.statsSummary?.stats.hardBinCounts,softBinCounts:E.statsSummary?.stats.softBinCounts}))})),onSaveImage:T,ownerDocument:d});return s.card.style.minHeight="360px",l.appendChild(s.card),{card:l,destroy:()=>{C?.destroy(),s.destroy()}}}let a="hbin";const r=s=>s===void 0?ae:(a==="hbin"?f.hard:f.soft).get(s)??ae,w=e.map(s=>({dies:s.dies,hardBinCounts:s.statsSummary?.stats.hardBinCounts,softBinCounts:s.statsSummary?.stats.softBinCounts})),te=()=>tt(w,a),Be={title:"Hard bin pareto",data:te(),selfControl:{current:a,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:s=>(a=s,{data:te(),title:`${a==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:s=>r(s.binCode),ownerDocument:d},q=re(Be,T);return q.card.style.minHeight="360px",l.appendChild(q.card),{card:l,destroy:()=>{C?.destroy(),q.destroy()}}}function z(){const e=d.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...Je(),display:"flex",flexDirection:"column",gap:S.lg,minWidth:"0"}),e}function Ce(e){if(O.innerHTML="",O.style.marginBottom="0",!ue)return;const n=ot(e.map(o=>({metadata:o.metadata})),void 0,{facetableOnly:!1});n&&(n.style.marginBottom="0",O.appendChild(n),O.style.marginBottom=S.xs)}function xe(e,n,o,c){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const l=e.map(r=>r.wafer),f=e.map(r=>r.dies),y=e.flatMap(r=>r.dies),u=e.map(r=>r.statsSummary),m=u.every(r=>r!==void 0)?u:void 0,i=pe?.()??4,b=[],h=[],p=Ke({groups:(c??[{key:"",items:e}]).map(r=>({key:r.key,items:r.items.map(w=>({dies:w.dies,testSpecYield:w.statsSummary?.stats.testSpecYield,functionalYield:w.statsSummary?.stats.functionalYield}))})),testDefs:o,onSaveImage:T,ownerDocument:d});p.card.style.minHeight="300px",b.push(p.card),h.push(p.destroy);const t=ie(f,l,i,r=>e[r].passBins,rt);if(t.length){const r=le({title:"Ring yield",mode:"ring",rows:t,onSaveImage:T,ownerDocument:d});b.push(r.card),h.push(r.destroy)}const x=ie(f,l,i,r=>e[r].passBins,st);if(x.length){const r=le({title:"Quadrant yield",mode:"quadrant",rows:x,onSaveImage:T,ownerDocument:d});b.push(r.card),h.push(r.destroy)}let C=null;if(n.length){const r=nt(y,n,void 0,m,V,f.map(w=>({dies:w})),void 0,"full");if(r){const w=z();w.appendChild(r),C=w}}let a=null;if(o.length){const r=at(y,o,m,V);if(r){const w=z();w.appendChild(r),a=w}}return{elements:b,testValuesCard:C,functionalCard:a,destroy:()=>{for(const r of h)r()}}}function Se(e){const n=ne(e.map(i=>i.passBins)),o=e.length===1,c=e[0],l=z();Object.assign(l.style,{flexDirection:"row",flexWrap:"wrap",gap:S.md});const f=i=>{const b=i.lastElementChild;return b&&Object.assign(b.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),i};function y(i,b,h){const g=d.createElement("div");Object.assign(g.style,{display:"flex",alignItems:"baseline",gap:S.sm,paddingRight:S.xxl,marginRight:S.md,borderRight:`1px solid ${k.menuBorder}`});const p=d.createElement("div");p.textContent=i,Object.assign(p.style,{fontSize:G.heading,fontWeight:"700",color:k.value,lineHeight:We.tight});const t=d.createElement("div");if(t.textContent=b,Object.assign(t.style,{fontSize:G.body,color:k.label}),g.append(p,t),h){const x=d.createElement("div");x.textContent=h,Object.assign(x.style,{fontSize:G.meta,color:k.label,opacity:je.muted}),g.appendChild(x)}return g}if(o){const i=M([{...c,key:c.waferIndex}],A)[0]?.percent;return i!==void 0&&l.appendChild(y(`${i.toFixed(1)}%`,`Yield \xB7 pass: ${n}`)),l.appendChild(y(String(c.dies.length),"Total dies")),f(l)}l.appendChild(y(String(e.length),"Wafers"));const u=M(e.map(i=>({...i,key:i.waferIndex})),A).map(i=>i.percent).filter(i=>Number.isFinite(i));if(u.length){const i=u.reduce((g,p)=>g+p,0)/u.length,b=se([{key:"all",items:e.map(g=>({...g,key:g.waferIndex}))}],A)[0]?.percent,h=b!==void 0&&b.toFixed(1)!==i.toFixed(1);l.appendChild(y(`${i.toFixed(1)}%`,`Mean per-wafer yield \xB7 pass: ${n}`,h?`${b.toFixed(1)}% by dies`:void 0))}const m=e.map(i=>i.statsSummary);if(m.every(i=>i!==void 0)){const i=m.reduce((h,g)=>h+g.stats.analyzedDies,0),b=m.reduce((h,g)=>h+g.stats.excludedDies,0);l.appendChild(y(i.toLocaleString(),"Dies analysed",b?`${b.toLocaleString()} excluded`:void 0))}else l.appendChild(y(e.reduce((i,b)=>i+b.dies.length,0).toLocaleString(),"Total dies"));return f(l)}function we(e,n,o,c,l){const f=d.createElement("div");Object.assign(f.style,{display:"flex",flexDirection:"column",gap:S.lg});const y=e.length===1&&!c;e.length&&!c&&f.appendChild(Se(e));const u=he(e,c,l,!y);f.appendChild(u.card);const m=xe(e,n,o,c);for(const i of m.elements)u.card.appendChild(i);return m.testValuesCard&&f.appendChild(m.testValuesCard),m.functionalCard&&f.appendChild(m.functionalCard),{card:f,destroy:()=>{u.destroy(),m.destroy()}}}function Te(e,n,o,c){const l=X(d),f=me(e),y=a=>{Y=a;for(const r of[g,p,t])r!==u&&r?.setAxisPrefs(a)};let u=null;const m=a=>r=>{u=a(),y(r),u=null},i=a=>({...a,testStats:a.statsSummary?.stats.perTestStats}),b=e.map(i),h=o?.map(a=>({key:a.key,items:a.items.map(i)})),g=Ve({title:"Test value distribution",axisPrefs:Y,onAxisPrefsChange:m(()=>g),onTestChange:a=>x(a),selectedTestNumber:L??void 0,items:b,testDefs:n,groups:h,groupLabelText:c,onSaveImage:T,onGroupChange:a=>_(a),onOpen:f?.open,openActionLabel:f?.label,ownerDocument:d}),p=Ue({title:"Value histogram",axisPrefs:Y,onAxisPrefsChange:m(()=>p),onTestChange:a=>x(a),selectedTestNumber:L??void 0,items:e,testDefs:n,groups:o,onGroupChange:a=>_(a),onSaveImage:T,ownerDocument:d}),t=_e({title:"Wafer-to-wafer trend",axisPrefs:Y,onAxisPrefsChange:m(()=>t),onTestChange:a=>x(a),selectedTestNumber:L??void 0,items:e.map(a=>({label:a.label,key:a.waferIndex,dies:a.dies,testStats:a.statsSummary?.stats.perTestStats})),testDefs:n,onSaveImage:T,centreLabel:Re(Ae(e.map(a=>a.wafer.metadata)),"mean"),onOpen:H?(a,r)=>U(a,`Wafer ${e.find(w=>w.waferIndex===a)?.label??a}`,r):void 0,ownerDocument:d}),x=a=>{L=a,g.setTest(a),p.setTest(a),t.setTest(a),C.setTest(a)},C=Me({title:"Process capability",items:e,testDefs:n,onSaveImage:T,selectedTestNumber:L??void 0,onSelectTest:a=>x(a),ownerDocument:d});return l.append(C.card,g.card,p.card,t.card),{card:l,destroy:()=>{C.destroy(),g.destroy(),p.destroy(),t.destroy()}}}function ve(e,n,o){const c=X(d),l=qe({title:"Test scatter",items:e,testDefs:n,groups:o,binColors:J().hard,onSaveImage:T,ownerDocument:d}),f=ze({title:"Test correlation matrix",items:e,testDefs:n,onSaveImage:T,onSaveText:V,onSelectPair:(y,u)=>l.setXY(y,u),ownerDocument:d});return c.append(f.card,l.card),{card:c,destroy:()=>{f.destroy(),l.destroy()}}}function N(){for(const t of F)t.destroy();F=[],W.innerHTML="",B.innerHTML="",D.backTab&&B.appendChild(ye(D.backTab));for(const t of it)B.appendChild(ge(t.key,t.label));const e=be();Ce(e);const n=De(e,{facetableOnly:!0}).filter(t=>t.splittable),o=d.createElement("div");Object.assign(o.style,{display:"flex",gap:S.md,alignItems:"center",marginLeft:S.xxl,paddingBottom:S.xs});let c;n.length>0?(o.appendChild(Ze("Group by:",[{value:"",label:"None"},...n.map(t=>({value:t.key,label:`${t.label} (${t.values.length})`}))],P??"",t=>{P=t||void 0,$=null,N()},{hook:"group-by",ownerDocument:d})),c=n.find(t=>t.key===P)?.label):P=void 0;let l;if(P){const t=new Map,x=[];for(const C of e){const a=$e(C.metadata,P)??Le;t.has(a)||(t.set(a,[]),x.push(a)),t.get(a).push(C)}l=x.map(C=>({key:C,items:t.get(C)}))}$!==null&&!l?.some(t=>t.key===$)&&($=null),l&&l.length>0&&o.appendChild(et(l.map(t=>t.key),"Show:",$,t=>_(t),{ownerDocument:d}).el);let f=null;if(D.onOpenGuide){const t=d.createElement("button");t.type="button",t.innerHTML=Ne.help,t.setAttribute("aria-label","User guide"),Object.assign(t.style,{...Ye("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),oe(t,"bare"),Fe(t,"User guide"),t.addEventListener("click",()=>D.onOpenGuide()),Object.assign(t.style,{marginLeft:"auto"}),f=t}B.appendChild(o),f&&B.appendChild(f);const y=$!==null?l?.find(t=>t.key===$):void 0,u=y?.items??e,m=y?void 0:l,i=Ee(u),b=i.defs,h=b.filter(Ie);L!==null&&!h.some(t=>t.testNumber===L)&&(L=null);const g=j==="overview"?we(u,h,b,m,c):j==="distributions"?Te(u,h,m,c):ve(u,h,m),p=i.conflicts.filter(t=>t.excluded);if(p.length>0){const t=d.createElement("div");Object.assign(t.style,{color:k.label,fontSize:G.body,padding:`0 ${R} ${S.sm}`});const x=p.slice(0,6).map(r=>r.testNumber).join(", "),C=p.length>6?`, and ${p.length-6} more`:"",a=p.length===1?"it":"them";t.textContent=`${p.length} test ${p.length===1?"number is":"numbers are"} hidden (${x}${C}) \u2014 the wafers in view describe ${a} differently, so their values cannot be pooled. `+(l&&l.length>0?`Pick one ${c??"group"} under "Show:" to analyse ${a}.`:"Load one test program at a time to analyse them."),t.dataset.wmapWithheldTests=String(p.length),W.appendChild(t)}F.push(g),W.appendChild(g.card)}return{el:I,render:N,destroy:()=>{for(const e of F)e.destroy();I.remove()}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getDieTestValue as $}from"../renderer/buildWaferMap.js";import{resolveTestNumber as T,findTestDef as B}from"../renderer/buildView.js";import{buildBinSection as V}from"./summaryPanel.js";import{buildTestHistogramData as z}from"../stats/histogram.js";import{resolveValueColorFn as F}from"../renderer/colorSchemes.js";import{TRACKING as N,SPACE as y,RADIUS as I,FONT as S,CLR as
|
|
1
|
+
import{getDieTestValue as $}from"../renderer/buildWaferMap.js";import{resolveTestNumber as T,findTestDef as B}from"../renderer/buildView.js";import{buildBinSection as V}from"./summaryPanel.js";import{buildTestHistogramData as z}from"../stats/histogram.js";import{resolveValueColorFn as F}from"../renderer/colorSchemes.js";import{TRACKING as N,SPACE as y,RADIUS as I,FONT as S,CLR as u,wireTooltip as A}from"./toolbar.js";import{fmt as v}from"../renderer/fmt.js";function d(o,n,t){const a=document.createElement(o);return n&&Object.assign(a.style,n),t!==void 0&&(a.textContent=t),a}function W(o,n,t,a,x){let p=1/0,m=-1/0;for(const r of o){const s=$(r,n);s!==void 0&&(s<p&&(p=s),s>m&&(m=s))}const b=a==="spec"&&t&&(t.limitLow!==void 0||t.limitHigh!==void 0);let e,l;b?(e=t.limitLow!==void 0?t.limitLow:isFinite(p)?p:0,l=t.limitHigh!==void 0?t.limitHigh:isFinite(m)?m:1):(e=isFinite(p)?p:0,l=isFinite(m)?m:1),e===l&&(l=e+1);const h=r=>Math.min(1,Math.max(0,r));if(x&&e>0&&l>0){const r=Math.log10(e),s=Math.log10(l)-r;return c=>c<=0?0:h((Math.log10(c)-r)/s)}return r=>h((r-e)/(l-e))}function E(o,n,t,a,x,p,m,b){const e=d("div",{display:"flex",flexDirection:"column",gap:y.xxs,minWidth:"0",flex:"1",minHeight:"0"});e.appendChild(d("div",{fontSize:S.meta,fontWeight:"700",letterSpacing:N,textTransform:"uppercase",color:u.label},n?.name?`${n.name} distribution`:"Value distribution"));const l=o[0].rangeLow,h=o[o.length-1].rangeHigh,r=h-l||1,s=i=>Math.min(1,Math.max(0,(i-l)/r)),c=F(a,x),f=W(p,t,n,m,b),w=Math.max(1,...o.map(i=>i.count)),L=d("div",{position:"relative",display:"flex",alignItems:"flex-end",gap:y.xxs,flex:"1",minHeight:"48px"});for(const i of o){const g=d("div",{flex:"1",minWidth:"2px",background:c(f((i.rangeLow+i.rangeHigh)/2)),height:`${i.count/w*100}%`,borderRadius:"1px 1px 0 0"});g.dataset.wmapBar="1",A(g,`${v(i.rangeLow,n?.unit)} \u2013 ${v(i.rangeHigh,n?.unit)}: ${i.count}`,{asDataPoint:!0}),L.appendChild(g)}const H=[];for(const[i,g]of[[n?.limitLow,"LSL"],[n?.limitHigh,"USL"]]){if(i===void 0||i<l||i>h)continue;const C=s(i)*100,M=d("div",{position:"absolute",top:"0",bottom:"0",left:`${C}%`,width:"2px",marginLeft:"-1px",background:u.value,boxShadow:`0 0 0 1px ${u.panelBg}`,pointerEvents:"none"});M.dataset.wmapLimitMarker="1",L.appendChild(M),H.push({pct:C,text:`${g} ${v(i,n?.unit)}`})}if(e.appendChild(L),H.length){const i=d("div",{position:"relative",height:"14px"});for(const{pct:g,text:C}of H){const M=Math.min(96,Math.max(4,g));i.appendChild(d("span",{position:"absolute",left:`${M}%`,top:"0",transform:"translateX(-50%)",whiteSpace:"nowrap",fontSize:S.body,fontWeight:"600",color:u.text,background:u.panelBg,border:`1px solid ${u.value}`,borderRadius:I.control,padding:"0 3px",lineHeight:"12px"},C))}e.appendChild(i)}const R=d("div",{display:"flex",justifyContent:"space-between",fontSize:S.body,color:u.label});return R.appendChild(d("span",{},v(o[0].rangeLow,n?.unit))),R.appendChild(d("span",{},v(o[o.length-1].rangeHigh,n?.unit))),e.appendChild(R),e}export function buildMaplessSummary(o,n,t){const{plotMode:a,activeTest:x,hbinDefs:p,sbinDefs:m,binColors:b,valueColorScheme:e,reverseValueScheme:l,logScale:h=!1,colorbarRangeMode:r="spec",valueRangeDies:s}=t;if(a==="hardBin"||a==="softBin"){const f=V(o,a==="hardBin"?p:m,a==="hardBin"?"hard":"soft",b);if(f)return f}else if(a==="value"){const{testNumber:c}=T(x??0,n),f=B(n,x??0),w=z([{dies:o}],c,12,f?.limitLow,f?.limitHigh);if(w.length)return E(w,f,c,e,l,s??o,r,h)}return d("div",{padding:y.xl,fontSize:S.body,color:u.label,textAlign:"center"},"No summary available for this view.")}
|