@wafertools/wafermap 0.30.2 → 0.30.4
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 +16 -5
- package/CHANGELOG.md +432 -0
- package/README.md +6 -3
- package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
- package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -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/chartShell.d.ts +82 -8
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +5 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/dieList.js +2 -2
- package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
- package/dist/packages/canvas-adapter/drilldown.js +1 -0
- package/dist/packages/canvas-adapter/icons.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +65 -3
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +30 -2
- 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 +162 -37
- 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/dies.d.ts +1 -1
- package/dist/packages/renderer/axisTicks.d.ts +32 -0
- package/dist/packages/renderer/axisTicks.js +1 -0
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +11 -37
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +97 -12
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +13 -5
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
- package/dist/packages/renderer/derivedTests/apply.js +1 -0
- package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
- package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
- package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
- package/dist/packages/renderer/derivedTests/parser.js +2 -0
- package/dist/packages/renderer/fmt.d.ts +24 -2
- package/dist/packages/renderer/fmt.js +1 -1
- package/dist/packages/renderer/index.d.ts +1 -0
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +66 -0
- package/dist/packages/renderer/spec.js +1 -0
- package/dist/packages/renderer/testLabel.d.ts +154 -0
- package/dist/packages/renderer/testLabel.js +1 -0
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +8 -2
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/correlation.d.ts +4 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/deprecated.d.ts +4 -1
- package/dist/packages/stats/deprecated.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -2
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +8 -28
- package/dist/packages/stats/renderSummaryReport.js +19 -28
- package/dist/packages/stats/reportHtml.d.ts +27 -0
- package/dist/packages/stats/reportHtml.js +35 -14
- package/dist/packages/stats/sweep.d.ts +186 -0
- package/dist/packages/stats/sweep.js +1 -0
- package/dist/packages/stats/sweepXFromName.d.ts +27 -0
- package/dist/packages/stats/sweepXFromName.js +1 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/types.d.ts +37 -4
- package/llms.txt +3 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -122,10 +122,21 @@ reporting, not a pattern to build on.
|
|
|
122
122
|
- **The analysis surfaces are already built — do not reimplement them.** Pass
|
|
123
123
|
`statsSummary` to `renderWaferMap` and it mounts the Summary panel; pass
|
|
124
124
|
`insights: { enabled: true }` and it mounts the chart suite (yield, bin pareto,
|
|
125
|
-
boxplot, histogram, correlation, scatter, capability
|
|
126
|
-
same option across a whole
|
|
127
|
-
`setStatsSummary()`. Hand-building
|
|
128
|
-
builders were for, and they go in
|
|
125
|
+
boxplot, histogram, correlation, scatter, capability, and one card per
|
|
126
|
+
`insights.sweeps` entry). `renderWaferGallery` takes the same option across a whole
|
|
127
|
+
lot. Supply or replace the analysis later with `setStatsSummary()`. Hand-building
|
|
128
|
+
those charts is what the deprecated chart-data builders were for, and they go in
|
|
129
|
+
0.31.0. Charting a selection or one wafer (right-click → histogram, capability,
|
|
130
|
+
sweeps) is built in too, with no wiring.
|
|
131
|
+
- **A value computed from other tests is a derived test, not a host-side column.** Pass
|
|
132
|
+
`derivedTests` (a `TestDef` plus an `expression`, e.g. `'abs(t[1020] - t[1010])'`) to
|
|
133
|
+
`buildWaferMap`; it then behaves as a measured test everywhere, marked `†` as not
|
|
134
|
+
measured. Computing it in the host and injecting it into `testValues` loses that
|
|
135
|
+
mark, the missing-input rule (absent, never 0) and the collision check. A boolean
|
|
136
|
+
expression must be declared `testType: 'F'`.
|
|
137
|
+
- **A sweep's x values are data, not guesses.** Give `xValues`, or `xFromName` (a
|
|
138
|
+
`{x}` placeholder pattern, not a regex) when the swept value is only in the test
|
|
139
|
+
name. Never derive x from test numbers: they are identifiers, not a scale.
|
|
129
140
|
- **Click-to-highlight is wired, not hand-rolled.** `onSelect` reports what the user
|
|
130
141
|
picked; `setSelection(dies)` / `clearSelection()` drive it from your own UI — for
|
|
131
142
|
example from a finding, whose `dieKeys` match `getDieKey(die)` exactly.
|
|
@@ -240,7 +251,7 @@ compatibility shims for them.
|
|
|
240
251
|
- [API reference](https://wafertools.github.io/wafermap/api/) — every type, option and return value
|
|
241
252
|
- [Developer guide](https://wafertools.github.io/wafermap/guide/) — worked walkthroughs
|
|
242
253
|
- [Troubleshooting](https://wafertools.github.io/wafermap/troubleshooting/)
|
|
243
|
-
- [Examples](https://wafertools.github.io/wafermap/examples/) —
|
|
254
|
+
- [Examples](https://wafertools.github.io/wafermap/examples/) — 22 runnable pages, also
|
|
244
255
|
[downloadable](https://wafertools.github.io/wafermap/wafermap-examples.zip) to run offline
|
|
245
256
|
|
|
246
257
|
When a rule here and the API reference disagree, the API reference wins — tell the
|
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,438 @@ under `### Breaking`.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## [Unreleased]
|
|
26
|
+
|
|
27
|
+
## [0.30.4] — 2026-09-25
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`input-values-outside-stdf` warning** from `buildWaferMap`: bins, coordinates, test
|
|
32
|
+
numbers or site numbers outside the STDF V4 ranges, test values that are not finite, and a
|
|
33
|
+
`waferConfig.orientation` other than 0, 90, 180 or 270. The values are used as given; a
|
|
34
|
+
future release treats them as missing.
|
|
35
|
+
- **`DieResult.supersedes`** (`'partId' | 'position'`): the tester marked the record as
|
|
36
|
+
replacing an earlier one. Such a record always wins, whatever `retestPolicy` says.
|
|
37
|
+
- **Retests of dies with no position are matched by part ID** within a wafer, resolved by
|
|
38
|
+
`retestPolicy` and reported with an `info` warning, `retests-by-part-id`. Blank part IDs
|
|
39
|
+
never match, and part IDs are not used on a wafer where one value covers more than 20% of
|
|
40
|
+
the unpositioned records.
|
|
41
|
+
- **`TestDef.specLow` / `TestDef.specHigh`**: a test's spec limits, separate from its test
|
|
42
|
+
limits (`limitLow`/`limitHigh`), as STDF records them. Process capability uses the spec
|
|
43
|
+
limits when both are given and the test limits otherwise, and reports which as
|
|
44
|
+
`TestCapability.limitBasis`. Wafers that disagree on a test's spec limits raise a
|
|
45
|
+
`specLimits` test-definition conflict.
|
|
46
|
+
- **Sweeps tab notice for sweeps that name no test in the data**, and
|
|
47
|
+
**`InsightsOptions.onRemoveSweeps`**, which adds a Remove button to it. The sweep's own card
|
|
48
|
+
says the same in place of an empty chart.
|
|
49
|
+
- **`TestDef.limitLowInclusive` / `TestDef.limitHighInclusive`**: whether a value exactly
|
|
50
|
+
equal to a test limit passes (default `true`), as STDF and ATDF state per test. Every
|
|
51
|
+
pass/fail judgement, yield and out-of-limit marker honours them. Wafers that disagree on
|
|
52
|
+
them raise a `limits` test-definition conflict.
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- **`DieResult.partId` and `Die.partId` accept text** (`number | string`), as STDF defines
|
|
57
|
+
part IDs; a number is still accepted.
|
|
58
|
+
- **Scatter chart legend:** hard bin 0 has its own entry and colour, and dies without a bin
|
|
59
|
+
are listed last as "No bin data".
|
|
60
|
+
- **A `NaN` bin is no bin**: it is neither a bin number nor a fail, and is counted in the new
|
|
61
|
+
warning.
|
|
62
|
+
- **One spec-limit judgement** (`classifySpec`) serves every surface, including the per-test
|
|
63
|
+
spec yield, lot-stack fail filtering, regional spec-fail findings and the Summary panel.
|
|
64
|
+
A value that is not a finite number has no verdict everywhere, rather than counting as a
|
|
65
|
+
pass on some surfaces.
|
|
66
|
+
- **Bin colours** for a bin that is not a whole number are taken from the whole bin below it.
|
|
67
|
+
- **Test limits and spec limits are labelled apart.** `limitLow`/`limitHigh` are test limits,
|
|
68
|
+
labelled "Lo limit"/"Hi limit" on charts; LSL/USL appear only where a test's spec limits
|
|
69
|
+
are shown. The pass/fail option is "Limit pass/fail", the Summary panel's yield column is
|
|
70
|
+
"Limit yield", and the capability chart, tooltip and report name the limits each test was
|
|
71
|
+
judged against.
|
|
72
|
+
|
|
73
|
+
## [0.30.3] — 2026-09-23
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- **Sweep x values from test names, an x unit, and a log x axis.** For a
|
|
78
|
+
program that records the swept quantity only in the test text — a
|
|
79
|
+
resistance CDF named `Normalized_LRS= LRS_STATS_12K / …`, one test per
|
|
80
|
+
threshold:
|
|
81
|
+
- `SweepSeriesSpec.xFromName` reads each test's x from its name with a
|
|
82
|
+
placeholder pattern, `"LRS_STATS_{x}"`: `{x}` reads a number with its SI
|
|
83
|
+
prefix (`12K` → 12,000; case-sensitive, `K` accepted as kilo; a letter is a
|
|
84
|
+
prefix only alone or before a unit, so `12Kangaroos` reads 12; in an all-capitals
|
|
85
|
+
name the unit's case is gone, so `5NS` is 5 ns and `2MV` — milli or mega? — is
|
|
86
|
+
reported, not guessed), `*` matches
|
|
87
|
+
anything, literal text matches in any case, anywhere in the name. Not a
|
|
88
|
+
regular expression: a sweeps file is shared, and JavaScript cannot
|
|
89
|
+
interrupt a runaway regex, whereas this matcher's cost is bounded whatever
|
|
90
|
+
the pattern (`stats/sweepXFromName.ts`). Each x stays attached to its own
|
|
91
|
+
test, so a missing test loses one point instead of shifting the rest.
|
|
92
|
+
- `SweepSpec.xUnit` prints the x axis, crossing and widths SI-prefixed
|
|
93
|
+
(`47.3 kΩ`), trailing zeros dropped (`2 kΩ`, not `2.00 kΩ`).
|
|
94
|
+
- `SweepSpec.xScale: 'log'` places x on a log axis; the crossing is
|
|
95
|
+
interpolated along log x and a width is reported as a ratio with both ends
|
|
96
|
+
(`×2.49 (15.9 kΩ → 39.7 kΩ)`), via new `SweepSeparation.ratio`/`from`/`to`.
|
|
97
|
+
`SweepData.xScale` is the axis actually drawn: a non-positive x keeps it
|
|
98
|
+
linear, with a warning.
|
|
99
|
+
- Reported, never guessed, and then not measured: a name the pattern does not
|
|
100
|
+
fit, a series giving both `xValues` and `xFromName`, and — for every x
|
|
101
|
+
source — a series that revisits an x value, which the crossing previously
|
|
102
|
+
mis-paired silently.
|
|
103
|
+
|
|
104
|
+
- **Drilldown: chart a selection or a wafer.** Right-click a population for a
|
|
105
|
+
menu of charts drawn from just those dies, opened in the same modal as an
|
|
106
|
+
expanded Insights card:
|
|
107
|
+
- **Populations:** the dies selected on a map (a single map or any gallery
|
|
108
|
+
card); a whole wafer, from empty map space with nothing selected, from a
|
|
109
|
+
gallery card anywhere outside its map, or from one wafer's bar, box or point
|
|
110
|
+
in *Yield by wafer*, *Test value distribution* or *Wafer-to-wafer trend* in
|
|
111
|
+
Insights (their tooltips say "right-click to chart this wafer"). The Menu
|
|
112
|
+
key, Shift+F10 and a new map toolbar **Chart** button (the selection if
|
|
113
|
+
there is one, else the wafer) reach the same menu without a mouse.
|
|
114
|
+
- **Charts:** value histogram (opening on the test the map shows), process
|
|
115
|
+
capability, and the sweeps defined in `insights.sweeps` — the gallery now
|
|
116
|
+
passes those to each card, and a single map offers them whether or not
|
|
117
|
+
`insights.enabled` is set.
|
|
118
|
+
- **Population stated on every chart**, in the modal title and on the card
|
|
119
|
+
("12 dies selected on W03", "10 of 12 … (partial and edge-excluded dies left
|
|
120
|
+
out)"). Capability below 30 dies adds that each Ppk is a rough estimate.
|
|
121
|
+
Charts are snapshots: changing the selection afterwards does not change them.
|
|
122
|
+
The wafer is named by the item's `label`, else its `metadata.waferId` — never a
|
|
123
|
+
positional "Wafer 3 (no ID)" — on the map, the gallery and Insights alike.
|
|
124
|
+
- **The library decides what is offered:** a chart that cannot be drawn stays
|
|
125
|
+
listed, greyed, with the reason (no values for its tests; fewer than two
|
|
126
|
+
dies for capability; a lot-stack map, whose dies are per-position aggregates
|
|
127
|
+
rather than measured dies). Right-clicking an unselected die selects it
|
|
128
|
+
first. A map with no parametric tests and no sweeps leaves right-click to
|
|
129
|
+
the browser or host, and the map canvas owns right-click on itself: one it
|
|
130
|
+
declines (the bin legend) never reaches a gallery card or host handler.
|
|
131
|
+
- Documented in api.md §5.12, the user guide §4.4 (with screenshots from
|
|
132
|
+
`capture-definitions.mjs`) and the developer guide.
|
|
133
|
+
- Built on one source/target mechanism (`canvas-adapter/chartPopulation.ts`
|
|
134
|
+
builds the populations, `drilldown.ts` the menu and charts, loaded on first
|
|
135
|
+
use and pinned by `tests/bundle-size.test.mjs`), so a further population or
|
|
136
|
+
chart is one more of either rather than a menu per pairing.
|
|
137
|
+
|
|
138
|
+
- **Derived tests** — `WaferMapInput.derivedTests`: tests computed from other tests
|
|
139
|
+
on the same die, rather than measured. Each entry is a `TestDef` plus an
|
|
140
|
+
`expression`, and from the build onwards it is an ordinary test — it appears in
|
|
141
|
+
`result.testDefs`, in the value plot modes, the colorbar, tooltips,
|
|
142
|
+
`analyzeWaferMap`, Insights and the report, with no other change required.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
derivedTests: [
|
|
146
|
+
{ testNumber: 900001, name: 'Leakage Shift', unit: 'uA',
|
|
147
|
+
expression: 'abs(t[1020] - t[1010])', limitHigh: 5 },
|
|
148
|
+
{ testNumber: 900002, name: 'Sweep All Pass', testType: 'F',
|
|
149
|
+
expression: 'all(testPass[1010..1025])' },
|
|
150
|
+
]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Expressions read `t[n]` (measured value), `testPass[n]` (the tester's recorded
|
|
154
|
+
verdict), `specPass[n]` (spec-limit judgement) and `diePass()` (the die's bin
|
|
155
|
+
verdict) — the same `'test'`/`'spec'` distinction `passFailDisplay` already
|
|
156
|
+
draws, because they are different questions and a single accessor would have
|
|
157
|
+
silently meant "recorded verdict" for parametric data that has none. A range
|
|
158
|
+
(`t[1010..1015]`) yields a set that must be reduced by `mean`, `sum`, `min`,
|
|
159
|
+
`max`, `all`, `any`, `none`, `countTrue`, `countFalse` or `countKnown`; there is
|
|
160
|
+
no vector arithmetic, which is what stops the grammar becoming an array
|
|
161
|
+
language. Operators `+ - * / % ^`, comparisons, `and`/`or`/`not`, and
|
|
162
|
+
`if(cond, a, b)`.
|
|
163
|
+
|
|
164
|
+
Parsed to a typed tree at build time and walked per die — **no `eval`, no
|
|
165
|
+
`Function`, no third-party expression engine, and no way to name a host
|
|
166
|
+
object** — so a chart spec can be shared between teams as plain JSON. This is
|
|
167
|
+
why the root bundle grows ~5 KB gzip.
|
|
168
|
+
|
|
169
|
+
Evaluated on raw probe records **before** lot stacking and retest resolution, so
|
|
170
|
+
every derived value comes from one real touchdown: deriving after a stack would
|
|
171
|
+
subtract one aggregate from another, and deriving after retest collapse could mix
|
|
172
|
+
a value from one touchdown with a verdict from another.
|
|
173
|
+
|
|
174
|
+
A boolean expression is a verdict — declare `testType: 'F'` and the result lands
|
|
175
|
+
in `die.testPass`, never as a 1/0 in `testValues` where it would enter the
|
|
176
|
+
correlation matrix and the Cpk table. An unknown input yields an **absent** value
|
|
177
|
+
(no-data grey), never `0` and never `false`.
|
|
178
|
+
|
|
179
|
+
Everything statically knowable is checked at build and reported as a
|
|
180
|
+
`derived-test-invalid` warning naming the position — a parse or type error, a
|
|
181
|
+
`testNumber` colliding with measured data, a `testType` that disagrees with the
|
|
182
|
+
expression, `t[n]` on a functional test, `specPass[n]` on a test with no limits.
|
|
183
|
+
A rejected derived test is **dropped**, never half-applied.
|
|
184
|
+
|
|
185
|
+
- **Parametric sweeps** — `insights.sweeps` on `renderWaferMap`/`renderWaferGallery`:
|
|
186
|
+
an ordered run of tests read as a response curve rather than as independent
|
|
187
|
+
tests, with the crossing point of the first two series and the horizontal
|
|
188
|
+
separation at named levels measured and stated. `xValues` supplies the real
|
|
189
|
+
swept quantity so the crossing is reported in physical units; without it the
|
|
190
|
+
axis is the ordinal position, because test numbers are identifiers and are not
|
|
191
|
+
guaranteed to be evenly spaced. Each line is the population median with a
|
|
192
|
+
p10–p90 band. A multiple crossing says so; a level neither curve reaches reads
|
|
193
|
+
"not measurable" naming the series, never `0`.
|
|
194
|
+
- **Test ranges in a sweep's `tests`** — an entry may be `"1010..1030"`, the exact
|
|
195
|
+
syntax of a derived-test expression's `t[1010..1030]` and parsed by the same code
|
|
196
|
+
(`parseTestReference`), so the same text always names the same tests. A range
|
|
197
|
+
expands to the declared tests inside it, ascending — a program numbered in steps
|
|
198
|
+
of 2 needs no step syntax. `xValues` is checked against the expanded list: when
|
|
199
|
+
a test is missing from a range, the footer lists what the range matched and the
|
|
200
|
+
card is drawn in test order with the crossing and widths **not measured**
|
|
201
|
+
(`SweepData.notMeasured`), rather than sliding every later x value onto the
|
|
202
|
+
wrong test. The same now applies when only some series carry `xValues`, which
|
|
203
|
+
before put physical and ordinal x positions on one axis and reported an ordinal
|
|
204
|
+
crossing under the physical x label. A range resolves by filtering the declared
|
|
205
|
+
tests rather than counting through every integer, so `0..2000000000` in a shared
|
|
206
|
+
template is not a two-billion-step loop — in expressions too.
|
|
207
|
+
- **Nested derived tests** — a derived test may read another. Compilation
|
|
208
|
+
topologically sorts by what each expression reads, so declaration order does
|
|
209
|
+
not matter; cycles, self-references and dependents of a rejected test are
|
|
210
|
+
dropped with the cause named.
|
|
211
|
+
- New example: **Derived tests and sweeps** (`docs/examples/derived-tests.html`).
|
|
212
|
+
- **Sweeps have their own Insights sub-tab**, shown only when `insights.sweeps`
|
|
213
|
+
defines at least one; `InsightsView` gains `'sweeps'` for `defaultView`, which
|
|
214
|
+
falls back to `'overview'` when there are none. Not an option and not a count
|
|
215
|
+
threshold: a threshold would move a sweep between tabs the day a colleague
|
|
216
|
+
added another, and an option would be a layout flag with one right answer.
|
|
217
|
+
They never belonged in Distributions either — that view is driven by one
|
|
218
|
+
selected test (capability → boxplot → histogram → trend), while a sweep draws
|
|
219
|
+
many tests as one curve and ignores the selection.
|
|
220
|
+
- New example: **Parametric sweeps** (`docs/examples/sweeps.html`) — four
|
|
221
|
+
characterisation sweeps in the Sweeps tab, each a different use of the
|
|
222
|
+
crossing and the width: the temperature-inversion voltage (Fmax vs VDD at
|
|
223
|
+
25/125 °C, with the iso-frequency VDD gap at two speeds), DIBL from an NMOS
|
|
224
|
+
transfer curve at two drain biases, the retention bake at which the read
|
|
225
|
+
window closes (uneven read times, which is what `xValues` is for), and an
|
|
226
|
+
output driver's maximum drive current (VOH vs VOL under load). The transfer
|
|
227
|
+
curve is swept over **derived** `log10(t[n])` tests — a sweep reads any test,
|
|
228
|
+
which is how a log-scale curve is drawn without a log option.
|
|
229
|
+
- **Derived tests are marked on the sweep card**: `SweepPoint` carries `derived`
|
|
230
|
+
and `expression`, ordinal tick labels keep the `†` in front of a truncated name, the
|
|
231
|
+
tooltip marks each series row and adds each expression, and the footer shows
|
|
232
|
+
the key.
|
|
233
|
+
- **Sweep card text.** The crossing line lower-cased the y label ("Cell Vt"
|
|
234
|
+
became "cell vt", "Fmax" became "fmax") — the width line had already been
|
|
235
|
+
fixed for the same reason ("dBm" → "dbm"); widths printed in engineering
|
|
236
|
+
notation (`32.7E-3`); and an x label carrying its own unit doubled the
|
|
237
|
+
brackets (`(VDD (V))`). X values — ticks, crossing and widths — now share one
|
|
238
|
+
plain-decimal rule, and a width reads "0.0327 on the VDD (V) axis".
|
|
239
|
+
- The derived-tests example opened on a Switching Window measured at the step
|
|
240
|
+
where its two curves cross, so the gap sat around zero against `limitLow: 0`
|
|
241
|
+
and nearly every die carried a ▽. It is now measured at a fixed read step
|
|
242
|
+
with a 0.33 V margin, so only an edge band fails.
|
|
243
|
+
- **A derived test is now identifiable from `result.testDefs` alone.** `derived`
|
|
244
|
+
was declared only on the `DerivedTestDef` *input* type, while `result.testDefs`
|
|
245
|
+
is a homogeneous `TestDef[]` — so every display surface needed a cast to ask
|
|
246
|
+
whether a value was measured or derived, and `expression`/`constants` were
|
|
247
|
+
stripped off the admitted def entirely, leaving nothing to say where a number
|
|
248
|
+
came from. All three now travel on `TestDef` as `readonly` fields set by
|
|
249
|
+
`buildWaferMap`. This is the prerequisite for marking derived tests in the UI:
|
|
250
|
+
a display that cannot ask the question cannot mark it, and an engineer reading a
|
|
251
|
+
Cpk table should never be left to assume a number came off the tester. The
|
|
252
|
+
alternative — hosts holding their own `derivedTests` input beside the result and
|
|
253
|
+
joining by test number — is a second source of truth that can disagree with the
|
|
254
|
+
def actually admitted, which carries the resolved `testType`.
|
|
255
|
+
- **`testLabel` / `isDerivedTest` / `derivedTestSource` / `hasDerivedTests`**
|
|
256
|
+
(`renderer/testLabel.ts`, internal) — one definition of how a test is named and
|
|
257
|
+
how a derived one is distinguished. The "name, or *Test N* when it has none"
|
|
258
|
+
fallback existed in six places (`stats/sweep.ts`, `capability.ts`, `testPassRate.ts`,
|
|
259
|
+
`analyzeWaferMap.ts`, `charts/chartShell.ts`), all now routed through it. Around
|
|
260
|
+
sixteen surfaces display a test name; a marker added per surface would have been
|
|
261
|
+
that rule twenty-odd times over, which is how two of them end up disagreeing.
|
|
262
|
+
- **Derived tests are marked in the process capability panel.** A `†` precedes
|
|
263
|
+
the test name, the legend gains the key "† Derived, not measured", and the
|
|
264
|
+
tooltip says the same in words and shows the expression the value was
|
|
265
|
+
computed from. A dagger, not `ƒ`: in this domain `f` reads as femto,
|
|
266
|
+
and a `ƒ` beside `fA`/`fF` units is a real misread.
|
|
267
|
+
`TestCapability`/`CapabilityDatum` gain `derived` and `expression`.
|
|
268
|
+
- **…and everywhere else a test is named, always in front of the name.** In a
|
|
269
|
+
list the marks then form a column down the left edge, so a derived test is found
|
|
270
|
+
at a glance, and truncating a long name can never cut the mark off; a list
|
|
271
|
+
holding a derived test pads its measured names by the mark's width so names stay
|
|
272
|
+
aligned, and a list without one is unchanged. The map title and colorbar
|
|
273
|
+
(`† Leak Shift (nA)`)
|
|
274
|
+
with the key on its own line beneath, drawn via a new optional
|
|
275
|
+
`MapTitleParts.note`; the map tooltip, which adds the key and the expression;
|
|
276
|
+
finding labels and sentences, marked at the source so no reader of a finding
|
|
277
|
+
can show it unmarked, with `StatsFinding.variable.derived`/`expression` as the
|
|
278
|
+
structured form; `stats.functionalYield[].label` (plus `derived`/`expression`);
|
|
279
|
+
the Summary panel's parametric and functional tables and its findings list,
|
|
280
|
+
each with a key line naming every expression; and both HTML reports, whose key
|
|
281
|
+
lists the expressions because a printed report has no hover. CSV exports carry
|
|
282
|
+
plain names plus a trailing **Derived from** column, only when a row is
|
|
283
|
+
derived — a CSV has nowhere to put a key. The key is a single short string,
|
|
284
|
+
*Derived, not measured*, so it fits the corner of a map and cannot drift
|
|
285
|
+
between surfaces. Every surface goes through `renderer/testLabel.ts`, which
|
|
286
|
+
also replaced two more hand-written *Test N* fallbacks in `buildView.ts` and
|
|
287
|
+
one in the gallery's stacked cards, whose synthetic def now keeps the flag.
|
|
288
|
+
The finding tooltip now has one rule, `formatFindingTooltip`, which the
|
|
289
|
+
Summary panel shares with both reports instead of using `summary` directly.
|
|
290
|
+
The places a test is *chosen* are marked too: the plot-mode test menu (a slot
|
|
291
|
+
in front of each name, reserved only when a derived test is listed, and a key
|
|
292
|
+
line at the foot), the Insights test pickers (`testOptionLabels`), the
|
|
293
|
+
correlation matrix's axis labels and tooltip (key on its hint line; its CSV
|
|
294
|
+
gains per-side *derived from* columns) and the die list's column headers (key
|
|
295
|
+
line above the table; the CSV header states the expression). The last two
|
|
296
|
+
hand-written *Test N* fallbacks, in the plot-mode menu and the die list, now go
|
|
297
|
+
through `testLabel`.
|
|
298
|
+
- **`DERIVED_MARK` and `DERIVED_KEY` are exported** (root and `/renderer`), so a
|
|
299
|
+
host listing tests in its own UI marks derived tests exactly as the library does
|
|
300
|
+
instead of keeping its own copy of the glyph and the words. tsmap's test selector
|
|
301
|
+
is the first use.
|
|
302
|
+
- Getting there turned up two places that dropped the flag. **`mergeTestDefs`**,
|
|
303
|
+
which builds a gallery's single test list, rebuilt each def field by field and
|
|
304
|
+
never named `derived`, so every cross-wafer panel would have shown a derived
|
|
305
|
+
test unmarked while the single-wafer view marked it. It now carries `derived`
|
|
306
|
+
and `expression`, marking a test derived when **any** wafer says so: marking
|
|
307
|
+
a measured test is an oddity someone queries, while leaving a derived one
|
|
308
|
+
unmarked goes unnoticed. The Summary panel's per-test table narrowed defs the
|
|
309
|
+
same way and now keeps both fields. It also held a seventh copy of the
|
|
310
|
+
*Test N* fallback, now routed through `testLabel`.
|
|
311
|
+
|
|
312
|
+
### Changed
|
|
313
|
+
|
|
314
|
+
- **Docs and examples brought up to date with this release.** The developer
|
|
315
|
+
guide's sweep section no longer says `tests` has no range syntax or that a
|
|
316
|
+
sweep has no log option; it covers ranges, `xFromName`, `xUnit`, `xScale` and
|
|
317
|
+
drilldown. The sweeps example adds a fifth sweep (an RRAM resistance
|
|
318
|
+
distribution read from test names on a log axis) and gives the output-driver
|
|
319
|
+
sweep an x unit; the derived-tests example's sweep uses ranges and a real swept
|
|
320
|
+
quantity (pulse amplitude in volts) instead of ordinal steps, and places the †
|
|
321
|
+
in front of the name as everywhere else. The README, `AGENTS.md` and
|
|
322
|
+
`llms.txt` now mention derived tests, sweeps and drilldown.
|
|
323
|
+
Those two examples' code cards, a four-across grid that cut every code
|
|
324
|
+
block off at about half its width in 12 px type, now show one example per
|
|
325
|
+
row — the code whole, in monospace at 13.5 px, beside notes at 14.5 px,
|
|
326
|
+
stacked on narrow screens — from one shared style in `demo.css`. `--mono`,
|
|
327
|
+
which every example's code referenced and nothing defined, is now set.
|
|
328
|
+
The derived-tests example is now in two labelled parts — derived tests are
|
|
329
|
+
*data* (`buildWaferMap`'s `derivedTests`; tsmap's test-definitions file), a
|
|
330
|
+
sweep is a *chart* (the renderer's `insights.sweeps`; tsmap's separate sweeps
|
|
331
|
+
file) — because a run of look-alike snippets read as one list. Its snippets
|
|
332
|
+
show the whole call each belongs to, it says the sweep is in Insights →
|
|
333
|
+
Sweeps, with a *Show the sweep* button that presses the gallery's own Insights
|
|
334
|
+
button, and it links to the sweeps example. The sweeps example opens with
|
|
335
|
+
where its entries go.
|
|
336
|
+
|
|
337
|
+
- **`scripts/check-bundle-size.mjs` attributes lazy chunks by what imports
|
|
338
|
+
them, not by file name.** Once drilldown and Insights shared the chart panels,
|
|
339
|
+
esbuild split the shared code into an anonymous `chunk-*.js`, which a file-name
|
|
340
|
+
test counted as core. That would have added ~5 KB of lazy code to the "always
|
|
341
|
+
downloaded" figure. The guide, Insights and drilldown now count as their own
|
|
342
|
+
static closure minus what the entry already loads. The Insights figure now
|
|
343
|
+
includes the chart code it shares with drilldown, because opening Insights
|
|
344
|
+
downloads it.
|
|
345
|
+
- **Axis ticks sit on round values, and are labelled to their spacing.** Tick
|
|
346
|
+
labels used to take their decimals from each value's own size, never from the
|
|
347
|
+
gap between ticks, so a 1–10 axis read "2.00 4.00 6.00 8.00" and a bandgap's
|
|
348
|
+
1.195–1.205 V read "1.20" five times — a sloped curve labelled as a flat line.
|
|
349
|
+
And only the colorbar placed ticks on round numbers: the box plot, scatter,
|
|
350
|
+
trend and sweep divided the data range into equal fractions (2.54 / 2.04 /
|
|
351
|
+
1.53 GHz), the histogram labelled its bucket edges, and its count axis carried
|
|
352
|
+
an inline copy of the rounding rule with different breakpoints. Every numeric
|
|
353
|
+
axis now goes through `renderer/axisTicks.ts` — one 1-2-5 rounding rule
|
|
354
|
+
(`niceStep`), round ticks inside the data range, and labels with exactly the
|
|
355
|
+
decimals the step needs (`stepDecimals`): "2 4 6 8", "1.196 1.198 1.200".
|
|
356
|
+
**Density comes from the space, not a tick count:** `fitTicks` takes the
|
|
357
|
+
finest round step whose labels fit — measured label widths plus an 8 px gap
|
|
358
|
+
on a horizontal axis, a line spacing on a vertical one. A fixed count, tried
|
|
359
|
+
first, rounded a 200 px Idsat box plot (0.863–1.56 mA) to 1.0 / 1.2 / 1.4 —
|
|
360
|
+
fewer ticks than the five it replaced; it now reads 0.9 / 1.0 / … / 1.5. The colorbar's exact min and max are data values rather than grid
|
|
361
|
+
values, so they get one decimal more, dropped when it is a zero — a 9.87
|
|
362
|
+
maximum no longer rounds to "10". Log-scale and integer-valued colorbars, die
|
|
363
|
+
labels and limit labels keep size-based formatting; they are not tick grids.
|
|
364
|
+
Every chart's tick labels change, so doc screenshots need recapturing.
|
|
365
|
+
- **The wafer and lot reports' findings table now uses plain bin terms**
|
|
366
|
+
("hard bin 2", not "HBin 2"). Both HTML reports render one findings table,
|
|
367
|
+
`findingsTableHtml`; each report module had kept its own, and only the
|
|
368
|
+
findings-only report translated the bin terms.
|
|
369
|
+
|
|
370
|
+
### Deprecated
|
|
371
|
+
|
|
372
|
+
- **`renderFindingsReportHtml`** — removed in 0.31.0. Use
|
|
373
|
+
`renderWaferReportHtml(result, summary)` or `renderLotReportHtml(results)`:
|
|
374
|
+
their Findings section is the same table, alongside the population and yield
|
|
375
|
+
it was found in. It was kept in 0.30.1 on the grounds that the Summary panel
|
|
376
|
+
used it, which had not been true since 0.20.0; nothing in wmap or tsmap calls
|
|
377
|
+
it. It ships deprecated in this release, so it goes in 0.31.0 with the 0.30.0
|
|
378
|
+
deprecations. `deprecated()` now takes a removal version per name, for a name
|
|
379
|
+
deprecated too late to go with the rest.
|
|
380
|
+
|
|
381
|
+
### Fixed
|
|
382
|
+
|
|
383
|
+
- **A menu row's hint is no longer drawn under its own menu.** Menus sit in a
|
|
384
|
+
dedicated layer (`menuLayerFor`) that outranks everything else in its root,
|
|
385
|
+
and the shared tooltip was placed beside that layer — so the reason on a
|
|
386
|
+
greyed-out row (the drilldown menu's, the Overlays menu's) appeared behind the
|
|
387
|
+
menu it explained. `positionTooltip` now places the tooltip inside the
|
|
388
|
+
anchor's menu layer at `Z_ABOVE2`, above every menu there, still resolved per
|
|
389
|
+
root (a host `<dialog>`, a wmap modal).
|
|
390
|
+
|
|
391
|
+
- **The map's hover tooltip no longer jumps away from the die.** The shared
|
|
392
|
+
tooltip steps clear of its anchor when the anchor is under 40% of the window
|
|
393
|
+
tall — right for a toolbar button, which its tip must not cover, and wrong
|
|
394
|
+
for the map canvas, where the pointer is on the die the tip describes. Every
|
|
395
|
+
map that small (every gallery card, the smaller maps on a page such as the
|
|
396
|
+
geometry example, any map in a tall window) had its tip thrown below or above
|
|
397
|
+
the whole canvas, measured at up to 266 px from the pointer. The map now
|
|
398
|
+
passes `followPointer` to `positionTooltip` and the tip stays beside the
|
|
399
|
+
pointer; control tips keep stepping around their buttons.
|
|
400
|
+
|
|
401
|
+
- **An expanded chart grows into its modal, in the direction that suits it.**
|
|
402
|
+
Several cards kept their grid-card size inside the expand modal: the trend
|
|
403
|
+
and sweep plots stayed a few hundred pixels tall, ring and quadrant yield
|
|
404
|
+
kept a fixed-size circle, the correlation matrix kept small cells, process
|
|
405
|
+
capability stopped at ~160 px per test, and row charts (yield by wafer,
|
|
406
|
+
pareto, pass rate, value distribution) left most of a 700 px box empty. Each
|
|
407
|
+
card now declares how it grows (`setChartGrow`): **plots** fill width and
|
|
408
|
+
height in a wider box; **row charts** keep bars full-width, show every row the
|
|
409
|
+
box has room for, and the box opens sized to its rows (within the viewport);
|
|
410
|
+
**square** charts grow to the shorter side in a square box. Capability's
|
|
411
|
+
columns spread across the width while each box keeps its grid maximum width.
|
|
412
|
+
Growth past the grid size happens only while expanded, so the Insights grid
|
|
413
|
+
is unchanged; maximise/restore and drag-resize shrink back correctly.
|
|
414
|
+
- **Chart cards measure their chrome, not their stretch.**
|
|
415
|
+
`growCardToFitContent` took the card's overhead as `card height − body
|
|
416
|
+
height`, which counts any stretch as overhead and grows the card on every
|
|
417
|
+
redraw — why several panels opted out of stretching (`alignSelf: 'start'`),
|
|
418
|
+
which is what kept them small in the modal. It now sums the card's other
|
|
419
|
+
children, so a stretched card is harmless. Trend and sweep no longer ask for
|
|
420
|
+
the height they were given as their minimum, which would have stopped a
|
|
421
|
+
shrinking modal.
|
|
422
|
+
- **Chart tooltips wrap inside their background.** They were `nowrap` under a
|
|
423
|
+
280 px cap, so a longer line — a sweep row with its p10–p90 and n, a derived
|
|
424
|
+
test's expression — ran out past the dark box. They now wrap within 320 px,
|
|
425
|
+
breaking an unbroken expression if they must. The sweep tooltip puts each
|
|
426
|
+
series' spread and n on a line of their own under its median.
|
|
427
|
+
|
|
428
|
+
- **A functional-test finding merged across adjacent regions reported no
|
|
429
|
+
difference, and hid the real one.** Since 0.20.3, when a functional pass-rate
|
|
430
|
+
signal spanned adjacent rings, sectors or quadrants, the merge pass that joins
|
|
431
|
+
them into one finding (`Rings 1–2`) had no functional branch and fell through
|
|
432
|
+
to the bin one. It counted dies whose hard bin equalled `variable.bin` — which
|
|
433
|
+
a functional finding does not have — so both sides counted 0, and the merged
|
|
434
|
+
finding read *"Rings 1–2 has HBin undefined occurrence 0.0 percentage points
|
|
435
|
+
lower than the rest of the map"*, with a meaningless p-value. It also
|
|
436
|
+
**replaced** the correct per-region findings it merged, so a real functional
|
|
437
|
+
signal spanning adjacent regions was shown as no difference at all. The merge
|
|
438
|
+
now recomputes the pass rate over the merged region exactly as the per-region
|
|
439
|
+
builder does (verdicts through `getTestPassStatus`, dies without a verdict
|
|
440
|
+
excluded), and the sentence names the test. Found while testing the
|
|
441
|
+
derived-test marker, whose test wafer happened to have exactly this shape.
|
|
442
|
+
- **Merged-region findings used a singular verb:** *"Rings 1–2 has HBin 3
|
|
443
|
+
occurrence…"*, *"Quadrants NE & SE has…"*. A merged region is plural; bin and
|
|
444
|
+
functional pass-rate sentences now say "have". The Summary panel, which strips
|
|
445
|
+
the region from a row shown under its own group header, accepts either verb.
|
|
446
|
+
- **A `NaN` test value was classified as in-spec.** The spec-limit rule existed in
|
|
447
|
+
two places — `classifySpec` (die colouring, ▽/△ markers, the legend's spec tally)
|
|
448
|
+
and a private `specJudgement` in `stats/testPassRate.ts` (spec pass-rate tables) —
|
|
449
|
+
and they disagreed on two cases. `classifySpec` compared with `<` and `>`, which
|
|
450
|
+
are both false for `NaN`, so a non-finite measurement fell through to `'pass'`:
|
|
451
|
+
the exact "never drawn as plain in-spec" failure its own doc comment described.
|
|
452
|
+
It also reported `'pass'` for a test declaring no limits at all, inflating a spec
|
|
453
|
+
pass rate to 100% for every unlimited test. There is now one implementation
|
|
454
|
+
(`renderer/spec.ts`), strict on both, used by the renderer, stats and the
|
|
455
|
+
`specPass[n]` accessor.
|
|
456
|
+
|
|
25
457
|
## [0.30.2] — 2026-09-20
|
|
26
458
|
|
|
27
459
|
### Added
|
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 ~61 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
|
|
|
@@ -32,6 +32,9 @@ wafermap renders interactive wafer maps from semiconductor prober output. Hard b
|
|
|
32
32
|
- `renderWaferMap` — interactive canvas map with toolbar, zoom/pan, tooltips, die selection, and summary panel
|
|
33
33
|
- `renderWaferGallery` — lot-level card grid with shared controls and click-to-expand
|
|
34
34
|
- Insights tab (`insights: { enabled: true }`) — an in-toolbar chart suite (yield, per-test pass rate, bin pareto, capability, boxplot, histogram, wafer-to-wafer trend, correlation, scatter) computed from the same wafer/lot data, for one wafer or the whole gallery
|
|
35
|
+
- Derived tests (`derivedTests`) — tests computed per die from other tests (`abs(t[1020] - t[1010])`), which then work everywhere a measured test does, marked † as not measured
|
|
36
|
+
- Parametric sweeps (`insights.sweeps`) — an ordered run of tests read as a response curve, with the crossing and widths between two curves measured; x values given directly or read from the test names, on a linear or log axis
|
|
37
|
+
- Drilldown — right-click selected dies, a wafer card or a wafer's bar to chart just that population, with the population stated on the chart
|
|
35
38
|
- `analyzeWaferMap` / `analyzeWaferLot` — spatial analysis across rings, quadrants, sectors, and reticle positions; failure cluster detection; lot trend series
|
|
36
39
|
- Pure ES modules, no server, no runtime dependencies — works in React, Svelte, Vue, plain HTML, or a Web Worker
|
|
37
40
|
|
|
@@ -72,7 +75,7 @@ export. Add `analyzeWaferMap` for the findings and summary panel, swap in
|
|
|
72
75
|
|
|
73
76
|
The API reference is long because it documents every option, not because you need
|
|
74
77
|
them: [tsmap](https://github.com/wafertools/tsmap), a complete cross-platform desktop
|
|
75
|
-
application built on this library, imports **
|
|
78
|
+
application built on this library, imports **17** of its ~100 exports. Read the
|
|
76
79
|
[Quick Start](https://wafertools.github.io/wafermap/quickstart/) first and treat the
|
|
77
80
|
[API reference](https://wafertools.github.io/wafermap/api/) as something to search,
|
|
78
81
|
not to read.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Die } from '../core/dies.js';
|
|
2
|
+
import { type TestDef } from '../renderer/buildWaferMap.js';
|
|
3
|
+
import type { SweepSpec } from '../stats/sweep.js';
|
|
4
|
+
/** One wafer's share of a population. */
|
|
5
|
+
export interface DrilldownItem {
|
|
6
|
+
/** The wafer's display label. */
|
|
7
|
+
label: string;
|
|
8
|
+
dies: Die[];
|
|
9
|
+
waferIndex?: number;
|
|
10
|
+
}
|
|
11
|
+
/** A population a chart can be opened on. */
|
|
12
|
+
export interface DrilldownSource {
|
|
13
|
+
/** One entry per wafer the population spans. A snapshot: the chart keeps
|
|
14
|
+
* these dies whatever happens to the selection afterwards. */
|
|
15
|
+
items: DrilldownItem[];
|
|
16
|
+
/** Who the dies are, read after a count — `"selected on W03"`, `"on W03"`. */
|
|
17
|
+
population: string;
|
|
18
|
+
testDefs: TestDef[] | undefined;
|
|
19
|
+
/** The test on screen where the gesture happened — a chart that shows one
|
|
20
|
+
* test at a time opens on it. */
|
|
21
|
+
activeTest?: number;
|
|
22
|
+
/** Set when the dies are not measured dies (a lot stack's per-position
|
|
23
|
+
* aggregates): every chart is then unavailable, with this as the reason. */
|
|
24
|
+
notMeasuredReason?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether any drilldown chart could exist for this data — decides whether a
|
|
28
|
+
* right-click is taken over at all, without loading the drilldown chunk. The
|
|
29
|
+
* distribution charts need a parametric test, a sweep needs to be defined;
|
|
30
|
+
* with neither (a bins-only map) right-click stays the browser's or host's.
|
|
31
|
+
* `targetsFor` in drilldown.ts is the list this summarises.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hasDrilldownTargets(testDefs: readonly TestDef[] | undefined, sweeps: readonly SweepSpec[] | undefined): boolean;
|
|
34
|
+
interface WaferFacts {
|
|
35
|
+
/** The wafer's real identity, if it has one — never a positional stand-in,
|
|
36
|
+
* which would read as an ID ("Wafer 3 (no ID)") in a chart title. */
|
|
37
|
+
waferLabel: string | undefined;
|
|
38
|
+
testDefs: TestDef[] | undefined;
|
|
39
|
+
isLotStack?: boolean;
|
|
40
|
+
activeTest?: number;
|
|
41
|
+
waferIndex?: number;
|
|
42
|
+
}
|
|
43
|
+
/** Dies the user selected on one wafer's map. */
|
|
44
|
+
export declare function selectionPopulation(selected: Die[], f: WaferFacts): DrilldownSource;
|
|
45
|
+
/** Every die of one wafer. */
|
|
46
|
+
export declare function waferPopulation(dies: Die[], f: WaferFacts): DrilldownSource;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=chartPopulation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isParametricTest as s}from"../renderer/buildWaferMap.js";export function hasDrilldownTargets(t,e){return(e?.length??0)>0||(t??[]).some(s)}const n="This map stacks a lot: its dies are per-position aggregates, not measured dies";function r(t,e,a){return{items:[{label:a.waferLabel??"this wafer",dies:t,waferIndex:a.waferIndex}],population:e,testDefs:a.testDefs,activeTest:a.activeTest,notMeasuredReason:a.isLotStack?n:void 0}}export function selectionPopulation(t,e){return r(t,e.waferLabel?`selected on ${e.waferLabel}`:"selected",e)}export function waferPopulation(t,e){return r(t,e.waferLabel?`on ${e.waferLabel}`:"on this wafer",e)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChartDatum } from '../../stats/yield.js';
|
|
2
|
-
import { type SaveImageHandler } from './chartShell.js';
|
|
2
|
+
import { type SaveImageHandler, type WaferContextMenuHandler } from './chartShell.js';
|
|
3
3
|
export interface ChartPanel {
|
|
4
4
|
title: string;
|
|
5
5
|
data: ChartDatum[];
|
|
@@ -59,6 +59,9 @@ export interface ChartPanel {
|
|
|
59
59
|
* instead, via `drill.onOpenGroup` — this is never called for those.
|
|
60
60
|
*/
|
|
61
61
|
onOpen?: (datum: ChartDatum) => void;
|
|
62
|
+
/** Right-click on a leaf row that carries a wafer (`datum.key`) — see
|
|
63
|
+
* `WaferContextMenuHandler`. Not called for a group row. */
|
|
64
|
+
onWaferContextMenu?: WaferContextMenuHandler;
|
|
62
65
|
/** Document to build this panel's DOM into. Default `document` — pass the
|
|
63
66
|
* host's own `ownerDocument` when the container might live in a
|
|
64
67
|
* different document (e.g. a gallery card detached into its own popup
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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 ut,makeSegmented as dt,fitRowsHeight as ht,setChartGrow as mt,resolveChartCanvasColors as xt,PADDING as u,VALUE_WIDTH as j,WAFER_MENU_HINT as bt,prepareCanvas as pt}from"./chartShell.js";import{escHtml as X}from"../../core/utils.js";const d=24,m=5,gt=15,z=110,yt=12;export function renderBarPanel(l,U){const{barColor:G,valueLabel:I}=l;let g=l.title,i=l.data;const{card:r,heading:E,controlsRow:P,body:S}=rt(g,U,l.ownerDocument);if(mt(r,"rows"),l.selfControl){const e=l.selfControl;P.appendChild(dt(e.options,e.current,n=>q(n),r.ownerDocument))}const{drill:f}=l;let y=!1,R=null;const A=r.ownerDocument.createElement("div");Object.assign(A.style,{color:it.label,fontSize:lt.body,marginBottom:nt.sm}),r.insertBefore(A,S);function B(){const e=[];l.onOpen&&e.push("click to open this wafer"),f&&!y&&e.push(`click a ${f.groupLabelText} to see it by wafer`);const n=e.join(", or ");let o=n?`${n[0].toUpperCase()}${n.slice(1)}.`:"";const t=l.reference?.(i);t&&(o=o?`${o} Dashed line: ${t.label}.`:`Dashed line: ${t.label}.`),A.textContent=o}B();const k=r.ownerDocument.createElement("div"),v=()=>l.reference?gt:0,Y=()=>u*2+v()+Math.min(i.length,yt)*(d+m);Object.assign(k.style,{overflowX:"hidden",overflowY:"auto",minHeight:"0",flex:"1",maxHeight:`${Y()}px`,scrollbarGutter:"stable"}),S.appendChild(k);const s=r.ownerDocument.createElement("canvas");s.style.display="block",s.style.cursor="default",k.appendChild(s);const w=at(r);let x=-1,M=Math.max(1,...i.map(e=>e.value));const N=e=>I?I(e):`${st(e.value)} (${e.percent.toFixed(1)}%)`;function F(e){const n=u+v()+e*(d+m),o=u+z,t=s.clientWidth-o-j-u;return{y:n,barX:o,barMaxWidth:Math.max(10,t)}}function C(){const e=u*2+v()+i.length*(d+m);k.style.maxHeight=`${ht(r,S,Y(),e)}px`;const n=k.clientWidth,o=pt(s,r,n,e);if(!o)return;const{ctx:t}=o;t.font=`${ot(-1)}px system-ui, sans-serif`,t.textBaseline="middle";const c=xt(r);i.forEach((a,p)=>{const{y:h,barX:W,barMaxWidth:H}=F(p);p===x&&(t.fillStyle=c.bgHover,t.fillRect(0,h-m/2,n,d+m)),t.fillStyle=c.text,t.textAlign="right";const T=a.label.length>16?`${a.label.slice(0,15)}\u2026`:a.label;t.fillText(T,u+z-8,h+d/2),t.fillStyle=c.track,t.fillRect(W,h,H,d);const $=Math.max(1,a.value/M*H);t.fillStyle=G?G(a,p):c.text,t.fillRect(W,h,$,d),t.fillStyle=c.text,t.textAlign="right",t.fillText(N(a),W+H+j,h+d/2)});const b=l.reference?.(i);if(b&&i.length){const{barX:a,barMaxWidth:p}=F(0),h=Math.round(a+Math.max(0,Math.min(1,b.value/M))*p)+.5,W=u+v()-3,H=u+v()+i.length*(d+m)-m,T=(Z,tt,et)=>{t.save(),t.strokeStyle=Z,t.lineWidth=tt,t.setLineDash(et),t.beginPath(),t.moveTo(h,W),t.lineTo(h,H),t.stroke(),t.restore()};T(c.bg,3.5,[]),T(c.text,1.5,[4,3]),t.save(),t.font="600 10px system-ui, sans-serif",t.textBaseline="middle",t.textAlign="left";const $=4,O=t.measureText(b.label).width+$*2,_=13,V=u+v()-_-1;let D=h-O/2;D=Math.max(a,Math.min(D,a+p-O)),t.fillStyle=c.bg,t.strokeStyle=c.border,t.lineWidth=1,t.beginPath(),t.rect(D,V,O,_),t.fill(),t.stroke(),t.fillStyle=c.text,t.fillText(b.label,D+$,V+_/2+.5),t.restore()}}function q(e){if(!l.selfControl)return;l.selfControl.current=e;const n=l.selfControl.onChange(e);i=n.data,M=Math.max(1,...i.map(o=>o.value)),x=-1,n.title&&(g=n.title,E.textContent=g),B(),C()}function J(e){if(!f)return;const n=f.onOpenGroup(e);i=n.data,g=n.title,E.textContent=g,M=Math.max(1,...i.map(o=>o.value)),x=-1,y||(y=!0,R=ut(()=>K(),r.ownerDocument),P.appendChild(R)),B(),C()}function K(){if(!f)return;const e=f.onBack();i=e.data,g=e.title,E.textContent=g,M=Math.max(1,...i.map(n=>n.value)),x=-1,y=!1,R?.remove(),R=null,B(),C()}function L(e){const n=Math.floor((e-u+m/2)/(d+m));return n>=0&&n<i.length?n:-1}s.addEventListener("mousemove",e=>{const n=s.getBoundingClientRect(),o=L(e.clientY-n.top),t=o>=0&&f&&!y&&i[o].itemCount>1,c=o>=0&&(t||!!l.onOpen&&o>=0&&!t);if(o!==x&&(x=o,s.style.cursor=c?"pointer":"default",C()),o>=0){const b=i[o],a=t?`<br><em>click to see this ${X(f.groupLabelText)} by wafer</em>`:l.onOpen?"<br><em>click to open this wafer</em>":"",p=!t&&l.onWaferContextMenu&&typeof b.key=="number"?bt:"";w.innerHTML=`<strong>${X(b.label)}</strong><br>${X(N(b))}${a}${p}`,w.style.display="block",ft(w,r,e.clientX,e.clientY)}else w.style.display="none"}),s.addEventListener("mouseleave",()=>{x!==-1&&(x=-1,C()),w.style.display="none"}),s.addEventListener("click",e=>{const n=s.getBoundingClientRect(),o=L(e.clientY-n.top);if(o===-1)return;const t=i[o];if(f&&!y&&t.itemCount>1){J(t);return}l.onOpen?.(t)}),s.addEventListener("contextmenu",e=>{const n=s.getBoundingClientRect(),o=L(e.clientY-n.top);if(o===-1||!l.onWaferContextMenu)return;const t=i[o];f&&!y&&t.itemCount>1||typeof t.key!="number"||(w.style.display="none",l.onWaferContextMenu(t.key,void 0,e))});const Q=ct(r,()=>C());return C(),{card:r,destroy:()=>Q.disconnect()}}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type BoxplotItem } from '../../stats/boxplot.js';
|
|
2
2
|
import type { TestDef } from '../../renderer/buildWaferMap.js';
|
|
3
|
-
import { type AxisPrefs, type SaveImageHandler } from './chartShell.js';
|
|
3
|
+
import { type AxisPrefs, type SaveImageHandler, type WaferContextMenuHandler } from './chartShell.js';
|
|
4
4
|
/** A `BoxplotItem` carrying enough identity for the panel to open its wafer
|
|
5
5
|
* on a leaf-row click — the tab's own item shape already has this. */
|
|
6
6
|
export type BoxplotPanelItem = BoxplotItem & {
|
|
@@ -43,6 +43,9 @@ export interface BoxplotPanelOptions {
|
|
|
43
43
|
* same test in value mode instead of defaulting to hard-bin mode.
|
|
44
44
|
* Never called for a pooled group-overview row (that drills instead). */
|
|
45
45
|
onOpen?: (waferIndex: number, testNumber: number) => void;
|
|
46
|
+
/** Right-click on a wafer's box — see `WaferContextMenuHandler`. Not called
|
|
47
|
+
* for a pooled group row. */
|
|
48
|
+
onWaferContextMenu?: WaferContextMenuHandler;
|
|
46
49
|
/** What `onOpen` will actually do, in the user's words — substituted into
|
|
47
50
|
* both click affordances ("click a box to …" / "click to …"). Default
|
|
48
51
|
* wording describes opening that wafer, which is what a gallery host does;
|