@wafertools/wafermap 0.29.0 → 0.30.0

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