@wafertools/wafermap 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +751 -0
  2. package/LICENSE +21 -0
  3. package/README.md +79 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +1 -0
  6. package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
  7. package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
  8. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
  9. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
  10. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
  11. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
  12. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
  13. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
  14. package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
  15. package/dist/packages/canvas-adapter/charts/capability.js +1 -0
  16. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
  17. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
  18. package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
  19. package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
  21. package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
  22. package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
  23. package/dist/packages/canvas-adapter/charts/index.js +1 -0
  24. package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
  25. package/dist/packages/canvas-adapter/charts/palette.js +1 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
  27. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
  29. package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
  30. package/dist/packages/canvas-adapter/icons.d.ts +2 -0
  31. package/dist/packages/canvas-adapter/icons.js +42 -0
  32. package/dist/packages/canvas-adapter/index.d.ts +12 -0
  33. package/dist/packages/canvas-adapter/index.js +1 -0
  34. package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
  35. package/dist/packages/canvas-adapter/insightsTab.js +1 -0
  36. package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
  37. package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
  38. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
  39. package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
  40. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
  41. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
  42. package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
  43. package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
  44. package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
  45. package/dist/packages/canvas-adapter/toCanvas.js +1 -0
  46. package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
  47. package/dist/packages/canvas-adapter/toolbar.js +9 -0
  48. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
  49. package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
  50. package/dist/packages/canvas-adapter/version.d.ts +3 -0
  51. package/dist/packages/canvas-adapter/version.js +1 -0
  52. package/dist/packages/core/aggregates.d.ts +54 -0
  53. package/dist/packages/core/aggregates.js +1 -0
  54. package/dist/packages/core/classify.d.ts +24 -0
  55. package/dist/packages/core/classify.js +1 -0
  56. package/dist/packages/core/dies.d.ts +92 -0
  57. package/dist/packages/core/dies.js +1 -0
  58. package/dist/packages/core/index.d.ts +13 -0
  59. package/dist/packages/core/index.js +1 -0
  60. package/dist/packages/core/inference/grid.d.ts +35 -0
  61. package/dist/packages/core/inference/grid.js +1 -0
  62. package/dist/packages/core/inference/index.d.ts +7 -0
  63. package/dist/packages/core/inference/index.js +1 -0
  64. package/dist/packages/core/inference/pitch.d.ts +26 -0
  65. package/dist/packages/core/inference/pitch.js +1 -0
  66. package/dist/packages/core/inference/wafer.d.ts +30 -0
  67. package/dist/packages/core/inference/wafer.js +1 -0
  68. package/dist/packages/core/metadata.d.ts +31 -0
  69. package/dist/packages/core/metadata.js +1 -0
  70. package/dist/packages/core/probe.d.ts +16 -0
  71. package/dist/packages/core/probe.js +1 -0
  72. package/dist/packages/core/reticle.d.ts +68 -0
  73. package/dist/packages/core/reticle.js +1 -0
  74. package/dist/packages/core/transforms.d.ts +142 -0
  75. package/dist/packages/core/transforms.js +1 -0
  76. package/dist/packages/core/utils.d.ts +60 -0
  77. package/dist/packages/core/utils.js +1 -0
  78. package/dist/packages/core/wafer.d.ts +45 -0
  79. package/dist/packages/core/wafer.js +1 -0
  80. package/dist/packages/renderer/buildView.d.ts +420 -0
  81. package/dist/packages/renderer/buildView.js +1 -0
  82. package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
  83. package/dist/packages/renderer/buildWaferMap.js +1 -0
  84. package/dist/packages/renderer/colorMap.d.ts +46 -0
  85. package/dist/packages/renderer/colorMap.js +1 -0
  86. package/dist/packages/renderer/colorSchemes.d.ts +40 -0
  87. package/dist/packages/renderer/colorSchemes.js +1 -0
  88. package/dist/packages/renderer/fmt.d.ts +30 -0
  89. package/dist/packages/renderer/fmt.js +1 -0
  90. package/dist/packages/renderer/index.d.ts +7 -0
  91. package/dist/packages/renderer/index.js +1 -0
  92. package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
  93. package/dist/packages/stats/analyzeWaferLot.js +1 -0
  94. package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
  95. package/dist/packages/stats/analyzeWaferMap.js +1 -0
  96. package/dist/packages/stats/binPareto.d.ts +49 -0
  97. package/dist/packages/stats/binPareto.js +1 -0
  98. package/dist/packages/stats/boxplot.d.ts +39 -0
  99. package/dist/packages/stats/boxplot.js +1 -0
  100. package/dist/packages/stats/capability.d.ts +55 -0
  101. package/dist/packages/stats/capability.js +1 -0
  102. package/dist/packages/stats/clusterDetection.d.ts +18 -0
  103. package/dist/packages/stats/clusterDetection.js +1 -0
  104. package/dist/packages/stats/correlation.d.ts +55 -0
  105. package/dist/packages/stats/correlation.js +1 -0
  106. package/dist/packages/stats/facets.d.ts +74 -0
  107. package/dist/packages/stats/facets.js +1 -0
  108. package/dist/packages/stats/filterFindings.d.ts +9 -0
  109. package/dist/packages/stats/filterFindings.js +1 -0
  110. package/dist/packages/stats/findingsNarrative.d.ts +3 -0
  111. package/dist/packages/stats/findingsNarrative.js +1 -0
  112. package/dist/packages/stats/histogram.d.ts +42 -0
  113. package/dist/packages/stats/histogram.js +1 -0
  114. package/dist/packages/stats/index.d.ts +27 -0
  115. package/dist/packages/stats/index.js +1 -0
  116. package/dist/packages/stats/math.d.ts +10 -0
  117. package/dist/packages/stats/math.js +1 -0
  118. package/dist/packages/stats/patternClassification.d.ts +107 -0
  119. package/dist/packages/stats/patternClassification.js +1 -0
  120. package/dist/packages/stats/regions.d.ts +57 -0
  121. package/dist/packages/stats/regions.js +1 -0
  122. package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
  123. package/dist/packages/stats/renderFindingsReport.js +41 -0
  124. package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
  125. package/dist/packages/stats/renderSummaryReport.js +68 -0
  126. package/dist/packages/stats/reportHtml.d.ts +42 -0
  127. package/dist/packages/stats/reportHtml.js +301 -0
  128. package/dist/packages/stats/scatter.d.ts +24 -0
  129. package/dist/packages/stats/scatter.js +1 -0
  130. package/dist/packages/stats/types.d.ts +269 -0
  131. package/dist/packages/stats/types.js +1 -0
  132. package/dist/packages/stats/yield.d.ts +56 -0
  133. package/dist/packages/stats/yield.js +1 -0
  134. package/dist/packages/worker/index.d.ts +44 -0
  135. package/dist/packages/worker/index.js +1 -0
  136. package/dist/packages/worker/wafermap.worker.d.ts +44 -0
  137. package/dist/packages/worker/wafermap.worker.js +1 -0
  138. package/package.json +122 -0
