@wafertools/wafermap 0.29.0 → 0.30.1
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 +26 -17
- package/CHANGELOG.md +410 -0
- package/README.md +6 -6
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +1 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/deprecated.d.ts +7 -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/exportName.d.ts +68 -0
- package/dist/packages/canvas-adapter/exportName.js +1 -0
- package/dist/packages/canvas-adapter/icons.js +2 -2
- package/dist/packages/canvas-adapter/index.d.ts +2 -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 +23 -40
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +96 -104
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +31 -13
- 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.d.ts +6 -0
- 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 +26 -15
- 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 +60 -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/utils.d.ts +12 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/core/waferLabel.d.ts +35 -0
- package/dist/packages/core/waferLabel.js +1 -0
- package/dist/packages/renderer/binColors.d.ts +105 -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 +76 -17
- 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 +31 -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 +4 -4
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +6 -1
- package/dist/packages/stats/capability.d.ts +16 -1
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +0 -2
- package/dist/packages/stats/deprecated.d.ts +82 -0
- package/dist/packages/stats/deprecated.js +1 -0
- package/dist/packages/stats/index.d.ts +5 -14
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/normalizeInput.d.ts +10 -0
- package/dist/packages/stats/normalizeInput.js +1 -0
- package/dist/packages/stats/regions.d.ts +9 -2
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +42 -1
- package/dist/packages/stats/renderSummaryReport.js +34 -34
- package/dist/packages/stats/reportHtml.d.ts +0 -1
- package/dist/packages/stats/reportHtml.js +10 -10
- package/dist/packages/stats/summaryFigures.d.ts +47 -0
- package/dist/packages/stats/summaryFigures.js +1 -0
- package/dist/packages/stats/types.d.ts +118 -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,12 +80,13 @@ 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
|
-
|
|
90
|
-
|
|
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
|
|
87
|
+
schemes: `binColorScheme` and `valueColorScheme`. Need the colours for a surface of
|
|
88
|
+
your own (a table swatch, an export)? Read `controller.getBinColors()` for a live map,
|
|
89
|
+
or `binColorsForMaps(results)` — never a palette lookup of your own.
|
|
91
90
|
- Build once, render many: `buildWaferMap()` handles data + geometry; re-render UI
|
|
92
91
|
changes through the controller's `setOptions()`, not by rebuilding.
|
|
93
92
|
- `result.view` is internal. Use the promoted fields: `result.plotMode`,
|
|
@@ -99,8 +98,8 @@ correctness guarantee that comes with it.
|
|
|
99
98
|
`w.message` to display, branch on `w.code`. Code that calls a string method on an
|
|
100
99
|
entry — `warnings[0].includes('…')` — is the old shape and will throw.
|
|
101
100
|
- **`summary.findings` is the complete list and contains restatements of the same
|
|
102
|
-
fact.** Building a list for a human to read?
|
|
103
|
-
|
|
101
|
+
fact.** Building a list for a human to read? Pass it through `visibleFindings()`,
|
|
102
|
+
which drops what other findings absorb — do not re-implement that filter.
|
|
104
103
|
Skip that and one edge failure is reported up to three times per region — a hard
|
|
105
104
|
bin row, its soft-bin twin, and the yield row that restates the pass bin. Do NOT
|
|
106
105
|
use `relatedIds` for this; it is a different relationship and some ids it names
|
|
@@ -136,6 +135,14 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
136
135
|
- **A Web Worker buys responsiveness, not speed.** `createWafermapWorker` copies data
|
|
137
136
|
across `postMessage`, so total time goes *up*. Use it when a build would otherwise
|
|
138
137
|
visibly freeze the page, not for small datasets.
|
|
138
|
+
- **Capability, pass rates and region yield come back from the analysis** —
|
|
139
|
+
`stats.capability` (with `computePerTestStats`), `stats.testSpecYield`,
|
|
140
|
+
`stats.testFlagYield`, `stats.functionalYield` and `stats.regionYield`, on wafer and
|
|
141
|
+
lot summaries alike. Do not compute Cp/Cpk or ring yield yourself: the pooled
|
|
142
|
+
within-wafer stddev and per-wafer pass bins are easy to get subtly wrong.
|
|
143
|
+
- **Reports from code: `renderWaferReportHtml(result, summary)` and
|
|
144
|
+
`renderLotReportHtml(results)`** — they take the built maps, so pass bins and ring
|
|
145
|
+
count cannot be wrong. They run in Node.
|
|
139
146
|
- **In a gallery, pass `perWaferSummaries` to `analyzeWaferLot`** so it reuses the
|
|
140
147
|
per-wafer analysis you already ran instead of redoing it.
|
|
141
148
|
|
|
@@ -146,6 +153,8 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
146
153
|
| `DieResult.values` / `Die.values` | `testValues` (keyed by test number) |
|
|
147
154
|
| `TestDef.index` | `TestDef.testNumber` (required) |
|
|
148
155
|
| `ViewOptions.colorBySpec` | `passFailDisplay: 'spec'` |
|
|
156
|
+
| `View.colorBySpec` | `view.passFailDisplay` |
|
|
157
|
+
| `WaferMapResult.inference.warnings` | `WaferMapResult.warnings` (structured, with a `code`) |
|
|
149
158
|
| `ViewOptions.testIndex` | `activeTest` |
|
|
150
159
|
| `mountWaferCanvas` | `renderWaferMap` |
|
|
151
160
|
| `HARD_BIN_COLORS` / `SOFT_BIN_COLORS` | `BIN_PALETTE` |
|
|
@@ -153,15 +162,15 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
153
162
|
| `MountOptions` | `RenderOptions` |
|
|
154
163
|
| `WaferCanvasController` | `WaferMapController` |
|
|
155
164
|
| `CanvasHitTarget` | `HitTarget` |
|
|
156
|
-
| `buildScene` / `BuildSceneOptions` / `SceneOptions` | `
|
|
165
|
+
| `buildScene` / `BuildSceneOptions` / `SceneOptions` | `renderWaferMap` |
|
|
157
166
|
| `WaferFlat`, field `flat` | `WaferNotch`, field `notch` |
|
|
158
|
-
| `isInsideWaferWithFlat` | `
|
|
167
|
+
| `isInsideWaferWithFlat` | nothing — `buildWaferMap` resolves the geometry |
|
|
159
168
|
| `DieSample` / `WaferMapPoint` | `DieResult` |
|
|
160
169
|
| `colorScheme` / `WaferViewOptions.colorScheme` | `binColorScheme` (bin maps) and `valueColorScheme` (value and stacked maps) |
|
|
161
170
|
| `registerColorScheme` / `getColorScheme` / `listColorSchemes` | `registerBinColorScheme` / `registerValueColorScheme` and their `get` / `list` pairs |
|
|
162
|
-
| `hardBinColor` / `softBinColor` / `hardBinGreyscale` | `
|
|
171
|
+
| `hardBinColor` / `softBinColor` / `hardBinGreyscale` | `BinDef.color`, or `registerBinColorScheme` |
|
|
163
172
|
| `plotMode: 'specLimit'` | `passFailDisplay: 'spec'` |
|
|
164
|
-
| standalone `getDieAtPoint` | `
|
|
173
|
+
| standalone `getDieAtPoint` | `onHover` / `onClick` on `renderWaferMap` |
|
|
165
174
|
| `RenderOptions.tooltipTestLimit` | (was a no-op; nothing replaces it) |
|
|
166
175
|
|
|
167
176
|
Passing a removed option is a type error, and is ignored at runtime. Do not add
|
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,416 @@ under `### Breaking`.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## [0.30.1] — 2026-09-16
|
|
26
|
+
|
|
27
|
+
### Security
|
|
28
|
+
|
|
29
|
+
- **Names from a data file could run script in a tooltip.** The die tooltip and the Insights
|
|
30
|
+
chart tooltips (correlation, capability, boxplot, trend, histogram, bar and grouped-bar
|
|
31
|
+
charts), plus the legend-row and toolbar tooltips, built HTML from test names, units, bin
|
|
32
|
+
names, wafer labels and metadata values without escaping them. A file with a test named
|
|
33
|
+
`<img src=x onerror=…>` ran that script when a user hovered, in a Tauri, Electron or
|
|
34
|
+
WebView2 host inside the app's own webview. Every such value is now escaped. The escape
|
|
35
|
+
helper, formerly private to the reports, is the one copy for the library (`core/utils.ts`),
|
|
36
|
+
and `tests/htmlEscaping.test.mjs` fails if a tooltip puts a raw label into HTML again.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **Every saved file is named for the data it came from.** Exports were named for their content
|
|
41
|
+
alone — `dies.csv`, `test-values.csv`, `wafermap.png`, `wafer-gallery.png`, a chart's title — so
|
|
42
|
+
the same export from two wafers of a lot saved as `dies.csv` and `dies (1).csv`, with nothing in
|
|
43
|
+
either name to say which wafer it described. Map, gallery and chart PNGs and every CSV export
|
|
44
|
+
are now named `[lot]_[wafer]_[content]`, for example `LOT123_W05_hard-bin.png`,
|
|
45
|
+
`LOT123_W05_die-list.csv` and `LOT123_25-wafers_yield-by-wafer.png` (see API §5.4.5).
|
|
46
|
+
- A part the data doesn't have is left out, never invented. A wafer with no `label` or
|
|
47
|
+
`waferId` gets no wafer part, not a position that could be read as an ID.
|
|
48
|
+
- A gallery writes its lot and wafer counts (`2-lots`, `25-wafers`), and a lot-stacked map
|
|
49
|
+
writes `stacked-N-wafers`. A card or detached window names files for its own wafer.
|
|
50
|
+
- The name is worked out at save time, so it follows `setResult`, `setItems` and plot-mode
|
|
51
|
+
changes.
|
|
52
|
+
- Names are safe on every common filesystem: path and reserved characters are replaced,
|
|
53
|
+
length is limited, and Windows device names are avoided.
|
|
54
|
+
- **`downloadFilename` is unchanged.** When a host sets it, the map or gallery PNG is still
|
|
55
|
+
named `<downloadFilename>.png`. Only its defaults change: without it, the map PNG was
|
|
56
|
+
`wafermap.png` and the gallery PNG `wafer-gallery.png`, and both now get the generated name.
|
|
57
|
+
Gallery cards, which never read it, also get the generated name, as do all CSVs and charts.
|
|
58
|
+
- `onSaveImage` and `onSaveText` receive the generated name as `suggestedName`. A host that
|
|
59
|
+
matched one of the old default names needs to change.
|
|
60
|
+
- The die-list export's content part is `die-list` (was `dies`).
|
|
61
|
+
- **A floating window's minimize button is now Collapse, and is hidden while the window is
|
|
62
|
+
maximized.** In a desktop host such as Tauri, where a detached gallery card falls back to an
|
|
63
|
+
in-page window, a maximized window's header sits directly under the app's own title bar. Its
|
|
64
|
+
`_` minimize looked like the OS button beside it but only shrank the window to a title strip.
|
|
65
|
+
The button is now drawn as chevrons, with the tooltip **Collapse** (**Show contents** while
|
|
66
|
+
collapsed), and a maximized window shows only restore and close, like a modal. This applies to
|
|
67
|
+
the detached-card fallback window and the user guide window. Modals are unchanged, and
|
|
68
|
+
`ICONS.windowMinimize`/`windowRestore` are still exported.
|
|
69
|
+
- **The data layer is about 3 KB larger (~52 KB gzip).** Analysis now returns capability, pass
|
|
70
|
+
rates by recorded verdict and region yield (see Added), so it includes the code that computes
|
|
71
|
+
them.
|
|
72
|
+
|
|
73
|
+
### Deprecated
|
|
74
|
+
|
|
75
|
+
- **`downloadFilename` (on `RenderOptions` and `GalleryOptions`) becomes a prefix in 0.31.0.** It
|
|
76
|
+
will lead the name of every file a map or gallery saves, CSVs included, with the lot, wafer and
|
|
77
|
+
content appended, for example `LOT123_sort_W05_hard-bin.png`. Parts it already names won't be
|
|
78
|
+
repeated, so a host passing a source-file stem that contains the lot won't see the lot twice.
|
|
79
|
+
It is not being removed. Until 0.31.0 it keeps its current meaning, and passing it logs a
|
|
80
|
+
one-time console notice. If you rely on setting a map's whole file name, say so at
|
|
81
|
+
https://github.com/wafertools/wafermap/issues.
|
|
82
|
+
- **Five 0.30.0 deprecations are withdrawn on review**, as each is the only supported path to
|
|
83
|
+
something a host needs: `visibleFindings` (the one rule for collapsing restated findings —
|
|
84
|
+
`summary.findings` is deliberately uncollapsed), `openReportModal` (shows a report when a host
|
|
85
|
+
has installed its own `setReportOpener`), `metadataDisplayValue` (the one rule for a metadata
|
|
86
|
+
value's text in an export), `getReticleCell` (the only source of a die's reticle cell outside
|
|
87
|
+
the tooltip) and `renderFindingsReportHtml` (it takes a summary alone, so it cannot be given
|
|
88
|
+
inconsistent inputs). They log no notice and stay.
|
|
89
|
+
- **Every remaining deprecation notice now names what to use instead**, or says plainly that
|
|
90
|
+
there is no data replacement (the histogram, scatter and correlation builders). The review
|
|
91
|
+
behind each verdict is recorded in the repository's `API_REMOVALS.md`.
|
|
92
|
+
|
|
93
|
+
### Fixed
|
|
94
|
+
|
|
95
|
+
- **A test could not be chosen from the plot-mode menu on a touchscreen.** With more tests than
|
|
96
|
+
fit inline, "Test Value ▶" opens a submenu, and it opened on hover only. On a phone or tablet a
|
|
97
|
+
tap opened the submenu and the same tap closed it again, so the list flashed and no test could
|
|
98
|
+
be picked. From the keyboard, Enter on the row did nothing. The row now also opens the submenu
|
|
99
|
+
when tapped or activated with Enter or Space. Focus moves into the submenu when it is opened from
|
|
100
|
+
the keyboard, the arrow keys move through its tests, and Escape closes it. Tapping the submenu's
|
|
101
|
+
filter box no longer closes the menu underneath. Applies to both `renderWaferMap` and
|
|
102
|
+
`renderWaferGallery`. Guarded by `tests/modeMenuCascade.test.mjs`.
|
|
103
|
+
- **Map lines and markers got thinner as display scaling rose.** Ring, quadrant and reticle
|
|
104
|
+
lines, the wafer outline, the probe path, the +X/+Y indicator, out-of-spec triangles and the
|
|
105
|
+
failing-die hatch were sized in device pixels, so on a 2× display they drew at half their
|
|
106
|
+
intended size and on a 3× display at a third. The ring and quadrant lines' light centre fell
|
|
107
|
+
below one CSS pixel and faded to a faint hairline. They are now sized in CSS pixels, like the
|
|
108
|
+
colorbar's limit markers, so they look the same at every scale and are sharper at higher ones.
|
|
109
|
+
A 1× display draws exactly as before. The die outline stays at its device-pixel width, so
|
|
110
|
+
dense maps on high-DPI screens are not greyed out. Guarded by `tests/strokeScale.test.mjs`.
|
|
111
|
+
- **Outlined buttons lost their border in a dark theme.** Summary report, View die list and the
|
|
112
|
+
Test values / Functional CSV buttons drew a fixed dark edge (`rgba(0,0,0,0.30)`) whatever the
|
|
113
|
+
theme, so on a dark panel they had no visible outline while the ring/quadrant toggles beside
|
|
114
|
+
them did. `--wmap-control-border` now falls back to `--wmap-border` — the edge the
|
|
115
|
+
toggles use — so a theme that sets `--wmap-border` gets the same outline on both. With neither
|
|
116
|
+
set the default is unchanged, and a host that sets `--wmap-control-border` is unaffected.
|
|
117
|
+
- **Bins and test values given as text built a wrong map without a warning.** A CSV parser gives
|
|
118
|
+
every field as a string. String `x`/`y` already made `buildWaferMap` throw, but a bin of `"1"`
|
|
119
|
+
is not pass bin `1`, so those dies counted as fails and yield read 0 %, and a test value of
|
|
120
|
+
`"0.5"` was not plotted or analysed as a number — with nothing to say so. `buildWaferMap` now
|
|
121
|
+
raises an `input-values-not-numbers` warning (severity `error`) counting text bins, text test
|
|
122
|
+
values and non-boolean verdicts, with an example. The values are not converted: convert them
|
|
123
|
+
and rebuild.
|
|
124
|
+
- **Changing the bin palette inside an expanded gallery card did not recolour the map.** A card
|
|
125
|
+
keeps the gallery-wide bin colours so every card agrees, and those took precedence over the
|
|
126
|
+
palette chosen in the card's own menu, so the menu appeared to do nothing — most visibly for soft
|
|
127
|
+
bins. A palette or "Use colours from bin definitions" change made in the card now resolves the
|
|
128
|
+
card's colours from that choice; a change made in the gallery still recolours every card
|
|
129
|
+
together.
|
|
130
|
+
- **A card reattached to the gallery kept the view it had while expanded.** Reattaching stored the
|
|
131
|
+
expanded window's options — plot mode, palettes, overlays — as that card's per-card overrides,
|
|
132
|
+
which win every time the card is built. A card switched to soft bins while expanded stayed a
|
|
133
|
+
soft-bin map under a gallery bar and legend strip describing hard bins, and later gallery palette
|
|
134
|
+
changes left it in colours matching no other card. A reattached card now takes the gallery's
|
|
135
|
+
shared options again, like every other card. This also stops reattaching from discarding
|
|
136
|
+
per-card `viewOptions` the host set on the item.
|
|
137
|
+
- **A lot report built for wafers without a precomputed summary analysed empty wafers.** The
|
|
138
|
+
report ran its lot analysis on `{ label, wafer, dies, passBins }` pieces, which the analysis
|
|
139
|
+
does not recognise as built maps, so it treated each as a fresh input with no results. The
|
|
140
|
+
report then had no findings, `N/A` per-wafer yields, and a title of "Summary — N wafers"
|
|
141
|
+
instead of "Lot Summary — <lot>". It now analyses the built maps (`renderLotReportHtml`, and
|
|
142
|
+
the gallery's and lot panel's report buttons, which use it).
|
|
143
|
+
- **A gallery with a fixed column count left most of the row empty.** Picking 2 columns drew two
|
|
144
|
+
480px cards and left the rest of the row empty, because a fixed column count was still capped
|
|
145
|
+
by die density (since 0.21.1). A fixed column count now divides the full width; Auto keeps the
|
|
146
|
+
cap.
|
|
147
|
+
- **`GalleryOptions.columns` was ignored at mount**, so the grid always drew a single column.
|
|
148
|
+
Invalid counts (`0`, negative, `NaN`) now fall back to automatic layout, and fractions round.
|
|
149
|
+
|
|
150
|
+
### Added
|
|
151
|
+
|
|
152
|
+
Supported replacements for exports deprecated in 0.30.0 that had no other path. Each deprecated
|
|
153
|
+
export's notice names its replacement; the old names are still removed in 0.31.0.
|
|
154
|
+
|
|
155
|
+
- **Capability, pass rates by recorded verdict, and region yield in the analysis output**
|
|
156
|
+
(API §7.4.1), replacing `buildCapabilityData`, `buildTestPassRateData`/`hasJudgeableTests`
|
|
157
|
+
and `buildRegionYieldData` with the ring/quadrant builders:
|
|
158
|
+
- `stats.capability` — Cp/Cpk/Pp/Ppk per parametric test, computed when per-test statistics
|
|
159
|
+
are (`computePerTestStats`). On a lot summary each wafer is a subgroup, so `stdWithin` is the
|
|
160
|
+
pooled within-wafer stddev and Cp and Pp differ. The chart's normalised five-number fields
|
|
161
|
+
are not included; they are not the test's real minimum and quartiles.
|
|
162
|
+
- `stats.testFlagYield` — per-test pass rate by the tester's recorded verdict — and
|
|
163
|
+
`stats.specVerdictDisagreementDies`, the dies where that verdict and the spec-limit judgement
|
|
164
|
+
disagree. They complement the existing `testSpecYield` and `functionalYield`.
|
|
165
|
+
- `stats.regionYield` — `{ ring, quadrant }` yield with each region's die and pass counts, each
|
|
166
|
+
die judged by its own wafer's pass bins.
|
|
167
|
+
- On `LotStatsSummary.stats`: `capability`, `regionYield`, `testSpecYield`, `functionalYield`,
|
|
168
|
+
`testFlagYield` and `specVerdictDisagreementDies`, all pooled exactly from the wafer summaries
|
|
169
|
+
— counts summed, and capability from each wafer's moments — so a lot analysis given
|
|
170
|
+
`perWaferSummaries` stays cheap. A pooled figure is absent unless every wafer reported it. Lot ring yield is also absent when wafers
|
|
171
|
+
were built with different ring counts.
|
|
172
|
+
- `analyzeWaferLot` now builds a raw `WaferMapInput` once and passes the built map to each
|
|
173
|
+
wafer's analysis.
|
|
174
|
+
- **`renderWaferReportHtml(result, summary?)` and `renderLotReportHtml(results)`**, replacing
|
|
175
|
+
`renderSummaryReportHtml` and `renderLotSummaryReportHtml`. They take built maps, so pass bins
|
|
176
|
+
and ring count come from the map rather than defaulting to `[1]` and `4`. They need no DOM.
|
|
177
|
+
The Summary panel's report buttons use them.
|
|
178
|
+
- **`binColorsForMaps(results, options?)` and `getBinColors()` on `WaferMapController` and
|
|
179
|
+
`GalleryController`** — the colours the maps draw, for a host's own table, export or chart —
|
|
180
|
+
replacing `resolveBinColors` and `getBinColorScheme`. `binColorsForMaps` judges each map by its
|
|
181
|
+
own pass bins; the gallery's own colouring now goes through the same function.
|
|
182
|
+
- **`buildWaferMap({ layout: true, waferConfig, dieConfig })`** — a die layout with no test data:
|
|
183
|
+
every site fully on the wafer (the gross die count), die `(0, 0)` at the centre, counted in the
|
|
184
|
+
configured axis directions. It replaces `createWafer` + `generateDies` + `clipDiesToWafer`, and
|
|
185
|
+
renders like any other result.
|
|
186
|
+
- **`stats.spatialPattern` on `analyzeWaferMap`'s result** — the spatial pattern classifier's label,
|
|
187
|
+
confidence and geometry features (failure density overall and at the edge, the failing
|
|
188
|
+
cluster's radial position, eccentricity, linearity, …), replacing a direct `classifyPattern`
|
|
189
|
+
call. It is present for every wafer the classifier can measure, including those labelled
|
|
190
|
+
`'random'` or `'none'`, which raise no finding, so the features can feed a model of your own with
|
|
191
|
+
negative examples as well as patterned ones.
|
|
192
|
+
- **`ICONS.collapse` and `ICONS.uncollapse`**, the floating-window header's collapse and
|
|
193
|
+
show-contents icons.
|
|
194
|
+
- **`WaferMapController.closeSummaryPanel()` and `GalleryController.setColumns()` are restored.**
|
|
195
|
+
Both were removed in 0.30.0 without a deprecation period. A host needs the first to get the
|
|
196
|
+
map's full width back, for example when loading a new file, and the second to apply its own
|
|
197
|
+
column control or a saved preference without rebuilding the gallery. Closing the panel no
|
|
198
|
+
longer resets the Summary button's notable-findings colour, as the 0.29 version did.
|
|
199
|
+
|
|
200
|
+
## [0.30.0] — 2026-09-15
|
|
201
|
+
|
|
202
|
+
### Breaking
|
|
203
|
+
|
|
204
|
+
- **Bin colour is keyed by bin number again, not by die count.** Since 0.28.0,
|
|
205
|
+
`resolveBinColors` ranked bins by die count and handed out palette colours in that order, so
|
|
206
|
+
a colour meant "the biggest fail bin in this view" rather than any particular bin: two lots
|
|
207
|
+
of one program drew hard bin 7 red in one and brown in the other, and filtering a gallery
|
|
208
|
+
could recolour bins. Industry wafer-map tools key colour on the bin number so engineers can
|
|
209
|
+
learn a program's colours and compare screenshots; wmap now does the same. A pass bin takes
|
|
210
|
+
`pass[(bin − 1) mod n]` and a fail bin `fail[(bin − 2) mod n]`, so bin 1 is the first green and
|
|
211
|
+
bin 2 is red in the default palette. Still pass/fail-aware (a failing bin 1 is never green),
|
|
212
|
+
still honours `BinDef.color`. The slot comes from the number alone, so changing `passBins`
|
|
213
|
+
recolours only bins whose verdict changed.
|
|
214
|
+
- **Soft bins read the palette shifted by half its length**, so hard bin *n* and soft bin *n*
|
|
215
|
+
are different colours by default.
|
|
216
|
+
- **`shared` now reports fixed pairs:** bins whose numbers are a palette-length apart (fail
|
|
217
|
+
bins 2 and 21 in `'default'`, 2 and 16 in `'accessible'`) when both are present, rather than
|
|
218
|
+
"more bins than colours".
|
|
219
|
+
- Same function, same types, different colours for the same input, which is why this is
|
|
220
|
+
breaking. Any host screenshot or test that pinned a rank-derived colour will change.
|
|
221
|
+
- **The `passBins` options on `analyzeWaferMap`, `analyzeWaferLot`, `renderWaferMap` and
|
|
222
|
+
`renderWaferGallery` are removed.** Pass bins are set once, on `buildWaferMap`'s input, and
|
|
223
|
+
carried on the result as `WaferMapResult.passBins` (new), which all four read — per wafer in a
|
|
224
|
+
gallery or lot. A second place to set them is how every surface came to judge pass/fail by `[1]`
|
|
225
|
+
(see Fixed), and an analysis override produced a summary whose findings and yield figure used
|
|
226
|
+
different pass bins. A map not built by `buildWaferMap` states them in that same `passBins`
|
|
227
|
+
field. Passing a removed option is a type error; at runtime `analyzeWaferMap` and
|
|
228
|
+
`analyzeWaferLot` ignore it and say so with an `analysis-option-corrected` warning, as they do
|
|
229
|
+
for a stale `ringCount`. To analyse or render with different pass bins, rebuild.
|
|
230
|
+
- **Options and methods removed as unused, duplicated or dead.** Found by measuring what tsmap,
|
|
231
|
+
the examples and the library itself actually use; each was either never set by anything, a
|
|
232
|
+
second way to set something that already had one, or accepted and ignored.
|
|
233
|
+
- `RenderOptions`: `minZoom`/`maxZoom` (fixed at 0.4× and 20× of the fitted view),
|
|
234
|
+
`renderTooltip`, `maxSize`, `toolbarControls` (its `'view-only'` value was never used),
|
|
235
|
+
`showPlotModeSelector`, and the top-level `legendPosition` and `fallbackFormat` — set both
|
|
236
|
+
through `viewOptions`.
|
|
237
|
+
- `GalleryOptions`: `cardPadding`, `maxSize` (the density-derived card size cap always
|
|
238
|
+
applies), `showPlotModeSelector`, `legendPosition` and `fallbackFormat` (through `viewOptions`).
|
|
239
|
+
- `WaferMapController`: `setDies` (it replaced dies but not the wafer geometry, so switching
|
|
240
|
+
wafers with it kept the previous wafer's outline — use `setResult`), `setFallbackFormat` (use
|
|
241
|
+
`setOptions({ fallbackFormat })`), `setHelpButtonVisible`, `setTooltipParent`.
|
|
242
|
+
`GalleryController`: `setFallbackFormat`, `setColumns`.
|
|
243
|
+
- `ViewOptions.dieGap` (fixed at 1 mm), `ToCanvasOptions.topClearance` (always passed 0),
|
|
244
|
+
`DieListDisplayOptions.csvFilename`.
|
|
245
|
+
- `AnalyzeWaferMapOptions.includePartial` / `includeEdgeExcluded`: analysing dies that yield
|
|
246
|
+
excludes could only make the findings describe a different population from the yield figure
|
|
247
|
+
beside them. `isYieldEligibleDie`'s own options are unchanged.
|
|
248
|
+
- `fallbackFormat` is now a view preference (`WaferPreferences.fallbackFormat`), reported
|
|
249
|
+
through `onViewOptionsChange` with the others.
|
|
250
|
+
- **Gallery-to-card plumbing is off the public API.** `WaferDisplayState` loses `binColors`,
|
|
251
|
+
`metadataValueOrder` and `lotSize`; `RenderOptions` loses `chromeInset` and `onExpand`;
|
|
252
|
+
`WaferMapController` loses `setSummaryVisible` and `setViewControlsVisible`. The gallery set
|
|
253
|
+
all of them on its own cards, and on a host's map they read as settings a host must supply to
|
|
254
|
+
get a valid map — a lone map resolves its bin colours, metadata order and lot size itself.
|
|
255
|
+
`renderWaferGallery`'s `getOptions()` and `onViewOptionsChange` no longer carry the three
|
|
256
|
+
fields either (`binColors` held `Map`s, which a host persisting its options could not
|
|
257
|
+
serialise). `ViewOptions.binColors`, `.metadataValueOrder` and `.lotSize` for `buildView` are
|
|
258
|
+
unchanged.
|
|
259
|
+
- **Ring count is set once, on `buildWaferMap`.** `WaferMapInput.ringCount` (default 4) is
|
|
260
|
+
carried as `WaferMapResult.ringCount`, and the ring overlay, ring findings, Summary panel,
|
|
261
|
+
report and Insights all read it. `AnalyzeWaferMapOptions.ringCount` and the view preference
|
|
262
|
+
`ringCount` are removed: they were two places that had to match, and when they did not, the
|
|
263
|
+
ring boundaries on the map described different rings from the ring findings beside them.
|
|
264
|
+
Validation moved with it — a bad value is corrected and reported in `result.warnings` as
|
|
265
|
+
`analysis-option-corrected`, still with no upper bound. A gallery whose wafers were built with
|
|
266
|
+
different ring counts raises the new `ring-count-mixed` warning; each card and each wafer's
|
|
267
|
+
findings use their own. `ViewOptions.ringCount` (for `buildView`) and `classifyDie`'s option
|
|
268
|
+
are unchanged.
|
|
269
|
+
- **`BinColors` has a new required field, `pass: { hard: Set<number>; soft: Set<number> }`** —
|
|
270
|
+
the bins that pass, per type, as `resolveBinColors` judged them. A host that builds a
|
|
271
|
+
`BinColors` by hand must add it; an object without it passed as `ViewOptions.binColors` is
|
|
272
|
+
now ignored and resolved afresh rather than trusted. `binPassSets(dies, passBins)` gives the
|
|
273
|
+
same sets without colours.
|
|
274
|
+
- **`WaferMapResult.inference.warnings` is removed.** It has been a deprecated string mirror since
|
|
275
|
+
0.13.5, when `WaferMapResult.warnings` replaced it with the same messages plus a stable `code` and
|
|
276
|
+
a severity. Read `result.warnings`. Internally each geometry advisory now records its code where it
|
|
277
|
+
is detected, rather than having it recovered by matching phrases in the message.
|
|
278
|
+
- **`View.colorBySpec` is removed.** Its input option went in 0.21.0; the output stayed, only ever
|
|
279
|
+
equal to `passFailDisplay === 'spec'`. Read `view.passFailDisplay`.
|
|
280
|
+
- **`ToCanvasOptions.activeBin`, `.hoverBin` and `.minRightReserve` are removed.** They carry the
|
|
281
|
+
interactive map's own legend highlight, pointer and mode-switch layout state, which a direct
|
|
282
|
+
`toCanvas` caller has none of — the same reason `RenderOptions` already dropped them.
|
|
283
|
+
- **`AnalyzeWaferMapOptions` loses its per-analysis switches:** `enableYieldAnalysis`, `enableHardBinAnalysis`, `enableSoftBinAnalysis`, `enableReticlePositionAnalysis`, `enableTestSiteAnalysis`, `enableClusterAnalysis`, `enableAngularAnalysis` and `enablePatternClassification`.
|
|
284
|
+
Each was cheap and on by default; every analysis now runs, and a caller wanting fewer findings
|
|
285
|
+
filters them (`filterFindings`). Passing one from untyped JavaScript is reported as
|
|
286
|
+
`analysis-option-corrected`. The two that cost real time stay: `enableTestValueAnalysis`, and
|
|
287
|
+
`computePerTestStats`, which triples analysis time on a 7,843-die, 50-test wafer (94 ms → 298 ms)
|
|
288
|
+
and so cannot simply always run.
|
|
289
|
+
- **`WaferMapController.setExpandVisible`, `setIdentityVisible`, `closeSummaryPanel` and
|
|
290
|
+
`getActiveLegend` are removed.** The first two duplicated the `showExpandButton` and `showIdentity`
|
|
291
|
+
options, and nothing used the other two. The gallery keeps expand-button control internally.
|
|
292
|
+
|
|
293
|
+
### Fixed
|
|
294
|
+
|
|
295
|
+
- **A wafer with no die positions covered the Insights view.** In `renderWaferMap`, a
|
|
296
|
+
coordinate-less wafer's "No die position data" summary stayed on top of the chart suite once
|
|
297
|
+
Insights was opened, hiding most of it. A mixed wafer's "+N dies without position data" footer
|
|
298
|
+
did the same. Their z-index beat the Insights layer's. The map view is now hidden outright while
|
|
299
|
+
Insights is open, so nothing inside it can show through, and its controls leave the tab order.
|
|
300
|
+
|
|
301
|
+
- **Input names removed in earlier releases vanished without a trace.** A plain-JavaScript caller
|
|
302
|
+
still passing `data` instead of `results` got an empty map, and `values` instead of `testValues`
|
|
303
|
+
a map with no test data — no error, no warning, nothing that looked wrong. `buildWaferMap` now
|
|
304
|
+
reports every removed name it finds (`data`, `die`, `stack`, `values`, `TestDef.index`,
|
|
305
|
+
`dieConfig.origin`, `waferConfig.flat`, `reticleConfig.anchor`, `lotStack.aggr`) as a new
|
|
306
|
+
`input-field-removed` warning naming each replacement, in `result.warnings` and on the console.
|
|
307
|
+
It still does not honour them: rename and rebuild.
|
|
308
|
+
- **Pass bins given to `buildWaferMap` were used for `result.yield` and nothing else.** The result
|
|
309
|
+
had nowhere to keep them, so every later surface fell back to `[1]` unless the caller repeated
|
|
310
|
+
them: `analyzeWaferMap`'s findings and yield statistics, bin colours and legend order, the
|
|
311
|
+
failing-die hatch, the Summary panel and report, region yield, the Insights yield charts, the
|
|
312
|
+
gallery strip's Yield, and the map's own internal view. A program whose bins 1 and 2 both pass
|
|
313
|
+
therefore showed bin 2 as a failure everywhere except the one yield figure — and the docs
|
|
314
|
+
already claimed `analyzeWaferMap` inferred them from the result. tsmap hit this on every file
|
|
315
|
+
whose pass bins are not just bin 1: it gives each wafer's pass bins to `buildWaferMap` and to
|
|
316
|
+
nothing else. `[1]` is now a default only at `buildWaferMap`'s input; `WaferMapResult.passBins`
|
|
317
|
+
carries the value everywhere after.
|
|
318
|
+
- **A gallery judges each wafer by its own pass bins**, so a lot mixing test programs is
|
|
319
|
+
correct per wafer in cards, strip yield, lot panel, report, region yield and Insights. Hard
|
|
320
|
+
bins that pass on one wafer and fail on another get one colour and one legend row, so the
|
|
321
|
+
gallery raises `pass-bins-mixed` naming them. Yield labels say "per wafer: bin 1 · bins 1, 2"
|
|
322
|
+
when wafers disagree, rather than naming one wafer's set.
|
|
323
|
+
- The report's lot region-yield table and the gallery strip's yield were separate copies of
|
|
324
|
+
the per-die pass rule; both now use the shared ones (`buildRegionYieldData`, `diePassStatus`).
|
|
325
|
+
- **A map's bin legend drew its title over the first row when there were many bins.** Rows
|
|
326
|
+
were fitted to the full canvas height and the "Soft Bin"/"Hard Bin" title placed above them
|
|
327
|
+
afterwards, clamped below the toolbar, so a legend tall enough to fill the height had its
|
|
328
|
+
title printed across row one. The title row (and a floating legend's padding and heading) is
|
|
329
|
+
now reserved before rows are fitted; overflow moves into "+ N more" one row sooner.
|
|
330
|
+
- **Bin legends list pass bins first, then failing bins by die count**, the order the Summary
|
|
331
|
+
panel, report and Insights pareto already used (`sortBinsForDisplay`). The per-map canvas
|
|
332
|
+
legend and the gallery's legend strip both sorted by bin number, so one lot was listed two
|
|
333
|
+
ways on one screen, and a program with dozens of bins buried its biggest failures.
|
|
334
|
+
- **Soft bins were ordered, and the gallery's soft-bin yield was totalled, as if hard pass bin
|
|
335
|
+
numbers applied to them.** The Summary panel, both report bin tables and the gallery strip
|
|
336
|
+
judged a soft bin "pass" by looking it up in `passBins`, which holds hard-bin numbers: soft bin
|
|
337
|
+
1 sorted as a pass and soft bin 100 as a fail whatever their dies did, and the gallery strip's
|
|
338
|
+
Yield read about 0% in soft-bin mode for any program whose passing soft bins are numbered
|
|
339
|
+
differently. They now use the soft bin's own verdict (`BinColors.pass.soft`).
|
|
340
|
+
- **A gallery card had no title unless the host passed `label`, and every other surface named
|
|
341
|
+
that wafer differently.** Without `label` the card header was blank, so an engineer could not
|
|
342
|
+
tell which wafer a card showed. The findings list, the lot report, the yield list and the die
|
|
343
|
+
list's Wafer column called it `W3`, a position that reads like a wafer ID and need not match
|
|
344
|
+
the real one; a detached window called it "Wafer map"; and the Insights tab used the wafer ID.
|
|
345
|
+
The wafer ID was on every item. One internal rule, `waferDisplayLabel` (`core/waferLabel.ts`),
|
|
346
|
+
now names a wafer everywhere: the host's `label`, else `wafer.metadata.waferId`, else
|
|
347
|
+
"Wafer 3 (no ID)". Hosts that pass `label` see no change.
|
|
348
|
+
- **The gallery's Legend style menu offered per-card legend positions as if they applied to
|
|
349
|
+
a legend nobody could see.** Per-card legends are off by default in `renderWaferGallery`
|
|
350
|
+
(the lot legend strip stands in for them), yet the menu listed the six positions first,
|
|
351
|
+
live, with **Default (right)** ticked, and the **Legend on each map** toggle last. It read
|
|
352
|
+
as though the positions moved the lot strip. The toggle now comes first, and the positions
|
|
353
|
+
follow under **Position on each map**, greyed and unticked, with the reason as a tooltip,
|
|
354
|
+
until per-card legends are on. The single-map menu, which has no toggle, is unchanged.
|
|
355
|
+
The rule is in `makeLegendStyleBtn` (`toolbar.ts`).
|
|
356
|
+
|
|
357
|
+
### Changed
|
|
358
|
+
|
|
359
|
+
- **Segments of the gallery legend's population bar are now separated by a 1px gap.**
|
|
360
|
+
Segments are ordered by die count, so any two palette colours can sit side by side, and at
|
|
361
|
+
8px tall the darker ones (black, indigo, dark teal, brown) and the pass greens ran together,
|
|
362
|
+
so the bar read as fewer, wider bins than it held. The gap is transparent, so
|
|
363
|
+
it shows the strip's own background in either theme, and it adds to each segment's 2px
|
|
364
|
+
minimum width rather than eating into it.
|
|
365
|
+
|
|
366
|
+
### Deprecated
|
|
367
|
+
|
|
368
|
+
- **`valueToViridis`, `valueToGreyscale` and `getValueColorScheme`**, to be removed in
|
|
369
|
+
0.31.0. Each still works, logs one console notice on first use, and is struck
|
|
370
|
+
through in editors. Use `resolveValueColorFn(name, reversed)` instead:
|
|
371
|
+
`resolveValueColorFn('default')` and `resolveValueColorFn('greyscale')` return exactly the
|
|
372
|
+
colours the first two did. None of the three applies `reverseValueScheme`, so a legend, chart
|
|
373
|
+
or export built on them could show a reading in a different colour from the map.
|
|
374
|
+
`listValueColorSchemes()` gives scheme names and labels.
|
|
375
|
+
- **The chart-data builders**, to be removed in 0.31.0: `buildYieldData`,
|
|
376
|
+
`buildYieldDataCombined`, `buildBinParetoData`, `buildBinClusterData`, `buildCapabilityData`,
|
|
377
|
+
`buildTestBoxplotData`, `buildTestTrendData`, `trendCentre`, `buildTestPassRateData`,
|
|
378
|
+
`hasJudgeableTests`, `buildTestHistogramData`, `buildTestHistogramSeries`,
|
|
379
|
+
`buildCorrelationMatrix`, `filterCorrelationMatrix`, `buildScatterData` and
|
|
380
|
+
`buildScatterDataGrouped`. They were made public so a host could draw the Insights charts
|
|
381
|
+
itself, before the Insights tab (`insights: { enabled: true }`) drew them; neither tsmap nor
|
|
382
|
+
any example calls them. Each still works and logs one console notice on first use. There is
|
|
383
|
+
no replacement: if you depend on one, say so at https://github.com/wafertools/wafermap/issues. Their types stay until the
|
|
384
|
+
functions go. `buildFacetTable`, `facetValueOf` and `mergeTestDefs` are not
|
|
385
|
+
deprecated.
|
|
386
|
+
- **The low-level drawing pipeline**, to be removed in 0.31.0: `buildView`, `toCanvas`, `createWafer`,
|
|
387
|
+
`generateDies`, `clipDiesToWafer`, `applyOrientation`, `transformDies`, `applyProbeSequence`,
|
|
388
|
+
`generateReticleGrid`, `mapDataToDies`, `isInsideWafer`, `getReticleCell`, `resolveGridPitch`,
|
|
389
|
+
`classifyDie`, `getRingLabel`, `aggregateValues`, `aggregateBinCounts`, `getUniqueBins`,
|
|
390
|
+
`buildHoverText`, `buildMapTitle` and the eight `affine*` helpers. It let a host draw a map without
|
|
391
|
+
`renderWaferMap`; nothing known does — tsmap draws every map through the renderers — and only the
|
|
392
|
+
pipeline example used it, which is removed. The types only the pipeline needs (`ViewOptions`,
|
|
393
|
+
`ToCanvasOptions` and the rest) go with it.
|
|
394
|
+
- **Helpers exported by accident**, to be removed in 0.31.0: the region builders (`buildRingRegions`,
|
|
395
|
+
`buildQuadrantRegions`, `buildSectorRegions`, `buildReticlePositionRegions`, `buildTestSiteRegions`,
|
|
396
|
+
`buildRegionYieldData`, `areQuadrantsAdjacent`, `parseRegionKey`, `sectorCompassNames`),
|
|
397
|
+
`classifyPattern`, `visibleFindings`, `computeFunctionalYield`, `resolveMetadataColumns`,
|
|
398
|
+
`discoverDieMetadataKeys`, the report builders (`renderSummaryReportHtml`,
|
|
399
|
+
`renderLotSummaryReportHtml`, `renderFindingsReportHtml`, `openHtmlReport`, `openReportModal`),
|
|
400
|
+
`resolveBinColors`, `getBinColorScheme`, `contrastTextColor`, `getDieTestValue`, `dieHasTestData`,
|
|
401
|
+
`isParametricTest`, `isPositionedDie`, `metadataDisplayValue`, `metadataCategoricalValue`,
|
|
402
|
+
`buildDieListSection`, `DEFAULT_FACET_CURATION` and `STANDARD_WAFER_DIAMETERS_MM`. The library
|
|
403
|
+
applies each itself. Staying, because each carries a rule or a hook a host needs: `getDieKey`,
|
|
404
|
+
`getTestPassStatus`, `diePassStatus`, `isYieldEligibleDie`, `hasPosition`, `filterFindings`,
|
|
405
|
+
`resolveValueColorFn`, `FACET_NONE_VALUE`, `setReportOpener` and `setDetachWindowOpener`. The two
|
|
406
|
+
constants cannot log a notice; the functions do.
|
|
407
|
+
- **The 0.31.0 removal is enforced**, not just announced: a test fails once the changelog or
|
|
408
|
+
`package.json` reaches 0.31.0 while any of these exports is still there.
|
|
409
|
+
|
|
410
|
+
### Docs
|
|
411
|
+
|
|
412
|
+
- **New example, "Bin colours across a full program"** (`docs/examples/bin-colours.html`).
|
|
413
|
+
A four-wafer gallery over a new generated dataset, `showcase-bin-rich`: 15 hard bins with two
|
|
414
|
+
pass grades and gaps in the numbering, and 32 soft bins numbered under their hard bin. Each
|
|
415
|
+
failure mode has its own spatial pattern. Every other bundled dataset has 2–6 bins, which is
|
|
416
|
+
too few to show how bin colour behaves across a real program. The soft bins deliberately
|
|
417
|
+
outnumber the palette, so the example also shows the `bin-colors-shared` warning. The data
|
|
418
|
+
comes from `scripts/gen-showcase-csvs.mjs` like the other showcase files.
|
|
419
|
+
|
|
420
|
+
### Internal
|
|
421
|
+
|
|
422
|
+
- **CI runs once per push to `main`, not twice.** `ci.yml` had its own `push` trigger while
|
|
423
|
+
`deploy.yml`, which also runs on every push, calls it as its gating job, so the same suite
|
|
424
|
+
ran twice in parallel on each commit. `ci.yml` now triggers only on pull requests (and as
|
|
425
|
+
a reusable workflow). `deploy.yml` is therefore the only thing testing pushes to `main`,
|
|
426
|
+
and says so. The README badge now points at `deploy.yml`.
|
|
427
|
+
- **The perf suite's ratio tests use the fastest of N runs instead of the median**
|
|
428
|
+
(`fastest()` in `tests/perf.test.mjs`). The two paths in a ratio are timed one after the
|
|
429
|
+
other, so background load inflated them unequally. The value-vs-hardBin check failed an
|
|
430
|
+
`npm publish` at 2.86× on a loaded machine where both paths had slowed about 7×. Absolute
|
|
431
|
+
budget tests keep the median, where a minimum would weaken the guard. The value-mode limit
|
|
432
|
+
stays at 2.5×: measured, the steadier statistic does not buy back enough headroom under
|
|
433
|
+
CPU saturation to tighten it.
|
|
434
|
+
|
|
25
435
|
## [0.29.0] — 2026-09-12
|
|
26
436
|
|
|
27
437
|
### 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 ~53 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
|
|
|
@@ -72,13 +72,13 @@ export. Add `analyzeWaferMap` for the findings and summary panel, swap in
|
|
|
72
72
|
|
|
73
73
|
The API reference is long because it documents every option, not because you need
|
|
74
74
|
them: [tsmap](https://github.com/wafertools/tsmap), a complete cross-platform desktop
|
|
75
|
-
application built on this library, imports **
|
|
75
|
+
application built on this library, imports **15** of its ~100 exports. Read the
|
|
76
76
|
[Quick Start](https://wafertools.github.io/wafermap/quickstart/) first and treat 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.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SPACE as
|
|
1
|
+
import{SPACE as nt,fontPx as ot,FONT as lt,CLR as it}from"../toolbar.js";import{cardShell as rt,formatValue as st,observeResize as ct,makeTooltip as at,positionChartTooltip as ft,makeBackButton as ht,makeSegmented as dt,growCardToFitContent as ut,resolveChartCanvasColors as mt,PADDING as f,VALUE_WIDTH as z,prepareCanvas as xt}from"./chartShell.js";import{escHtml as _}from"../../core/utils.js";const h=24,m=5,bt=15,N=110,pt=12;export function renderBarPanel(i,U){const{barColor:P,valueLabel:G}=i;let b=i.title,l=i.data;const{card:r,heading:R,controlsRow:I,body:A}=rt(b,U,i.ownerDocument);if(i.selfControl){const e=i.selfControl;I.appendChild(dt(e.options,e.current,n=>q(n),r.ownerDocument))}const{drill:d}=i;let y=!1,B=null;const L=r.ownerDocument.createElement("div");Object.assign(L.style,{color:it.label,fontSize:lt.body,marginBottom:nt.sm}),r.insertBefore(L,A);function D(){const e=[];i.onOpen&&e.push("click to open this wafer"),d&&!y&&e.push(`click a ${d.groupLabelText} to see it by wafer`);const n=e.join(", or ");let o=n?`${n[0].toUpperCase()}${n.slice(1)}.`:"";const t=i.reference?.(l);t&&(o=o?`${o} Dashed line: ${t.label}.`:`Dashed line: ${t.label}.`),L.textContent=o}D();const w=r.ownerDocument.createElement("div"),v=()=>i.reference?bt:0,E=()=>f*2+v()+Math.min(l.length,pt)*(h+m);Object.assign(w.style,{overflowX:"hidden",overflowY:"auto",minHeight:"0",flex:"1",maxHeight:`${E()}px`,scrollbarGutter:"stable"}),A.appendChild(w);const a=r.ownerDocument.createElement("canvas");a.style.display="block",a.style.cursor="default",w.appendChild(a);const k=at(r);let x=-1,M=Math.max(1,...l.map(e=>e.value));const Y=e=>G?G(e):`${st(e.value)} (${e.percent.toFixed(1)}%)`;function F(e){const n=f+v()+e*(h+m),o=f+N,t=a.clientWidth-o-z-f;return{y:n,barX:o,barMaxWidth:Math.max(10,t)}}function p(){w.style.maxHeight=`${E()}px`,ut(r,A,E());const e=w.clientWidth,n=f*2+v()+l.length*(h+m),o=xt(a,r,e,n);if(!o)return;const{ctx:t}=o;t.font=`${ot(-1)}px system-ui, sans-serif`,t.textBaseline="middle";const s=mt(r);l.forEach((c,C)=>{const{y:u,barX:T,barMaxWidth:W}=F(C);C===x&&(t.fillStyle=s.bgHover,t.fillRect(0,u-m/2,e,h+m)),t.fillStyle=s.text,t.textAlign="right";const H=c.label.length>16?`${c.label.slice(0,15)}\u2026`:c.label;t.fillText(H,f+N-8,u+h/2),t.fillStyle=s.track,t.fillRect(T,u,W,h);const S=Math.max(1,c.value/M*W);t.fillStyle=P?P(c,C):s.text,t.fillRect(T,u,S,h),t.fillStyle=s.text,t.textAlign="right",t.fillText(Y(c),T+W+z,u+h/2)});const g=i.reference?.(l);if(g&&l.length){const{barX:c,barMaxWidth:C}=F(0),u=Math.round(c+Math.max(0,Math.min(1,g.value/M))*C)+.5,T=f+v()-3,W=f+v()+l.length*(h+m)-m,H=(Z,tt,et)=>{t.save(),t.strokeStyle=Z,t.lineWidth=tt,t.setLineDash(et),t.beginPath(),t.moveTo(u,T),t.lineTo(u,W),t.stroke(),t.restore()};H(s.bg,3.5,[]),H(s.text,1.5,[4,3]),t.save(),t.font="600 10px system-ui, sans-serif",t.textBaseline="middle",t.textAlign="left";const S=4,O=t.measureText(g.label).width+S*2,X=13,j=f+v()-X-1;let $=u-O/2;$=Math.max(c,Math.min($,c+C-O)),t.fillStyle=s.bg,t.strokeStyle=s.border,t.lineWidth=1,t.beginPath(),t.rect($,j,O,X),t.fill(),t.stroke(),t.fillStyle=s.text,t.fillText(g.label,$+S,j+X/2+.5),t.restore()}}function q(e){if(!i.selfControl)return;i.selfControl.current=e;const n=i.selfControl.onChange(e);l=n.data,M=Math.max(1,...l.map(o=>o.value)),x=-1,n.title&&(b=n.title,R.textContent=b),D(),p()}function J(e){if(!d)return;const n=d.onOpenGroup(e);l=n.data,b=n.title,R.textContent=b,M=Math.max(1,...l.map(o=>o.value)),x=-1,y||(y=!0,B=ht(()=>K(),r.ownerDocument),I.appendChild(B)),D(),p()}function K(){if(!d)return;const e=d.onBack();l=e.data,b=e.title,R.textContent=b,M=Math.max(1,...l.map(n=>n.value)),x=-1,y=!1,B?.remove(),B=null,D(),p()}function V(e){const n=Math.floor((e-f+m/2)/(h+m));return n>=0&&n<l.length?n:-1}a.addEventListener("mousemove",e=>{const n=a.getBoundingClientRect(),o=V(e.clientY-n.top),t=o>=0&&d&&!y&&l[o].itemCount>1,s=o>=0&&(t||!!i.onOpen&&o>=0&&!t);if(o!==x&&(x=o,a.style.cursor=s?"pointer":"default",p()),o>=0){const g=l[o],c=t?`<br><em>click to see this ${_(d.groupLabelText)} by wafer</em>`:i.onOpen?"<br><em>click to open this wafer</em>":"";k.innerHTML=`<strong>${_(g.label)}</strong><br>${_(Y(g))}${c}`,k.style.display="block",ft(k,r,e.clientX,e.clientY)}else k.style.display="none"}),a.addEventListener("mouseleave",()=>{x!==-1&&(x=-1,p()),k.style.display="none"}),a.addEventListener("click",e=>{const n=a.getBoundingClientRect(),o=V(e.clientY-n.top);if(o===-1)return;const t=l[o];if(d&&!y&&t.itemCount>1){J(t);return}i.onOpen?.(t)});const Q=ct(r,()=>p());return p(),{card:r,destroy:()=>Q.disconnect()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildBinClusterData as
|
|
1
|
+
import{buildBinClusterData as H}from"../../stats/binPareto.js";import{SPACE as T,FONT as w,CLR as E}from"../toolbar.js";import{cardShell as L,makeTooltip as $,makeSegmented as D,renderEmptyState as I}from"./chartShell.js";import{renderGroupedBarPlot as M}from"./groupedBarPlot.js";import{escHtml as g}from"../../core/utils.js";const R=90,_=8;export function renderBinClusterPanel(s){const{groups:f,onSaveImage:h}=s;let n="hbin",c=s.title??"Hard bin pareto";const{card:o,heading:S,body:r,controlsRow:y}=L(c,h,s.ownerDocument);y.appendChild(D([["hbin","Hard bins"],["sbin","Soft bins"]],n,e=>{n=e,c=`${n==="hbin"?"Hard":"Soft"} bin pareto`,S.textContent=c,m()},o.ownerDocument));const u=o.ownerDocument.createElement("div");u.textContent="One cluster per bin \xB7 a sub-bar per group",Object.assign(u.style,{color:E.label,fontSize:w.body,marginBottom:T.sm}),o.insertBefore(u,r);const B=$(o);let l=null;function m(){l?.destroy(),l=null,r.innerHTML="";const e=H(f,n);if(e.bins.length===0||e.groups.length===0){I(r,"No bin data available for the current grouping.");return}const p=e.groups,b=e.bins,C=Math.max(1,...b.flatMap(t=>t.counts));l=M(o,r,B,{rows:b.map(t=>({label:t.label,bars:t.counts.map(a=>({fraction:a/C})),trailing:`${t.total}`})),groups:p,labelWidth:R,maxVisibleRows:_,tooltipHtml:(t,a)=>{const i=b[t],d=i.counts[a],x=i.total>0?d/i.total*100:0;return`<strong>${g(i.label)}</strong> \xB7 ${g(String(p[a]))}<br>${d} dies (${x.toFixed(1)}% of bin)`}})}return m(),{card:o,destroy:()=>l?.destroy()}}
|