@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.
- package/CHANGELOG.md +751 -0
- package/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
- package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
- package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
- package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
- package/dist/packages/canvas-adapter/charts/capability.js +1 -0
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
- package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
- package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
- package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/index.js +1 -0
- package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
- package/dist/packages/canvas-adapter/charts/palette.js +1 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
- package/dist/packages/canvas-adapter/icons.d.ts +2 -0
- package/dist/packages/canvas-adapter/icons.js +42 -0
- package/dist/packages/canvas-adapter/index.d.ts +12 -0
- package/dist/packages/canvas-adapter/index.js +1 -0
- package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
- package/dist/packages/canvas-adapter/insightsTab.js +1 -0
- package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
- package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
- package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
- package/dist/packages/canvas-adapter/toCanvas.js +1 -0
- package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
- package/dist/packages/canvas-adapter/toolbar.js +9 -0
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
- package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
- package/dist/packages/canvas-adapter/version.d.ts +3 -0
- package/dist/packages/canvas-adapter/version.js +1 -0
- package/dist/packages/core/aggregates.d.ts +54 -0
- package/dist/packages/core/aggregates.js +1 -0
- package/dist/packages/core/classify.d.ts +24 -0
- package/dist/packages/core/classify.js +1 -0
- package/dist/packages/core/dies.d.ts +92 -0
- package/dist/packages/core/dies.js +1 -0
- package/dist/packages/core/index.d.ts +13 -0
- package/dist/packages/core/index.js +1 -0
- package/dist/packages/core/inference/grid.d.ts +35 -0
- package/dist/packages/core/inference/grid.js +1 -0
- package/dist/packages/core/inference/index.d.ts +7 -0
- package/dist/packages/core/inference/index.js +1 -0
- package/dist/packages/core/inference/pitch.d.ts +26 -0
- package/dist/packages/core/inference/pitch.js +1 -0
- package/dist/packages/core/inference/wafer.d.ts +30 -0
- package/dist/packages/core/inference/wafer.js +1 -0
- package/dist/packages/core/metadata.d.ts +31 -0
- package/dist/packages/core/metadata.js +1 -0
- package/dist/packages/core/probe.d.ts +16 -0
- package/dist/packages/core/probe.js +1 -0
- package/dist/packages/core/reticle.d.ts +68 -0
- package/dist/packages/core/reticle.js +1 -0
- package/dist/packages/core/transforms.d.ts +142 -0
- package/dist/packages/core/transforms.js +1 -0
- package/dist/packages/core/utils.d.ts +60 -0
- package/dist/packages/core/utils.js +1 -0
- package/dist/packages/core/wafer.d.ts +45 -0
- package/dist/packages/core/wafer.js +1 -0
- package/dist/packages/renderer/buildView.d.ts +420 -0
- package/dist/packages/renderer/buildView.js +1 -0
- package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
- package/dist/packages/renderer/buildWaferMap.js +1 -0
- package/dist/packages/renderer/colorMap.d.ts +46 -0
- package/dist/packages/renderer/colorMap.js +1 -0
- package/dist/packages/renderer/colorSchemes.d.ts +40 -0
- package/dist/packages/renderer/colorSchemes.js +1 -0
- package/dist/packages/renderer/fmt.d.ts +30 -0
- package/dist/packages/renderer/fmt.js +1 -0
- package/dist/packages/renderer/index.d.ts +7 -0
- package/dist/packages/renderer/index.js +1 -0
- package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
- package/dist/packages/stats/analyzeWaferLot.js +1 -0
- package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
- package/dist/packages/stats/analyzeWaferMap.js +1 -0
- package/dist/packages/stats/binPareto.d.ts +49 -0
- package/dist/packages/stats/binPareto.js +1 -0
- package/dist/packages/stats/boxplot.d.ts +39 -0
- package/dist/packages/stats/boxplot.js +1 -0
- package/dist/packages/stats/capability.d.ts +55 -0
- package/dist/packages/stats/capability.js +1 -0
- package/dist/packages/stats/clusterDetection.d.ts +18 -0
- package/dist/packages/stats/clusterDetection.js +1 -0
- package/dist/packages/stats/correlation.d.ts +55 -0
- package/dist/packages/stats/correlation.js +1 -0
- package/dist/packages/stats/facets.d.ts +74 -0
- package/dist/packages/stats/facets.js +1 -0
- package/dist/packages/stats/filterFindings.d.ts +9 -0
- package/dist/packages/stats/filterFindings.js +1 -0
- package/dist/packages/stats/findingsNarrative.d.ts +3 -0
- package/dist/packages/stats/findingsNarrative.js +1 -0
- package/dist/packages/stats/histogram.d.ts +42 -0
- package/dist/packages/stats/histogram.js +1 -0
- package/dist/packages/stats/index.d.ts +27 -0
- package/dist/packages/stats/index.js +1 -0
- package/dist/packages/stats/math.d.ts +10 -0
- package/dist/packages/stats/math.js +1 -0
- package/dist/packages/stats/patternClassification.d.ts +107 -0
- package/dist/packages/stats/patternClassification.js +1 -0
- package/dist/packages/stats/regions.d.ts +57 -0
- package/dist/packages/stats/regions.js +1 -0
- package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
- package/dist/packages/stats/renderFindingsReport.js +41 -0
- package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
- package/dist/packages/stats/renderSummaryReport.js +68 -0
- package/dist/packages/stats/reportHtml.d.ts +42 -0
- package/dist/packages/stats/reportHtml.js +301 -0
- package/dist/packages/stats/scatter.d.ts +24 -0
- package/dist/packages/stats/scatter.js +1 -0
- package/dist/packages/stats/types.d.ts +269 -0
- package/dist/packages/stats/types.js +1 -0
- package/dist/packages/stats/yield.d.ts +56 -0
- package/dist/packages/stats/yield.js +1 -0
- package/dist/packages/worker/index.d.ts +44 -0
- package/dist/packages/worker/index.js +1 -0
- package/dist/packages/worker/wafermap.worker.d.ts +44 -0
- package/dist/packages/worker/wafermap.worker.js +1 -0
- package/package.json +122 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare two label strings in natural (alphanumeric) order, so embedded numbers
|
|
3
|
+
* sort by value rather than character-by-character: `D0, D1, D2, D10, D11` rather
|
|
4
|
+
* than the plain-`.sort()` result `D0, D1, D10, D11, D2`.
|
|
5
|
+
*
|
|
6
|
+
* THE comparator for any user-visible list of mixed alpha-numeric labels (metadata
|
|
7
|
+
* values, legend entries, facet values, yield rows) — semiconductor identifiers are
|
|
8
|
+
* overwhelmingly of this `<prefix><number>` shape, so lexicographic ordering reads
|
|
9
|
+
* as scrambled to an engineer scanning a legend.
|
|
10
|
+
*
|
|
11
|
+
* Locale is pinned to `'en'` rather than left to the host: this ordering also drives
|
|
12
|
+
* categorical colour assignment, and a library must not hand the same wafer different
|
|
13
|
+
* colours on machines with different locales.
|
|
14
|
+
*/
|
|
15
|
+
export declare function compareNatural(a: string, b: string): number;
|
|
16
|
+
/**
|
|
17
|
+
* camelCase/snake_case key → "Title Case" label (`dieSize` → "Die Size",
|
|
18
|
+
* `test_program` → "Test Program").
|
|
19
|
+
*
|
|
20
|
+
* Lives in `core/` because both `renderer/` (on-canvas map title) and `stats/`
|
|
21
|
+
* (facet tables, HTML report, summary panel, metadata badge) need it, and
|
|
22
|
+
* `renderer/` must not depend on `stats/`. Previously duplicated in both with a
|
|
23
|
+
* "keep in sync" comment — the same field would otherwise be labelled two
|
|
24
|
+
* different ways depending on which surface rendered it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function prettyKey(key: string): string;
|
|
27
|
+
/** Clamp `v` into [0, 1] — the normalization range every colour scale expects. */
|
|
28
|
+
export declare function clamp01(v: number): number;
|
|
29
|
+
/**
|
|
30
|
+
* Arithmetic mean. Returns **NaN** for an empty array — deliberately, not 0.
|
|
31
|
+
*
|
|
32
|
+
* This feeds statistical comparisons (Welch tests, effect sizes), where "no data"
|
|
33
|
+
* must never masquerade as a real measurement of zero: a 0 would silently become
|
|
34
|
+
* a genuine-looking delta in a finding. NaN propagates and is caught by the
|
|
35
|
+
* existing non-finite guards. Same reasoning as never defaulting a missing bin
|
|
36
|
+
* to `0` elsewhere in the library.
|
|
37
|
+
*/
|
|
38
|
+
export declare function mean(values: number[]): number;
|
|
39
|
+
/**
|
|
40
|
+
* Median of an ALREADY-SORTED ascending array. Returns 0 when empty — these two
|
|
41
|
+
* median helpers back display/summary surfaces whose call sites already guard for
|
|
42
|
+
* emptiness, and 0 keeps them from rendering "NaN" in a panel. Do not use them
|
|
43
|
+
* where the result feeds a statistical test; see {@link mean} on why that case
|
|
44
|
+
* wants NaN instead.
|
|
45
|
+
*/
|
|
46
|
+
export declare function medianOfSorted(sorted: number[]): number;
|
|
47
|
+
/** Median of an unsorted array (sorts a copy, leaving the input untouched). */
|
|
48
|
+
export declare function median(values: number[]): number;
|
|
49
|
+
/**
|
|
50
|
+
* The 98th-percentile value (nearest-rank, on a sorted copy). Used by wafer-size
|
|
51
|
+
* inference to take a robust maximum — the true extreme is too easily thrown off
|
|
52
|
+
* by a single stray coordinate. Returns 0 for an empty array.
|
|
53
|
+
*/
|
|
54
|
+
export declare function percentile98(values: number[]): number;
|
|
55
|
+
/**
|
|
56
|
+
* Return the most-frequent (mode) value from an array of numbers.
|
|
57
|
+
* For pitch estimation, values should be pre-rounded to absorb noise.
|
|
58
|
+
*/
|
|
59
|
+
export declare function modeOf(values: number[]): number | null;
|
|
60
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function compareNatural(t,n){return t.localeCompare(n,"en",{numeric:!0})}export function prettyKey(t){return t.replace(/([A-Z])/g," $1").replace(/_/g," ").trim().replace(/^./,n=>n.toUpperCase())}export function clamp01(t){return Math.max(0,Math.min(1,t))}export function mean(t){if(t.length===0)return NaN;let n=0;for(const e of t)n+=e;return n/t.length}export function medianOfSorted(t){const n=t.length;if(n===0)return 0;const e=Math.floor(n/2);return n%2===1?t[e]:(t[e-1]+t[e])/2}export function median(t){return medianOfSorted([...t].sort((n,e)=>n-e))}export function percentile98(t){if(t.length===0)return 0;const n=[...t].sort((o,r)=>o-r),e=Math.floor(n.length*.98);return n[Math.min(e,n.length-1)]}export function modeOf(t){if(!t.length)return null;const n=new Map;for(const r of t)n.set(r,(n.get(r)??0)+1);let e=0,o=t[0];for(const[r,c]of n)c>e&&(e=c,o=r);return o}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { WaferMetadata } from './metadata.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wafer orientation mark. Pass only `type`; the library derives the standard
|
|
4
|
+
* dimensions from the wafer diameter automatically.
|
|
5
|
+
*
|
|
6
|
+
* - ≤ 150 mm wafers: orientation **flat** (straight chord, 32.5–57.5 mm).
|
|
7
|
+
* - > 150 mm wafers: orientation **notch** (small V-indentation, ~3.5 mm wide).
|
|
8
|
+
*
|
|
9
|
+
* Both are modelled with the same type; the appropriate geometry is chosen
|
|
10
|
+
* internally based on the wafer diameter.
|
|
11
|
+
*/
|
|
12
|
+
export interface WaferNotch {
|
|
13
|
+
type: 'top' | 'bottom' | 'left' | 'right';
|
|
14
|
+
}
|
|
15
|
+
export interface WaferSpec {
|
|
16
|
+
diameter: number;
|
|
17
|
+
center?: {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
};
|
|
21
|
+
notch?: WaferNotch;
|
|
22
|
+
orientation?: number;
|
|
23
|
+
metadata?: WaferMetadata;
|
|
24
|
+
}
|
|
25
|
+
export interface Wafer {
|
|
26
|
+
diameter: number;
|
|
27
|
+
radius: number;
|
|
28
|
+
center: {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Resolved orientation mark. `length` is the standard chord length (flat) or
|
|
34
|
+
* half-width (V-notch), derived from the wafer diameter by `createWafer`.
|
|
35
|
+
*/
|
|
36
|
+
notch?: {
|
|
37
|
+
type: 'top' | 'bottom' | 'left' | 'right';
|
|
38
|
+
length: number;
|
|
39
|
+
};
|
|
40
|
+
orientation: number;
|
|
41
|
+
metadata?: WaferMetadata;
|
|
42
|
+
}
|
|
43
|
+
/** Create a wafer model from spec. Defaults: center={0,0}, orientation=0. */
|
|
44
|
+
export declare function createWafer(config: WaferSpec): Wafer;
|
|
45
|
+
//# sourceMappingURL=wafer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(e){return e<=100?32.5:e<=150?57.5:1.75}export function createWafer(e){if(e.diameter<=0)throw new RangeError(`createWafer: diameter must be > 0 (got ${e.diameter})`);return{diameter:e.diameter,radius:e.diameter/2,center:e.center??{x:0,y:0},notch:e.notch?{type:e.notch.type,length:t(e.diameter)}:void 0,orientation:e.orientation??0,metadata:e.metadata}}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import type { Wafer } from '../core/wafer.js';
|
|
2
|
+
import type { Die } from '../core/dies.js';
|
|
3
|
+
import type { Reticle } from '../core/reticle.js';
|
|
4
|
+
import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
|
|
5
|
+
import { type Affine } from '../core/transforms.js';
|
|
6
|
+
import type { TestDef, BinDef, MetadataFieldDef, ReticleConfig } from './buildWaferMap.js';
|
|
7
|
+
/** Resolve a toolbar activeTest cursor to the canonical test number for getDieTestValue. */
|
|
8
|
+
export declare function resolveTestNumber(activeTest: number, testDefs?: TestDef[]): {
|
|
9
|
+
testNumber: number;
|
|
10
|
+
};
|
|
11
|
+
/** Find a TestDef by its testNumber. Returns undefined when testDefs is absent or no match. */
|
|
12
|
+
export declare function findTestDef(testDefs: TestDef[] | undefined, testNumber: number): TestDef | undefined;
|
|
13
|
+
/** Return sorted unique test numbers present across a set of dies (from testValues keys). */
|
|
14
|
+
export declare function getUniqueTestNumbers(dies: Die[]): number[];
|
|
15
|
+
export type PlotMode = 'value' | 'hardBin' | 'softBin' | 'stackedValues' | 'stackedBins' | 'stackedSoftBins' | 'metadata';
|
|
16
|
+
interface Point {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ViewRect {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
fill: string | number;
|
|
26
|
+
type: 'hardBin' | 'softBin' | 'value' | 'stacked' | 'metadata';
|
|
27
|
+
stack?: number[];
|
|
28
|
+
metadata?: DieMetadata;
|
|
29
|
+
/**
|
|
30
|
+
* Out-of-spec indicator for value-mode dies drawn with the value gradient.
|
|
31
|
+
* The die keeps its gradient fill (so the distribution stays readable) and the
|
|
32
|
+
* renderer draws a ▽ (`failLow`) / △ (`failHigh`) marker on top so the die is
|
|
33
|
+
* never shown as plain in-spec. Set in both `'spec'` and `'data'` colorbar
|
|
34
|
+
* ranges. Absent only under a pass/fail display (`passFailDisplay: 'spec'`),
|
|
35
|
+
* where the solid categorical fill is itself the indication and no marker is needed.
|
|
36
|
+
*/
|
|
37
|
+
specMark?: 'failLow' | 'failHigh';
|
|
38
|
+
}
|
|
39
|
+
export interface ViewText {
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
text: string;
|
|
43
|
+
fontSize: number;
|
|
44
|
+
color: string;
|
|
45
|
+
align: 'center';
|
|
46
|
+
/** Marks fixed-size overlay labels (currently only the +X/+Y orientation
|
|
47
|
+
* indicators) so canvas-adapter excludes them from die-label uniform sizing.
|
|
48
|
+
* Absent = an ordinary die label; its `fontSize` here is a build-time
|
|
49
|
+
* (mm-space, zoom-unaware) default for non-canvas consumers of ViewText —
|
|
50
|
+
* canvas-adapter recomputes the actual on-screen size at draw time. */
|
|
51
|
+
role?: 'indicator';
|
|
52
|
+
}
|
|
53
|
+
export interface ViewHoverPoint {
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
}
|
|
57
|
+
export interface ViewOverlay {
|
|
58
|
+
kind: 'wafer-boundary' | 'reticle' | 'probe-path' | 'ring-boundary' | 'quadrant-boundary' | 'xy-indicator';
|
|
59
|
+
points: Point[][];
|
|
60
|
+
closed: boolean;
|
|
61
|
+
lineColor: string;
|
|
62
|
+
lineWidth: number;
|
|
63
|
+
fill?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface View {
|
|
66
|
+
rectangles: ViewRect[];
|
|
67
|
+
hoverPoints: ViewHoverPoint[];
|
|
68
|
+
texts: ViewText[];
|
|
69
|
+
overlays: ViewOverlay[];
|
|
70
|
+
plotMode: PlotMode;
|
|
71
|
+
colorScheme: string;
|
|
72
|
+
metadata: WaferMetadata | null;
|
|
73
|
+
dies: Die[];
|
|
74
|
+
/** Actual [min, max] of the value data used for color normalization. */
|
|
75
|
+
valueRange: [number, number];
|
|
76
|
+
/** True when every observed value for the active test is a whole number. Drives integer-only colorbar ticks. */
|
|
77
|
+
allIntegerValues?: boolean;
|
|
78
|
+
/** Named test definitions — populated when `testDefs` is passed to `buildWaferMap`. */
|
|
79
|
+
testDefs?: TestDef[];
|
|
80
|
+
/** Which `values[]` index is being displayed (for `value` plot mode). Default 0. */
|
|
81
|
+
activeTest: number;
|
|
82
|
+
/** True when log₁₀ scale is both requested and valid (vMin > 0). */
|
|
83
|
+
logScale: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* True when log₁₀ scale was *requested* (option or per-test default), regardless of whether it
|
|
86
|
+
* was applied. When `logScaleRequested && !logScale` the data range included ≤ 0 and the view
|
|
87
|
+
* fell back to linear — the colorbar reports this as "linear — log n/a".
|
|
88
|
+
*/
|
|
89
|
+
logScaleRequested: boolean;
|
|
90
|
+
/** Aggregation method label for `stackedValues` hover tooltips (e.g. `'mean'`). */
|
|
91
|
+
aggrMethod?: string;
|
|
92
|
+
/** Total wafers in lot — for `stackedBins` hover percentage calculation. */
|
|
93
|
+
lotSize?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Total effective axis flip (data-pipeline flip XOR interactive flip).
|
|
96
|
+
*
|
|
97
|
+
* @deprecated for coordinate maths — this and {@link rotation} are a *lossy*
|
|
98
|
+
* summary: a summed angle plus XOR'd flip flags cannot represent
|
|
99
|
+
* rotate→mirror→rotate, because rotation and mirroring don't commute. They are
|
|
100
|
+
* correct only while `wafer.orientation` is 0. Use {@link gridToScreen} (or its
|
|
101
|
+
* inverse) for anything positional. Retained for display code that only needs a
|
|
102
|
+
* rough orientation hint, and because it is read back as a data-flip hint below.
|
|
103
|
+
*/
|
|
104
|
+
axisFlip: {
|
|
105
|
+
x: boolean;
|
|
106
|
+
y: boolean;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* The data-pipeline axis flip ALONE (from `xAxisDirection`/`yAxisDirection`/
|
|
110
|
+
* `coordinateOrigin`), with no interactive flip mixed in. `renderWaferMap` feeds
|
|
111
|
+
* this back into `buildView` as `dataAxisFlip` on every rebuild; reading
|
|
112
|
+
* {@link axisFlip} for that purpose double-counts an active interactive flip.
|
|
113
|
+
*/
|
|
114
|
+
dataAxisFlip: {
|
|
115
|
+
x: boolean;
|
|
116
|
+
y: boolean;
|
|
117
|
+
};
|
|
118
|
+
/** Total effective rotation in degrees (wafer orientation + interactive rotation). See {@link axisFlip} on why this is lossy. */
|
|
119
|
+
rotation: number;
|
|
120
|
+
/**
|
|
121
|
+
* Die-grid geometry → screen. THE authoritative display transform: composed from
|
|
122
|
+
* the real pipeline in order, so it stays correct for any combination of wafer
|
|
123
|
+
* orientation, data-axis flip and interactive rotate/flip. Invert it
|
|
124
|
+
* (`affineInvert`) to map a screen position back to a die-grid position — this is
|
|
125
|
+
* what axis tick labels do.
|
|
126
|
+
*/
|
|
127
|
+
gridToScreen: Affine<'grid', 'screen'>;
|
|
128
|
+
/** True when reticle geometry is present — used to conditionally show the reticle toolbar button. */
|
|
129
|
+
hasReticle: boolean;
|
|
130
|
+
/** True when the scene was built from lot-aggregated data (lotStack). Controls toolbar stacked-mode visibility. */
|
|
131
|
+
isLotStack: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* The *effective* pass/fail display for value mode, resolved by the library
|
|
134
|
+
* from the request and the data (never trust the caller's flag alone):
|
|
135
|
+
* - `'spec'` — dies coloured by spec pass/fail against the active test's limits.
|
|
136
|
+
* - `'test'` — dies coloured by the tester's recorded verdict
|
|
137
|
+
* (`getTestPassStatus`). Forced automatically when the active test is
|
|
138
|
+
* functional (`testType: 'F'`) — a functional test has no value to plot.
|
|
139
|
+
* - `'off'` — continuous value gradient (or a non-value plot mode).
|
|
140
|
+
*/
|
|
141
|
+
passFailDisplay: 'off' | 'spec' | 'test';
|
|
142
|
+
/** True when `passFailDisplay === 'spec'` — kept for back-compat with existing consumers. */
|
|
143
|
+
colorBySpec: boolean;
|
|
144
|
+
/** Whether the colorbar range is anchored to spec limits ('spec') or data extents ('data'). */
|
|
145
|
+
colorbarRangeMode: 'spec' | 'data';
|
|
146
|
+
/** Wafer centre in scene/display mm coordinates (the rotation pivot). */
|
|
147
|
+
waferCenter: {
|
|
148
|
+
x: number;
|
|
149
|
+
y: number;
|
|
150
|
+
};
|
|
151
|
+
/** Wafer radius in mm. */
|
|
152
|
+
waferRadius: number;
|
|
153
|
+
/** Unit vector pointing toward the notch/flat in display space (post-transform). Null when no notch. */
|
|
154
|
+
notchDir: {
|
|
155
|
+
x: number;
|
|
156
|
+
y: number;
|
|
157
|
+
} | null;
|
|
158
|
+
/** Bin → die count for the active bin mode (hardBin or softBin). Undefined in value modes. */
|
|
159
|
+
binCounts?: Map<number, number>;
|
|
160
|
+
/** Value → die count for the active metadata field, `'metadata'` mode only. */
|
|
161
|
+
metadataCounts?: Map<string, number>;
|
|
162
|
+
/**
|
|
163
|
+
* Value → resolved fill colour for the active metadata field, `'metadata'` mode only. The
|
|
164
|
+
* single source of truth for metadata colours — built from the same filtered die population
|
|
165
|
+
* as `metadataCounts`, so legend swatches (built from this map) always match die fills (also
|
|
166
|
+
* built from this map). Consumers must read this rather than recomputing colours from
|
|
167
|
+
* `metadataCounts`'s keys, or the two can diverge in ranking whenever iteration order differs.
|
|
168
|
+
*/
|
|
169
|
+
metadataColorMap?: Map<string, string> | null;
|
|
170
|
+
/** Which `die.metadata` key is active in `'metadata'` mode. */
|
|
171
|
+
activeMetadataKey?: string;
|
|
172
|
+
/** Named metadata-field definitions — populated when `metadataFields` is passed to `buildWaferMap`. */
|
|
173
|
+
metadataFields?: MetadataFieldDef[];
|
|
174
|
+
/**
|
|
175
|
+
* Per-category die counts for `value` + `passFailDisplay: 'spec'` — drives the spec legend. Counts only
|
|
176
|
+
* dies with a value (no-data dies excluded). Undefined outside spec mode.
|
|
177
|
+
*/
|
|
178
|
+
specCounts?: {
|
|
179
|
+
pass: number;
|
|
180
|
+
failHigh: number;
|
|
181
|
+
failLow: number;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Per-category die counts for `value` mode with `passFailDisplay: 'test'` — drives the
|
|
185
|
+
* Pass/Fail legend. Counts only dies with a recorded verdict (no-data dies excluded).
|
|
186
|
+
* Undefined outside test pass/fail display.
|
|
187
|
+
*/
|
|
188
|
+
passFailCounts?: {
|
|
189
|
+
pass: number;
|
|
190
|
+
fail: number;
|
|
191
|
+
};
|
|
192
|
+
/** Bounding box of all die centres in scene coordinates (mm). */
|
|
193
|
+
dieBounds: {
|
|
194
|
+
minX: number;
|
|
195
|
+
maxX: number;
|
|
196
|
+
minY: number;
|
|
197
|
+
maxY: number;
|
|
198
|
+
} | null;
|
|
199
|
+
}
|
|
200
|
+
export interface ViewOptions {
|
|
201
|
+
plotMode?: PlotMode;
|
|
202
|
+
showDieLabels?: boolean;
|
|
203
|
+
showReticle?: boolean;
|
|
204
|
+
showProbePath?: boolean;
|
|
205
|
+
ringCount?: number;
|
|
206
|
+
showRingBoundaries?: boolean;
|
|
207
|
+
showQuadrantBoundaries?: boolean;
|
|
208
|
+
showXYIndicator?: boolean;
|
|
209
|
+
dieGap?: number;
|
|
210
|
+
/** Named colour scheme — any scheme registered via registerColorScheme(). Default: 'default'. */
|
|
211
|
+
colorScheme?: string;
|
|
212
|
+
highlightBin?: number;
|
|
213
|
+
/** Dim every die except this metadata value, `'metadata'` mode's analogue of `highlightBin`. */
|
|
214
|
+
highlightMetadataValue?: string;
|
|
215
|
+
interactiveTransform?: {
|
|
216
|
+
rotation?: number;
|
|
217
|
+
flipX?: boolean;
|
|
218
|
+
flipY?: boolean;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Explicit value colour normalization range.
|
|
222
|
+
*
|
|
223
|
+
* - Tuple `[min, max]`: applied to whichever test is active. The caller is
|
|
224
|
+
* responsible for keeping it consistent with `activeTest`.
|
|
225
|
+
* - Object `{ test, range }`: applied **only** when `test` matches the active
|
|
226
|
+
* test. On mismatch the range is ignored and the scene auto-scales — this
|
|
227
|
+
* makes it impossible to colour one test's data against another test's
|
|
228
|
+
* range. Prefer this form when the range was computed for a specific test.
|
|
229
|
+
*
|
|
230
|
+
* When omitted entirely, the range is auto-computed from the die values present.
|
|
231
|
+
*/
|
|
232
|
+
valueRange?: [number, number] | {
|
|
233
|
+
test: number;
|
|
234
|
+
range: [number, number];
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Controls the default colorbar range when the active testDef has spec limits.
|
|
238
|
+
* `'spec'` (default when limits present): colorbar spans [limitLow, limitHigh].
|
|
239
|
+
* `'data'`: colorbar spans the actual data min/max regardless of limits.
|
|
240
|
+
*/
|
|
241
|
+
colorbarRangeMode?: 'spec' | 'data';
|
|
242
|
+
/**
|
|
243
|
+
* Reticle rectangles to overlay on the map.
|
|
244
|
+
* Generated by `generateReticleGrid` or the `reticle` option on `buildWaferMap`.
|
|
245
|
+
*/
|
|
246
|
+
reticles?: Reticle[];
|
|
247
|
+
/** Named test definitions — one per `values[]` entry. */
|
|
248
|
+
testDefs?: TestDef[];
|
|
249
|
+
/**
|
|
250
|
+
* Which `values[]` index to display in `value` plot mode. Default `0`.
|
|
251
|
+
* When `testDefs` is provided, the toolbar mode dropdown offers one item per test.
|
|
252
|
+
*/
|
|
253
|
+
activeTest?: number;
|
|
254
|
+
/**
|
|
255
|
+
* Which `die.metadata` key to display in `'metadata'` plot mode. Must match
|
|
256
|
+
* a `key` in `metadataFields` (passed to `buildWaferMap`, not here — see
|
|
257
|
+
* the `binDefs` parameter of `buildView`) for the field to be colored/
|
|
258
|
+
* legended; dies without that key set render as no-data.
|
|
259
|
+
*/
|
|
260
|
+
activeMetadataKey?: string;
|
|
261
|
+
/**
|
|
262
|
+
* When true, apply log₁₀ scale to value normalization and the colorbar.
|
|
263
|
+
* Overrides the per-test `TestDef.logScale` default.
|
|
264
|
+
* Falls back to linear when vMin ≤ 0.
|
|
265
|
+
*/
|
|
266
|
+
logScale?: boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Format to use for unitless values outside the normal display range [0.1, 9999].
|
|
269
|
+
* `'engineering'` (default): multiples-of-3 exponent notation (e.g. `12E-6`).
|
|
270
|
+
* `'si'`: SI prefix with no unit suffix (e.g. `12 µ`).
|
|
271
|
+
* Values with a unit always use SI prefix regardless of this setting.
|
|
272
|
+
*/
|
|
273
|
+
fallbackFormat?: 'si' | 'engineering';
|
|
274
|
+
/**
|
|
275
|
+
* Aggregation method label shown in hover tooltips for `stackedValues` mode.
|
|
276
|
+
* E.g. `'mean'`, `'median'`, `'stddev'`, `'min'`, `'max'`.
|
|
277
|
+
*/
|
|
278
|
+
aggregationMethod?: string;
|
|
279
|
+
/**
|
|
280
|
+
* Total number of wafers in the lot — used to compute bin occurrence percentage
|
|
281
|
+
* in `stackedBins` hover tooltips.
|
|
282
|
+
*/
|
|
283
|
+
lotSize?: number;
|
|
284
|
+
/**
|
|
285
|
+
* Axis flip baked in by the data pipeline (for LL/LR/UL/UR origins or explicit
|
|
286
|
+
* `xAxisDirection`/`yAxisDirection`). Combined with `interactiveTransform` flip
|
|
287
|
+
* to produce the total effective axis flip for tick labels.
|
|
288
|
+
*/
|
|
289
|
+
dataAxisFlip?: {
|
|
290
|
+
x: boolean;
|
|
291
|
+
y: boolean;
|
|
292
|
+
};
|
|
293
|
+
/** Set to true when the scene is built from lot-aggregated data (lotStack). */
|
|
294
|
+
isLotStack?: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Requested pass/fail display for `value` mode. The library resolves the
|
|
297
|
+
* *effective* display from this request and the data (see `View.passFailDisplay`):
|
|
298
|
+
* - `'spec'` — colour dies by spec pass/fail against the active test's limits
|
|
299
|
+
* (solid green / blue fail-low / red fail-high). Degrades to `'off'` when the
|
|
300
|
+
* active test has no limits.
|
|
301
|
+
* - `'test'` — colour dies by the tester's recorded pass/fail verdict
|
|
302
|
+
* (`die.testPass`, via `getTestPassStatus`; solid green pass / red fail).
|
|
303
|
+
* Degrades to `'off'` when no die has a recorded verdict for the active test.
|
|
304
|
+
* - `'off'` (default) — continuous value gradient.
|
|
305
|
+
* A functional active test (`testType: 'F'`) always renders as `'test'`
|
|
306
|
+
* regardless of this option — it has no value to plot.
|
|
307
|
+
*/
|
|
308
|
+
passFailDisplay?: 'off' | 'spec' | 'test';
|
|
309
|
+
/**
|
|
310
|
+
* When true (default), partial (edge) dies — positions that only partially
|
|
311
|
+
* overlap the wafer circle — are rendered in a muted grey.
|
|
312
|
+
* Set to false to hide them entirely, matching real prober behaviour where
|
|
313
|
+
* edge positions are never tested.
|
|
314
|
+
*/
|
|
315
|
+
showPartialDies?: boolean;
|
|
316
|
+
}
|
|
317
|
+
interface ColorFns {
|
|
318
|
+
forValue: (t: number) => string;
|
|
319
|
+
forBin: (bin: number) => string;
|
|
320
|
+
}
|
|
321
|
+
/** A die's spec classification for `value` mode against the active test's limits. */
|
|
322
|
+
export type SpecCategory = 'pass' | 'failHigh' | 'failLow';
|
|
323
|
+
/**
|
|
324
|
+
* Classify a value against the active test's spec limits, using the SAME rules as the value-mode
|
|
325
|
+
* die colouring in `pushDieRectangles`. Returns null when there is no value. Shared by the colour
|
|
326
|
+
* branch and the spec-count tally so the two never diverge.
|
|
327
|
+
*
|
|
328
|
+
* Out-of-spec *classification* depends ONLY on whether limits are defined — never on
|
|
329
|
+
* `colorbarRangeMode`. An out-of-spec die is always flagged when limits exist, regardless of
|
|
330
|
+
* how the colorbar is scaled. The *form* of the indication, decided in `pushDieRectangles`,
|
|
331
|
+
* depends only on the effective `passFailDisplay`: under `'spec'` (pass/fail mode) the die gets a solid
|
|
332
|
+
* green/blue/red categorical fill; in normal value/gradient mode it keeps the value gradient
|
|
333
|
+
* fill (like every other die, so the distribution stays readable and out-of-spec colours don't
|
|
334
|
+
* collide with the scheme) and is flagged with a ▽/△ marker (`ViewRect.specMark`). Either way
|
|
335
|
+
* the die is never drawn as plain in-spec — the silent correctness bug this guards against.
|
|
336
|
+
*/
|
|
337
|
+
export declare function classifySpec(value: number | undefined, activeTestDef: {
|
|
338
|
+
limitLow?: number;
|
|
339
|
+
limitHigh?: number;
|
|
340
|
+
} | undefined): SpecCategory | null;
|
|
341
|
+
/** Options for {@link buildHoverText}. All fields are optional. */
|
|
342
|
+
export interface HoverTextOptions {
|
|
343
|
+
testDefs?: TestDef[];
|
|
344
|
+
hbinDefs?: BinDef[];
|
|
345
|
+
sbinDefs?: BinDef[];
|
|
346
|
+
fallbackFormat?: 'si' | 'engineering';
|
|
347
|
+
/** Lot-stack aggregation method name — shown so the reader knows what the scalar means. */
|
|
348
|
+
aggrMethod?: string;
|
|
349
|
+
/** Number of wafers in the stack, used to annotate a count with its share of the lot. */
|
|
350
|
+
lotSize?: number;
|
|
351
|
+
waferMeta?: WaferMetadata | null;
|
|
352
|
+
/** Active test number (value mode only) — leads the tooltip and gets an out-of-spec note. */
|
|
353
|
+
activeTest?: number;
|
|
354
|
+
/** Reticle geometry, when configured — appended as a "Reticle (col, row)" line below Die (x, y). */
|
|
355
|
+
reticleConfig?: ReticleConfig;
|
|
356
|
+
}
|
|
357
|
+
export declare function buildHoverText(die: Die, plotMode: PlotMode, opts?: HoverTextOptions): string;
|
|
358
|
+
/**
|
|
359
|
+
* The on-canvas map title split into two parts for placement around the colorbar/legend:
|
|
360
|
+
* - `primary` — the most important identifier, drawn ABOVE the colorbar/legend.
|
|
361
|
+
* - `secondary` — supporting context (stack/wafer-count), drawn BELOW it. Empty when not needed.
|
|
362
|
+
*/
|
|
363
|
+
export interface MapTitleParts {
|
|
364
|
+
primary: string;
|
|
365
|
+
secondary: string;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Build the on-canvas map title for any plot mode, derived from the View (+ optional bin defs so a
|
|
369
|
+
* single-bin stacked card can name its bin). Returns a primary/secondary split so the renderer can
|
|
370
|
+
* place the key identifier above the scale and the supporting context below it — keeping each line
|
|
371
|
+
* short and never obscured.
|
|
372
|
+
*
|
|
373
|
+
* Primary / secondary per mode:
|
|
374
|
+
* - value → "{name} ({unit})" / —
|
|
375
|
+
* - stackedValues → "{name} ({unit}) · {method}" / "stacked ({n} wafers)"
|
|
376
|
+
* - hardBin/softBin → "Hard Bin" / "Soft Bin" / —
|
|
377
|
+
* - stackedBins → "Hard Bin {bin}" (or "Hard Bin") / "stacked ({n} wafers)"
|
|
378
|
+
* - stackedSoftBins → "Soft Bin {bin}" (or "Soft Bin") / "stacked ({n} wafers)"
|
|
379
|
+
*
|
|
380
|
+
* @param fallbackFormat unitless value formatting — matches the colorbar tick formatting.
|
|
381
|
+
* @param binDefs the active bin defs (hbinDefs for hard modes, sbinDefs for soft) — used to name
|
|
382
|
+
* the bin on single-bin stacked cards.
|
|
383
|
+
*/
|
|
384
|
+
export declare function buildMapTitle(view: View, fallbackFormat?: 'si' | 'engineering', binDefs?: BinDef[]): MapTitleParts;
|
|
385
|
+
export declare function generateTextOverlay(dies: Die[], txCoords: Float64Array | null, options: {
|
|
386
|
+
plotMode: PlotMode;
|
|
387
|
+
colorFns: ColorFns;
|
|
388
|
+
normalize: (v: number) => number;
|
|
389
|
+
activeTest: number;
|
|
390
|
+
valueRange: [number, number];
|
|
391
|
+
testDefs?: TestDef[];
|
|
392
|
+
fallbackFormat?: 'si' | 'engineering';
|
|
393
|
+
/** Effective pass/fail display (see View.passFailDisplay). 'test' renders P/F verdict labels. */
|
|
394
|
+
passFailDisplay?: 'off' | 'spec' | 'test';
|
|
395
|
+
/** Active `die.metadata` key + its resolved value→color map, `'metadata'` mode only. */
|
|
396
|
+
activeMetadataKey?: string;
|
|
397
|
+
metadataColorMap?: Map<string, string> | null;
|
|
398
|
+
}): ViewText[];
|
|
399
|
+
/**
|
|
400
|
+
* Build a renderer-agnostic scene from a wafer, dies, and display options.
|
|
401
|
+
*
|
|
402
|
+
* ```ts
|
|
403
|
+
* buildView(wafer, dies, { plotMode: 'hardBin', reticles })
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
export declare function buildView(wafer: Wafer, dies: Die[], options?: ViewOptions, binDefs?: {
|
|
407
|
+
hbinDefs?: BinDef[];
|
|
408
|
+
sbinDefs?: BinDef[];
|
|
409
|
+
metadataFields?: MetadataFieldDef[];
|
|
410
|
+
}): View;
|
|
411
|
+
/**
|
|
412
|
+
* Return a stable string key for a die — guaranteed format `"x,y"`.
|
|
413
|
+
*
|
|
414
|
+
* Re-exported from `core/dies.ts`, where the canonical implementation lives so
|
|
415
|
+
* that `stats/` and `canvas-adapter/` can share it without depending on
|
|
416
|
+
* `renderer/`. Kept exported here to preserve the public `@wafertools/wafermap/renderer`
|
|
417
|
+
* entry point.
|
|
418
|
+
*/
|
|
419
|
+
export { getDieKey } from '../core/dies.js';
|
|
420
|
+
//# sourceMappingURL=buildView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getReticleCell as Qt}from"../core/reticle.js";import{affineRotation as ft,affineMirror as Vt,affineCompose as at,affinePoint as X,affineVector as dt,affineSwapsAxes as Ut}from"../core/transforms.js";import{contrastTextColor as Y,SPEC_PASS_FILL as ht,SPEC_FAIL_LOW as Jt,SPEC_FAIL_HIGH as pt}from"./colorMap.js";import{getColorScheme as Zt}from"./colorSchemes.js";import{getDieTestValue as R,getTestPassStatus as G,isParametricTest as mt}from"./buildWaferMap.js";import{fmt as yt,fmtColorbarAxis as gt,fmtAggregationMethod as Rt}from"./fmt.js";import{metadataValueColor as te}from"./colorMap.js";import{compareNatural as ee,clamp01 as Ct}from"../core/utils.js";import{prettyKey as ne}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function findTestDef(t,e){return t?.find(n=>n.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,n)=>e-n)}const ae="#d3d6db",Nt="#e8e9ea",ie="#eceef0",w="#d6d9dd";function it(t,e){const n=e?t.metadata?.[e]:void 0;return n!=null&&(typeof n=="string"||typeof n=="number"||typeof n=="boolean")?String(n):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function se(t,e,n){const{x:i,y:s}=t.center,a=ft(t.orientation,i,s),o=at(Vt(e?.x??!1,e?.y??!1,i,s),ft(t.orientation,i,s)),l=at(Vt(n?.flipX??!1,n?.flipY??!1,i,s),ft(n?.rotation??0,i,s));return{bakedToScreen:l,physicalToScreen:at(l,a),gridToScreen:at(l,o)}}function _(t,e=!1){return{points:[t],closed:e}}function oe(t,e){const{center:n,radius:i,notch:s}=t;let a=n.x+i*Math.cos(e),o=n.y+i*Math.sin(e);if(!s)return{x:a,y:o};const l=Math.sqrt(i**2-(s.length/2)**2),f=s.length/2,y=a-n.x,m=o-n.y;return s.type==="bottom"&&m<-l?(o=n.y-l,a=n.x+Math.max(-f,Math.min(f,y))):s.type==="top"&&m>l?(o=n.y+l,a=n.x+Math.max(-f,Math.min(f,y))):s.type==="left"&&y<-l?(a=n.x-l,o=n.y+Math.max(-f,Math.min(f,m))):s.type==="right"&&y>l&&(a=n.x+l,o=n.y+Math.max(-f,Math.min(f,m))),{x:a,y:o}}function Ht(t,e){return t.map(e).join(" / ")}function re(t,e=256){const n=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>n[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function le(t,e){const n=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(n*.55,i*1.8))))}function ce(t,e,n){if(e?.length){const i=e.flatMap(s=>{const a=s.testNumber;if(!mt(s)){const f=G(t,a,s);return f===void 0?[]:[{key:a,label:s.name,value:f?"Pass":"Fail"}]}const o=R(t,a);if(o===void 0)return[];const l=t.testPass?.[a]===!1?" <i>(recorded fail)</i>":"";return[{key:a,label:s.name,value:`${yt(o,s.unit,n)}${l}`}]});if(i.length)return i}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:yt(s,void 0,n)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,n={}){const{testDefs:i,hbinDefs:s,sbinDefs:a,fallbackFormat:o,aggrMethod:l,lotSize:f,waferMeta:y,activeTest:m,reticleConfig:$}=n,D=s?new Map(s.map(r=>[r.bin,r])):null,v=a?new Map(a.map(r=>[r.bin,r])):null,x=[`Die (${t.x}, ${t.y})`];if($){const r=Qt(t,$);x.push(`Reticle (${r.column}, ${r.row})`)}if(e==="stackedValues"){const r=R(t,0);if(r!==void 0){const c=i?.[0],p=c?.testNumber,h=c?.name??(p!=null?`Test ${p}`:"Value"),M=l?` (${l})`:"";x.push(`${h}${M}: ${yt(r,c?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const r=R(t,0),c=e==="stackedSoftBins"?t.sbin:t.hbin;if(r!==void 0){const p=e==="stackedSoftBins"?v:D,h=c!==void 0?p?.get(c)?.name?`${c} \xB7 ${p.get(c).name}`:`Bin ${c}`:"Bin";let M;if(l==="percent")M=`${r.toFixed(0)}%`;else{const b=f?` (${(r/f*100).toFixed(0)}% of lot)`:"";M=`${r}${b}`}const T=l?` [${Rt(l)}]`:"";x.push(`${h}: ${M}${T}`)}}else{const r=ce(t,i,o);if(e==="value"&&r.length){const{testNumber:c}=resolveTestNumber(m??0,i),p=findTestDef(i,c),h=R(t,c);let M=r.findIndex(A=>A.key===c);M<0&&(M=0);const T=r[M];let b=`<b>${T.label}: ${T.value}</b>`;if(T.key===c&&p){const A=classifySpec(h,p);(A==="failLow"||A==="failHigh")&&(b+=" <i>(out of spec)</i>")}x.push(b);const O=r.length-1;O>0&&x.push(`<i>+${O} more test${O===1?"":"s"}</i>`)}else if(r.length){const c=r.length;x.push(`<i>${c} test result${c===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const c=[];if(t.hbin!==void 0){const p=D?.get(t.hbin)?.name,h=p?`${t.hbin} \xB7 ${p}`:String(t.hbin);c.push(`HBin: ${h}`)}if(t.sbin!==void 0){const p=v?.get(t.sbin)?.name,h=p?`${t.sbin} \xB7 ${p}`:String(t.sbin);c.push(`SBin: ${h}`)}x.push(c.join(" \xB7 "))}}t.retestCount!==void 0&&x.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&x.push(`Site: ${t.siteNum}`),t.partId!==void 0&&x.push(`Part ID: ${t.partId}`),t.partial&&x.push("<i>partial die</i>"),t.probeIndex!==void 0&&x.push(`Probe: #${t.probeIndex}`);const k={...y??{},...t.metadata??{}};for(const[r,c]of Object.entries(k))c!=null&&x.push(`${r}: ${String(c)}`);return x.join("<br>")}export function buildMapTitle(t,e="engineering",n){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=a=>{const o=n?.length===1?n[0]:void 0;return o===void 0?`${a} Bin`:o.name?`${a} Bin ${o.bin} \xB7 ${o.name}`:`${a} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const a=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?ne(t.activeMetadataKey):"Metadata";return{primary:a?.label??o,secondary:""}}case"stackedValues":{const a=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=gt(o,a?.name,a?.unit,e),f=l||"Value",y=t.aggrMethod?` \xB7 ${Rt(t.aggrMethod)}`:"";return{primary:`${f}${y}`,secondary:i}}default:{const a=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=gt(o,a?.name,a?.unit,e),f=l||a?.name;if(t.passFailDisplay!=="off"){const y=a?.testNumber??t.activeTest,m=f?`${f} \xB7 #${y}`:`Test ${y}`,$=t.passFailDisplay==="spec"?"Spec pass/fail":a!==void 0&&!mt(a)?"Functional pass/fail":"Tester pass/fail";return{primary:m,secondary:$}}return{primary:f??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,n){const{plotMode:i,colorFns:s,normalize:a,activeTest:o,valueRange:l,testDefs:f,fallbackFormat:y,passFailDisplay:m="off",activeMetadataKey:$,metadataColorMap:D}=n,v=findTestDef(f,o),{tickFmt:x}=gt(l[1],v?.name,v?.unit,y),{testNumber:k}=resolveTestNumber(o,f);return t.flatMap((r,c)=>{let p="",h="#111111";if(i==="value"&&m==="test"){const b=G(r,k,v);if(b===void 0)return[];p=b?"P":"F",h=Y(b?ht:pt)}else if(i==="value"){const b=R(r,k);if(b===void 0)return[];p=Ht([b],x),h=Y(s.forValue(a(b)))}else if(i==="hardBin"||i==="softBin"){const b=i==="softBin"?r.sbin:r.hbin;if(b===void 0)return[];p=String(b),h=Y(s.forBin(b))}else if(i==="metadata"){const b=it(r,$);if(b===void 0)return[];p=b,h=Y(D?.get(p)??w)}else{const b=R(r,0);if(b===void 0)return[];p=Ht([b],x),h=Y(s.forValue(a(b)))}const M=e?e[c*2]:r.physX,T=e?e[c*2+1]:r.physY;return[{x:M,y:T,text:p,fontSize:le(r,p),color:h,align:"center"}]})}function Ot(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function ue(t,e,n=720){const{center:i,radius:s,notch:a}=t,o=k=>X(e,k.x,k.y);if(!(a!==void 0&&t.diameter>150)){const k=[];for(let r=0;r<=n;r++){const c=2*Math.PI*r/n;k.push(o(oe(t,c)))}return[{kind:"wafer-boundary",..._(k,!0),lineColor:"#888888",lineWidth:1}]}const f=1.25,y=a.length,m=Ot(a.type),$=Math.atan2(y,s),D=s-f,v=[];let x=!1;for(let k=0;k<=n;k++){const r=2*Math.PI*k/n;let c=r-m;for(;c>Math.PI;)c-=2*Math.PI;for(;c<-Math.PI;)c+=2*Math.PI;if(Math.abs(c)<=$){if(!x){x=!0;const p={x:i.x+s*Math.cos(m-$),y:i.y+s*Math.sin(m-$)},h={x:i.x+D*Math.cos(m),y:i.y+D*Math.sin(m)},M={x:i.x+s*Math.cos(m+$),y:i.y+s*Math.sin(m+$)};v.push(o(p),o(h),o(M))}continue}v.push(o({x:i.x+s*Math.cos(r),y:i.y+s*Math.sin(r)}))}return[{kind:"wafer-boundary",..._(v,!0),lineColor:"#888888",lineWidth:1}]}function fe(t,e,n,i=360){const s=[],a=Math.max(1,Math.floor(n));for(let o=1;o<a;o++){const l=t.radius*o/a,f=[];for(let y=0;y<=i;y++){const m=2*Math.PI*y/i,$={x:t.center.x+l*Math.cos(m),y:t.center.y+l*Math.sin(m)};f.push(X(e,$.x,$.y))}s.push({kind:"ring-boundary",..._(f,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function de(t,e,n,i){const{x:s,y:a}=t.center,o=t.radius,l=(r,c)=>X(e,r,c),f=n-s,y=Math.sqrt(Math.max(0,o*o-f*f)),m=l(n,a-y),$=l(n,a+y),D=i-a,v=Math.sqrt(Math.max(0,o*o-D*D)),x=l(s-v,i),k=l(s+v,i);return[{kind:"quadrant-boundary",..._([m,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",..._([x,k]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function he(t,e){return t.map(n=>{const i=n.width/2,s=n.height/2;return{kind:"reticle",points:[[[n.x-i,n.y-s],[n.x+i,n.y-s],[n.x+i,n.y+s],[n.x-i,n.y+s]].map(([o,l])=>X(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function pe(t){const e=t.filter(n=>n.probeIndex!==void 0).sort((n,i)=>(n.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",..._(e.map(n=>({x:n.physX,y:n.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function me(t,e,n,i,s,a,o,l,f,y,m,$,D,v="off",x,k,r){const c=e.width-o,p=e.height-o,h=a?p:c,M=a?c:p,T=B=>s==="softBin"?B.sbin:B.hbin,b=B=>it(B,x);if(e.partial){t.push({x:n,y:i,width:h,height:M,fill:ae,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:n,y:i,width:h,height:M,fill:ie,type:"stacked",metadata:e.metadata});return}if(f!==void 0&&(s==="hardBin"||s==="softBin")&&T(e)!==f){t.push({x:n,y:i,width:h,height:M,fill:Nt,type:"hardBin",metadata:e.metadata});return}if(r!==void 0&&s==="metadata"&&b(e)!==r){t.push({x:n,y:i,width:h,height:M,fill:Nt,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(v==="test"){const U=G(e,m,D),st=U===void 0?w:U?ht:pt;t.push({x:n,y:i,width:h,height:M,fill:st,type:"value",metadata:e.metadata});return}const B=R(e,m),P=classifySpec(B,D);let W,Q;B===void 0?W=w:v==="spec"?W=P==="failLow"?Jt:P==="failHigh"?pt:ht:(W=l.forValue(y(B)),(P==="failLow"||P==="failHigh")&&(Q=P)),t.push({x:n,y:i,width:h,height:M,fill:W,type:"value",specMark:Q,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const B=T(e),P=B!=null?l.forBin(B):w;t.push({x:n,y:i,width:h,height:M,fill:P,type:s,metadata:e.metadata});return}if(s==="metadata"){const B=b(e),P=B!==void 0?k?.get(B)??w:w;t.push({x:n,y:i,width:h,height:M,fill:P,type:"metadata",metadata:e.metadata});return}const O=R(e,0),A=O!==void 0?l.forValue(y(O)):w;t.push({x:n,y:i,width:h,height:M,fill:A,type:"value",metadata:e.metadata})}function ye(t,e,n){const i=t.radius*.15,s=dt(e,i,0),a=dt(e,0,i),o=s.x+a.x>=0?-1:1,l=s.y+a.y>=0?-1:1,f={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},y={x:f.x+s.x,y:f.y+s.y},m={x:f.x+a.x,y:f.y+a.y};return n.push({x:y.x+s.x*.35,y:y.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:m.x+a.x*.35,y:m.y+a.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",..._([f,y]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",..._([f,m]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,n={},i){const s=n.reticles??[],{plotMode:a="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:f=!1,ringCount:y=4,showRingBoundaries:m=!1,showQuadrantBoundaries:$=!1,showXYIndicator:D=!1,dieGap:v=1,colorScheme:x="default",highlightBin:k,highlightMetadataValue:r,interactiveTransform:c,valueRange:p,testDefs:h,activeTest:M=0,activeMetadataKey:T,fallbackFormat:b="engineering",aggregationMethod:O,lotSize:A,dataAxisFlip:B,isLotStack:P=!1,logScale:W,colorbarRangeMode:Q="spec",passFailDisplay:U,showPartialDies:st=!0}=n,xt=U??"off",bt=i?.hbinDefs,Mt=i?.sbinDefs,St=i?.metadataFields,At=St?.find(u=>u.key===T),Et={x:(B?.x??!1)!==(c?.flipX??!1),y:(B?.y??!1)!==(c?.flipY??!1)},_t=bt?new Map(bt.map(u=>[u.bin,u])):null,wt=Mt?new Map(Mt.map(u=>[u.bin,u])):null,ot=a==="softBin"?wt:_t,rt=Zt(x),$t={forValue:re(rt.forValue),forBin:x==="custom"&&ot?u=>ot.get(u)?.color??rt.forBin(u):rt.forBin};let{testNumber:H}=resolveTestNumber(M,h);if(!h?.length&&a==="value"&&!e.some(d=>d.testValues&&H in d.testValues)){const d=e.reduce((g,L)=>{if(!L.testValues)return g;const S=Object.keys(L.testValues).map(Number),K=S.length?Math.min(...S):void 0;return K!==void 0&&(g===void 0||K<g)?K:g},void 0);d!==void 0&&(H=d)}let J;if(Array.isArray(p))J=p;else if(p){const{testNumber:u}=resolveTestNumber(p.test,h);J=u===H?p.range:void 0}const I=findTestDef(h,H),kt=a==="value"&&I!==void 0&&!mt(I),Wt=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let C="off";a==="value"&&(kt?C="test":xt==="spec"&&Wt?C="spec":xt==="test"&&e.some(u=>G(u,H,I)!==void 0)&&(C="test"));const Bt=C==="spec"?"spec":Q;let V,N,vt=!1;if(J)[V,N]=J;else{const u=a==="stackedValues"||a==="stackedBins"||a==="stackedSoftBins",d=!u&&a==="value"&&Bt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let g=1/0,L=-1/0,S=!0;for(const K of e){const q=u?R(K,0):R(K,H);q!==void 0&&(q<g&&(g=q),q>L&&(L=q),Number.isInteger(q)||(S=!1))}isFinite(g)||(S=!1),vt=S,d?(V=I.limitLow!==void 0?I.limitLow:isFinite(g)?g:0,N=I.limitHigh!==void 0?I.limitHigh:isFinite(L)?L:1):(V=isFinite(g)?g:0,N=isFinite(L)?L:1)}V===N&&(N=V+1);const Tt=kt?!1:W??I?.logScale??!1,It=Tt&&V>0&&N>0,jt=It;let Z;if(It){const u=Math.log10(V),d=Math.log10(N)-u;Z=g=>g<=0?0:Ct((Math.log10(g)-u)/d)}else Z=u=>Ct((u-V)/(N-V));const F=se(t,B,c);let Lt=null;if(t.notch){const u=Ot(t.notch.type),d=dt(F.physicalToScreen,Math.cos(u)*t.radius,Math.sin(u)*t.radius),g=Math.hypot(d.x,d.y);g>0&&(Lt={x:d.x/g,y:d.y/g})}let j=1/0;for(let u=0;u<e.length;u++){const d=e[u];d.width<j&&(j=d.width),d.height<j&&(j=d.height)}const zt=j!==1/0?Math.min(v,j*.12):v,Dt=[],lt=[],Kt=F.bakedToScreen.a===1&&F.bakedToScreen.b===0&&F.bakedToScreen.c===0&&F.bakedToScreen.d===1&&F.bakedToScreen.e===0&&F.bakedToScreen.f===0;let E=null;if(!Kt){E=new Float64Array(e.length*2);for(let u=0;u<e.length;u++){const d=X(F.bakedToScreen,e[u].physX,e[u].physY);E[u*2]=d.x,E[u*2+1]=d.y}}const qt=Ut(F.gridToScreen),Xt=a==="hardBin"||a==="softBin"||a==="stackedBins"||a==="stackedSoftBins",Yt=a==="softBin"||a==="stackedSoftBins";let tt=Xt?new Map:void 0;const ct=a==="value"&&C==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ut=a==="value"&&C==="test"?{pass:0,fail:0}:void 0,Ft=a==="metadata";let et=Ft?new Map:void 0,nt=null;if(Ft&&T){const u=new Set;for(const g of e){if(g.partial||g.edgeExcluded)continue;const L=it(g,T);L!==void 0&&u.add(L)}const d=[...u].sort(ee);nt=new Map(d.map((g,L)=>[g,At?.values?.find(S=>S.value===g)?.color??te(L)]))}for(let u=0;u<e.length;u++){const d=e[u],g=E?E[u*2]:d.physX,L=E?E[u*2+1]:d.physY;if(lt.push({x:g,y:L}),tt&&!d.partial&&!d.edgeExcluded){const S=Yt?d.sbin:d.hbin;S!=null&&tt.set(S,(tt.get(S)??0)+1)}if(ct&&!d.partial&&!d.edgeExcluded){const S=classifySpec(R(d,H),I);S&&ct[S]++}if(ut&&!d.partial&&!d.edgeExcluded){const S=G(d,H,I);S!==void 0&&ut[S?"pass":"fail"]++}if(et&&!d.partial&&!d.edgeExcluded){const S=it(d,T);S!==void 0&&et.set(S,(et.get(S)??0)+1)}d.partial&&!st||me(Dt,d,g,L,a,qt,zt,$t,k,Z,H,ot,I,C,T,nt,r)}const Pt=o?generateTextOverlay(e,E,{plotMode:a,colorFns:$t,normalize:Z,activeTest:M,valueRange:[V,N],testDefs:h,fallbackFormat:b,passFailDisplay:C,activeMetadataKey:T,metadataColorMap:nt}):[],z=ue(t,F.physicalToScreen);m&&z.push(...fe(t,F.physicalToScreen,y)),$&&z.push(...de(t,F.physicalToScreen,t.center.x,t.center.y)),l&&z.push(...he(s,F.gridToScreen)),f&&z.push(...pe(e)),D&&z.push(...ye(t,F.gridToScreen,Pt));const Gt=lt.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Dt,hoverPoints:lt,texts:Pt,overlays:z,plotMode:a,colorScheme:x,metadata:t.metadata??null,dies:e,valueRange:[V,N],allIntegerValues:vt,testDefs:h,activeTest:M,logScale:jt,logScaleRequested:Tt,aggrMethod:O,lotSize:A,axisFlip:Et,dataAxisFlip:{x:B?.x??!1,y:B?.y??!1},gridToScreen:F.gridToScreen,rotation:((t.orientation+(c?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:P,passFailDisplay:C,colorBySpec:C==="spec",colorbarRangeMode:Bt,waferCenter:t.center,waferRadius:t.radius,notchDir:Lt,binCounts:tt,metadataCounts:et,metadataColorMap:nt,activeMetadataKey:T,metadataFields:St,specCounts:ct,passFailCounts:ut,dieBounds:Gt}}export{getDieKey}from"../core/dies.js";
|