@@ -0,0 +1,262 @@
1
+ import type { Wafer } from '../core/wafer.js';
2
+ import type { Die } from '../core/dies.js';
3
+ import { type BinDef, type TestDef, type YieldSummary } from '../renderer/buildWaferMap.js';
4
+ import type { StatsFinding, StatsSummary, LotStatsSummary } from '../stats/types.js';
5
+ import { type FindingsFilter } from '../stats/filterFindings.js';
6
+ import { type SaveTextHandler } from './toolbar.js';
7
+ export interface SummaryPanelOptions {
8
+ /** Which side of the content area to place the panel. Default 'right'. */
9
+ placement?: 'right' | 'left' | 'top' | 'bottom';
10
+ /** Open the panel immediately on render without requiring the user to click the toolbar button. Default false. */
11
+ defaultOpen?: boolean;
12
+ }
13
+ export declare function buildWarningsBanner(warnings: string[]): HTMLDivElement;
14
+ /** Order-preserving [key, value] pairs from a metadata-like record, with
15
+ * null/undefined/empty-string entries dropped. The single source of truth
16
+ * for "what counts as a displayable metadata field" — shared by the map
17
+ * metadata badge and `buildMetadataStripRow`/`buildFacetSummaryChips` so
18
+ * they can't drift on what they consider "no metadata". */
19
+ export declare function metadataEntries(meta: Record<string, unknown>): Array<[string, string]>;
20
+ /** Single-line, wrap-when-needed "Label: value1, value2 [+N more]" chips —
21
+ * the gallery's top-of-grid strip summary over a whole (possibly multi-lot)
22
+ * population, one chip per `FacetField` from `stats/facets.ts`'s
23
+ * `buildFacetTable`. Shows every distinct value a field takes
24
+ * across the population — so a mixed-lot gallery still surfaces "Lot:
25
+ * LOT123, LOT456" instead of silently dropping a field the moment it
26
+ * varies. `field.values` is already sorted by coverage (`buildFacetTable`),
27
+ * so the values shown inline are the most common ones. Labels use
28
+ * `prettyKey(field.key)`, not `field.label` — `DEFAULT_FACET_CURATION`'s own
29
+ * labels (e.g. "Program") differ from the `prettyKey` convention every other
30
+ * metadata surface in this library uses ("Test Program"), and this strip
31
+ * must read as the same field as those surfaces, not a differently-named one.
32
+ * Returns `null` for an empty table. */
33
+ export declare function buildFacetSummaryChips(table: Array<{
34
+ key: string;
35
+ values: Array<{
36
+ value: string;
37
+ }>;
38
+ }>, maxValuesPerField?: number): HTMLDivElement | null;
39
+ export interface MetadataStripStacked {
40
+ lotSize: number;
41
+ aggrMethod?: string;
42
+ }
43
+ /** The single correct way to summarize a population's identity as an
44
+ * always-visible strip: computed via `buildFacetTable`/`buildFacetSummaryChips`
45
+ * over every item's own metadata, so a field that varies across items (e.g. a
46
+ * lot with mixed `split` values) shows every distinct value it takes — never
47
+ * silently collapsed to one item's value (e.g. `analyzeWaferLot`'s
48
+ * first-wafer-wins `lot` field), which misrepresents the population (see
49
+ * CLAUDE.md: aggregated/filtered populations must be identified). Shared by
50
+ * the gallery legend strip and the Insights header strip so the two surfaces
51
+ * can't drift on content or field order again. Returns `null` when there's
52
+ * nothing to show. */
53
+ export declare function buildMetadataStripRow(items: Array<{
54
+ metadata?: Record<string, unknown>;
55
+ }>, stacked?: MetadataStripStacked, options?: {
56
+ facetableOnly?: boolean;
57
+ }): HTMLDivElement | null;
58
+ /** `buildMetadataStripRow` wrapped in the boxed-strip chrome every
59
+ * standalone, always-visible metadata strip uses (border/background/shadow
60
+ * — the same look as the gallery legend strip). Not used by the gallery
61
+ * legend itself, which stacks a bin-swatch row inside the same box below
62
+ * the metadata row and so builds its own box around both rather than two
63
+ * nested boxes — that caller uses `buildMetadataStripRow` directly. */
64
+ export declare function buildMetadataStripBox(items: Array<{
65
+ metadata?: Record<string, unknown>;
66
+ }>, stacked?: MetadataStripStacked, options?: {
67
+ facetableOnly?: boolean;
68
+ }): HTMLDivElement | null;
69
+ /** Same row visual language as `metaRow`/`prettyKey`, one field per line —
70
+ * for the metadata badge's expand-on-click popover, where each field
71
+ * reading on its own line is more legible than wrapped inline chips and the
72
+ * cost is only paid while the popover is open, not by default.
73
+ * Returns `null` for empty input. */
74
+ export declare function buildCompactMetadataRows(meta: Record<string, unknown>): HTMLDivElement | null;
75
+ /**
76
+ * The single source of metadata display in the Summary panel — used by both
77
+ * the single-wafer and lot paths, always built via `buildMetadataStripRow`/
78
+ * `buildFacetTable` (the same function the gallery legend strip and Insights
79
+ * header strip use) rather than reading `wafer.metadata` directly (drifts
80
+ * from the strip's truncation/formatting) or `LotStatsSummary.lot`
81
+ * (`analyzeWaferLot`'s first-wafer-wins field, which silently collapses any
82
+ * field that varies across the lot to one wafer's value — see CLAUDE.md on
83
+ * identifying aggregated populations). A population of one wafer is just a
84
+ * facet table where every field has exactly one value, so the single-wafer
85
+ * and lot panels can never render different content for the same
86
+ * underlying metadata.
87
+ */
88
+ export declare function buildMetadataInfoSection(items: Array<{
89
+ metadata?: Record<string, unknown>;
90
+ }>, stacked?: MetadataStripStacked): HTMLDivElement | null;
91
+ export declare function buildYieldSection(yieldSummary: YieldSummary, dataCoverage: {
92
+ filledDies: number;
93
+ totalDies: number;
94
+ edgeExcludedDies: number;
95
+ ratio: number;
96
+ }, passBins?: number[]): HTMLDivElement;
97
+ export declare function buildBinSection(dies: Die[], binDefs: BinDef[] | undefined, mode: 'hard' | 'soft', colorScheme?: string,
98
+ /**
99
+ * Precomputed counts (e.g. `StatsSummary.stats.hardBinCounts`/`.softBinCounts`,
100
+ * already scoped to the yield-eligible population) — used directly instead
101
+ * of re-walking `dies` when supplied.
102
+ */
103
+ precomputedCounts?: Record<number, number>): HTMLDivElement | null;
104
+ /** Aggregate bin counts across all wafers in the lot — no lot-pooled
105
+ * precomputed bin counts exist (`hardBinCounts`/`softBinCounts` are
106
+ * per-wafer only), so this scans the pooled `Die[]` directly, same as
107
+ * `buildBinSection` does when it has no `precomputedCounts`. */
108
+ export declare function buildLotBinSection(allDies: Die[], binDefs: BinDef[] | undefined, mode: 'hard' | 'soft', colorScheme?: string): HTMLDivElement | null;
109
+ export declare function buildRingSection(dies: Die[], wafer: Wafer, ringCount: number, passBins: number[]): HTMLDivElement | null;
110
+ export declare function buildQuadrantSection(dies: Die[], wafer: Wafer, ringCount: number, passBins: number[]): HTMLDivElement | null;
111
+ /** Aggregate ring yield across all wafers in the lot. */
112
+ export declare function buildLotRingSection(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: number[]): HTMLDivElement | null;
113
+ /** Aggregate quadrant yield across all wafers in the lot. */
114
+ export declare function buildLotQuadrantSection(diesByWafer: Die[][], allWafers: Wafer[], ringCount: number, passBins: number[]): HTMLDivElement | null;
115
+ /** Lot overview — wafer count and mean (unweighted arithmetic mean of each
116
+ * wafer's own yield%, not a die-weighted lot yield — see CLAUDE.md on
117
+ * correctly labelling aggregation methods) wafer yield. Metadata is a
118
+ * separate section (`buildMetadataInfoSection`) built from the lot's own
119
+ * items, not from `lotSummary.lot` — see that function's doc comment. */
120
+ export declare function buildLotOverviewSection(lotSummary: LotStatsSummary): HTMLDivElement;
121
+ /** Per-wafer yield bars, absolute 0–100% scale, with a median marker line and
122
+ * below-median wafers muted — lets an engineer spot outlier wafers within
123
+ * the lot at a glance. */
124
+ export declare function buildPerWaferYieldSection(lotSummary: LotStatsSummary, items: Array<{
125
+ label?: string;
126
+ } | null>, onWaferClick?: (waferIndex: number) => void): HTMLDivElement | null;
127
+ export declare function buildTestSection(dies: Die[], testDefs: TestDef[] | undefined, fallbackFormat?: 'si' | 'engineering',
128
+ /**
129
+ * When supplied, each test's descriptive stats and spec-yield are read from
130
+ * `.perTestStats`/`.testSpecYield` (already computed once by
131
+ * `analyzeWaferMap`, or pooled across a lot by `buildLotTestSection`)
132
+ * instead of re-scanning `dies`. A test falls back to a raw-die scan when
133
+ * it's missing from `perTestStats` entirely, or when `stddev`/`median`/
134
+ * `q1`/`q3` weren't included (e.g. lot pooling, which can reconstruct
135
+ * min/max/mean/count exactly across wafers but not quantiles — see
136
+ * `buildLotTestSection`'s doc comment). Structurally a subset of
137
+ * `StatsSummary['stats']`, so a real `StatsSummary` can be passed directly.
138
+ */
139
+ precomputedTestStats?: {
140
+ perTestStats?: Array<{
141
+ testNumber: number;
142
+ min: number;
143
+ max: number;
144
+ mean: number;
145
+ count: number;
146
+ stddev?: number;
147
+ median?: number;
148
+ q1?: number;
149
+ q3?: number;
150
+ }>;
151
+ testSpecYield?: Array<{
152
+ testNumber: number;
153
+ totalDies: number;
154
+ yieldPercent: number | null;
155
+ }>;
156
+ },
157
+ /** Optional host hook for the "Export CSV" button — see `saveTextFile` (toolbar.ts). */
158
+ onSaveText?: SaveTextHandler): HTMLDivElement | null;
159
+ /**
160
+ * "Functional Tests" section — one row per functional (`testType: 'F'`) test
161
+ * with pass/fail counts and pass rate. The functional counterpart of
162
+ * `buildTestSection`, which shows parametric statistics functional tests are
163
+ * excluded from. Shared by the Summary panel, the Insights Overview tab, and
164
+ * the pooled lot variant (`buildLotFunctionalSection`).
165
+ *
166
+ * `precomputed` takes `StatsSummary.stats.functionalYield` (already computed
167
+ * by `analyzeWaferMap`, or pooled by `buildLotFunctionalSection`); without it
168
+ * the rows are computed from `dies` via `computeFunctionalYield`, so both
169
+ * paths share one computation.
170
+ */
171
+ export declare function buildFunctionalTestSection(dies: Die[], testDefs: TestDef[] | undefined, precomputed?: NonNullable<StatsSummary['stats']['functionalYield']>,
172
+ /** Optional host hook for the "Export CSV" button — see `saveTextFile` (toolbar.ts). */
173
+ onSaveText?: SaveTextHandler): HTMLDivElement | null;
174
+ /**
175
+ * Lot-pooled "Functional Tests" section: when every wafer's
176
+ * `StatsSummary.stats.functionalYield` is available, sums pass/fail/total
177
+ * counts across wafers (exact — counts pool losslessly, unlike quantiles);
178
+ * otherwise recomputes from the pooled dies.
179
+ */
180
+ export declare function buildLotFunctionalSection(allDies: Die[], testDefs: TestDef[] | undefined, perWaferSummaries?: StatsSummary[], onSaveText?: SaveTextHandler): HTMLDivElement | null;
181
+ export declare function buildFindingsSection(findings: StatsFinding[], statsSummary: StatsSummary | LotStatsSummary, onFindingClick: (finding: StatsFinding, row: HTMLButtonElement) => void, activeFindingId: string | null,
182
+ /**
183
+ * When true, skips the collapsible "Findings" header/badge and renders
184
+ * content directly — used by `buildFindingsSectionWithFilter`, which
185
+ * builds its own collapsible wrapper (header + filter row) around this.
186
+ * Default false (collapsible-section behavior, used when this is one
187
+ * section among several, e.g. inside the Insights Overview tab's legacy
188
+ * embedding).
189
+ */
190
+ standalone?: boolean): HTMLDivElement | null;
191
+ /**
192
+ * Findings section with severity/kind/region filter controls — the
193
+ * panel-level entry point (wraps `buildFindingsSection(..., standalone:
194
+ * true)` in its own collapsible "Findings" header + filter row). Returns
195
+ * `null` when the source has no findings at all (nothing to filter).
196
+ */
197
+ export declare function buildFindingsSectionWithFilter(source: StatsSummary | LotStatsSummary, onFindingClick: (finding: StatsFinding, row: HTMLButtonElement) => void, activeFindingId: string | null, filter: FindingsFilter, onFilterChange: () => void): HTMLDivElement | null;
198
+ /**
199
+ * Aggregate test value stats across all wafers in the lot. When every
200
+ * wafer's `StatsSummary.stats.perTestStats`/`.testSpecYield` is available
201
+ * (`perWaferSummaries`), pools mean (n-weighted)/min/max and sums spec
202
+ * pass/fail counts directly from those instead of re-scanning `allDies` —
203
+ * exact, since these are the same per-wafer aggregates `analyzeWaferMap`
204
+ * already computed once each. (Quartiles/median aren't pooled this way —
205
+ * they aren't reconstructable from per-wafer quartiles alone — but
206
+ * `buildTestSection`'s display doesn't need them.)
207
+ */
208
+ export declare function buildLotTestSection(allDies: Die[], testDefs: TestDef[] | undefined, fallbackFormat?: 'si' | 'engineering', perWaferSummaries?: StatsSummary[],
209
+ /** Optional host hook for the "Export CSV" button — see `saveTextFile` (toolbar.ts). */
210
+ onSaveText?: SaveTextHandler): HTMLDivElement | null;
211
+ export declare function createSummaryPanelEl(placement: 'right' | 'left' | 'top' | 'bottom'): HTMLDivElement;
212
+ /** Wrap the canvas + panel in a flex container according to placement. */
213
+ export declare function wrapWithSummaryPanel(content: HTMLElement, panel: HTMLDivElement, placement: 'right' | 'left' | 'top' | 'bottom'): HTMLDivElement;
214
+ /** Render all wafer-level sections into a panel element. Clears existing content. */
215
+ export declare function renderWaferSummaryContent(panel: HTMLDivElement, params: {
216
+ wafer: Wafer;
217
+ dies: Die[];
218
+ yieldSummary?: YieldSummary;
219
+ dataCoverage?: {
220
+ filledDies: number;
221
+ totalDies: number;
222
+ edgeExcludedDies: number;
223
+ ratio: number;
224
+ };
225
+ hbinDefs?: BinDef[];
226
+ sbinDefs?: BinDef[];
227
+ testDefs?: TestDef[];
228
+ statsSummary?: StatsSummary;
229
+ passBins?: number[];
230
+ ringCount?: number;
231
+ colorScheme?: string;
232
+ fallbackFormat?: 'si' | 'engineering';
233
+ onFindingClick?: (finding: StatsFinding, row: HTMLButtonElement) => void;
234
+ activeFindingId?: string | null;
235
+ findingsFilter?: FindingsFilter;
236
+ onFindingsFilterChange?: () => void;
237
+ onSaveText?: SaveTextHandler;
238
+ }): void;
239
+ /** Render lot-level content into the panel. Clears existing content. */
240
+ export declare function renderLotSummaryContent(panel: HTMLDivElement, params: {
241
+ lotSummary: LotStatsSummary;
242
+ items: Array<{
243
+ label?: string;
244
+ wafer?: Wafer;
245
+ dies?: Die[];
246
+ statsSummary?: StatsSummary;
247
+ } | null>;
248
+ hbinDefs?: BinDef[];
249
+ sbinDefs?: BinDef[];
250
+ testDefs?: TestDef[];
251
+ passBins?: number[];
252
+ ringCount?: number;
253
+ colorScheme?: string;
254
+ fallbackFormat?: 'si' | 'engineering';
255
+ onFindingClick?: (finding: StatsFinding, row: HTMLButtonElement) => void;
256
+ activeFindingId?: string | null;
257
+ onWaferClick?: (waferIndex: number) => void;
258
+ findingsFilter?: FindingsFilter;
259
+ onFindingsFilterChange?: () => void;
260
+ onSaveText?: SaveTextHandler;
261
+ }): void;
262
+ //# sourceMappingURL=summaryPanel.d.ts.map
@@ -0,0 +1,3 @@
1
+ import{isParametricTest as ae}from"../renderer/buildWaferMap.js";import{buildRingRegions as Q,buildQuadrantRegions as G,buildRegionYieldData as se}from"../stats/regions.js";import{computeFunctionalYield as le}from"../stats/analyzeWaferMap.js";import{openHtmlReport as U}from"../stats/renderFindingsReport.js";import{renderSummaryReportHtml as de,renderLotSummaryReportHtml as pe}from"../stats/renderSummaryReport.js";import{buildFindingsNarrative as ce}from"../stats/findingsNarrative.js";import{filterFindings as ue}from"../stats/filterFindings.js";import{buildFacetTable as fe,prettyKey as X}from"../stats/facets.js";import{getColorScheme as me}from"../renderer/colorSchemes.js";import{fmt as K,fmtAggregationMethod as he,plainBinTerms as I}from"../renderer/fmt.js";import{getUniqueTestNumbers as ge}from"../renderer/buildView.js";import{quantile as O}from"../stats/math.js";import{makeLabeledSelect as Z}from"./charts/chartShell.js";import{CLR as b,sevColor as q,openModal as xe,saveTextFile as J}from"./toolbar.js";import{medianOfSorted as be}from"../core/utils.js";const ve=b.panelBg,M=`1px solid ${b.menuBorder}`,ye="12px",F=b.label,W=b.value,ee="10px";function a(e,n,t){const i=document.createElement(e);return n&&Object.assign(i.style,n),t!==void 0&&(i.textContent=t),i}export function buildWarningsBanner(e){const n=a("div",{background:b.warnBg,border:`1px solid ${b.warnBorder}`,borderRadius:"4px",padding:"7px 9px",marginBottom:"10px",fontSize:"10px",color:b.warnText,lineHeight:"1.5"});for(const t of e)n.appendChild(a("div",{},`\u26A0 ${t}`));return n}function z(e){return a("div",{fontSize:ee,fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:F,marginBottom:"6px"},e)}function te(e,n=!0,t){const i=a("div"),r=document.createElement("button");r.type="button",Object.assign(r.style,{display:"flex",alignItems:"center",gap:"4px",width:"100%",background:"none",border:"none",cursor:"pointer",padding:"0 0 6px",textAlign:"left"});const d=a("span",{fontSize:"9px",color:F,transition:"transform 0.15s",transform:n?"rotate(90deg)":"rotate(0deg)",display:"inline-block",lineHeight:"1",marginRight:"1px"},"\u25B6"),c=a("span",{fontSize:ee,fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:F,flex:"1"},e);if(r.setAttribute("aria-expanded",n?"true":"false"),r.appendChild(d),r.appendChild(c),t){const l=a("span",{fontSize:"9px",fontWeight:"700",background:b.warnBg,color:b.warnText,borderRadius:"10px",padding:"1px 5px"},t);r.appendChild(l)}const p=a("div");p.style.display=n?"block":"none";let o=n;return r.addEventListener("click",()=>{o=!o,p.style.display=o?"block":"none",d.style.transform=o?"rotate(90deg)":"rotate(0deg)",r.setAttribute("aria-expanded",o?"true":"false")}),i.appendChild(r),i.appendChild(p),{outer:i,content:p}}function ne(){return a("div",{height:"1px",background:b.separator,margin:`${ye} 0`,flexShrink:"0"})}function _(e,n,t="#2a6fc0",i,r,d){const c=a("div",{marginBottom:"5px"}),p=a("div",{display:"flex",justifyContent:"space-between",fontSize:"11px",color:W,marginBottom:"2px"}),o=a("span",{},e),l=a("span",{fontWeight:"600"},`${n.toFixed(1)}%`);p.appendChild(o),p.appendChild(l);const u=i!==void 0?i:Math.min(100,Math.max(0,n)),v=d?"#94a3b8":t,y=a("div",{position:"relative",height:"9px",background:b.bgActive,borderRadius:"4px",overflow:"hidden"}),R=a("div",{height:"100%",width:`${u}%`,background:v,borderRadius:"4px",transition:"width 0.3s ease"});if(y.appendChild(R),r!==void 0){const B=a("div",{position:"absolute",top:"0",bottom:"0",left:`${r}%`,width:"1px",background:b.infoText,opacity:"0.5"});y.appendChild(B)}return c.appendChild(p),c.appendChild(y),c}function P(e,n,t){const i=a("div",{background:b.menuBg,border:M,borderRadius:"6px",padding:"8px 10px",textAlign:"center",flex:"1"}),r=a("div",{fontSize:"20px",fontWeight:"700",color:W,lineHeight:"1.2"},e),d=a("div",{fontSize:"10px",color:F,marginTop:"2px"},n);return i.appendChild(r),i.appendChild(d),t&&i.appendChild(a("div",{fontSize:"9px",color:F,opacity:"0.85"},t)),i}function Se(e,n){const t=a("div",{display:"flex",justifyContent:"space-between",fontSize:"11px",gap:"8px",marginBottom:"3px"}),i=a("span",{color:F,flexShrink:"0"},e),r=a("span",{color:W,textAlign:"right",fontWeight:"500",wordBreak:"break-all"},n);return t.appendChild(i),t.appendChild(r),t}function we(e,n){const t=a("div",{display:"flex",fontSize:"11px",gap:"6px",marginBottom:"3px"}),i=a("span",{color:F,flexShrink:"0"},`${e}:`),r=a("span",{color:W,fontWeight:"500",wordBreak:"break-all"},n);return t.appendChild(i),t.appendChild(r),t}export function metadataEntries(e){return Object.entries(e).filter(([,n])=>n!=null&&n!=="").map(([n,t])=>[n,String(t)])}export function buildFacetSummaryChips(e,n=3){if(!e.length)return null;const t=a("div",{display:"flex",flexWrap:"wrap",alignItems:"baseline",gap:"4px 10px",fontSize:"11px"});for(const i of e){const r=a("span",{whiteSpace:"nowrap"}),d=a("span",{color:F},`${X(i.key)}: `);r.appendChild(d);const c=i.values.slice(0,n),p=i.values.length-c.length;let o=c.map(l=>l.value).join(", ");p>0&&(o+=` +${p} more`),r.appendChild(document.createTextNode(o)),t.appendChild(r)}return t}export function buildMetadataStripRow(e,n,t){const i=fe(e,t),r=buildFacetSummaryChips(i);if(!r&&!n)return null;const d=a("div",{display:"flex",flexWrap:"wrap",alignItems:"baseline",gap:"4px 14px"});if(n){const c=a("span",{fontWeight:"500",whiteSpace:"nowrap"},n.aggrMethod?`${n.lotSize} wafers stacked \xB7 ${n.aggrMethod}`:`${n.lotSize} wafers stacked`);d.appendChild(c)}return r&&d.appendChild(r),d}const Ce={display:"flex",flexDirection:"column",gap:"6px",background:b.menuBg,border:`1px solid ${b.menuBorder}`,borderRadius:"6px",padding:"6px 10px",boxShadow:"0 1px 4px rgba(0,0,0,0.10)",fontSize:"12px",lineHeight:"1",boxSizing:"border-box",width:"100%",minWidth:"0"};export function buildMetadataStripBox(e,n,t){const i=buildMetadataStripRow(e,n,t);if(!i)return null;const r=a("div",Ce);return r.appendChild(i),r}export function buildCompactMetadataRows(e){const n=metadataEntries(e);if(!n.length)return null;const t=a("div");for(const[i,r]of n)t.appendChild(we(X(i),r));return t}export function buildMetadataInfoSection(e,n){const t=buildMetadataStripRow(e,n,{facetableOnly:e.length>1});if(!t)return null;const i=a("div");return i.appendChild(z("Wafer Info")),i.appendChild(t),i}export function buildYieldSection(e,n,t=[1]){const i=a("div");i.appendChild(z("Summary"));const r=a("div",{display:"flex",gap:"6px",marginBottom:"8px"});if(r.appendChild(P(String(n.totalDies),"Total dies")),e.partialDies>0&&r.appendChild(P(String(e.partialDies),"Partial")),e.yieldPercent!==null){const d=t.length===1?`bin ${t[0]}`:`bins ${t.join(", ")}`;r.appendChild(P(`${e.yieldPercent.toFixed(1)}%`,"Yield",`pass: ${d}`))}return i.appendChild(r),e.edgeExcludedDies>0&&i.appendChild(Se("Edge excluded (outer zone)",String(e.edgeExcludedDies))),i}export function buildBinSection(e,n,t,i,r){const d=new Map;if(r)for(const[u,v]of Object.entries(r))d.set(Number(u),v);else for(const u of e){if(u.partial||u.edgeExcluded)continue;const v=t==="hard"?u.hbin:u.sbin;v!=null&&d.set(v,(d.get(v)??0)+1)}if(!d.size)return null;const c=[...d.values()].reduce((u,v)=>u+v,0),p=n?new Map(n.map(u=>[u.bin,u])):null,o=[...d.entries()].sort((u,v)=>u[0]-v[0]),l=a("div");l.appendChild(z(t==="hard"?"Hard Bin Breakdown":"Soft Bin Breakdown"));for(const[u,v]of o){const y=p?.get(u),R=y?.name?`Bin ${u} \xB7 ${y.name}`:`Bin ${u}`,B=v/c*100,$=me(i),L=(i==="custom"?y?.color:void 0)??$.forBin(u);l.appendChild(_(`${R} (${v})`,B,L))}return l}export function buildLotBinSection(e,n,t,i){return buildBinSection(e,n,t,i)}function Y(e,n,t,i,r,d){const c=se(e,n,t,i,r);if(!c.length)return null;const p=a("div");p.appendChild(z(d));for(const{label:o,n:l,yieldPercent:u}of c)p.appendChild(_(`${o} (N=${l})`,u));return p}export function buildRingSection(e,n,t,i){return Y([e],[n],t,i,Q,"Ring Yield")}export function buildQuadrantSection(e,n,t,i){return Y([e],[n],t,i,G,"Quadrant Yield")}export function buildLotRingSection(e,n,t,i){return Y(e,n,t,i,Q,"Ring Yield")}export function buildLotQuadrantSection(e,n,t,i){return Y(e,n,t,i,G,"Quadrant Yield")}export function buildLotOverviewSection(e){const n=a("div");n.appendChild(z("Lot Summary"));const t=a("div",{display:"flex",gap:"6px",marginBottom:"8px"});t.appendChild(P(String(e.stats.waferCount),"Wafers"));const i=e.perWafer.map(r=>r.summary.stats.yieldPercent).filter(r=>r!==null);if(i.length){const r=i.reduce((d,c)=>d+c,0)/i.length;t.appendChild(P(`${r.toFixed(1)}%`,"Mean wafer yield"))}return n.appendChild(t),n}export function buildPerWaferYieldSection(e,n,t){const i=e.perWafer.map(l=>({waferIndex:l.waferIndex,label:(n[l.waferIndex]?.label??`W${l.waferIndex+1}`).replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPct:l.summary.stats.yieldPercent})).filter(l=>l.yieldPct!==null);if(!i.length)return null;const r=Math.min(...i.map(l=>l.yieldPct)),d=Math.max(...i.map(l=>l.yieldPct)),c=r===d?"":` (${r.toFixed(1)}\u2013${d.toFixed(1)}%)`,p=be([...i.map(l=>l.yieldPct)].sort((l,u)=>l-u)),o=a("div");o.appendChild(z("Wafer Yield"+c));for(const{waferIndex:l,label:u,yieldPct:v}of i){const y=_(u,v,void 0,void 0,p,v<p);t&&(y.style.cursor="pointer",y.style.borderRadius="4px",y.style.padding="2px 3px",y.style.marginLeft="-3px",y.style.marginRight="-3px",y.setAttribute("role","button"),y.setAttribute("aria-label",`${u}, ${v.toFixed(1)}% yield \u2014 view wafer`),y.tabIndex=0,y.addEventListener("mouseenter",()=>{y.style.background=b.bgHover}),y.addEventListener("mouseleave",()=>{y.style.background=""}),y.addEventListener("focus",()=>{y.style.background=b.bgHover}),y.addEventListener("blur",()=>{y.style.background=""}),y.addEventListener("click",()=>t(l)),y.addEventListener("keydown",R=>{(R.key==="Enter"||R.key===" ")&&(R.preventDefault(),t(l))})),o.appendChild(y)}return o}function ke(e){const n=[...e].sort((r,d)=>r-d),t=e.reduce((r,d)=>r+d,0)/e.length,i=e.reduce((r,d)=>r+(d-t)**2,0)/e.length;return{min:n[0],max:n[n.length-1],mean:t,count:e.length,stddev:Math.sqrt(i),median:O(n,.5),q1:O(n,.25),q3:O(n,.75)}}function A(e){return/[",\n]/.test(e)?`"${e.replace(/"/g,'""')}"`:e}export function buildTestSection(e,n,t,i,r){const d=e.filter(s=>!s.partial&&!s.edgeExcluded),c=new Map((i?.perTestStats??[]).map(s=>[s.testNumber,s])),p=new Map((i?.testSpecYield??[]).map(s=>[s.testNumber,s]));let o;if(n?.length?o=n.filter(ae).map(s=>({testNumber:s.testNumber,name:s.name,unit:s.unit,limitLow:s.limitLow,limitHigh:s.limitHigh})):o=ge(d).map(h=>({testNumber:h,name:`Test ${h}`})),!o.length)return null;const l=o.filter(s=>d.some(h=>{const S=h.testValues?.[s.testNumber];return S!==void 0&&isFinite(S)}));if(!l.length)return null;const u=l.some(s=>s.limitLow!==void 0||s.limitHigh!==void 0),v=[];for(const s of l){const h=c.get(s.testNumber),S=h&&h.stddev!==void 0&&h.median!==void 0&&h.q1!==void 0&&h.q3!==void 0;let C;if(S){const g=h;C={testNumber:s.testNumber,min:g.min,max:g.max,mean:g.mean,count:g.count,stddev:g.stddev,median:g.median,q1:g.q1,q3:g.q3}}else{const g=d.map(D=>D.testValues?.[s.testNumber]).filter(D=>D!==void 0&&isFinite(D));if(!g.length)continue;C={testNumber:s.testNumber,...ke(g)}}let x=null,f=0;if(s.limitLow!==void 0||s.limitHigh!==void 0){const g=p.get(s.testNumber);if(g)({yieldPercent:x,totalDies:f}=g);else{const D=d.map(N=>N.testValues?.[s.testNumber]).filter(N=>N!==void 0&&isFinite(N)),j=D.filter(N=>s.limitLow!==void 0&&N<s.limitLow||s.limitHigh!==void 0&&N>s.limitHigh).length;f=D.length,x=D.length>0?(D.length-j)/D.length*100:null}}v.push({entry:s,stats:C,specYieldPct:x,specN:f})}if(!v.length)return null;const y=a("div"),R=a("div",{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"6px"}),B=z(`Test Values (${v.length})`);B.style.marginBottom="0",R.appendChild(B);const $=document.createElement("button");$.type="button",$.textContent="Export CSV",Object.assign($.style,{background:"none",border:M,borderRadius:"4px",cursor:"pointer",fontSize:"10px",color:b.iconHover,padding:"2px 7px"}),$.addEventListener("click",()=>{const s=["Test","Unit","N","Min","Q1","Median","Mean","Q3","Max","StdDev"];u&&s.push("LSL","USL","Spec Yield %","Spec Yield N");const h=[s.map(A).join(",")],S=C=>K(C,void 0,"engineering");for(const{entry:C,stats:x,specYieldPct:f,specN:g}of v){const D=[C.name,C.unit??"",String(x.count),S(x.min),S(x.q1),S(x.median),S(x.mean),S(x.q3),S(x.max),S(x.stddev)];u&&D.push(C.limitLow!==void 0?S(C.limitLow):"",C.limitHigh!==void 0?S(C.limitHigh):"",f!==null?f.toFixed(1):"",C.limitLow!==void 0||C.limitHigh!==void 0?String(g):""),h.push(D.map(A).join(","))}J(h.join(`
2
+ `),"test-values.csv","text/csv",r)}),R.appendChild($),y.appendChild(R);const L=a("table",{width:"100%",borderCollapse:"collapse",fontSize:"11px"}),E=document.createElement("thead"),T=document.createElement("tr"),m=["Test","N","Min","Q1","Median","Mean","Q3","Max","StdDev"];u&&m.push("LSL","USL","Spec yield");for(const s of m){const h=a("th",{textAlign:s==="Test"?"left":"right",fontWeight:"700",fontSize:"10px",letterSpacing:"0.03em",textTransform:"uppercase",color:F,padding:"3px 8px 5px",borderBottom:`1px solid ${b.menuBorder}`,whiteSpace:"nowrap"},s);T.appendChild(h)}E.appendChild(T),L.appendChild(E);const k=document.createElement("tbody");for(const{entry:s,stats:h,specYieldPct:S,specN:C}of v){const x=D=>K(D,s.unit,t),f=document.createElement("tr"),g=(D,j="right")=>{const N=a("td",{textAlign:j,padding:"4px 8px",borderBottom:`1px solid ${b.menuBorder}`,color:W,whiteSpace:"nowrap"},D);f.appendChild(N)};g(s.name,"left"),g(`${h.count}`),g(x(h.min)),g(x(h.q1)),g(x(h.median)),g(x(h.mean)),g(x(h.q3)),g(x(h.max)),g(x(h.stddev)),u&&(g(s.limitLow!==void 0?x(s.limitLow):"\u2014"),g(s.limitHigh!==void 0?x(s.limitHigh):"\u2014"),g(S!==null?`${S.toFixed(1)}% (N=${C})`:"\u2014")),k.appendChild(f)}L.appendChild(k);const w=a("div",{overflowX:"auto"});return w.appendChild(L),y.appendChild(w),y}export function buildFunctionalTestSection(e,n,t,i){const r=t??le(e,n);if(!r?.length)return null;const d=a("div"),c=a("div",{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"6px"}),p=z(`Functional Tests (${r.length})`);p.style.marginBottom="0",c.appendChild(p);const o=document.createElement("button");o.type="button",o.textContent="Export CSV",Object.assign(o.style,{background:"none",border:M,borderRadius:"4px",cursor:"pointer",fontSize:"10px",color:b.iconHover,padding:"2px 7px"}),o.addEventListener("click",()=>{const B=[["Test","N","Pass","Fail","Pass Rate %"].map(A).join(",")];for(const $ of r)B.push([$.label,String($.totalDies),String($.passDies),String($.failDies),$.passRatePercent!==null?$.passRatePercent.toFixed(1):""].map(A).join(","));J(B.join(`
3
+ `),"functional-tests.csv","text/csv",i)}),c.appendChild(o),d.appendChild(c);const l=a("table",{width:"100%",borderCollapse:"collapse",fontSize:"11px"}),u=document.createElement("thead"),v=document.createElement("tr");for(const B of["Test","N","Pass","Fail","Pass rate"])v.appendChild(a("th",{textAlign:B==="Test"?"left":"right",fontWeight:"700",fontSize:"10px",letterSpacing:"0.03em",textTransform:"uppercase",color:F,padding:"3px 8px 5px",borderBottom:`1px solid ${b.menuBorder}`,whiteSpace:"nowrap"},B));u.appendChild(v),l.appendChild(u);const y=document.createElement("tbody");for(const B of r){const $=document.createElement("tr"),L=(E,T="right")=>{$.appendChild(a("td",{textAlign:T,padding:"4px 8px",borderBottom:`1px solid ${b.menuBorder}`,color:W,whiteSpace:"nowrap"},E))};L(B.label,"left"),L(String(B.totalDies)),L(String(B.passDies)),L(String(B.failDies)),L(B.passRatePercent!==null?`${B.passRatePercent.toFixed(1)}% (N=${B.totalDies})`:"\u2014"),y.appendChild($)}l.appendChild(y);const R=a("div",{overflowX:"auto"});return R.appendChild(l),d.appendChild(R),d}export function buildLotFunctionalSection(e,n,t,i){if(t?.length&&t.every(r=>r.stats.functionalYield!==void 0)){const r=new Map;for(const c of t)for(const p of c.stats.functionalYield??[]){const o=r.get(p.testNumber)??{label:p.label,passDies:0,failDies:0,totalDies:0};o.passDies+=p.passDies,o.failDies+=p.failDies,o.totalDies+=p.totalDies,r.set(p.testNumber,o)}const d=[...r.entries()].map(([c,p])=>({testNumber:c,...p,passRatePercent:p.totalDies>0?p.passDies/p.totalDies*100:null}));if(d.length)return buildFunctionalTestSection(e,n,d,i)}return buildFunctionalTestSection(e,n,void 0,i)}const H={unusual:0,notable:1,info:2};function V(e){return a("span",{display:"inline-block",width:"7px",height:"7px",borderRadius:"50%",background:q(e),flexShrink:"0"})}function ie(e,n){let t=e.summary;return n&&(t.startsWith(`${n} has `)?t=t.slice(n.length+5):t.startsWith(`${n} `)&&(t=t.slice(n.length+1))),t=I(t),t.length?t[0].toUpperCase()+t.slice(1):t}export function buildFindingsSection(e,n,t,i,r=!1){if(!e.length)return null;const d=e.some(m=>m.severity==="unusual"||m.severity==="notable"),c=d?e.filter(m=>m.severity!=="info").length.toString():void 0,{outer:p,content:o}=r?(()=>{const m=a("div");return{outer:m,content:m}})():te(`Findings (${e.length})`,d,c);function l(m){return m.reduce((k,w)=>H[w.severity]<H[k]?w.severity:k,"info")}function u(m){const k=new Map;for(const s of m){const h=`${s.comparison.family}\0${s.comparison.left}`,S=k.get(h)??[];S.push(s),k.set(h,S)}const w=[...k.entries()].map(([s,h])=>{const S=[...h].sort((C,x)=>H[C.severity]-H[x.severity]);return{key:s,findings:S,worst:l(S)}});return w.sort((s,h)=>H[s.worst]-H[h.worst]),w}function v(m,k){const s={ring:"Ring",quadrant:"Quadrant",sector:"Sector","reticle-position":"Reticle","test-site":"Test site",cluster:"Cluster","edge-arc":"Edge arc",wafer:"Wafer"}[m]??m;return k.toLowerCase().startsWith(s.toLowerCase())?k:`${s}: ${k}`}const y=e.filter(m=>m.comparison.family==="spatial-pattern"),R=new Set(y.flatMap(m=>m.relatedIds??[])),B=e.filter(m=>m.comparison.family!=="spatial-pattern"&&!R.has(m.id)),$=u(B);function L(m,k=!1,w){const s=i===m.id,h=document.createElement("button");return h.type="button",h.dataset.wmapFinding=m.id,h.textContent=ie(m,w),h.title=m.summary,Object.assign(h.style,{border:`1px solid ${b.menuBorder}`,borderLeft:`3px solid ${q(m.severity)}`,background:s?b.bgActive:b.menuBg,borderRadius:"6px",padding:k?"6px 10px":"8px 10px",textAlign:"left",fontSize:k?"10px":"11px",fontWeight:s?"600":"400",color:b.iconHover,cursor:"pointer",width:"100%",marginBottom:"4px"}),h.addEventListener("click",()=>t(m,h)),h}const E=I(ce(e)??"");if(E){const m=a("div",{background:b.bgActive,borderRadius:"6px",padding:"8px 10px",marginBottom:"8px",display:"flex",gap:"8px",alignItems:"flex-start"}),k=a("span",{flex:"1",fontSize:"12px",lineHeight:"1.5",color:b.iconHover},E);m.appendChild(k);const w=a("button",{flexShrink:"0",border:"none",background:"none",fontSize:"10px",color:b.icon,cursor:"pointer",padding:"0",lineHeight:"1.5",whiteSpace:"nowrap"},"Detail \u25B8");w.type="button",w.addEventListener("click",()=>{const s=xe({title:"Findings Summary",onClose:()=>{},anchor:w}),h=a("p",{fontSize:"16px",lineHeight:"1.6",color:b.iconHover,padding:"20px 24px 16px",margin:"0",borderBottom:`1px solid ${b.menuBorder}`,flexShrink:"0"},E);s.contentWrap.appendChild(h);const S=a("div",{overflowY:"auto",padding:"16px 24px",flex:"1"}),C=(x,f)=>{const g=a("div",{display:"flex",alignItems:"center",gap:"7px",fontSize:"13px",fontWeight:"600",color:W,marginTop:"10px",marginBottom:"4px"});return g.appendChild(V(x)),g.appendChild(a("span",{},f)),g};for(const x of y){S.appendChild(C(x.severity,x.comparison.left)),S.appendChild(a("div",{fontSize:"13px",color:b.iconHover,padding:"3px 0 3px 15px",marginBottom:"2px"},I(x.summary)));const f=e.filter(g=>x.relatedIds?.includes(g.id));for(const g of f)S.appendChild(a("div",{fontSize:"12px",color:b.icon,padding:"2px 0 2px 23px",marginBottom:"2px"},I(g.summary)))}for(const x of $){const[f,g]=x.key.split("\0");S.appendChild(C(x.worst,v(f,g)));for(const D of x.findings)S.appendChild(a("div",{fontSize:"13px",color:b.iconHover,padding:"3px 0 3px 15px",marginBottom:"2px"},ie(D,g)))}s.contentWrap.appendChild(S),Object.assign(s.contentWrap.style,{flexDirection:"column",overflow:"hidden"})}),m.appendChild(w),o.appendChild(m)}let T=!0;for(const m of y){T||o.appendChild(a("div",{height:"1px",background:b.separator,margin:"4px 0"})),T=!1;const k=e.filter(C=>m.relatedIds?.includes(C.id)),w=k.length>0,s=a("div",{position:"relative",marginBottom:w?"2px":"4px"}),h=i===m.id,S=document.createElement("button");if(S.type="button",S.dataset.wmapFinding=m.id,Object.assign(S.style,{border:`1px solid ${b.menuBorder}`,borderLeft:`3px solid ${q(m.severity)}`,background:h?b.bgActive:b.menuBg,borderRadius:"6px",padding:"8px 32px 8px 10px",textAlign:"left",fontSize:"11px",fontWeight:h?"600":"500",color:b.iconHover,cursor:"pointer",width:"100%"}),S.textContent=I(m.summary),S.title=m.summary,S.addEventListener("click",()=>t(m,S)),s.appendChild(S),w){const C=a("div",{display:"none",paddingLeft:"12px",marginBottom:"4px"});for(const g of k)C.appendChild(L(g,!0));s.appendChild(C);let x=!1;const f=a("button",{position:"absolute",top:"50%",right:"8px",transform:"translateY(-50%)",border:"none",background:"none",fontSize:"10px",color:b.icon,cursor:"pointer",padding:"2px 4px",lineHeight:"1"},"\u25B8");f.type="button",f.title="Show supporting findings",f.addEventListener("click",g=>{g.stopPropagation(),x=!x,C.style.display=x?"block":"none",f.textContent=x?"\u25BE":"\u25B8",f.title=x?"Hide supporting findings":"Show supporting findings"}),s.appendChild(f)}o.appendChild(s)}for(const m of $){T||o.appendChild(a("div",{height:"1px",background:b.separator,margin:"4px 0"})),T=!1;const[k,w]=m.key.split("\0"),s=a("div",{display:"flex",alignItems:"center",gap:"6px",fontSize:"11px",fontWeight:"600",color:W,margin:"2px 0 4px"});s.appendChild(V(m.worst)),s.appendChild(a("span",{},v(k,w))),o.appendChild(s);for(const h of m.findings)o.appendChild(L(h,!1,w))}return p}const Be=[{value:"",label:"All kinds"},{value:"yield",label:"Yield"},{value:"hardBin",label:"Hard bin"},{value:"softBin",label:"Soft bin"},{value:"test",label:"Test value"},{value:"functionalTest",label:"Functional test"},{value:"spatialPattern",label:"Spatial pattern"}],$e=[{value:"",label:"All regions"},{value:"ring",label:"Ring"},{value:"quadrant",label:"Quadrant"},{value:"reticle-position",label:"Reticle position"},{value:"test-site",label:"Test site"},{value:"wafer",label:"Wafer"},{value:"sector",label:"Sector"},{value:"cluster",label:"Cluster"},{value:"edge-arc",label:"Edge arc"},{value:"spatial-pattern",label:"Spatial pattern"}],Re=["unusual","notable","info"],Le={unusual:"Unusual",notable:"Notable",info:"Info"};function De(e,n,t){const i=a("div",{display:"flex",flexWrap:"wrap",alignItems:"center",gap:"10px",marginBottom:"10px",paddingBottom:"10px",borderBottom:`1px solid ${b.separator}`}),r={unusual:0,notable:0,info:0};for(const o of e)r[o.severity]++;const d=Re.filter(o=>r[o]>0),c=new Set(n.severity===void 0?d:Array.isArray(n.severity)?n.severity:[n.severity]),p=a("div",{display:"flex",gap:"4px",flexWrap:"wrap"});for(const o of d){const l=document.createElement("button");l.type="button";const u=()=>{const v=c.has(o);l.setAttribute("aria-pressed",v?"true":"false"),Object.assign(l.style,{display:"inline-flex",alignItems:"center",gap:"5px",border:`1px solid ${b.menuBorder}`,borderRadius:"10px",background:v?b.bgActive:"none",opacity:v?"1":"0.5",color:b.text,fontSize:"11px",padding:"2px 8px",cursor:"pointer"})};l.appendChild(V(o)),l.appendChild(a("span",{},`${Le[o]} ${r[o]}`)),l.title=c.has(o)?"Click to hide these findings":"Click to show these findings",u(),l.addEventListener("click",()=>{c.has(o)?c.delete(o):c.add(o),n.severity=c.size===d.length?void 0:[...c],t()}),p.appendChild(l)}return i.appendChild(p),i.appendChild(Z("Kind:",Be,n.kind??"",o=>{n.kind=o||void 0,t()},{maxWidth:"130px"})),i.appendChild(Z("Region:",$e,n.family??"",o=>{n.family=o||void 0,t()},{maxWidth:"150px"})),i}export function buildFindingsSectionWithFilter(e,n,t,i,r){if(!e.findings.length)return null;const d=e.findings.some(v=>v.severity==="unusual"||v.severity==="notable"),c=d?e.findings.filter(v=>v.severity!=="info").length.toString():void 0,{outer:p,content:o}=te(`Findings (${e.findings.length})`,d,c);o.appendChild(De(e.findings,i,r));const l=ue(e,i);if(!l.length)return o.appendChild(a("div",{color:F,fontSize:"11px",textAlign:"center",padding:"16px 8px"},"No findings match the current filter.")),p;const u=buildFindingsSection(l,e,n,t,!0);return u&&o.appendChild(u),p}export function buildLotTestSection(e,n,t,i,r){let d;if(i?.length&&i.every(c=>c.stats.perTestStats!==void 0)){const c=new Map;for(const p of i)for(const o of p.stats.perTestStats??[]){const l=c.get(o.testNumber);l?(l.n+=o.count,l.sum+=o.mean*o.count,l.min=Math.min(l.min,o.min),l.max=Math.max(l.max,o.max)):c.set(o.testNumber,{n:o.count,sum:o.mean*o.count,min:o.min,max:o.max})}d={perTestStats:[...c.entries()].map(([p,o])=>({testNumber:p,count:o.n,min:o.min,max:o.max,mean:o.sum/o.n}))}}if(i?.length&&i.every(c=>c.stats.testSpecYield!==void 0)){const c=new Map;for(const p of i)for(const o of p.stats.testSpecYield??[]){const l=c.get(o.testNumber);l?(l.passDies+=o.passDies,l.totalDies+=o.totalDies):c.set(o.testNumber,{passDies:o.passDies,totalDies:o.totalDies})}d={...d,testSpecYield:[...c.entries()].map(([p,o])=>({testNumber:p,totalDies:o.totalDies,yieldPercent:o.totalDies>0?o.passDies/o.totalDies*100:null}))}}return buildTestSection(e,n,t,d,r)}export function createSummaryPanelEl(e){const n=e==="top"||e==="bottom",t=a("div",{background:ve,border:M,borderRadius:"8px",padding:"12px",overflowY:n?"hidden":"auto",overflowX:n?"auto":"hidden",flexShrink:"0",boxSizing:"border-box",fontFamily:"system-ui, sans-serif",fontSize:"12px",boxShadow:"0 1px 4px rgba(0,0,0,0.08)"});return n?(t.style.height="180px",t.style.width="100%"):(t.style.width="260px",t.style.maxHeight="100%"),t}export function wrapWithSummaryPanel(e,n,t){const i=t==="top"||t==="bottom",r=a("div",{display:"flex",flexDirection:i?"column":"row",gap:"8px",width:"100%",height:"100%",flex:"1 1 0",minHeight:"0",boxSizing:"border-box",alignItems:i?"flex-start":"stretch"});return e.style.flex="1 1 0",e.style.minWidth="0",e.style.minHeight="0",t==="right"||t==="bottom"?(r.appendChild(e),r.appendChild(n)):(r.appendChild(n),r.appendChild(e)),r}function oe(e){return a("div",{fontSize:"13px",fontWeight:"700",color:W,marginBottom:"10px"},e)}function re(e,n){const t=a("button",{background:"none",border:M,borderRadius:"4px",cursor:"pointer",fontSize:"10px",color:b.iconHover,padding:"2px 7px",marginBottom:"10px",display:"block"},e);return t.type="button",t.addEventListener("click",n),t}export function renderWaferSummaryContent(e,n){const t=e.scrollTop;e.innerHTML="";const{wafer:i,dies:r,yieldSummary:d,dataCoverage:c,hbinDefs:p,sbinDefs:o,testDefs:l,statsSummary:u,passBins:v=[1],ringCount:y=4,colorScheme:R,fallbackFormat:B,onFindingClick:$,activeFindingId:L=null,findingsFilter:E,onFindingsFilterChange:T,onSaveText:m}=n;e.appendChild(oe("Wafer Summary"));const k=u?.stats.warnings;k?.length&&e.appendChild(buildWarningsBanner(k)),d&&c&&e.appendChild(re("Summary report",()=>{U(de({wafer:i,dies:r,yieldSummary:d,dataCoverage:c,hbinDefs:p,sbinDefs:o,testDefs:l,statsSummary:u,passBins:v,ringCount:y}))}));const w=[],s=u?.stats.isLotStack?u.stats:void 0,h=s?.lotSize!==void 0?{lotSize:s.lotSize,aggrMethod:he(s.aggregationMethod)}:void 0;w.push(buildMetadataInfoSection([{metadata:i.metadata??void 0}],h)),d&&c&&w.push(buildYieldSection(d,c,v));const S=r.some(f=>f.hbin!=null),C=r.some(f=>f.sbin!=null);S?w.push(buildBinSection(r,p,"hard",R,u?.stats.hardBinCounts)):C&&w.push(buildBinSection(r,o,"soft",R,u?.stats.softBinCounts)),w.push(buildRingSection(r,i,y,v)),w.push(buildQuadrantSection(r,i,y,v)),w.push(buildTestSection(r,l,B,u?.stats,m)),w.push(buildFunctionalTestSection(r,l,u?.stats.functionalYield,m)),u&&$&&E&&T&&w.push(buildFindingsSectionWithFilter(u,$,L,E,T));let x=!0;for(const f of w)f&&(x||e.appendChild(ne()),x=!1,e.appendChild(f));e.appendChild(a("div",{height:"12px",flexShrink:"0"})),e.scrollTop=t}export function renderLotSummaryContent(e,n){const t=e.scrollTop;e.innerHTML="";const{lotSummary:i,items:r,hbinDefs:d,sbinDefs:c,testDefs:p,passBins:o=[1],ringCount:l=4,colorScheme:u,fallbackFormat:v,onFindingClick:y,activeFindingId:R=null,onWaferClick:B,findingsFilter:$,onFindingsFilterChange:L,onSaveText:E}=n;e.appendChild(oe(`Lot Summary \u2014 ${i.stats.waferCount} wafer${i.stats.waferCount===1?"":"s"}`));const T=[...new Set(i.perWafer.flatMap(f=>f.summary.stats.warnings??[]))];T.length&&e.appendChild(buildWarningsBanner(T)),e.appendChild(re("Summary report",()=>{U(pe({items:r.map((f,g)=>({label:f?.label??`W${g+1}`,wafer:f?.wafer,dies:f?.dies,statsSummary:f?.statsSummary})),hbinDefs:d,sbinDefs:c,testDefs:p,passBins:o,ringCount:l}))}));const m=[],k=[],w=[];for(const f of r){if(!f){k.push([]);continue}f.wafer&&m.push(f.wafer);const g=f.dies??[];k.push(g),w.push(...g)}const s=w.some(f=>f.hbin!=null),h=w.some(f=>f.sbin!=null),S=r.map(f=>f?.statsSummary).filter(f=>!!f),C=[buildLotOverviewSection(i),buildMetadataInfoSection(r.filter(f=>f!==null).map(f=>({metadata:f.wafer?.metadata}))),buildPerWaferYieldSection(i,r,B),s?buildLotBinSection(w,d,"hard",u):h?buildLotBinSection(w,c,"soft",u):null,buildLotRingSection(k,m,l,o),buildLotQuadrantSection(k,m,l,o),p?.length?buildLotTestSection(w,p,v,S,E):null,p?.length?buildLotFunctionalSection(w,p,S,E):null];y&&$&&L&&C.push(buildFindingsSectionWithFilter(i,y,R,$,L));let x=!0;for(const f of C)f&&(x||e.appendChild(ne()),x=!1,e.appendChild(f));e.appendChild(a("div",{height:"12px",flexShrink:"0"})),e.scrollTop=t}
@@ -0,0 +1,106 @@
1
+ import type { View } from '../renderer/buildView.js';
2
+ import type { Die } from '../core/dies.js';
3
+ export interface ToCanvasOptions {
4
+ /** Padding in CSS pixels inside the canvas edge. Default 16. */
5
+ padding?: number;
6
+ /** Draw a continuous colorbar (value modes) or bin legend (bin modes). Default true. */
7
+ showColorbar?: boolean;
8
+ /** Width in CSS pixels of the colorbar strip. Default 16. */
9
+ colorbarWidth?: number;
10
+ /** Canvas background colour. Default '#f5f5f5'. */
11
+ background?: string;
12
+ /** Legend position for bin modes. Default 'default'. */
13
+ legendPosition?: 'default' | 'compact' | 'bottom' | 'top' | 'left' | 'floating';
14
+ /** Floating legend offset in CSS pixels. Used when `legendPosition === 'floating'`. */
15
+ legendOffset?: {
16
+ x: number;
17
+ y: number;
18
+ };
19
+ /**
20
+ * Draw axis tick marks and labels. Default false.
21
+ * When true, labels show die grid indices if `diePitchMm` is provided,
22
+ * otherwise mm values.
23
+ */
24
+ showAxes?: boolean;
25
+ /** Die pitch in mm — used to convert mm axis values to die-index labels. */
26
+ diePitchMm?: {
27
+ x: number;
28
+ y: number;
29
+ };
30
+ /**
31
+ * Override the viewport transform. When provided, `originX`, `originY`,
32
+ * and `ppm` replace the auto-fitted values — used by renderWaferMap for
33
+ * zoom/pan. Also accepts a zoom-adjusted `snapDist` for hit testing.
34
+ */
35
+ viewport?: ViewportTransform;
36
+ /** Currently highlighted bin (or metadata value) — drawn with an active indicator in the bin legend. */
37
+ activeBin?: number | string;
38
+ /**
39
+ * Format to use for unitless values outside the normal display range [0.1, 9999].
40
+ * `'engineering'` (default): multiples-of-3 exponent notation (e.g. `12E-6`).
41
+ * `'si'`: SI prefix with no unit suffix (e.g. `12 µ`).
42
+ * Values with a unit always use SI prefix regardless of this setting.
43
+ */
44
+ fallbackFormat?: 'si' | 'engineering';
45
+ /** Extra space reserved at the top of the canvas in CSS pixels. Default 0. Used by renderWaferMap to prevent the floating toolbar from obscuring the wafer. */
46
+ topClearance?: number;
47
+ /** Minimum right-side reserve in CSS pixels. Ensures the wafer draw width stays stable across plot mode switches. */
48
+ minRightReserve?: number;
49
+ /** Named hard bin definitions — used to label the bin legend. */
50
+ hbinDefs?: import('../renderer/buildWaferMap.js').BinDef[];
51
+ /** Named soft bin definitions — used to label the bin legend. */
52
+ sbinDefs?: import('../renderer/buildWaferMap.js').BinDef[];
53
+ /** Named metadata-field definitions — used to label/color the metadata legend. */
54
+ metadataFields?: import('../renderer/buildWaferMap.js').MetadataFieldDef[];
55
+ /**
56
+ * Draw the map title (test name / mode + stack context) above the colorbar or bin legend.
57
+ * Default true. The title is placed in empty space adjacent to the legend/colorbar and does NOT
58
+ * shrink the map. Set false to suppress (e.g. when a host renders its own heading).
59
+ */
60
+ showTitle?: boolean;
61
+ }
62
+ /** Internal viewport state shared between toCanvas and renderWaferMap. */
63
+ export interface ViewportTransform {
64
+ originX: number;
65
+ originY: number;
66
+ ppm: number;
67
+ snapDist: number;
68
+ }
69
+ export interface HitTarget {
70
+ /** Given a CSS-pixel position on the canvas, return the die at that point or null. */
71
+ getDieAtPoint(x: number, y: number): Die | null;
72
+ }
73
+ /** A hit-testable row in the bin legend — one entry per unique bin (or metadata value). */
74
+ export interface BinLegendRow {
75
+ bin: number | string;
76
+ x: number;
77
+ /** Top CSS-pixel of the row (relative to canvas). */
78
+ y: number;
79
+ /** Width in CSS pixels of the row. */
80
+ w: number;
81
+ /** Height in CSS pixels of the row. */
82
+ h: number;
83
+ /** Optional full label text for tooltips. */
84
+ label?: string;
85
+ }
86
+ export interface ToCanvasResult {
87
+ hitTarget: HitTarget;
88
+ /** The fitted viewport — useful as initial state for renderWaferMap. */
89
+ viewport: ViewportTransform;
90
+ /** Non-empty only when a bin legend was drawn (hardbin / softbin modes). */
91
+ binLegendRows: BinLegendRow[];
92
+ /** Floating legend box bounds, when rendered. */
93
+ legendBox?: {
94
+ x: number;
95
+ y: number;
96
+ w: number;
97
+ h: number;
98
+ };
99
+ }
100
+ export declare const BIN_LEGEND_W = 110;
101
+ export declare const BIN_LEGEND_W_COMPACT = 64;
102
+ export declare const BIN_LEGEND_ADAPT_COMPACT = 280;
103
+ export declare const BIN_LEGEND_ADAPT_FLOATING = 180;
104
+ export declare function toCanvas(canvas: HTMLCanvasElement, view: View, options?: ToCanvasOptions): ToCanvasResult;
105
+ export { fmt, fmtColorbarAxis } from '../renderer/fmt.js';
106
+ //# sourceMappingURL=toCanvas.d.ts.map
@@ -0,0 +1 @@
1
+ import{findTestDef as ue,buildMapTitle as pe}from"../renderer/buildView.js";import{affineInvert as Fe,affineVector as xe}from"../core/transforms.js";import{compareNatural as Oe}from"../core/utils.js";import{getColorScheme as ye}from"../renderer/colorSchemes.js";import{SPEC_PASS_FILL as Me,SPEC_FAIL_LOW as Ie,SPEC_FAIL_HIGH as be,contrastTextColor as Ne}from"../renderer/colorMap.js";import{fmt as Se,fmtColorbarAxis as Ee}from"../renderer/fmt.js";import{resolveCanvasTheme as Ye}from"./canvasTheme.js";function Te(h,e,F,p,c){h.moveTo(e,F+c*p),h.lineTo(e-p*.9,F-c*p*.6),h.lineTo(e+p*.9,F-c*p*.6),h.closePath()}const Xe=new Set(["value","stackedValues","stackedBins","stackedSoftBins"]),we=new Set(["hardBin","softBin","metadata"]),v="10px system-ui, sans-serif",He="600 12px system-ui, sans-serif",$t="11px system-ui, sans-serif",$e="600 11px system-ui, sans-serif",Gt=128,Ge="10px system-ui, sans-serif",ke=4,E=17,w=11;export const BIN_LEGEND_W=110,BIN_LEGEND_W_COMPACT=64,BIN_LEGEND_ADAPT_COMPACT=280,BIN_LEGEND_ADAPT_FLOATING=180;const Ut=28,xt=5,Vt=8;export function toCanvas(h,e,F={}){const p=Ye(h.parentElement??h),{padding:c=16,showColorbar:U=!0,colorbarWidth:S=16,background:lt=p.background,legendPosition:gt="default",legendOffset:j,showAxes:mt=!1,diePitchMm:Tt,viewport:H,activeBin:Q,fallbackFormat:O,topClearance:Y=0,minRightReserve:kt,hbinDefs:Mt,sbinDefs:it,metadataFields:Lt,showTitle:tt=!0}=F,ut=U&&Xe.has(e.plotMode)&&e.passFailDisplay==="off",bt=U&&we.has(e.plotMode),pt=U&&e.plotMode==="value"&&(e.passFailDisplay==="spec"&&e.specCounts!==void 0||e.passFailDisplay==="test"&&e.passFailCounts!==void 0),at=bt||pt,Rt=bt&&e.plotMode!=="metadata"&&e.binCounts?[...e.binCounts.entries()].sort(([o],[a])=>o-a):[],At=bt&&e.plotMode==="metadata"&&e.metadataCounts?[...e.metadataColorMap?.keys()??[...e.metadataCounts.keys()].sort(Oe)].map(o=>[o,e.metadataCounts.get(o)??0]):[],J=[];if(pt&&e.passFailDisplay==="test"){const o=e.passFailCounts;J.push({key:-1,color:Me,label:"Pass",compactLabel:"Pass",count:o.pass,tooltip:`Pass \xB7 ${o.pass} dies`}),J.push({key:-2,color:be,label:"Fail",compactLabel:"Fail",count:o.fail,tooltip:`Fail \xB7 ${o.fail} dies`})}else if(pt){const o=e.specCounts,a=ue(e.testDefs,e.activeTest);J.push({key:-1,color:Me,label:"Pass",compactLabel:"Pass",count:o.pass,tooltip:`Pass \xB7 ${o.pass} dies`}),a?.limitHigh!==void 0&&J.push({key:-2,color:be,label:"Fail high",compactLabel:"Fail hi",count:o.failHigh,tooltip:`Fail high \xB7 ${o.failHigh} dies`}),a?.limitLow!==void 0&&J.push({key:-3,color:Ie,label:"Fail low",compactLabel:"Fail lo",count:o.failLow,tooltip:`Fail low \xB7 ${o.failLow} dies`})}const yt=pt?J.length:e.plotMode==="metadata"?At.length:Rt.length,$=(h.ownerDocument.defaultView??window).devicePixelRatio??1,I=Math.floor(h.clientWidth||h.width),V=Math.floor(h.clientHeight||h.height);if(I<=0||V<=0)return{hitTarget:{getDieAtPoint:()=>null},viewport:{originX:0,originY:0,ppm:1,snapDist:1},binLegendRows:[]};const K=gt!=="default"?gt:I<BIN_LEGEND_ADAPT_FLOATING?"floating":I<BIN_LEGEND_ADAPT_COMPACT?"compact":"default",y=K==="default"||K==="compact",G=K==="left",ct=K==="bottom",Ct=K==="top",Pt=K==="floating",Wt=K==="compact"||K==="floating"?BIN_LEGEND_W_COMPACT:BIN_LEGEND_W,oe=Math.floor((V-2*c)/E),ne=yt>oe?oe-1:yt,se=at&&ct?ne*E:0,zt=at&&Ct?ne*E:0,Kt=ut?S+28:at&&y?Wt:0,Jt=at&&G?Wt:0,Zt=mt?32:0,Bt=mt?36:0,Le=kt!=null?Math.max(Kt,kt):Kt,Re=I-2*c-Le-Bt-Jt,le=V-2*c-Zt-se-zt;h.width=Math.round(I*$),h.height=Math.round(V*$);const t=h.getContext("2d");t.scale($,$),t.fillStyle=lt,t.fillRect(0,0,I,V);const q=e.hoverPoints;if(!q.length)return{hitTarget:{getDieAtPoint:()=>null},viewport:{originX:0,originY:0,ppm:1,snapDist:1},binLegendRows:[]};const _t=e.rectangles[0],jt=_t?_t.width/2:0,Qt=_t?_t.height/2:0,Dt=e.dieBounds,ie=(Dt?.minX??0)-jt,Ae=(Dt?.maxX??0)+jt,Ce=(Dt?.minY??0)-Qt,ae=(Dt?.maxY??0)+Qt,ce=Ae-ie,re=ae-Ce;let et,ot,k;if(H)({originX:et,originY:ot,ppm:k}=H);else{const a=e.notchDir,l=a&&a.y>.1?13:0,r=a&&a.y<-.1?13:0,f=a&&a.x<-.1?13:0,d=a&&a.x>.1?13:0,b=Re-f-d,i=le-Y-l-r;k=Math.min(b/ce,i/re),et=c+Bt+Jt+f+(b-ce*k)/2-ie*k,ot=c+Y+zt+l+(i-re*k)/2+ae*k}const Ft=H?.snapDist??Math.max(jt,Qt,1)*1.5,Ot=et+e.waferCenter.x*k,Pe=ot-e.waferCenter.y*k,vt=e.waferRadius*k,rt=o=>{const a=Math.abs(o-Pe);return a>=vt?0:Math.sqrt(vt*vt-a*a)},ft=(o,a,l,r,f,d,b=He,i="#333")=>{if(!o)return;t.save(),t.fillStyle=i,t.font=b,t.textAlign=r,t.textBaseline=f;const x=r==="right"?a-d:d-a;let s=o;if(x>8&&t.measureText(s).width>x){for(;s.length>1&&t.measureText(s+"\u2026").width>x;)s=s.slice(0,-1);s=s+"\u2026"}x>8&&t.fillText(s,a,l),t.restore()};if(t.save(),t.setTransform(k*$,0,0,-k*$,et*$,ot*$),e.rectangles.length>0){const o=new Map;for(const l of e.rectangles){const r=String(l.fill);let f=o.get(r);f||(f=[],o.set(r,f)),f.push(l)}for(const[l,r]of o){t.beginPath();for(const f of r)t.rect(f.x-f.width/2,f.y-f.height/2,f.width,f.height);t.fillStyle=l,t.fill()}t.strokeStyle="rgba(0,0,0,0.18)",t.lineWidth=.5/(k*$),t.beginPath();for(const l of e.rectangles)t.rect(l.x-l.width/2,l.y-l.height/2,l.width,l.height);t.stroke();const a=e.rectangles.filter(l=>l.specMark);if(a.length>0){const l=new Map;for(const f of a){const d=Ne(String(f.fill)),b=`${f.specMark}|${d}`;let i=l.get(b);i||(i=[],l.set(b,i)),i.push(f)}const r=2.5/(k*$);t.lineJoin="round";for(const[f,d]of l){const[b,i]=f.split("|"),x=i==="#000000"?"#ffffff":"#000000",s=b==="failHigh"?1:-1,u=Math.min(d[0].width,d[0].height),X=Math.max(2.5/(k*$),u*.3);t.beginPath();for(const T of d)Te(t,T.x,T.y,X,s);t.strokeStyle=x,t.lineWidth=r,t.stroke(),t.fillStyle=i,t.fill()}}}for(const o of e.overlays){t.beginPath();for(const a of o.points){for(let l=0;l<a.length;l++)l===0?t.moveTo(a[l].x,a[l].y):t.lineTo(a[l].x,a[l].y);o.closed&&t.closePath()}o.fill&&!o.fill.startsWith("rgba(0,0,0,0)")&&(t.fillStyle=o.fill,t.fill()),o.kind==="ring-boundary"||o.kind==="quadrant-boundary"||o.kind==="reticle"?(t.strokeStyle="rgba(0,0,0,0.45)",t.lineWidth=3/(k*$),t.stroke(),t.strokeStyle="rgba(255,255,255,0.8)",t.lineWidth=1/(k*$),t.stroke()):(t.strokeStyle=o.lineColor,t.lineWidth=o.lineWidth/(k*$),t.stroke())}if(t.restore(),e.notchDir){const o=et+e.waferCenter.x*k,a=ot-e.waferCenter.y*k,l=e.notchDir.x,r=-e.notchDir.y,f=e.waferRadius*k,d=4,b=9,i=5,x=o+l*(f+d),s=a+r*(f+d),u=x+l*b,X=s+r*b,T={x:-r,y:l},B=c+Bt+Jt,nt=I-c-Kt,z=c+Y+zt,dt=V-c-se-Zt;t.save(),t.beginPath(),t.rect(B,z,nt-B,dt-z),t.clip(),t.beginPath(),t.moveTo(x,s),t.lineTo(u+T.x*i,X+T.y*i),t.lineTo(u-T.x*i,X-T.y*i),t.closePath(),t.fillStyle=p.text,t.fill(),t.restore()}t.save(),t.textAlign="center",t.textBaseline="middle";let fe=0;if(e.rectangles.length>0){let o=1;for(const d of e.texts)d.role!=="indicator"&&d.text.length>o&&(o=d.text.length);const a=e.rectangles[0].width*k,l=e.rectangles[0].height*k,r=Math.min(a,l),f=a/o;fe=Math.max(8,Math.min(64,Math.round(Math.min(r*.55,f*1.8))))}for(const o of e.texts){const a=et+o.x*k,l=ot-o.y*k;t.font=`${o.role==="indicator"?o.fontSize:fe}px system-ui, sans-serif`,t.fillStyle=o.color,t.fillText(o.text,a,l)}if(t.restore(),mt&&Ue(t,I,V,et,ot,k,c,Zt,Bt,Tt,e.gridToScreen,e.rotation,p),ut){const o=ye(e.colorScheme),a=20,l=le-Y,r=Math.round(l*.75),f=c+Y+Math.round((l-r)/2),d=I-c-S-a,[b,i]=e.valueRange,x=i-b,s=e.plotMode==="stackedSoftBins"?it:Mt,{primary:u,secondary:X}=tt?pe(e,O,s):{primary:"",secondary:""};if(tt){const n=Math.max(f-6,c+Y+11),g=Ot+Math.max(rt(n),rt(n-12))+8;ft(u,I-c,n,"right","bottom",g)}let T=f+r+4;const B=n=>Ot+Math.max(rt(n),rt(n+12))+8,nt=e.logScale&&b>0?Math.log10(b):0,z=e.logScale&&i>0?Math.log10(i)-nt:1,dt=n=>e.logScale?(1-(Math.log10(n)-nt)/z)*r:(1-(n-b)/x)*r;for(let n=0;n<Gt;n++){const g=1-n/(Gt-1),_=f+n/Gt*r,W=r/Gt+1;t.fillStyle=o.forValue(g),t.fillRect(d,_,S,W)}t.strokeStyle="rgba(0,0,0,0.25)",t.lineWidth=.5,t.strokeRect(d,f,S,r),t.fillStyle=p.text,t.font=v,t.textAlign="left",t.strokeStyle="rgba(0,0,0,0.35)",t.lineWidth=.5;const L=3,N=36,st=14,Xt=e.logScale&&z>0?Ve(b,i,r,N,st,nt,z):e.allIntegerValues?(()=>{const n=Math.max(1,qt(x*N/r)),g=[],_=Math.ceil(b/n)*n;for(let W=_;W<=i+n*1e-6;W+=n){const D=(1-(W-b)/x)*r;D>st&&D<r-st&&g.push(Math.round(W))}return g})():(()=>{const n=x>0?qt(x*N/r):0,g=[];if(n>0){const _=Math.ceil(b/n)*n;for(let W=_;W<=i+n*1e-6;W+=n){const D=(1-(W-b)/x)*r;D>st&&D<r-st&&g.push(W)}}return g})(),A=ue(e.testDefs,e.activeTest),C=e.plotMode==="stackedBins"||e.plotMode==="stackedSoftBins",m=C?"Count":A?.name??(e.activeTest!=null?`Test ${e.activeTest}`:void 0),M=C?void 0:A?.unit,{tickFmt:R}=Ee(i,m,M,O),P=e.allIntegerValues?n=>String(Math.round(n)):R;t.textBaseline="middle";for(const n of Xt){const g=f+dt(n);t.beginPath(),t.moveTo(d+S,g),t.lineTo(d+S+L,g),t.stroke(),t.fillText(P(n),d+S+L+2,g)}if(t.beginPath(),t.moveTo(d+S,f),t.lineTo(d+S+L,f),t.stroke(),t.textBaseline="top",t.fillText(P(i),d+S+L+2,f),t.beginPath(),t.moveTo(d+S,f+r),t.lineTo(d+S+L,f+r),t.stroke(),t.textBaseline="bottom",t.fillText(P(b),d+S+L+2,f+r),!C&&A&&(A.limitLow!==void 0||A.limitHigh!==void 0)){const n=[],g=e.colorbarRangeMode==="spec";for(const[_,W]of[[A.limitLow,"LSL"],[A.limitHigh,"USL"]]){if(_===void 0||e.logScale&&_<=0)continue;const D=dt(_);!g&&(D<-st||D>r+st)||n.push({value:_,label:W,py:Math.max(0,Math.min(r,D))})}if(n.length>0){t.save(),t.font=v,t.textAlign="right",t.textBaseline="middle";for(const{py:_,label:W}of n){const D=f+_;g||(t.strokeStyle="rgba(255,255,255,0.85)",t.lineWidth=3,t.beginPath(),t.moveTo(d,D),t.lineTo(d+S,D),t.stroke(),t.strokeStyle="rgba(0,0,0,0.7)",t.lineWidth=1,t.beginPath(),t.moveTo(d,D),t.lineTo(d+S,D),t.stroke()),t.fillStyle=p.text,t.fillText(W,d-3,D);const wt=W==="USL"?-1:1,Ht=4,De=d-3-t.measureText(W).width-Ht-4;t.beginPath(),Te(t,De,D,Ht,wt),t.strokeStyle="#fff",t.lineWidth=2,t.lineJoin="round",t.stroke(),t.fillStyle=p.text,t.fill()}t.restore()}}if(!C){const n=e.logScale?"log\u2081\u2080":e.logScaleRequested?"linear \u2014 log n/a":null;n&&(t.save(),t.fillStyle=p.text,t.font=$e,t.textAlign="right",t.textBaseline="top",t.fillText(n,I-c,T),t.restore(),T+=15)}tt&&X&&ft(X,I-c,T,"right","top",B(T),$t,p.text)}const te=[];let Z;if(at){const o=ye(e.colorScheme),a=K==="compact",l=e.plotMode==="softBin"?it:Mt,r=l?new Map(l.map(s=>[s.bin,s])):null,f=s=>{const u=r?.get(s);return(e.colorScheme==="custom"?u?.color:void 0)??o.forBin(s)},d=(s,u)=>{const X=t.measureText(s).width,T=a?0:t.measureText(String(u)).width,B=w+xt+X+(T?Ut:0);return{labelWidth:X,countWidth:T,totalWidth:B}},b=Lt?.find(s=>s.key===e.activeMetadataKey),i=pt?J.map(s=>{const u=a?s.compactLabel:s.label;return{key:s.key,color:s.color,count:s.count,label:u,tooltipLabel:s.tooltip,...d(u,s.count)}}):e.plotMode==="metadata"?At.map(([s,u])=>{const T=b?.values?.find(z=>z.value===s)?.label??s,B=a?s:T,nt=e.metadataColorMap?.get(s)??"#d6d9dd";return{key:s,color:nt,count:u,label:B,tooltipLabel:`${T} \xB7 ${u} dies`,...d(B,u)}}):Rt.map(([s,u])=>{const X=r?.get(s),T=X?.name?`${s} \xB7 ${X.name}`:`Bin ${s}`,B=a?String(s):T;return{key:s,color:f(s),count:u,label:B,tooltipLabel:`${T} \xB7 ${u} dies`,...d(B,u)}}),x=I-c*2;if(i.length>0){const s=Math.max(1,Math.floor((V-2*c)/E));let u=i.length,X=1,T=u*E,B=[],nt=u,z=0;const dt=ct||Ct;if(dt){const A=Math.max(...i.map(m=>m.totalWidth)),C=Math.max(1,Math.min(i.length,Math.floor((x+8)/(A+8))));for(let m=C;m>=1;m--){const M=Math.ceil(i.length/m);if(M>s)continue;const R=new Array(m).fill(0);for(let n=0;n<i.length;n++){const g=n%m;R[g]=Math.max(R[g],i[n].totalWidth)}if(R.reduce((n,g)=>n+g,0)+(m-1)*8<=x||m===1){X=m,B=R,nt=M,u=M,T=M*E;break}}}else u=Math.min(i.length,s),T=u*E,i.length>s&&(z=i.length-(s-1)),B=[Wt];let L,N;if(Pt){const A=Math.max(...i.map(n=>w+xt+t.measureText(n.label).width+Ut)),C=Math.ceil(A);B=[C];const m=C+Vt*2,M=T+Vt*2,R=j?.x??0,P=j?.y??0;L=Math.min(Math.max(I-c-m+R,c),I-c-m),N=Math.min(Math.max(V-c-M+P,c),V-c-M),Z={x:L,y:N,w:m,h:M},t.save(),t.fillStyle="rgba(255,255,255,0.92)",t.strokeStyle="rgba(0,0,0,0.1)",t.lineWidth=1,t.fillRect(L,N,m,M),t.strokeRect(L,N,m,M),t.restore(),L+=Vt,N+=Vt}else ct?(L=c,N=V-c-T):Ct?(L=c,N=c+Y):G?(L=c+4,N=c+Math.round((V-2*c-T)/2)):(L=I-c-Wt+4,N=c+Math.round((V-2*c-T)/2));if(tt){const A=e.plotMode==="softBin"?it:Mt,{primary:C,secondary:m}=pe(e,O,A),M=6,R=g=>Ot-Math.max(rt(g),rt(g-12))-8,P=g=>y?I-c:R(g),n=Pt?Z.y+Z.h:N+T;if(Ct){const g=n+M;ft(C,L,g,"left","top",Ot-Math.max(rt(g),rt(g+12))-8),m&&ft(m,L,g+16,"left","top",R(g+16),$t,p.text)}else if(Pt)ft(C,Z.x,Z.y-M,"left","bottom",Z.x+Z.w),m&&ft(m,Z.x,n+M,"left","top",Z.x+Z.w,$t,p.text);else{const g=Math.max(N-M,c+Y+12);if(ft(C,L,g,"left","bottom",P(g)),m){const _=n+M;ft(m,L,_,"left","top",P(_),$t,p.text)}}}t.save(),t.font=v;const st=dt?8:0,Xt=(A,C)=>{if(t.measureText(A).width<=C)return A;let m=0,M=A.length;const R="\u2026",P=t.measureText(R).width;for(;m<M;){const n=m+M+1>>1;t.measureText(A.slice(0,n)).width+P<=C?m=n:M=n-1}return m===0?R:A.slice(0,m)+R};if(dt){let A=0;for(let C=0;C<u;C++){let m=L;for(let M=0;M<X;M++){const R=i[A++];if(!R)break;const P=R.key===Q,n=m,g=m+w+xt,_=N+C*E+E/2,W=N+C*E+Math.round((E-w)/2),D=B[M]-w-xt-Ut;t.fillStyle=R.color,t.fillRect(n,W,w,w),t.strokeStyle=P?p.accent:"rgba(0,0,0,0.25)",t.lineWidth=P?2:.75,t.strokeRect(n,W,w,w),t.fillStyle=P?p.accent:p.text,t.font=P?`bold ${v}`:v,t.textAlign="left",t.textBaseline="middle",t.fillText(Xt(R.label,D),g,_),t.fillStyle=p.textMuted,t.font=v,t.textAlign="right",t.fillText(String(R.count),m+B[M]-2,_),te.push({bin:R.key,x:m,y:N+C*E,w:B[M],h:E,label:R.tooltipLabel}),m+=B[M]+st}}}else{const A=B[0],C=K!=="compact",m=A-w-xt-(C?Ut:0),M=L+A-2;let R=i;z>0&&(R=i.slice(0,s-1));let P=N;for(const n of R){const g=n.key===Q,_=L,W=L+w+xt,D=P+E/2,wt=P+Math.round((E-w)/2),Ht=n.label;t.fillStyle=n.color,t.fillRect(_,wt,w,w),t.strokeStyle=g?p.accent:"rgba(0,0,0,0.25)",t.lineWidth=g?2:.75,t.strokeRect(_,wt,w,w),t.fillStyle=g?p.accent:p.text,t.font=g?`bold ${v}`:v,t.textAlign="left",t.textBaseline="middle",t.fillText(Xt(Ht,m),W,D),(C||Pt)&&(t.fillStyle=p.textMuted,t.font=v,t.textAlign="right",t.fillText(String(n.count),M,D)),te.push({bin:n.key,x:L,y:P,w:A,h:E,label:n.tooltipLabel}),P+=E}z>0&&(t.fillStyle=p.textMuted,t.font=v,t.textAlign="left",t.textBaseline="middle",t.fillText(`+ ${z} more`,L+w+xt,P+E/2))}}i.length>0&&t.restore()}const We={originX:et,originY:ot,ppm:k,snapDist:Ft},he=e.rectangles[0]?.width??1,de=e.rectangles[0]?.height??1,It=he*1.5,Nt=de*1.5,ht=e.dieBounds,Et=ht?ht.minX:q.length?Math.min(...q.map(o=>o.x)):0,Yt=ht?ht.minY:q.length?Math.min(...q.map(o=>o.y)):0,Be=ht?ht.maxX:q.length?Math.max(...q.map(o=>o.x)):1,_e=ht?ht.maxY:q.length?Math.max(...q.map(o=>o.y)):1,St=Math.max(1,Math.ceil((Be-Et)/It)+1),ee=Math.max(1,Math.ceil((_e-Yt)/Nt)+1),ge=Array.from({length:St*ee},()=>[]);for(let o=0;o<q.length;o++){const a=Math.floor((q[o].x-Et)/It),l=Math.floor((q[o].y-Yt)/Nt),r=Math.max(0,Math.min(St-1,a)),f=Math.max(0,Math.min(ee-1,l));ge[f*St+r].push(o)}function me(o,a,l){const r=Math.max(0,Math.floor((o-l-Et)/It)),f=Math.min(St-1,Math.floor((o+l-Et)/It)),d=Math.max(0,Math.floor((a-l-Yt)/Nt)),b=Math.min(ee-1,Math.floor((a+l-Yt)/Nt)),i=[];for(let x=d;x<=b;x++)for(let s=r;s<=f;s++)for(const u of ge[x*St+s])i.push(u);return i}return{hitTarget:{getDieAtPoint(o,a){const l=(o-et)/k,r=(ot-a)/k,f=Math.max(he,de);for(const i of me(l,r,f)){const x=e.rectangles[i];if(x&&Math.abs(l-x.x)<=x.width/2&&Math.abs(r-x.y)<=x.height/2)return e.dies[i]??null}let d=null,b=Ft*Ft;for(const i of me(l,r,Ft)){const x=q[i].x-l,s=q[i].y-r,u=x*x+s*s;u<b&&(b=u,d=e.dies[i]??null)}return d}},viewport:We,binLegendRows:te,legendBox:Z}}function Ue(h,e,F,p,c,U,S,lt,gt,j,mt,Tt,H){h.save(),h.font=Ge,h.fillStyle=H.text,h.strokeStyle=H.axisLine,h.lineWidth=.5;const Q=F-lt+4,O=S+gt-4,Y=Fe(mt),kt=xe(Y,1,0),Mt=xe(Y,0,1),it=1e-9;function Lt(y){return j?Math.abs(y.y)<it&&Math.abs(y.x)>it?{pitch:j.x,sign:Math.sign(y.x)}:Math.abs(y.x)<it&&Math.abs(y.y)>it?{pitch:j.y,sign:Math.sign(y.y)}:null:null}const tt=Lt(kt),ut=Lt(Mt);function bt(y){return tt?Math.round(y*tt.sign/tt.pitch):y}function pt(y){return ut?Math.round(y*ut.sign/ut.pitch):y}function at(y){if(!y)return qt(50/U);const G=50/(y*U);return Math.max(1,Math.round(G))*y}const Rt=tt?.pitch,At=ut?.pitch,J=at(Rt),yt=at(At);h.textAlign="center",h.textBaseline="top";const $=Math.ceil((S-p)/U/J)*J,I=(e-S-p)/U;for(let y=$;y<=I;y+=J){const G=p+y*U;if(G<S||G>e-S)continue;h.beginPath(),h.moveTo(G,Q-ke),h.lineTo(G,Q),h.stroke();const ct=j?String(bt(y)):Se(y);h.fillText(ct,G,Q+2)}h.textAlign="right",h.textBaseline="middle";const V=Math.ceil((c-(F-S))/U/yt)*yt,K=(c-S)/U;for(let y=V;y<=K;y+=yt){const G=c-y*U;if(G<S||G>F-S)continue;h.beginPath(),h.moveTo(O,G),h.lineTo(O+ke,G),h.stroke();const ct=j?String(pt(y)):Se(y);h.fillText(ct,O-2,G)}h.restore()}function qt(h){const e=Math.pow(10,Math.floor(Math.log10(h))),F=h/e;return(F<1.5?1:F<3.5?2:F<7.5?5:10)*e}function Ve(h,e,F,p,c,U,S){const lt=[],gt=S;if(gt<1){const H=qt((e-h)*p/F);if(H>0){const Q=Math.ceil(h/H)*H;for(let O=Q;O<=e+H*1e-6;O+=H){if(O<=h||O>=e)continue;const Y=(1-(Math.log10(O)-U)/S)*F;Y>c&&Y<F-c&&lt.push(O)}}return lt}const j=gt<3?[1,2,5]:[1],mt=Math.floor(Math.log10(h)),Tt=Math.ceil(Math.log10(e));for(let H=mt;H<=Tt;H++)for(const Q of j){const O=Q*Math.pow(10,H);if(O<=h||O>=e)continue;const Y=(1-(Math.log10(O)-U)/S)*F;Y>c&&Y<F-c&&lt.push(O)}return lt}export{fmt,fmtColorbarAxis}from"../renderer/fmt.js";