@wafertools/wafermap 0.30.4 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +21 -31
- package/CHANGELOG.md +265 -1
- package/README.md +3 -3
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +89 -6
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +11 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/dieList.d.ts +1 -1
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/drilldown.js +1 -1
- package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
- package/dist/packages/canvas-adapter/exportName.js +1 -1
- 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.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +32 -34
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +38 -24
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/aggregates.d.ts +0 -6
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/dieTable.d.ts +137 -0
- package/dist/packages/core/dieTable.js +1 -0
- package/dist/packages/core/dies.d.ts +22 -2
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/index.d.ts +8 -10
- package/dist/packages/core/index.js +1 -1
- package/dist/packages/core/stdf.d.ts +9 -0
- package/dist/packages/core/stdf.js +1 -0
- package/dist/packages/core/transforms.d.ts +0 -23
- package/dist/packages/core/transforms.js +1 -1
- package/dist/packages/core/utils.d.ts +14 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +4 -12
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +42 -32
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +0 -2
- package/dist/packages/renderer/colorMap.js +1 -1
- package/dist/packages/renderer/columnarInput.d.ts +87 -0
- package/dist/packages/renderer/columnarInput.js +1 -0
- package/dist/packages/renderer/deprecate.d.ts +1 -1
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/index.d.ts +7 -5
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +3 -7
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +134 -2
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/boxplot.js +1 -1
- package/dist/packages/stats/capability.d.ts +7 -7
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +2 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.js +1 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/facets.d.ts +1 -1
- package/dist/packages/stats/filterFindings.d.ts +5 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +6 -16
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/math.d.ts +22 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/normalizeInput.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +5 -4
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/regions.d.ts +9 -1
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
- package/dist/packages/stats/renderFindingsReport.js +1 -21
- package/dist/packages/stats/renderSummaryReport.js +18 -18
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +5 -0
- package/dist/packages/worker/index.js +1 -1
- package/dist/packages/worker/wafermap.worker.d.ts +3 -0
- package/dist/packages/worker/wafermap.worker.js +1 -1
- package/package.json +2 -2
- package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
- package/dist/packages/canvas-adapter/deprecated.js +0 -1
- package/dist/packages/core/deprecated.d.ts +0 -60
- package/dist/packages/core/deprecated.js +0 -1
- package/dist/packages/renderer/deprecated.d.ts +0 -45
- package/dist/packages/renderer/deprecated.js +0 -1
- package/dist/packages/stats/deprecated.d.ts +0 -85
- package/dist/packages/stats/deprecated.js +0 -1
package/AGENTS.md
CHANGED
|
@@ -19,7 +19,7 @@ loudly over guessing.
|
|
|
19
19
|
|
|
20
20
|
### Entry points
|
|
21
21
|
|
|
22
|
-
- `@wafertools/wafermap` — `buildWaferMap()`,
|
|
22
|
+
- `@wafertools/wafermap` — `buildWaferMap()`, `registerBinColorScheme()` / `registerValueColorScheme()`. Pure, no DOM, server-safe.
|
|
23
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.
|
|
@@ -27,22 +27,13 @@ loudly over guessing.
|
|
|
27
27
|
Default path: `buildWaferMap()` once when data loads, then `renderWaferMap()` for a
|
|
28
28
|
single wafer or `renderWaferGallery()` for several.
|
|
29
29
|
|
|
30
|
-
**
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
**There is no low-level drawing pipeline, chart-data builder or region builder to reach
|
|
31
|
+
for.** The library draws through the renderers, and every figure it shows (yield, bin
|
|
32
|
+
counts, region yield, per-test statistics, capability) comes back from
|
|
33
|
+
`analyzeWaferMap()` / `analyzeWaferLot()`.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- the chart-data builders — `buildYieldData()`, `buildCorrelationMatrix()`,
|
|
37
|
-
`buildTestBoxplotData()` and the rest: these were the internals of the Insights tab,
|
|
38
|
-
which the renderers now mount for you (see below);
|
|
39
|
-
- the region builders — `buildRingRegions()`, `buildQuadrantRegions()` and friends:
|
|
40
|
-
region yield comes back from `analyzeWaferMap()`;
|
|
41
|
-
- per-die and per-colour helpers — `getDieTestValue()`, `buildHoverText()`,
|
|
42
|
-
`resolveBinColors()`, `getValueColorScheme()`, `valueToViridis()`.
|
|
43
|
-
|
|
44
|
-
If the only way to do something is through one of these, that is a library gap worth
|
|
45
|
-
reporting, not a pattern to build on.
|
|
35
|
+
If the only way to do something is to rebuild one of those pieces, that is a library gap
|
|
36
|
+
worth reporting, not a pattern to build on.
|
|
46
37
|
|
|
47
38
|
### Traps that produce silently wrong maps
|
|
48
39
|
|
|
@@ -54,12 +45,11 @@ reporting, not a pattern to build on.
|
|
|
54
45
|
pre-multiply. The geometry inputs are `waferConfig` (type `WaferConfig`) and
|
|
55
46
|
`dieConfig` (type `DieConfig`) — both optional, both inferred when omitted.
|
|
56
47
|
- **Bins and test values must be numbers, and verdicts booleans.** Every parser —
|
|
57
|
-
CSV, JSON, a spreadsheet export — hands you `"1"`, and `"1"` is not pass bin 1
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
warnings.
|
|
48
|
+
CSV, JSON, a spreadsheet export — hands you `"1"`, and `"1"` is not pass bin 1.
|
|
49
|
+
`buildWaferMap` never converts them: each is treated as missing and reported as
|
|
50
|
+
`input-values-not-numbers` (severity `'error'`), so those dies have no bin or no value
|
|
51
|
+
and a build that "works" can be mostly empty — read the warnings. Convert with
|
|
52
|
+
`Number()` at parse time.
|
|
63
53
|
- **`passBins` and `ringCount` are set once, on `buildWaferMap`, and travel on the
|
|
64
54
|
result.** Neither is an option on `analyzeWaferMap`, `analyzeWaferLot`,
|
|
65
55
|
`renderWaferMap` or `renderWaferGallery` — passing one there is a type error in
|
|
@@ -124,9 +114,8 @@ reporting, not a pattern to build on.
|
|
|
124
114
|
`insights: { enabled: true }` and it mounts the chart suite (yield, bin pareto,
|
|
125
115
|
boxplot, histogram, correlation, scatter, capability, and one card per
|
|
126
116
|
`insights.sweeps` entry). `renderWaferGallery` takes the same option across a whole
|
|
127
|
-
lot. Supply or replace the analysis later with `setStatsSummary()`.
|
|
128
|
-
|
|
129
|
-
0.31.0. Charting a selection or one wafer (right-click → histogram, capability,
|
|
117
|
+
lot. Supply or replace the analysis later with `setStatsSummary()`. There is no
|
|
118
|
+
chart-data API to hand-build them from. Charting a selection or one wafer (right-click → histogram, capability,
|
|
130
119
|
sweeps) is built in too, with no wiring.
|
|
131
120
|
- **A value computed from other tests is a derived test, not a host-side column.** Pass
|
|
132
121
|
`derivedTests` (a `TestDef` plus an `expression`, e.g. `'abs(t[1020] - t[1010])'`) to
|
|
@@ -147,9 +136,9 @@ reporting, not a pattern to build on.
|
|
|
147
136
|
but `'traffic'` and `'jet'` reads low = dark, high = light; if you draw your own
|
|
148
137
|
colorbar or swatch, resolve the colour through `resolveValueColorFn(name, reversed)`
|
|
149
138
|
so it cannot disagree with the dies.
|
|
150
|
-
-
|
|
151
|
-
`result.
|
|
152
|
-
|
|
139
|
+
- What a map was built with is on the result: `result.plotMode`, `result.metadata`,
|
|
140
|
+
`result.isLotStack`, `result.hbinDefs`, `result.sbinDefs`, `result.testDefs`. The renderers
|
|
141
|
+
read them; pass nothing again.
|
|
153
142
|
- Die keys come from `getDieKey(die)`. A hand-rolled `` `${x},${y}` `` breaks
|
|
154
143
|
click-to-highlight silently, because findings carry `dieKeys` in that exact format.
|
|
155
144
|
- `stats.warnings` is `WaferWarning[]` (it was `string[]` before 0.22.0). Read
|
|
@@ -190,9 +179,10 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
190
179
|
`computePerTestStats` is modest; `enableTestValueAnalysis` is the expensive one
|
|
191
180
|
(roughly 10× the base analysis on a large wafer) and exists to find spatial
|
|
192
181
|
patterns automatically — do not enable it by default just because it sounds good.
|
|
193
|
-
- **A Web Worker buys responsiveness, not speed.** `createWafermapWorker`
|
|
194
|
-
|
|
195
|
-
|
|
182
|
+
- **A Web Worker buys responsiveness, not speed.** `createWafermapWorker` moves a
|
|
183
|
+
result's test values to the page without copying them, but still copies the dies and the
|
|
184
|
+
input, so total time goes *up*. Use it when a build would otherwise visibly freeze the
|
|
185
|
+
page, not for small datasets.
|
|
196
186
|
- **Capability, pass rates, region yield and the spatial-pattern label come back from
|
|
197
187
|
the analysis** — `stats.capability` (with `computePerTestStats`), `stats.testSpecYield`,
|
|
198
188
|
`stats.testFlagYield`, `stats.functionalYield`, `stats.regionYield` and
|
package/CHANGELOG.md
CHANGED
|
@@ -22,7 +22,271 @@ under `### Breaking`.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
## [
|
|
25
|
+
## [0.32.0] — 2026-09-27
|
|
26
|
+
|
|
27
|
+
### Breaking
|
|
28
|
+
|
|
29
|
+
- **Input values of the wrong type are treated as missing.** A bin, site number or test value
|
|
30
|
+
that is not a number, or a pass/fail verdict that is not `true`/`false`, is left out of the
|
|
31
|
+
die — the same rule as a value outside the STDF V4 ranges — and still reported as
|
|
32
|
+
`input-values-not-numbers`. A die whose only bin was text therefore has no verdict rather
|
|
33
|
+
than a fail, which can change `yield` for such input. The input objects are not modified.
|
|
34
|
+
- **Test values on dies from `buildWaferMap` are read-only snapshots.** A map holds test values and
|
|
35
|
+
verdicts as one column per test. `die.testValues` and `die.testPass` on a built die build a frozen
|
|
36
|
+
object from those columns on each read, keeping nothing on the die, so `die.testValues !==
|
|
37
|
+
die.testValues`. Assigning to either field throws a `TypeError`; changing a key of the frozen
|
|
38
|
+
object throws in strict-mode code and is ignored otherwise. Pass the values to `buildWaferMap`,
|
|
39
|
+
or copy the die with your own object (`{ ...die, testValues: mine }`). A spread, `structuredClone` or `JSON.stringify` of a built die
|
|
40
|
+
gives plain objects with the same values. A die no longer shares the input record's
|
|
41
|
+
`testValues`/`testPass` objects, so changing the input after the build does not change the map.
|
|
42
|
+
Dies a host builds itself keep ordinary objects.
|
|
43
|
+
- **Pre-built `dies` get the same input checks as `results`.** Wrong-type and out-of-range
|
|
44
|
+
values are treated as missing, and a die whose coordinates STDF cannot store is kept as an
|
|
45
|
+
unpositioned die.
|
|
46
|
+
- **`WaferMapResult.view` is removed.** It was marked `@internal`: the renderers build their own
|
|
47
|
+
draw list whenever they draw, so a result no longer carries one. Read the result's own fields
|
|
48
|
+
(`plotMode`, `metadata`, `isLotStack`, `hbinDefs`, `sbinDefs`, `testDefs`). This makes each
|
|
49
|
+
result smaller (about 86 bytes less per die) and roughly halves the copy the Web Worker makes of
|
|
50
|
+
a result. The internal `dataAxisFlip` field takes its place.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **The legend filters to several bins or metadata values at once.** Ctrl/Cmd+click on a legend
|
|
55
|
+
entry — the map's legend or the gallery's strip, where Ctrl/Cmd+Enter/Space works from the
|
|
56
|
+
keyboard — adds or removes a value; a plain click shows only that value, or clears the
|
|
57
|
+
filter when it is the only one shown. `highlightBin` accepts `number | number[]` and
|
|
58
|
+
`highlightMetadataValue` accepts `string | string[]`.
|
|
59
|
+
- **A finding and the legend filter agree.** Clicking a finding about a bin filters the legend
|
|
60
|
+
to that bin, and one about yield or a test clears the filter — in a single map and in the
|
|
61
|
+
gallery alike. Changing the legend filter releases the finding, as changing the selection
|
|
62
|
+
does, including a selection made on a gallery card while a lot finding is active.
|
|
63
|
+
- **Lot regional findings are tested on all wafers' data together.** A yield, bin,
|
|
64
|
+
functional pass-rate, limit-fail or test-value difference in a region is combined across
|
|
65
|
+
every wafer (Stouffer's Z over each wafer's own test, weighted by die count) and reported
|
|
66
|
+
with the wafer analysis's gates, redundancy collapse and opposite-region re-test, instead of
|
|
67
|
+
by counting wafers whose own analysis reported it — a pattern present on every wafer but
|
|
68
|
+
too faint on some to pass alone is the lot's pattern. The sentence gives the lot's figure
|
|
69
|
+
and "higher/lower on N/M wafers, all wafers' data combined", N counting the wafers whose
|
|
70
|
+
region differs in that direction; `stats.method` is `'stouffer-z'`. Clusters, edge arcs and
|
|
71
|
+
spatial-pattern labels are still counted by the wafers that report them. Lot findings keep
|
|
72
|
+
absorbed restatements in the list, marked by `absorbedIds`, as wafer findings do.
|
|
73
|
+
- **Selected dies are shown by fading the rest of the map.** Every unselected die is faded
|
|
74
|
+
towards the map background and the selection is outlined, so selected dies keep their full
|
|
75
|
+
colour and it is clear which dies are selected whatever the selection's shape — a block, a
|
|
76
|
+
ring, an edge arc. A finding highlighted from the Summary panel, a gallery lot finding and
|
|
77
|
+
`setSelection` are drawn the same way. The fade and outline sit under the axes and legend.
|
|
78
|
+
- **A map opens in select mode.** A drag draws a selection box; hold Space and drag, use the
|
|
79
|
+
arrow keys, or choose Pan in the toolbar to pan.
|
|
80
|
+
- **Clicking the only selected die again clears the selection.** Clicking a die inside a
|
|
81
|
+
larger selection still selects just that die.
|
|
82
|
+
- **Changing the selection on the map releases an active finding.** A click, box select,
|
|
83
|
+
right-click, Esc or `clearSelection` clears the Summary panel's active finding and its bin
|
|
84
|
+
highlight, so the panel never shows a finding the map no longer highlights.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- **Regional findings report patterns confined to one region.** A bin, limit-fail or
|
|
89
|
+
functional-fail rate that is zero in the rest of the wafer and raised in a region (a bin
|
|
90
|
+
found only at the edge) counts as the largest relative change for the effect gate and for
|
|
91
|
+
severity, for every region family: rings, quadrants, sectors, reticle positions and test
|
|
92
|
+
sites. On a lot whose wafers carry bin 2 only at the edge, the lot finding reads "seen on
|
|
93
|
+
8/8 wafers".
|
|
94
|
+
- **Yield and functional pass-rate findings are judged by their failure rate.** The relative
|
|
95
|
+
effect of a pass rate is measured on its failures: yield 98% → 94% is failures 2% → 6%, and
|
|
96
|
+
reaches the same verdict as the bin rate of the same dies.
|
|
97
|
+
- **A region is not reported as deviating only because another region deviates more.** A
|
|
98
|
+
finding opposite in direction to a stronger finding for the same variable and region family
|
|
99
|
+
must still hold when compared with the rest of the wafer without that region, so an edge
|
|
100
|
+
rich in a bin, a limit fail or high test values no longer makes the inner rings read as low
|
|
101
|
+
in it (or high in yield).
|
|
102
|
+
- **The soft bin that restates yield is absorbed into the yield finding**, as the hard pass bin
|
|
103
|
+
is: the one soft bin every passing die carries and no failing die does.
|
|
104
|
+
- **An edge ring made of scattered fails is classified as an edge ring.** The spatial-pattern
|
|
105
|
+
classifier recognises an edge ring from the failing dies' positions — most fails at the rim
|
|
106
|
+
and spread round at least 60% of it — when no connected group of fails is large enough to
|
|
107
|
+
judge shape by, and before the scratch rule, so a short run along the rim is not a scratch.
|
|
108
|
+
Against WM-811K: detection 86.4%, edge-ring recall 75% (precision 92%), scratch recall 24%.
|
|
109
|
+
- **Lot spatial-pattern rows count related labels together and state the lot's figures.**
|
|
110
|
+
Edge-ring and edge-local wafers count as one edge pattern, and centre and donut as one,
|
|
111
|
+
naming each label's wafer count ("Spatial pattern: edge (edge-ring on 5, edge-local on 2) —
|
|
112
|
+
seen on 7/13 wafers"); the sentence gives the lot's count, not any one wafer's confidence or figures.
|
|
113
|
+
The gallery highlights each counted wafer's failing dies.
|
|
114
|
+
- Findings, their severities and "seen on N wafers" counts can change.
|
|
115
|
+
|
|
116
|
+
### Performance
|
|
117
|
+
|
|
118
|
+
- **Insights no longer blocks while its test statistics are computed.** The Overview's Test Values
|
|
119
|
+
table is built in slices, showing "Computing test statistics…" until it is ready, and when the
|
|
120
|
+
gallery's summary panel is computing the same statistics at the time, the two share the one
|
|
121
|
+
calculation instead of each doing it.
|
|
122
|
+
- **Gallery cards are drawn only when on screen.** A card below the fold keeps its map up to date
|
|
123
|
+
and is drawn as it scrolls into view. Printing and the gallery PNG draw every card first, so
|
|
124
|
+
both still show the whole gallery. On a 25-wafer, 266k-die lot, with 4 cards on screen:
|
|
125
|
+
switching to value mode takes 0.4 s in Chrome (was 1.1 s) and 0.5 s in WebKit (was 2.0 s);
|
|
126
|
+
the gallery opens in 1.8 s and 2.0 s.
|
|
127
|
+
- **The gallery draws about twice as fast in WebKit** (the desktop app on Linux and macOS, and
|
|
128
|
+
Safari). Die fills and outlines are drawn as many small canvas paths rather than one per
|
|
129
|
+
colour, which WebKit rasterises far faster. On a 25-wafer, 266k-die lot the gallery opens in
|
|
130
|
+
4.0 s (was 7.0 s) and a switch to value or stacked mode takes 1.8–2.3 s (was 3.8–4.4 s).
|
|
131
|
+
Chrome draws identical pixels; in WebKit only anti-aliased edge pixels differ.
|
|
132
|
+
- **The lot Summary report opens about five times faster on a large lot.** Its Test Values table
|
|
133
|
+
finds each test's minimum, mean and maximum in one pass instead of sorting every die's value.
|
|
134
|
+
On a 25-wafer, 266k-die lot the "Summary report" button takes 0.4 s in Chrome (was 6.0 s) and
|
|
135
|
+
0.9 s from click to report in WebKit (was 4.7 s). The report is unchanged (compared as HTML).
|
|
136
|
+
Boxplot quartiles and the histogram's outlier range are found the same way as the Summary
|
|
137
|
+
panel's, by selection, with the same figures.
|
|
138
|
+
- **Test-value analysis (`enableTestValueAnalysis`) is two to three times faster on large lots.**
|
|
139
|
+
Spec-limit findings count each region's dies in one pass instead of re-reading every die for
|
|
140
|
+
every region, the test list is read column by column, and per-test statistics find their
|
|
141
|
+
quartiles by selection. Findings are unchanged (compared byte for byte on a 266k-die lot with
|
|
142
|
+
5,289 findings). On that lot: 18.9 s → 7.2 s in Chrome, 25.6 s → 10.7 s in WebKit.
|
|
143
|
+
- **Analysis is about a third faster in Chrome on large lots.** Merging findings in adjacent
|
|
144
|
+
regions, pairing hard and soft bins that cover the same dies, and assigning dies to regions
|
|
145
|
+
now compare die positions as numbers rather than as a text key per die. Findings are
|
|
146
|
+
unchanged (compared byte for byte on a 266k-die lot). On that lot, load-time analysis takes
|
|
147
|
+
2.5 s in Chrome (was 3.7 s).
|
|
148
|
+
- **Stacked modes and mode switches in the gallery are faster.** Stacked cards key die positions by
|
|
149
|
+
number rather than by a string per die, and a map no longer rebuilds a key for every die on
|
|
150
|
+
each redraw unless dies are selected. On a 25-wafer, 266k-die lot in Chrome: switching to
|
|
151
|
+
stacked bins 2.0 s → 1.6 s, to value mode 1.3 s → 1.1 s.
|
|
152
|
+
- **The lot summary panel's test statistics are computed about three times faster.** The
|
|
153
|
+
quartiles of each test's pooled values are found by selection instead of sorting every value
|
|
154
|
+
(the same order statistics, so the same figures). On a 25-wafer, 266k-die lot the gallery
|
|
155
|
+
with its panel opens in 3.1 s in Chrome (was 3.9 s) and about 3.5 s in WebKit (was 4.0 s).
|
|
156
|
+
- **Insights opens several times faster on a large lot** (25 wafers, 266k dies: the Overview
|
|
157
|
+
2.7 s → 0.65 s in Chrome, 0.9 s in WebKit). The Overview's Test Values table reuses the
|
|
158
|
+
pooled statistics the gallery's summary panel has already computed, whether or not the lot
|
|
159
|
+
has functional tests, and the pass-rate chart reads each die's values and recorded verdicts
|
|
160
|
+
in one pass per die.
|
|
161
|
+
Figures are unchanged.
|
|
162
|
+
- **`analyzeWaferMap` is about five times faster on large wafers** (a 25-wafer, 266k-die lot:
|
|
163
|
+
32 s → 6.5 s in Chrome). Region membership, die keys and cluster neighbour lookups are each
|
|
164
|
+
computed once per analysis, not once per finding builder. Findings are unchanged.
|
|
165
|
+
- **Faster building and analysis in WebKit** (the desktop app on Linux and macOS, and Safari).
|
|
166
|
+
Checks that asked whether a die holds any test data read the die's keys one at a time and stop
|
|
167
|
+
at the first; the coverage count checks bins before test data; input checking decides each
|
|
168
|
+
test number's legality once per wafer instead of once per die. On a 266k-die lot in WebKitGTK,
|
|
169
|
+
building fell from 2.5 s to 1.1 s and analysis from 14 s to 7 s. Results are unchanged.
|
|
170
|
+
- **A hidden Summary panel is rendered when it is opened**, not on every mount and option
|
|
171
|
+
change. Every gallery card has one, so a large gallery mounts faster (266k-die lot: 7.3 s →
|
|
172
|
+
5.0 s) and switches plot mode faster (3.4 s → 1.4 s).
|
|
173
|
+
- **Test values are held as one column per test**, not as an object on each die, so a map
|
|
174
|
+
holds a small fraction of the memory it did: on a 266k-die, 51-test lot in Chrome, about 330 MB
|
|
175
|
+
where it was 1.1 GB. A result from `createWafermapWorker` moves its columns to the page without
|
|
176
|
+
copying them.
|
|
177
|
+
- **Die outlines are drawn as merged lines**, one per run of shared edges instead of four sides
|
|
178
|
+
per die. Galleries draw 10–17% faster in WebKit (the desktop app on Linux), where stroking each
|
|
179
|
+
die was most of the drawing time. Every outline is now the same weight in every browser: Chrome
|
|
180
|
+
drew the edge two dies share slightly darker than the wafer's outer edge, WebKit did not.
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
|
|
184
|
+
- **`HighlightWaferTarget.dieKeysByWafer`** — a lot regional finding names its region's dies
|
|
185
|
+
on each counted wafer, keyed by wafer index; the gallery highlights the region on every
|
|
186
|
+
counted card from it.
|
|
187
|
+
|
|
188
|
+
- **`results` can be columns (`DieColumns`).** A host that already holds its results as columns
|
|
189
|
+
(a parser, Arrow, Parquet) passes one array per field and, for test values and verdicts, the
|
|
190
|
+
indices of the records that have one with their values. The map is built with no object per
|
|
191
|
+
record or die. Retests, derived tests, input checks and warnings behave exactly as for rows.
|
|
192
|
+
Missing entries are `NaN` or STDF's missing values (−32768 for coordinates, 65535 for bins and
|
|
193
|
+
sites). Mismatched lengths or out-of-range and repeated indices throw.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## [0.31.0] — 2026-09-25
|
|
198
|
+
|
|
199
|
+
### Breaking
|
|
200
|
+
|
|
201
|
+
- **The 74 deprecated exports are removed**: the 73 deprecated in 0.30.0 (the low-level drawing
|
|
202
|
+
pipeline, the chart-data builders, the region builders and the helpers exported by accident)
|
|
203
|
+
and `renderFindingsReportHtml`, deprecated in 0.30.3. Each named its replacement in a console
|
|
204
|
+
notice. [Upgrading](https://wafertools.github.io/wafermap/upgrading/) lists every name with
|
|
205
|
+
what to use instead; the main replacements:
|
|
206
|
+
|
|
207
|
+
| Removed | Use instead |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `resolveBinColors`, `getBinColorScheme` | `binColorsForMaps(results)`, or `getBinColors()` on a map or gallery controller |
|
|
210
|
+
| `valueToViridis`, `valueToGreyscale`, `getValueColorScheme` | `resolveValueColorFn(name, reversed)` |
|
|
211
|
+
| `buildYieldData`, `buildYieldDataCombined` | `lotYieldSeries` on `analyzeWaferLot`'s result |
|
|
212
|
+
| `buildBinParetoData`, `buildBinClusterData` | `stats.hardBinCounts`, `stats.softBinCounts` |
|
|
213
|
+
| `buildCapabilityData`, `buildTestBoxplotData`, `buildTestTrendData`, `trendCentre` | `stats.capability`, `stats.perTestStats`, `perWaferTestStats` (with `computePerTestStats`) |
|
|
214
|
+
| `buildTestPassRateData`, `hasJudgeableTests`, `computeFunctionalYield` | `stats.testSpecYield`, `stats.testFlagYield`, `stats.functionalYield` |
|
|
215
|
+
| `buildRegionYieldData`, `buildRingRegions`, `buildQuadrantRegions`, `classifyDie`, `getRingLabel` | `stats.regionYield` |
|
|
216
|
+
| `classifyPattern` | `stats.spatialPattern` |
|
|
217
|
+
| `renderSummaryReportHtml`, `renderLotSummaryReportHtml`, `renderFindingsReportHtml` | `renderWaferReportHtml(result, summary?)`, `renderLotReportHtml(results)` |
|
|
218
|
+
| `openHtmlReport` | `openReportModal(html)`, `setReportOpener` |
|
|
219
|
+
| `createWafer`, `generateDies`, `clipDiesToWafer` | `buildWaferMap({ layout: true, waferConfig, dieConfig })` |
|
|
220
|
+
| `aggregateValues`, `aggregateBinCounts`, `getUniqueBins` | `buildWaferMap`'s `lotStack` |
|
|
221
|
+
| `STANDARD_WAFER_DIAMETERS_MM`, `resolveGridPitch` | `buildWaferMap`'s `standardDiameters`; each die's `width`/`height` |
|
|
222
|
+
| `getDieTestValue`, `isParametricTest`, `isPositionedDie`, `metadataCategoricalValue` | `die.testValues?.[n]`, `testType !== 'F'`, `hasPosition`, `metadataDisplayValue` |
|
|
223
|
+
|
|
224
|
+
`buildView`, `toCanvas`, `buildHoverText`, `buildMapTitle`, the transform and `affine*`
|
|
225
|
+
helpers, the histogram, scatter and correlation data builders, the sector, reticle and
|
|
226
|
+
test-site region builders, `parseRegionKey`, `contrastTextColor`, `dieHasTestData`,
|
|
227
|
+
`resolveMetadataColumns`, `discoverDieMetadataKeys`, `buildDieListSection` and
|
|
228
|
+
`DEFAULT_FACET_CURATION` have no public replacement: the renderers and the analysis do that
|
|
229
|
+
work themselves.
|
|
230
|
+
- **63 types that belonged only to removed functions are removed with them**, among them the
|
|
231
|
+
chart-data types, the draw-list types (`View`, `ViewOptions`, `ViewRect` and the rest),
|
|
232
|
+
`PitchResult`, `ToCanvasResult`, `HitTarget`, `DieListOptions` and the geometry inputs
|
|
233
|
+
`WaferSpec`, `DieSpec` and `ReticleSpec`. Upgrading lists them all. `PlotMode` stays.
|
|
234
|
+
- **`buildWaferMap` takes one argument.** The second (`WaferMapOptions`) set only the starting
|
|
235
|
+
`result.plotMode`, which the Web Worker never passed, so a build on and off the main thread
|
|
236
|
+
could start in different modes. Set the starting mode with `renderWaferMap`'s or
|
|
237
|
+
`renderWaferGallery`'s `viewOptions.plotMode`.
|
|
238
|
+
- **`downloadFilename` is a prefix for every saved file**, on `renderWaferMap` and
|
|
239
|
+
`renderWaferGallery`: `<downloadFilename>_W05_hard-bin.png`, `<downloadFilename>_W05_die-list.csv`.
|
|
240
|
+
It applies to CSVs, charts, gallery cards and detached windows as well as the map's or gallery's
|
|
241
|
+
PNG, which it no longer names outright. Parts the prefix already names are not repeated. A host
|
|
242
|
+
that matched the exact name its `onSaveImage` receives should match on the prefix.
|
|
243
|
+
- **Values outside the STDF V4 ranges are treated as missing.** 0.30.4 reported them with the
|
|
244
|
+
`input-values-outside-stdf` warning and used them as given; `buildWaferMap` now leaves them out,
|
|
245
|
+
and the warning says what it did:
|
|
246
|
+
- a bin outside 0–32767 or not a whole number: the die has no bin (so it is neither pass nor fail);
|
|
247
|
+
- a coordinate outside ±32767 or not a whole number: the die has no position, in either axis;
|
|
248
|
+
- a test number outside 0–4294967295: that test is left out of every die and of `testDefs`;
|
|
249
|
+
- a test value that is not finite: that value is left out;
|
|
250
|
+
- a site number outside 0–255: the die has no site;
|
|
251
|
+
- a `waferConfig.orientation` other than 0, 90, 180 or 270 (or an equivalent such as −90): the
|
|
252
|
+
map is built at 0. `WaferConfig.orientation` is typed `0 | 90 | 180 | 270`.
|
|
253
|
+
|
|
254
|
+
The caller's input objects are not modified. A derived test whose `testNumber` is outside
|
|
255
|
+
0–4294967295 is dropped with a `derived-test-invalid` warning.
|
|
256
|
+
- **`WaferViewOptions.showPartialDies` and `isYieldEligibleDie`'s `includePartial` are removed.**
|
|
257
|
+
No map `buildWaferMap` builds has partial dies, so neither had anything to act on. A saved
|
|
258
|
+
preference that still carries `showPartialDies` is ignored. Partial dies a host supplies
|
|
259
|
+
itself are drawn in muted grey and always left out of yield.
|
|
260
|
+
|
|
261
|
+
### Added
|
|
262
|
+
|
|
263
|
+
- **Insights charts draw spec limits as well as test limits.** The boxplot, histogram,
|
|
264
|
+
wafer-to-wafer trend and scatter show a test's spec limits (`specLow`/`specHigh`, labelled
|
|
265
|
+
LSL/USL, long dashes) beside its test limits (Lo/Hi limit, short dashes). When a test has
|
|
266
|
+
both, a **Limits** choice (Test + spec, the default; Test limits; Spec limits; None) applies to
|
|
267
|
+
all four charts. Labels that would overlap move to a second row, **Axis includes limits**
|
|
268
|
+
covers every limit shown, and the scatter marks limits outside its plotted range at the edge,
|
|
269
|
+
as the other charts do. The wafer map still judges pass/fail by the test limits.
|
|
270
|
+
- **Chart gridlines are lighter** (the border colour at 40% opacity), so the data and the limit
|
|
271
|
+
lines stand out from the grid in every theme. The scatter draws its limits in the same amber
|
|
272
|
+
as the other charts, and its limit labels sit on a panel-coloured backing where lines cross
|
|
273
|
+
them.
|
|
274
|
+
|
|
275
|
+
### Changed
|
|
276
|
+
|
|
277
|
+
- **The data-and-stats layer is ~49 KB gzipped**, from ~61 KB, with the removed exports gone.
|
|
278
|
+
|
|
279
|
+
### Fixed
|
|
280
|
+
|
|
281
|
+
- **Charts opened from a gallery card's right-click menu are saved under the card's lot and
|
|
282
|
+
wafer**, as they are from a single map.
|
|
283
|
+
|
|
284
|
+
### Documentation
|
|
285
|
+
|
|
286
|
+
- **New page, [Upgrading](https://wafertools.github.io/wafermap/upgrading/)**: what to change
|
|
287
|
+
for each breaking release, starting with 0.31.0.
|
|
288
|
+
- **Every version has a GitHub release**, created when its tag is pushed, with that version's
|
|
289
|
+
changelog section as its notes.
|
|
26
290
|
|
|
27
291
|
## [0.30.4] — 2026-09-25
|
|
28
292
|
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/wafertools/wafermap/actions/workflows/deploy.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/@wafertools/wafermap)
|
|
7
7
|

|
|
8
|
-

|
|
9
9
|
[](./LICENSE)
|
|
10
10
|
|
|
11
11
|
<img src="docs/images/hero-test-values.png" alt="wafermap demo" style="max-width:640px; display:block; margin:8px 0;" />
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
Browser-first wafer map visualization for semiconductor test data.
|
|
14
14
|
|
|
15
15
|
**Zero runtime dependencies.** Pure ES modules with TypeScript types — works in React,
|
|
16
|
-
Svelte, Vue, plain HTML, or a Web Worker. The DOM-free data-and-stats layer is ~
|
|
16
|
+
Svelte, Vue, plain HTML, or a Web Worker. The DOM-free data-and-stats layer is ~50 kB
|
|
17
17
|
min+gz; the interactive renderer is larger, and its chart suite and in-app guide are
|
|
18
18
|
loaded on demand rather than shipped up front — [measured sizes](docs/performance.md).
|
|
19
19
|
|
|
@@ -75,7 +75,7 @@ export. Add `analyzeWaferMap` for the findings and summary panel, swap in
|
|
|
75
75
|
|
|
76
76
|
The API reference is long because it documents every option, not because you need
|
|
77
77
|
them: [tsmap](https://github.com/wafertools/tsmap), a complete cross-platform desktop
|
|
78
|
-
application built on this library, imports **
|
|
78
|
+
application built on this library, imports **16** of its ~100 exports. Read the
|
|
79
79
|
[Quick Start](https://wafertools.github.io/wafermap/quickstart/) first and treat the
|
|
80
80
|
[API reference](https://wafertools.github.io/wafermap/api/) as something to search,
|
|
81
81
|
not to read.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestBoxplotData as
|
|
1
|
+
import{buildTestBoxplotData as rt}from"../../stats/boxplot.js";import{SPACE as He,fontPx as _,FONT as ct,CLR as ft}from"../toolbar.js";import{fmt as ge}from"../../renderer/fmt.js";import{fitTicks as ut}from"../../renderer/axisTicks.js";import{QUANTITY as De}from"./palette.js";import{cardShell as mt,observeResize as dt,makeTooltip as xt,positionChartTooltip as pt,makeBackButton as ht,makeLinkedTestSelect as gt,makeToggle as bt,makeLinkedAxisPrefs as yt,renderEmptyState as Ee,fitRowsHeight as Tt,setChartGrow as wt,resolveChartCanvasColors as vt,makeAxisFormat as Oe,horizontalTickSpacing as kt,resolveAxisRange as Mt,shouldIncludeLimitsByDefault as Lt,drawOffAxisLimits as Ct,limitLabelSide as At,limitLines as We,limitExtent as $t,hasBothLimitKinds as St,stackLabelRows as It,strokeLimitLine as Bt,PADDING as M,VALUE_WIDTH as Rt,WAFER_MENU_HINT as Ht,prepareCanvas as Dt}from"./chartShell.js";import{escHtml as L,maxOf as Et,minOf as Ot}from"../../core/utils.js";const C=24,A=5,Pe=110,Wt=12,_e=20;export function renderBoxplotPanel(u){const{title:te="Test value distribution",items:Ge,testDefs:be,onSaveImage:Ne,groups:g,groupLabelText:ne="group",onOpen:Q}=u,Xe=u.openActionLabel??"open that wafer",qe=u.openActionLabel??"open this wafer",{card:a,heading:Ye,body:x,controlsRow:U}=mt(te,Ne,u.ownerDocument);wt(a,"rows"),a.style.flex="0 0 auto",x.style.flex="0 0 auto",a.style.alignSelf="start";const oe=be.filter(i=>i.testNumber!==void 0);let T=u.selectedTestNumber??oe[0]?.testNumber??null,ie=!1,F=0,b=null,D=null,le=null,ye=null;function ze(){const i=!g||g.length===0?"":b??"\0overview";if(le&&ye===i)return le;const p=!g||g.length===0?Ge:b!==null?g.find(v=>v.key===b)?.items??[]:g.map(v=>({label:v.key,dies:v.items.flatMap(S=>S.dies??[])}));return le=p,ye=i,p}function Te(){b!==null&&!D?(D=ht(()=>{we(null),u.onGroupChange?.(null)},a.ownerDocument),U.appendChild(D)):b===null&&D&&(D.remove(),D=null),Ye.textContent=b!==null?`${te} \u2014 ${ne}: ${b}`:te}function we(i){return b===i||i!==null&&!(g??[]).some(p=>p.key===i)?!1:(b=i,Te(),V(),$(),!0)}const ve=gt(oe,T,i=>{T=i,$(),u.onTestChange?.(i)},{maxWidth:"240px",emptyText:"No parametric tests",ownerDocument:a.ownerDocument}),Qe=ve.el;U.appendChild(Qe),U.appendChild(bt("Log scale",ie,i=>{ie=i,$()},a.ownerDocument));const se=a.ownerDocument.createElement("span");Object.assign(se.style,{display:"inline-flex",gap:He.lg,alignItems:"center"}),U.appendChild(se);const ae=yt(se,u.axisPrefs,i=>{u.onAxisPrefsChange?.(i),$()},a.ownerDocument),re=a.ownerDocument.createElement("div");Object.assign(re.style,{color:ft.label,fontSize:ct.body,marginBottom:He.sm}),a.insertBefore(re,x);function V(){const i=!!g&&g.length>0&&b===null,p=[];i?p.push(`click a ${ne}'s box to see it by wafer`):Q&&p.push(`click a box to ${Xe}`);const v=p.length?`${p[0][0].toUpperCase()}${p[0].slice(1)} \xB7 `:"";re.textContent=`${v}box = Q1\u2013Q3, line = median, whiskers = min/max \xB7 value shown is the median`+(F?` \xB7 axis clipped, ${F} value${F===1?"":"s"} outside`:"")}V(),Te();const E=xt(a);let ce=null;function $(){const{includeLimits:i,clipOutliers:p,limits:v}=ae.get();if(V(),x.innerHTML="",oe.length===0||T===null){Ee(x,"No parametric test data available for box plots.");return}const S=ze(),h=rt(S,T),j=be.find(t=>t.testNumber===T),O=j?.unit,fe=We(j,v),{lo:Ve,hi:je}=$t(fe);if(h.every(t=>t.count===0)){Ee(x,"No parametric test data available for box plots.");return}const m=a.ownerDocument.createElement("canvas");m.style.display="block",m.style.cursor="default",x.appendChild(m);const ke=M*2+Math.min(h.length,Wt)*(C+A)+_e;x.style.maxHeight=`${ke}px`,x.style.overflowY="auto",x.style.scrollbarGutter="stable";let G=-1;const ue=h.filter(t=>t.count>0),Me=Ot(ue.map(t=>t.min)),Le=Et(ue.map(t=>t.max)),Ce=i??Lt(Me,Le,Ve,je);ae.sync(Ce,We(j).length>0,St(j));const N=Mt({dataMin:Me,dataMax:Le,limits:fe,includeLimits:Ce,clipOutliers:p,values:ue.flatMap(t=>[t.min,t.q1,t.median,t.q3,t.max])});F=N.clippedCount,V();const W=N.lo,K=N.hi,Ke=K-W||1,J=ie&&W>0,me=J?Math.log10(W):0,Ae=(J?Math.log10(K):0)-me||1,de=Math.max(Math.abs(W),Math.abs(K));function Je(){const t=M+Pe,s=m.clientWidth-t-Rt-M;return{plotX:t,plotMaxWidth:Math.max(10,s)}}function I(t,s,n){return J?s+(Math.log10(t)-me)/Ae*n:s+(t-W)/Ke*n}function Ze(t,s){if(J){const d=[];for(let e=0;e<=4;e++)d.push(Math.pow(10,me+Ae*e/4));return{ticks:d,axis:Oe(de,O)}}const n=ut(W,K,s,kt(t,de,O));return{ticks:n.ticks,axis:Oe(de,O,n.step||void 0)}}function Z(){const t=vt(a),s=M*2+h.length*(C+A)+_e;x.style.maxHeight=`${Tt(a,x,ke,s)}px`;const n=x.clientWidth,d=Dt(m,a,n,s);if(!d)return;const{ctx:e}=d;e.font=`${_(-1)}px system-ui, sans-serif`,e.textBaseline="middle";const{plotX:l,plotMaxWidth:c}=Je();h.forEach((o,f)=>{const y=M+f*(C+A),r=y+C/2;f===G&&(e.fillStyle=t.bgHover,e.fillRect(0,y-A/2,n,C+A)),e.fillStyle=t.text,e.textAlign="right";const ee=o.label.length>16?`${o.label.slice(0,15)}\u2026`:o.label;if(e.fillText(ee,M+Pe-8,r),o.count===0){e.fillStyle=t.textMuted,e.textAlign="left",e.fillText("no data",l,r);return}const it=l+c,q=at=>Math.min(it,Math.max(l,at)),Ie=I(o.min,l,c),Be=I(o.max,l,c),B=q(Ie),Y=q(I(o.q1,l,c)),Re=q(I(o.median,l,c)),he=q(I(o.q3,l,c)),R=q(Be),lt=Ie<B,st=Be>R,H=y+3,z=y+C-3;e.strokeStyle=t.textMuted,e.lineWidth=1,e.beginPath(),e.moveTo(B,r),e.lineTo(Y,r),e.moveTo(he,r),e.lineTo(R,r);const k=3;lt?(e.moveTo(B+k,r-k),e.lineTo(B,r),e.lineTo(B+k,r+k)):(e.moveTo(B,H),e.lineTo(B,z)),st?(e.moveTo(R-k,r-k),e.lineTo(R,r),e.lineTo(R-k,r+k)):(e.moveTo(R,H),e.lineTo(R,z)),e.stroke(),e.fillStyle=De,e.globalAlpha=.35,e.fillRect(Y,H,Math.max(1,he-Y),z-H),e.globalAlpha=1,e.strokeStyle=De,e.strokeRect(Y,H,Math.max(1,he-Y),z-H),e.strokeStyle=t.text,e.lineWidth=2,e.beginPath(),e.moveTo(Re,H),e.lineTo(Re,z),e.stroke(),e.lineWidth=1,e.fillStyle=t.textMuted,e.textAlign="left",e.fillText(ge(o.median,O,"engineering"),l+c+10,r)});const w=M+h.length*(C+A);e.font=`${_(-1)}px system-ui, sans-serif`,Ct(e,N.offAxis,{left:l,right:l+c,top:0,bottom:w},"horizontal",t.limitLine,o=>ge(o,O,"engineering"));const et=new Set(N.offAxis.map(o=>o.value)),Se=fe.filter(o=>!et.has(o.value)).map(o=>{const f=I(o.value,l,c),y=e.measureText(o.label).width,r=At(f,y,l,l+c,o.end==="lo"),ee=r<0?f-3-y:f+3;return{l:o,x:f,dir:r,start:ee,end:ee+y}}),tt=It(Se),nt=_(-1)+2;Se.forEach(({l:o,x:f,dir:y},r)=>{Bt(e,o,t.limitLine,f,0,f,w),e.fillStyle=t.limitLine,e.textAlign=y<0?"right":"left",e.textBaseline="bottom",e.fillText(o.label,f+y*3,w-1-tt[r]*nt)}),e.font=`${_(-1)}px system-ui, sans-serif`,e.strokeStyle=t.border,e.lineWidth=1,e.beginPath(),e.moveTo(l,w),e.lineTo(l+c,w),e.stroke(),e.fillStyle=t.textMuted,e.textAlign="center",e.textBaseline="top";const{ticks:ot,axis:pe}=Ze(e,c);for(const o of ot){const f=I(o,l,c);e.beginPath(),e.moveTo(f,w),e.lineTo(f,w+4),e.stroke(),e.fillText(pe.tick(o),f,w+6)}pe.unitLabel&&(e.textAlign="left",e.font=`${_(-1)}px system-ui, sans-serif`,e.fillText(`(${pe.unitLabel})`,l+c+22,w+6),e.font=`${_(-1)}px system-ui, sans-serif`),e.textBaseline="middle"}function xe(t){const s=Math.floor((t-M+A/2)/(C+A));return s>=0&&s<h.length?s:-1}function X(t){return ge(t,O,"engineering")}const P=!!g&&g.length>0&&b===null,$e=t=>!P&&!!Q&&S[t]?.waferIndex!==void 0;m.addEventListener("mousemove",t=>{const s=m.getBoundingClientRect(),n=xe(t.clientY-s.top),d=n>=0&&h[n].count>0&&(P||$e(n));if(n!==G&&(G=n,m.style.cursor=d?"pointer":"default",Z()),n>=0&&h[n].count>0){const e=h[n],l=P?`<br><em>click to see this ${L(ne)} by wafer</em>`:$e(n)?`<br><em>click to ${L(qe)}</em>`:"",c=!P&&u.onWaferContextMenu&&S[n]?.waferIndex!==void 0?Ht:"";E.innerHTML=`<strong>${L(e.label)}</strong> (${e.count} dies)<br>max ${L(X(e.max))}<br>q3 ${L(X(e.q3))}<br>median ${L(X(e.median))}<br>q1 ${L(X(e.q1))}<br>min ${L(X(e.min))}${l}${c}`,E.style.display="block",pt(E,a,t.clientX,t.clientY)}else E.style.display="none"}),m.addEventListener("mouseleave",()=>{G!==-1&&(G=-1,Z()),E.style.display="none"}),m.addEventListener("click",t=>{const s=m.getBoundingClientRect(),n=xe(t.clientY-s.top);if(n===-1||h[n].count===0)return;if(P){const e=h[n].label;we(e),u.onGroupChange?.(e);return}const d=S[n]?.waferIndex;Q&&d!==void 0&&T!==null&&Q(d,T)}),m.addEventListener("contextmenu",t=>{const s=m.getBoundingClientRect(),n=xe(t.clientY-s.top),d=n===-1?void 0:S[n]?.waferIndex;P||d===void 0||!u.onWaferContextMenu||(E.style.display="none",u.onWaferContextMenu(d,T??void 0,t))}),ce?.disconnect(),ce=dt(a,()=>Z()),Z()}$();function Ue(i){ve.set(i)&&(T=i,$())}function Fe(i){ae.set(i)&&$()}return{card:a,setTest:Ue,setAxisPrefs:Fe,destroy:()=>ce?.disconnect()}}
|
|
@@ -43,7 +43,20 @@ export interface ChartCanvasColors {
|
|
|
43
43
|
* limit line and its label need.
|
|
44
44
|
*/
|
|
45
45
|
limitLine: string;
|
|
46
|
+
/**
|
|
47
|
+
* Gridlines: `border` at `GRID_ALPHA`. A gridline is a reading aid behind the
|
|
48
|
+
* data; drawn at full `border` strength (the axis colour) it competed with the
|
|
49
|
+
* points and with the limit lines, which in a theme with a strong border were
|
|
50
|
+
* hard to pick out from the grid at all.
|
|
51
|
+
*/
|
|
52
|
+
grid: string;
|
|
46
53
|
}
|
|
54
|
+
/** Opacity of gridlines relative to the border colour. */
|
|
55
|
+
export declare const GRID_ALPHA = 0.4;
|
|
56
|
+
/** `color` at `alpha` × its own opacity. Understands the forms a computed
|
|
57
|
+
* custom property holds — `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb()`, `rgba()` —
|
|
58
|
+
* and returns anything else unchanged rather than guessing. */
|
|
59
|
+
export declare function withAlpha(color: string, alpha: number): string;
|
|
47
60
|
/** Resolve `--wmap-*` custom properties to concrete color strings for canvas
|
|
48
61
|
* drawing. Call once per draw (not cached) so a live theme change is picked
|
|
49
62
|
* up on the next redraw, matching `resolveCanvasTheme`'s own contract. */
|
|
@@ -349,6 +362,7 @@ export interface LinkedAxisPrefs {
|
|
|
349
362
|
get(): {
|
|
350
363
|
includeLimits: boolean | undefined;
|
|
351
364
|
clipOutliers: boolean;
|
|
365
|
+
limits: LimitsShown;
|
|
352
366
|
};
|
|
353
367
|
/**
|
|
354
368
|
* Rebuild the toggles for the state the panel has just RESOLVED.
|
|
@@ -356,9 +370,10 @@ export interface LinkedAxisPrefs {
|
|
|
356
370
|
* Called from the redraw, because `includeLimits` is a tri-state: undefined
|
|
357
371
|
* means "decide from the data" (`shouldIncludeLimitsByDefault`), and the
|
|
358
372
|
* resolved answer is only known once the data range is. `hasLimits` decides
|
|
359
|
-
* whether the limits toggle is offered at all
|
|
373
|
+
* whether the limits toggle is offered at all, and `hasBothKinds` whether the
|
|
374
|
+
* "Limits:" choice between test and spec limits is.
|
|
360
375
|
*/
|
|
361
|
-
sync(resolvedIncludeLimits: boolean, hasLimits: boolean): void;
|
|
376
|
+
sync(resolvedIncludeLimits: boolean, hasLimits: boolean, hasBothKinds?: boolean): void;
|
|
362
377
|
/**
|
|
363
378
|
* Adopt prefs chosen elsewhere. Returns false — changing nothing — when they
|
|
364
379
|
* already match. Deliberately does NOT fire `onUserChange`, the same
|
|
@@ -490,7 +505,72 @@ export declare function positionChartTooltip(tooltip: HTMLElement, card: HTMLEle
|
|
|
490
505
|
export interface AxisPrefs {
|
|
491
506
|
includeLimits?: boolean;
|
|
492
507
|
clipOutliers: boolean;
|
|
508
|
+
/** Which limits the charts draw. Undefined = `'both'`. */
|
|
509
|
+
limits?: LimitsShown;
|
|
493
510
|
}
|
|
511
|
+
export type LimitKind = 'test' | 'spec';
|
|
512
|
+
/** The Insights "Limits" choice. */
|
|
513
|
+
export type LimitsShown = 'test' | 'spec' | 'both' | 'none';
|
|
514
|
+
export interface LimitLine {
|
|
515
|
+
value: number;
|
|
516
|
+
kind: LimitKind;
|
|
517
|
+
/** Which end of its pair: the low limit or the high one. */
|
|
518
|
+
end: 'lo' | 'hi';
|
|
519
|
+
label: string;
|
|
520
|
+
}
|
|
521
|
+
export declare const LIMIT_LABEL: Readonly<Record<LimitKind, {
|
|
522
|
+
lo: string;
|
|
523
|
+
hi: string;
|
|
524
|
+
}>>;
|
|
525
|
+
/** Canvas dash pattern per kind: short dashes for test limits, long for spec. */
|
|
526
|
+
export declare const LIMIT_DASH: Readonly<Record<LimitKind, number[]>>;
|
|
527
|
+
export declare const LIMITS_SHOWN_OPTIONS: ReadonlyArray<{
|
|
528
|
+
value: LimitsShown;
|
|
529
|
+
label: string;
|
|
530
|
+
}>;
|
|
531
|
+
type LimitSource = {
|
|
532
|
+
limitLow?: number;
|
|
533
|
+
limitHigh?: number;
|
|
534
|
+
specLow?: number;
|
|
535
|
+
specHigh?: number;
|
|
536
|
+
} | undefined;
|
|
537
|
+
/** Whether a test has both kinds of limit — the only case with a choice to make. */
|
|
538
|
+
export declare function hasBothLimitKinds(def: LimitSource): boolean;
|
|
539
|
+
/**
|
|
540
|
+
* The limit lines to draw for a test.
|
|
541
|
+
*
|
|
542
|
+
* `'none'` draws nothing. Otherwise the chosen kinds are drawn — and when the
|
|
543
|
+
* test has none of the chosen kind but does have the other, the other is drawn
|
|
544
|
+
* rather than nothing: a test with only test limits must not look unlimited
|
|
545
|
+
* because another test's spec limits were chosen. Each line carries its own
|
|
546
|
+
* label, so which kind is on screen is never in doubt.
|
|
547
|
+
*/
|
|
548
|
+
export declare function limitLines(def: LimitSource, shown?: LimitsShown): LimitLine[];
|
|
549
|
+
/** The span the lines cover, for axis-range decisions. */
|
|
550
|
+
export declare function limitExtent(lines: readonly LimitLine[]): {
|
|
551
|
+
lo?: number;
|
|
552
|
+
hi?: number;
|
|
553
|
+
};
|
|
554
|
+
/**
|
|
555
|
+
* Stack labels into rows so none overlaps another in the same row. Each label
|
|
556
|
+
* is an interval along the axis it is placed on; returns a row index per label
|
|
557
|
+
* (0 = the chart's usual label position), in input order. Greedy by position,
|
|
558
|
+
* so it is stable: labels that never collide all stay on row 0.
|
|
559
|
+
*/
|
|
560
|
+
export declare function stackLabelRows(spans: ReadonlyArray<{
|
|
561
|
+
start: number;
|
|
562
|
+
end: number;
|
|
563
|
+
}>, gap?: number): number[];
|
|
564
|
+
/**
|
|
565
|
+
* `fillText` on a backing of `halo` (the panel colour), so a label stays
|
|
566
|
+
* readable where a gridline, a limit line or data runs under it. Uses the
|
|
567
|
+
* current font, alignment and baseline; `halo` undefined draws plain text.
|
|
568
|
+
*/
|
|
569
|
+
export declare function fillTextOnHalo(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, halo?: string): void;
|
|
570
|
+
/** Stroke one limit line in its kind's dash pattern. */
|
|
571
|
+
export declare function strokeLimitLine(ctx: CanvasRenderingContext2D, line: LimitLine, color: string, x0: number, y0: number, x1: number, y1: number): void;
|
|
572
|
+
/** The "Limits:" dropdown, offered only where a test has both kinds. */
|
|
573
|
+
export declare function makeLimitsSelect(current: LimitsShown, onChange: (v: LimitsShown) => void, ownerDocument?: Document): HTMLLabelElement;
|
|
494
574
|
/** Robust outlier fence over a value list: Tukey's `Q1 − k·IQR … Q3 + k·IQR`.
|
|
495
575
|
*
|
|
496
576
|
* Deliberately NOT mean ± 3σ. σ is computed FROM the data including the
|
|
@@ -524,7 +604,7 @@ export interface AxisRange {
|
|
|
524
604
|
* is merely off-screen is indistinguishable from a test having no limit. */
|
|
525
605
|
offAxis: Array<{
|
|
526
606
|
value: number;
|
|
527
|
-
label:
|
|
607
|
+
label: string;
|
|
528
608
|
side: 'lo' | 'hi';
|
|
529
609
|
}>;
|
|
530
610
|
/** Values excluded by the robust fence, when clipping is on. */
|
|
@@ -542,8 +622,8 @@ export interface AxisRange {
|
|
|
542
622
|
export declare function resolveAxisRange(opts: {
|
|
543
623
|
dataMin: number;
|
|
544
624
|
dataMax: number;
|
|
545
|
-
|
|
546
|
-
|
|
625
|
+
/** The limit lines the chart draws (`limitLines`). */
|
|
626
|
+
limits?: readonly LimitLine[];
|
|
547
627
|
includeLimits: boolean;
|
|
548
628
|
clipOutliers?: boolean;
|
|
549
629
|
/** Raw values, needed only when `clipOutliers` is set. */
|
|
@@ -580,5 +660,8 @@ export declare function drawOffAxisLimits(ctx: CanvasRenderingContext2D, offAxis
|
|
|
580
660
|
right: number;
|
|
581
661
|
top: number;
|
|
582
662
|
bottom: number;
|
|
583
|
-
}, orient: 'horizontal' | 'vertical', color: string, format: (v: number) => string
|
|
663
|
+
}, orient: 'horizontal' | 'vertical', color: string, format: (v: number) => string,
|
|
664
|
+
/** Panel colour to back each marker with (`fillTextOnHalo`), for charts
|
|
665
|
+
* where lines can run under the markers. */
|
|
666
|
+
halo?: string): void;
|
|
584
667
|
//# sourceMappingURL=chartShell.d.ts.map
|