@wafertools/wafermap 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +16 -5
- package/CHANGELOG.md +101 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/dieList.d.ts +41 -0
- package/dist/packages/canvas-adapter/dieList.js +2 -0
- package/dist/packages/canvas-adapter/index.d.ts +2 -0
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.d.ts +34 -0
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +2 -18
- package/dist/packages/canvas-adapter/renderWaferGallery.js +8 -8
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +17 -1
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +2 -0
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +1 -1
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.d.ts +6 -1
- package/dist/packages/core/classify.d.ts +6 -2
- package/dist/packages/core/dies.d.ts +67 -7
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/probe.d.ts +5 -0
- package/dist/packages/core/probe.js +1 -1
- package/dist/packages/core/transforms.d.ts +4 -4
- package/dist/packages/core/transforms.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +3 -3
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +25 -8
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +2 -2
- package/dist/packages/stats/patternClassification.d.ts +2 -2
- package/dist/packages/stats/regions.d.ts +6 -6
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.js +17 -17
- package/llms.txt +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -53,15 +53,26 @@ correctness guarantee that comes with it.
|
|
|
53
53
|
sites that fit. Never recompute a `partial` flag by testing die corners against
|
|
54
54
|
the wafer circle — that manufactures fake partial dies which are then greyed out
|
|
55
55
|
and dropped from yield. A die outside the wafer means the *geometry* is wrong.
|
|
56
|
-
- **Check `result.warnings` and `summary.stats.warnings`.** Both
|
|
57
|
-
`WaferWarning
|
|
58
|
-
prose.
|
|
56
|
+
- **Check `result.warnings` and `summary.stats.warnings`.** Both carry
|
|
57
|
+
`WaferWarning` — `{ code, message, severity }`. Branch on `code`, never on the
|
|
58
|
+
prose. **The two differ in shape and the difference throws:**
|
|
59
|
+
`result.warnings` is required and always an array (`[]` when clean), but
|
|
60
|
+
`summary.stats.warnings` is *optional* and is `undefined` when there is nothing
|
|
61
|
+
to report. Write `summary.stats.warnings?.length` — a bare
|
|
62
|
+
`summary.stats.warnings.length` is a TypeError on every clean wafer, which is
|
|
63
|
+
most of them, so it will pass your testing and fail in production.
|
|
64
|
+
Geometry advisories are severity `'error'`: they mean dies may be drawn in
|
|
59
65
|
the wrong place. The renderers surface these themselves in a toolbar indicator, so
|
|
60
66
|
do NOT hand-roll a second display — pass
|
|
61
67
|
`warnings: { display: false, onWarning }` if the app has its own notification UI.
|
|
62
68
|
(`result.inference.warnings` is a deprecated string mirror; do not use it.)
|
|
63
|
-
- **
|
|
64
|
-
|
|
69
|
+
- **Give the container a resolved height.** `renderWaferMap` fills its container.
|
|
70
|
+
A bare block-flow `<div>` is fine — it grows to the canvas. The real failure is a
|
|
71
|
+
flex/grid child whose ancestors never resolve a height: it stays 0-tall and the
|
|
72
|
+
map is invisible. The library detects exactly that case after layout settles and
|
|
73
|
+
`console.warn`s with the fix, so read the console before debugging further. Either
|
|
74
|
+
give the container a real CSS height, or pass `{ height: 600 }` in the render
|
|
75
|
+
options and the library will size it for you.
|
|
65
76
|
|
|
66
77
|
### API facts that are easy to guess wrong
|
|
67
78
|
|
package/CHANGELOG.md
CHANGED
|
@@ -19,6 +19,107 @@ under `### Breaking`.
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
+
## [0.23.0] — 2026-08-16
|
|
23
|
+
|
|
24
|
+
### Breaking
|
|
25
|
+
|
|
26
|
+
- **`Die.x`/`y`/`physX`/`physY` are now optional**, not `number`. Existing code
|
|
27
|
+
that reads `die.x` as a bare number (e.g. arithmetic without a guard) now gets
|
|
28
|
+
a type error under strict TS — the runtime value for already-positioned data
|
|
29
|
+
is unchanged, only the type widened. New `isPositionedDie(die): die is PositionedDie`
|
|
30
|
+
(`@wafertools/wafermap/core`) narrows it back; every spatial function in the
|
|
31
|
+
library (region builders, cluster/pattern detection, `buildView`) now takes
|
|
32
|
+
`PositionedDie[]` rather than `Die[]`, so a caller passing unfiltered `Die[]`
|
|
33
|
+
into one of these will see the type error at the call site rather than a
|
|
34
|
+
runtime surprise. Its sibling `hasPosition(die)` is the looser guard for
|
|
35
|
+
inputs that carry `x`/`y` but no `physX`/`physY` (a raw `DieResult`, before
|
|
36
|
+
layout); use `isPositionedDie` for anything holding built `Die`s.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`renderWaferMap` accepts a `RenderableWaferMap`** — `{ wafer, dies }` plus
|
|
41
|
+
every other `WaferMapResult` field as optional — rather than requiring a full
|
|
42
|
+
`WaferMapResult`. A `WaferMapResult` still satisfies it, so no caller needs to
|
|
43
|
+
change. This makes the gallery's own use honest: it renders each card through
|
|
44
|
+
`renderWaferMap`, but a card is a `WaferMapDisplayItem` carrying no
|
|
45
|
+
`dataCoverage`/`viewport`/`legendBox`/`binLegendRows`/`reticleConfig`, and it
|
|
46
|
+
used to be passed via `item as WaferMapResult`. That cast asserted fields
|
|
47
|
+
which were absent at runtime; reading one unguarded threw, and each such read
|
|
48
|
+
had to be found by hitting it. `collectWarnings` likewise now types
|
|
49
|
+
`result.warnings` as optional, which its body already handled.
|
|
50
|
+
|
|
51
|
+
- **Support for dies/wafers with no reported X/Y position.** Real-world data
|
|
52
|
+
sometimes has no spatial layout at all (wafer-number-only test logs), or a
|
|
53
|
+
lot/wafer where some dies report a position and others don't — previously
|
|
54
|
+
such a die was silently dropped by every parser upstream of this library, and
|
|
55
|
+
wmap itself had no representation for "no position" beyond crashing or
|
|
56
|
+
drawing garbage.
|
|
57
|
+
- `hasPosition(die)` / `isPositionedDie(die)` (`packages/core/dies.ts`) are
|
|
58
|
+
the predicates every spatial code path filters on — the first narrows
|
|
59
|
+
`x`/`y`, the second narrows `physX`/`physY` with them and so yields a
|
|
60
|
+
`PositionedDie`. `getDieKey` falls back to `` `id:${id}` `` for an
|
|
61
|
+
unpositioned die so two of them never collide.
|
|
62
|
+
- `buildWaferMap` partitions positioned/unpositioned dies before geometry
|
|
63
|
+
inference runs; unpositioned dies are folded back into the returned
|
|
64
|
+
`dies`/`dataCoverage`/`yield` afterward. `dataCoverage` gained
|
|
65
|
+
`unpositionedDies: number` (always present); `totalDies`/`filledDies`/
|
|
66
|
+
`ratio` stay scoped to positioned dies only. Yield is deliberately
|
|
67
|
+
**not** spatial — a coordinate-less die with bin data still counts toward
|
|
68
|
+
it, same as `isYieldEligibleDie` always ignored position.
|
|
69
|
+
- `analyzeWaferMap`/`regions.ts` exclude unpositioned dies from spatial
|
|
70
|
+
region families only (ring/quadrant/sector/reticle-position, cluster and
|
|
71
|
+
pattern detection). Everything value-based (yield, bin counts, per-test
|
|
72
|
+
stats, spec-limit yield) is unaffected — coordinate-less dies still count.
|
|
73
|
+
- A wafer with **zero** positioned dies never renders as a wafer-shaped
|
|
74
|
+
visual — a fabricated mosaic risks being misread as real spatial data.
|
|
75
|
+
`renderWaferMap`/`renderWaferGallery` instead show a compact,
|
|
76
|
+
plot-mode-aware summary in its place: a bin breakdown (reusing the
|
|
77
|
+
existing summary-panel component, so colours match the map's own bin
|
|
78
|
+
legend) for hard/soft-bin modes, or a small histogram for value mode
|
|
79
|
+
(coloured through the same colour-scheme/log-scale/spec-range resolution
|
|
80
|
+
the map's own colorbar uses). A **View die list** toggle reaches the full
|
|
81
|
+
per-die table (`buildDieListSection`, new, exported from `/render`) for
|
|
82
|
+
CSV export and per-die inspection, and **View chart** returns. A mixed
|
|
83
|
+
wafer keeps its normal map for the positioned dies, plus an expandable
|
|
84
|
+
"+N dies without position data" footer giving the same chart/die-list
|
|
85
|
+
toggle for the rest.
|
|
86
|
+
- The toolbar's spatial-only controls (zoom/pan/select/download,
|
|
87
|
+
orientation, overlays, legend position) are hidden on a fully
|
|
88
|
+
coordinate-less card — nothing to act on with no map drawn. Plot mode and
|
|
89
|
+
colour scheme stay, since both drive the summary's own appearance.
|
|
90
|
+
- A gallery card's own "expand" button now preserves that card's live view
|
|
91
|
+
state (plot mode, active test, colour scheme, …) when opening the
|
|
92
|
+
detached window/popup, rather than always reverting to the gallery's
|
|
93
|
+
shared default — a pre-existing gap for any card (not specific to
|
|
94
|
+
coordinate-less data) that became far more visible once a card's
|
|
95
|
+
representation could change shape between modes (bin breakdown vs.
|
|
96
|
+
histogram vs. map).
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- **`buildDieListSection`'s table was capped at a fixed 360px**, regardless of
|
|
101
|
+
how much room its container actually had — a short table floated in a
|
|
102
|
+
half-empty card, and a host mounting it inside a scrolling container of its
|
|
103
|
+
own got two nested scrollbars. It now fills its container by default
|
|
104
|
+
(`flex: 1; min-height: 0`); a new optional `maxHeight` re-imposes a cap for a
|
|
105
|
+
host with no definite-height container to fill.
|
|
106
|
+
|
|
107
|
+
### Known gaps (not fixed this pass, tracked for a follow-up)
|
|
108
|
+
|
|
109
|
+
- Stacked plot modes (`stackedValues`/`stackedBins`/`stackedSoftBins`) discard
|
|
110
|
+
every individual wafer card from the gallery grid and pool across the lot —
|
|
111
|
+
a coordinate-less wafer's dies are correctly excluded from those pooled
|
|
112
|
+
aggregates (stacking is inherently position-based), but nothing in the UI
|
|
113
|
+
currently indicates a wafer was excluded; it's indistinguishable from that
|
|
114
|
+
wafer not existing in the lot at all.
|
|
115
|
+
- `hasPosition`, `PositionedDie`, `dataCoverage.unpositionedDies`, and
|
|
116
|
+
`buildDieListSection`/`DieListOptions` are documented in `docs/api.md`
|
|
117
|
+
(§12.1, §7); a dedicated numbered reference entry for `hasPosition` and
|
|
118
|
+
`buildDieListSection` alongside the library's other numbered API entries
|
|
119
|
+
has not been added yet.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
22
123
|
## [0.22.0] — 2026-08-04
|
|
23
124
|
|
|
24
125
|
### Breaking
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestHistogramData as Ht,buildTestHistogramSeries as At}from"../../stats/histogram.js";import{CLR as lt}from"../toolbar.js";import{fmt as st}from"../../renderer/fmt.js";import{QUANTITY as Wt,categorical as Ct}from"./palette.js";import{cardShell as Et,observeResize as xt,makeTooltip as Pt,positionChartTooltip as bt,makeTestSelect as $t,makeWaferSelect as Bt,makeToggle as Rt,renderEmptyState as pt,chartFillHeight as yt,applyCanvasFlow as Mt,resolveChartCanvasColors as Tt,makeAxisFormat as vt,PADDING as tt}from"./chartShell.js";const mt=230,St=36,at=18;function kt(s,R,N,z,rt,I,b,u=4){const j=Math.max(1,I/u),F=Math.pow(10,Math.floor(Math.log10(j))),f=j/F,G=(f<=1?1:f<=2?2:f<=5?5:10)*F,$=z-N;s.save(),s.font="10px system-ui, sans-serif",s.textAlign="right",s.textBaseline="middle";for(let K=0;K<=I+1e-9;K+=G){const _=z-K/(I||1)*$;s.strokeStyle=b.grid,s.lineWidth=1,s.beginPath(),s.moveTo(R,_),s.lineTo(R+rt,_),s.stroke(),s.fillStyle=b.text,s.fillText(String(Math.round(K)),R-4,_)}s.strokeStyle=b.axis,s.lineWidth=1,s.beginPath(),s.moveTo(R,N),s.lineTo(R,z),s.stroke(),s.restore()}export function renderHistogramPanel(s){const{title:R="Test value distribution",items:N,testDefs:z,onSaveImage:rt,groups:I}=s,{card:b,body:u,controlsRow:j}=Et(R,rt),F=z.filter(a=>a.testNumber!==void 0);let f=s.selectedTestNumber??F[0]?.testNumber??null,G=null,$=!1;const K=$t(F,f,a=>{f=a,U()},{maxWidth:"200px"});j.appendChild(K);const _=Bt(N,G,a=>{G=a,U()});j.appendChild(_),j.appendChild(Rt("Axis includes limits",$,a=>{$=a,U()}));const A=Pt(b);let J=null,
|
|
1
|
+
import{buildTestHistogramData as Ht,buildTestHistogramSeries as At}from"../../stats/histogram.js";import{CLR as lt}from"../toolbar.js";import{fmt as st}from"../../renderer/fmt.js";import{QUANTITY as Wt,categorical as Ct}from"./palette.js";import{cardShell as Et,observeResize as xt,makeTooltip as Pt,positionChartTooltip as bt,makeTestSelect as $t,makeWaferSelect as Bt,makeToggle as Rt,renderEmptyState as pt,chartFillHeight as yt,applyCanvasFlow as Mt,resolveChartCanvasColors as Tt,makeAxisFormat as vt,PADDING as tt}from"./chartShell.js";const mt=230,St=36,at=18;function kt(s,R,N,z,rt,I,b,u=4){const j=Math.max(1,I/u),F=Math.pow(10,Math.floor(Math.log10(j))),f=j/F,G=(f<=1?1:f<=2?2:f<=5?5:10)*F,$=z-N;s.save(),s.font="10px system-ui, sans-serif",s.textAlign="right",s.textBaseline="middle";for(let K=0;K<=I+1e-9;K+=G){const _=z-K/(I||1)*$;s.strokeStyle=b.grid,s.lineWidth=1,s.beginPath(),s.moveTo(R,_),s.lineTo(R+rt,_),s.stroke(),s.fillStyle=b.text,s.fillText(String(Math.round(K)),R-4,_)}s.strokeStyle=b.axis,s.lineWidth=1,s.beginPath(),s.moveTo(R,N),s.lineTo(R,z),s.stroke(),s.restore()}export function renderHistogramPanel(s){const{title:R="Test value distribution",items:N,testDefs:z,onSaveImage:rt,groups:I}=s,{card:b,body:u,controlsRow:j}=Et(R,rt),F=z.filter(a=>a.testNumber!==void 0);let f=s.selectedTestNumber??F[0]?.testNumber??null,G=null,$=!1;const K=$t(F,f,a=>{f=a,U()},{maxWidth:"200px"});j.appendChild(K);const _=Bt(N,G,a=>{G=a,U()});j.appendChild(_),j.appendChild(Rt("Axis includes limits",$,a=>{$=a,U()}));const A=Pt(b);let J=null,y=null;function et(a){const w=z.find(O=>O.testNumber===a);return{unit:w?.unit,limitLow:w?.limitLow,limitHigh:w?.limitHigh}}function U(){if(J?.disconnect(),J=null,u.innerHTML="",f===null){pt(u,"No parametric test data available for a histogram.");return}const a=I&&I.length>0?At(I,f,16,$?et(f).limitLow:void 0,$?et(f).limitHigh:void 0):null;if(_.style.display=a?"none":"",a){if(a.series.length===0){pt(u,"No parametric test data available for a histogram.");return}Lt(a);return}const w=G!==null?[N[G]]:N,{unit:O,limitLow:nt,limitHigh:x}=et(f),i=Ht(w,f,16,$?nt:void 0,$?x:void 0);if(i.length===0){pt(u,"No parametric test data available for a histogram.");return}const D=Math.max(...i.map(p=>p.count),1),P=document.createElement("div");Object.assign(P.style,{fontSize:"12px",color:lt.label,marginBottom:"2px"}),P.textContent=`max ${D} dies/bucket`,u.appendChild(P);const h=document.createElement("canvas");h.style.display="block",h.style.cursor="default",u.appendChild(h);let v=-1;const g=window.devicePixelRatio||1,B=i[0].rangeLow,X=i[i.length-1].rangeHigh,ot=X-B||1,Z=vt(Math.max(Math.abs(B),Math.abs(X)),O);function Y(p){const m=tt+36,e=h.clientWidth-m-tt,n=p-St-at;return{plotX:m,plotMaxWidth:Math.max(10,e),plotMaxHeight:n,plotTop:at}}function ct(p){const{plotX:m,plotMaxWidth:e}=Y(mt);if(p<m||p>m+e)return-1;const n=Math.floor((p-m)/e*i.length);return n>=0&&n<i.length?n:-1}function H(){Mt(h,P.offsetHeight);const p=u.clientWidth,m=yt(b,u,h,mt);h.width=Math.max(1,Math.floor(p*g)),h.height=Math.max(1,Math.floor(m*g)),h.style.width=`${p}px`,h.style.height=`${m}px`;const e=h.getContext("2d");e.setTransform(g,0,0,g,0,0),e.clearRect(0,0,p,m),e.font="11px system-ui, sans-serif";const n=Tt(b),{plotX:c,plotMaxWidth:t,plotMaxHeight:d,plotTop:o}=Y(m),l=o+d,S=t/i.length;kt(e,c,o,l,t,D,{text:n.textMuted,axis:n.border,grid:n.border}),i.forEach((M,C)=>{const k=M.count/D*d,r=c+C*S,L=l-k;e.fillStyle=C===v?n.text:Wt,e.globalAlpha=C===v?1:.7,e.fillRect(r+1,L,Math.max(1,S-2),k),e.globalAlpha=1,C===v&&(e.fillStyle=n.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${M.count}`,r+S/2,L-2))});const V=M=>c+(M-B)/ot*t;e.font="10px system-ui, sans-serif";for(const[M,C]of[[nt,"LSL"],[x,"USL"]]){if(M===void 0)continue;const k=V(M);e.strokeStyle=n.warnBorder,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(k,o),e.lineTo(k,l),e.stroke(),e.setLineDash([]),e.fillStyle=n.warnBorder,e.textAlign="center",e.textBaseline="bottom",e.fillText(C,k,o-2)}e.font="11px system-ui, sans-serif",e.strokeStyle=n.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,l),e.lineTo(c+t,l),e.stroke(),e.fillStyle=n.textMuted,e.textAlign="center",e.textBaseline="top";const W=Math.max(2,Math.floor(t/44)),dt=Math.ceil(i.length/W),ht=Math.max(1,dt);for(let M=0;M<=i.length;M+=ht){const C=M<i.length?i[M].rangeLow:i[i.length-1].rangeHigh,k=c+M*S;e.beginPath(),e.moveTo(k,l),e.lineTo(k,l+4),e.stroke(),e.fillText(Z.tick(C),k,l+6)}Z.unitLabel&&(e.save(),e.font="10px system-ui, sans-serif",e.fillStyle=n.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${Z.unitLabel})`,c+t,l+20),e.restore())}h.addEventListener("mousemove",p=>{const m=h.getBoundingClientRect(),e=ct(p.clientX-m.left);if(e!==v&&(v=e,H()),e>=0){const n=i[e];A.innerHTML=`<strong>${st(n.rangeLow,O,"engineering")} \u2013 ${st(n.rangeHigh,O,"engineering")}</strong><br>${n.count} dies`,A.style.display="block",bt(A,b,p.clientX,p.clientY)}else A.style.display="none"}),h.addEventListener("mouseleave",()=>{v!==-1&&(v=-1,H()),A.style.display="none"}),J=xt(b,()=>H()),H()}function Lt(a){const{unit:w,limitLow:O,limitHigh:nt}=et(f),x=a.ranges,i=a.series,D=Ct;y&&!i.some(n=>n.groupKey===y)&&(y=null);const P=Math.max(1,...i.flatMap(n=>n.counts)),h=document.createElement("div");Object.assign(h.style,{fontSize:"12px",color:lt.label,marginBottom:"2px"}),h.textContent=`${i.length} groups \xB7 max ${P} dies/bucket`,u.appendChild(h);const v=document.createElement("div");Object.assign(v.style,{display:"flex",flexWrap:"wrap",gap:"4px 12px",marginBottom:"4px"}),i.forEach((n,c)=>{const t=document.createElement("button");t.type="button",t.title=`${n.groupKey} \u2014 click to emphasize (dim the rest)`,Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:"5px",fontSize:"11px",padding:"1px 4px",border:"none",background:"none",cursor:"pointer",color:lt.text,borderRadius:"3px"});const d=document.createElement("span");Object.assign(d.style,{width:"10px",height:"10px",borderRadius:"2px",background:D(c),flex:"0 0 auto"});const o=document.createElement("span");o.textContent=n.groupKey,t.append(d,o);const l=y!==null&&y!==n.groupKey;t.style.opacity=l?"0.45":"1",y===n.groupKey&&(t.style.background=lt.bgHover),t.setAttribute("aria-pressed",y===n.groupKey?"true":"false"),t.addEventListener("click",()=>{y=y===n.groupKey?null:n.groupKey,U()}),v.appendChild(t)}),u.appendChild(v);const g=document.createElement("canvas");g.style.display="block",g.style.cursor="default",u.appendChild(g);const B=window.devicePixelRatio||1,X=x[0].rangeLow,ot=x[x.length-1].rangeHigh,Z=ot-X||1,Y=vt(Math.max(Math.abs(X),Math.abs(ot)),w),ct=()=>h.offsetHeight+v.offsetHeight;let H=-1,p={plotX:tt+36,plotMaxWidth:10,barWidth:10};function m(){Mt(g,ct());const n=u.clientWidth,c=yt(b,u,g,mt);g.width=Math.max(1,Math.floor(n*B)),g.height=Math.max(1,Math.floor(c*B)),g.style.width=`${n}px`,g.style.height=`${c}px`;const t=g.getContext("2d");t.setTransform(B,0,0,B,0,0),t.clearRect(0,0,n,c),t.font="11px system-ui, sans-serif";const d=Tt(b),o=tt+36,l=Math.max(10,n-o-tt),S=at,V=c-St-at,T=S+V,W=l/x.length;p={plotX:o,plotMaxWidth:l,barWidth:W},kt(t,o,S,T,l,P,{text:d.textMuted,axis:d.border,grid:d.border}),H>=0&&(t.fillStyle=d.bgHover,t.globalAlpha=.5,t.fillRect(o+H*W,S,W,V),t.globalAlpha=1);const dt=i.map((r,L)=>({s:r,i:L})).sort((r,L)=>(r.s.groupKey===y?1:0)-(L.s.groupKey===y?1:0));for(const{s:r,i:L}of dt){const E=y===r.groupKey,ut=y!==null&&!E,ft=D(L);t.strokeStyle=ft,t.fillStyle=ft,t.lineWidth=E?2:1,t.globalAlpha=ut?.12:y?.32:.22,t.beginPath(),t.moveTo(o,T),r.counts.forEach((gt,it)=>{const Q=o+it*W,q=T-gt/P*V;t.lineTo(Q,q),t.lineTo(Q+W,q)}),t.lineTo(o+l,T),t.closePath(),t.fill(),t.globalAlpha=ut?.3:1,t.beginPath(),r.counts.forEach((gt,it)=>{const Q=o+it*W,q=T-gt/P*V;it===0?t.moveTo(Q,q):t.lineTo(Q,q),t.lineTo(Q+W,q)}),t.stroke()}t.globalAlpha=1;const ht=r=>o+(r-X)/Z*l;t.font="10px system-ui, sans-serif";for(const[r,L]of[[O,"LSL"],[nt,"USL"]]){if(r===void 0)continue;const E=ht(r);t.strokeStyle=d.warnBorder,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(E,S),t.lineTo(E,T),t.stroke(),t.setLineDash([]),t.fillStyle=d.warnBorder,t.textAlign="center",t.textBaseline="bottom",t.fillText(L,E,S-2)}t.font="11px system-ui, sans-serif",t.strokeStyle=d.border,t.lineWidth=1,t.beginPath(),t.moveTo(o,T),t.lineTo(o+l,T),t.stroke(),t.fillStyle=d.textMuted,t.textAlign="center",t.textBaseline="top";const C=Math.max(2,Math.floor(l/44)),k=Math.max(1,Math.ceil(x.length/C));for(let r=0;r<=x.length;r+=k){const L=r<x.length?x[r].rangeLow:x[x.length-1].rangeHigh,E=o+r*W;t.beginPath(),t.moveTo(E,T),t.lineTo(E,T+4),t.stroke(),t.fillText(Y.tick(L),E,T+6)}Y.unitLabel&&(t.save(),t.font="10px system-ui, sans-serif",t.fillStyle=d.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${Y.unitLabel})`,o+l,T+20),t.restore())}function e(n){const{plotX:c,plotMaxWidth:t,barWidth:d}=p;if(n<c||n>c+t)return-1;const o=Math.floor((n-c)/d);return o>=0&&o<x.length?o:-1}g.addEventListener("mousemove",n=>{const c=g.getBoundingClientRect(),t=e(n.clientX-c.left);if(t!==H&&(H=t,m()),t>=0){const d=x[t],o=i.map((l,S)=>`<span style="display:inline-block;width:9px;height:9px;border-radius:2px;background:${D(S)};margin-right:5px;"></span>${l.groupKey}: ${l.counts[t]}`).join("<br>");A.innerHTML=`<strong>${st(d.rangeLow,w,"engineering")} \u2013 ${st(d.rangeHigh,w,"engineering")}</strong><br>${o}`,A.style.display="block",bt(A,b,n.clientX,n.clientY)}else A.style.display="none"}),g.addEventListener("mouseleave",()=>{H!==-1&&(H=-1,m()),A.style.display="none"}),J=xt(b,()=>m()),m()}U();function wt(a){!F.some(w=>w.testNumber===a)||a===f||(f=a,K.value=String(a),U())}return{card:b,setTest:wt,destroy:()=>J?.disconnect()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getColorScheme as ht}from"../../renderer/colorSchemes.js";import{categorical as gt}from"./palette.js";import{buildScatterData as bt,buildScatterDataGrouped as yt}from"../../stats/scatter.js";import{buildFacetTable as St}from"../../stats/facets.js";import{CLR as M}from"../toolbar.js";import{cardShell as Mt,observeResize as Tt,makeTooltip as wt,makeTestSelect as kt,makeWaferSelect as vt,chartFillHeight as Lt,applyCanvasFlow as Ct,drawAxisUnit as At,resolveChartCanvasColors as Bt,makeAxisFormat as nt}from"./chartShell.js";const
|
|
1
|
+
import{getColorScheme as ht}from"../../renderer/colorSchemes.js";import{categorical as gt}from"./palette.js";import{buildScatterData as bt,buildScatterDataGrouped as yt}from"../../stats/scatter.js";import{buildFacetTable as St}from"../../stats/facets.js";import{CLR as M}from"../toolbar.js";import{cardShell as Mt,observeResize as Tt,makeTooltip as wt,makeTestSelect as kt,makeWaferSelect as vt,chartFillHeight as Lt,applyCanvasFlow as Ct,drawAxisUnit as At,resolveChartCanvasColors as Bt,makeAxisFormat as nt}from"./chartShell.js";const r=52,Ot=16,l=16,Et=44;export function renderScatterPanel(j){const{title:ot="Test scatter",items:B,testDefs:_,colorScheme:it="default",onSaveImage:lt,groups:P}=j,{card:T,body:C,controlsRow:X}=Mt(ot,lt),R=_.filter(e=>e.testNumber!==void 0),b=!!(P&&P.length>0),Y=(P??[]).map(e=>e.key),at=new Map(Y.map((e,n)=>[e,n]));let m=j.xTestNumber??R[0]?.testNumber??null,f=j.yTestNumber??R[1]?.testNumber??m,O=null;const q=()=>O!==null?B[O]?[B[O]]:[]:B;function K(e,n,o){const i=document.createElement("label");Object.assign(i.style,{display:"inline-flex",alignItems:"center",gap:"4px",fontSize:"11px",color:M.label});const d=document.createElement("span");d.textContent=e;const p=kt(R,n,o,{maxWidth:"180px",emptyText:"No tests"});return i.append(d,p),{wrap:i,select:p}}const{wrap:st,select:rt}=K("X:",m,e=>{m=e,H()}),{wrap:ct,select:dt}=K("Y:",f,e=>{f=e,H()});X.append(st,ct),!b&&B.length>1&&X.appendChild(vt(B,O,e=>{O=e,H()}));const E=document.createElement("div");Object.assign(E.style,{color:M.warnText,background:M.warnBg,border:`1px solid ${M.warnBorder}`,borderRadius:"4px",padding:"4px 8px",fontSize:"11px",marginBottom:"4px",display:"none"}),T.insertBefore(E,C);function J(){const e=b?[]:St(q(),{facetableOnly:!0}).filter(n=>n.splittable).map(n=>n.label);e.length>0?(E.textContent=`\u26A0 Mixed ${e.join(", ")} across these points \u2014 trends here may be confounded (Simpson's paradox). Use Group by, or the Wafer picker, to narrow to a like-for-like set.`,E.style.display=""):E.style.display="none"}J();const D=document.createElement("div");D.textContent=b?"One point per die \xB7 coloured by group \xB7 click legend to filter":"One point per die across all wafers \xB7 coloured by hard bin \xB7 click legend to filter",Object.assign(D.style,{color:M.label,fontSize:"11px",marginBottom:"4px"}),T.insertBefore(D,C);const pt=ht(it),{forBin:mt}=pt,G=e=>b?e.group??"\u2014":String(e.hbin??0),Q=e=>b?gt(at.get(e)??0):mt(Number(e)),V=e=>b?e:e==="0"?"No bin data":`Bin ${e}`,g=new Set,A=document.createElement("div");Object.assign(A.style,{display:"flex",flexWrap:"wrap",gap:"4px",marginBottom:"6px"}),C.appendChild(A);const u=document.createElement("canvas");u.style.display="block",u.style.cursor="crosshair",C.appendChild(u);const F=window.devicePixelRatio||1,Ht=wt(T);let h=[],w=0,I=1,k=0,N=1;function $(e){const n=_.find(o=>o.testNumber===e);return{unit:n?.unit,limitLow:n?.limitLow,limitHigh:n?.limitHigh}}function xt(){const e=C.clientWidth,n=Math.max(200,Math.min(400,e*.65)),o=Lt(T,C,u,n);return{w:e,h:o,plotW:Math.max(10,e-r-Ot),plotH:Math.max(10,o-l-Et)}}function Z(){for(const e of A.querySelectorAll("[data-cat]")){const n=e.dataset.cat,o=g.size===0||g.has(n);e.style.opacity=o?"1":"0.35";const i=g.has(n);e.style.boxShadow=i?`0 0 0 2px ${M.text}`:"none",e.setAttribute("aria-pressed",i?"true":"false")}}function tt(e){A.innerHTML="",g.clear();for(const n of e){const o=document.createElement("button");o.type="button",o.dataset.cat=n,o.title=`${V(n)} \u2014 click to filter`;const i=Q(n);Object.assign(o.style,{display:"inline-flex",alignItems:"center",gap:"4px",padding:"2px 7px",borderRadius:"10px",border:`1px solid ${M.menuBorder}`,background:"none",cursor:"pointer",fontSize:"11px",color:M.text,whiteSpace:"nowrap"});const d=document.createElement("span");Object.assign(d.style,{display:"inline-block",width:"9px",height:"9px",borderRadius:"50%",background:i,flexShrink:"0"}),o.append(d,document.createTextNode(V(n))),o.addEventListener("click",()=>{g.has(n)?g.delete(n):g.add(n),Z(),z()}),A.appendChild(o)}Z()}function z(){Ct(u,A.offsetHeight);const e=Bt(T),n=I-w,o=N-k,{w:i,h:d,plotW:p,plotH:c}=xt();u.width=Math.max(1,Math.floor(i*F)),u.height=Math.max(1,Math.floor(d*F)),u.style.width=`${i}px`,u.style.height=`${d}px`;const t=u.getContext("2d");if(t.setTransform(F,0,0,F,0,0),t.clearRect(0,0,i,d),h.length===0){t.font="12px system-ui, sans-serif",t.fillStyle=e.textMuted,t.textAlign="center",t.textBaseline="middle",t.fillText("No data \u2014 select two parametric tests with values.",i/2,d/2);return}const y=4,v=nt(Math.max(Math.abs(w),Math.abs(I)),m!==null?$(m).unit:void 0),U=nt(Math.max(Math.abs(k),Math.abs(N)),f!==null?$(f).unit:void 0);t.font="10px system-ui, sans-serif",t.strokeStyle=e.border,t.lineWidth=.5,t.fillStyle=e.textMuted;for(let a=0;a<=y;a++){const S=w+n*a/y,s=r+a/y*p;t.beginPath(),t.moveTo(s,l),t.lineTo(s,l+c),t.stroke(),t.textAlign="center",t.textBaseline="top",t.fillText(v.tick(S),s,l+c+4);const L=k+o*a/y,x=l+(1-a/y)*c;t.beginPath(),t.moveTo(r,x),t.lineTo(r+p,x),t.stroke(),t.textAlign="right",t.textBaseline="middle",t.fillText(U.tick(L),r-4,x)}v.unitLabel&&At(t,v.unitLabel,r+p/2,l+c+24,e.textMuted),U.unitLabel&&(t.save(),t.font="10px system-ui, sans-serif",t.fillStyle=e.textMuted,t.textAlign="center",t.textBaseline="middle",t.translate(6,l+c/2),t.rotate(-Math.PI/2),t.fillText(`(${U.unitLabel})`,0,0),t.restore());const W=g.size===0?h:h.filter(a=>g.has(G(a))),et=W.length>5e3?Math.ceil(W.length/5e3):1;t.globalAlpha=Math.max(.15,Math.min(.7,200/(W.length/et)));for(let a=0;a<W.length;a+=et){const S=W[a],s=r+(S.x-w)/n*p,L=l+(1-(S.y-k)/o)*c;t.beginPath(),t.arc(s,L,2.5,0,Math.PI*2),t.fillStyle=Q(G(S)),t.fill()}if(t.globalAlpha=1,m!==null&&f!==null){const a=$(m),S=$(f);t.strokeStyle=e.textMuted,t.fillStyle=e.textMuted,t.lineWidth=1,t.setLineDash([4,3]),t.font="9px system-ui, sans-serif",t.textBaseline="top";for(const[s,L]of[[a.limitLow,"LSL"],[a.limitHigh,"USL"]]){if(s===void 0||s<w||s>I)continue;const x=r+(s-w)/n*p;t.beginPath(),t.moveTo(x,l),t.lineTo(x,l+c),t.stroke(),t.textAlign="left",t.fillText(L,x+2,l+2)}for(const[s,L]of[[S.limitLow,"LSL"],[S.limitHigh,"USL"]]){if(s===void 0||s<k||s>N)continue;const x=l+(1-(s-k)/o)*c;t.beginPath(),t.moveTo(r,x),t.lineTo(r+p,x),t.stroke(),t.textAlign="left",t.fillText(L,r+3,x+2)}t.setLineDash([])}t.strokeStyle=e.border,t.lineWidth=1,t.beginPath(),t.moveTo(r,l),t.lineTo(r,l+c),t.moveTo(r,l+c),t.lineTo(r+p,l+c),t.stroke()}function H(){if(J(),R.length<2||m===null||f===null){h=[],tt([]),z();return}h=b?yt(P,m,f):bt(q(),m,f);let e;if(b){const n=new Set(h.map(G));e=Y.filter(o=>n.has(o))}else e=Array.from(new Set(h.map(n=>n.hbin??0))).sort((n,o)=>n-o).map(String);if(tt(e),h.length>0){const n=h.map(v=>v.x),o=h.map(v=>v.y),i=Math.min(...n),d=Math.max(...n),p=Math.min(...o),c=Math.max(...o),t=(d-i)*.05||1,y=(c-p)*.05||1;w=i-t,I=d+t,k=p-y,N=c+y}z()}const ft=Tt(T,()=>z());H();function ut(e,n){m=e,f=n,rt.value=String(e),dt.value=String(n),H()}return{card:T,setXY:ut,destroy:()=>ft.disconnect()}}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Die } from '../core/dies.js';
|
|
2
|
+
import { type TestDef } from '../renderer/buildWaferMap.js';
|
|
3
|
+
import { type SaveTextHandler } from './toolbar.js';
|
|
4
|
+
export interface DieListOptions {
|
|
5
|
+
/** Heading text. Default: `Die list (N)`. */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Optional note shown under the heading, e.g. explaining why this replaced the map. */
|
|
8
|
+
note?: string;
|
|
9
|
+
/** CSV filename, including extension. Default: `'dies.csv'`. */
|
|
10
|
+
csvFilename?: string;
|
|
11
|
+
/** Host hook for "Export CSV" — see `saveTextFile` (toolbar.ts). */
|
|
12
|
+
onSaveText?: SaveTextHandler;
|
|
13
|
+
/**
|
|
14
|
+
* Extra leading column, e.g. a wafer-id label for a lot-level combined
|
|
15
|
+
* list spanning multiple wafers. `get(die)` returning `undefined` renders
|
|
16
|
+
* an empty cell for that row.
|
|
17
|
+
*/
|
|
18
|
+
extraColumn?: {
|
|
19
|
+
label: string;
|
|
20
|
+
get: (die: Die) => string | undefined;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Cap the scrolling table at this CSS length instead of letting it fill.
|
|
24
|
+
*
|
|
25
|
+
* Default (omitted) is **fill**: the table grows to consume whatever height
|
|
26
|
+
* its container gives it (`flex: 1; min-height: 0`), which is what every
|
|
27
|
+
* in-library caller wants — a card overlay, a footer panel and a resizable
|
|
28
|
+
* modal all have a definite height, and a fixed cap left a short table
|
|
29
|
+
* floating in a half-empty box. In a container with NO definite height the
|
|
30
|
+
* table simply renders at its natural full length and the host page
|
|
31
|
+
* scrolls; set this if that isn't wanted (e.g. mounting a 10k-row list
|
|
32
|
+
* directly into a long document).
|
|
33
|
+
*/
|
|
34
|
+
maxHeight?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build a scrollable die-list table + "Export CSV" button. Returns `null`
|
|
38
|
+
* when `dies` is empty (nothing to show).
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildDieListSection(dies: Die[], testDefs: TestDef[] | undefined, options?: DieListOptions): HTMLDivElement | null;
|
|
41
|
+
//# sourceMappingURL=dieList.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{hasPosition as S}from"../core/dies.js";import{isParametricTest as v}from"../renderer/buildWaferMap.js";import{CLR as r,saveTextFile as w}from"./toolbar.js";import{csvField as C}from"./summaryPanel.js";import{fmt as y}from"../renderer/fmt.js";function o(n,d,t){const l=document.createElement(n);return d&&Object.assign(l.style,d),t!==void 0&&(l.textContent=t),l}function B(n){return S(n)?`(${n.x}, ${n.y})`:"\u2014"}function $(n,d){if(d?.length)return d.filter(v);const t=new Map;for(const l of n)for(const a of Object.keys(l.testValues??{})){const s=Number(a);t.has(s)||t.set(s,{testNumber:s,name:`Test ${s}`})}return[...t.values()].sort((l,a)=>l.testNumber-a.testNumber)}export function buildDieListSection(n,d,t={}){if(!n.length)return null;const l=$(n,d),a=o("div",{display:"flex",flexDirection:"column",gap:"8px",flex:"1",minHeight:"0"}),s=o("div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});s.appendChild(o("div",{fontSize:"10px",fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:r.label},t.title??`Die list (${n.length})`));const f=o("button",{fontSize:"11px",padding:"3px 8px",borderRadius:"4px",border:`1px solid ${r.menuBorder}`,background:r.menuBg,color:r.text,cursor:"pointer"},"Export CSV");f.type="button",s.appendChild(f),a.appendChild(s),t.note&&a.appendChild(o("div",{fontSize:"11px",color:r.label,lineHeight:"1.4",flexShrink:"0"},t.note));const m=o("div",{overflow:"auto",flex:"1",minHeight:"0",border:`1px solid ${r.menuBorder}`,borderRadius:"4px",...t.maxHeight?{maxHeight:t.maxHeight}:{}}),c=o("table",{width:"100%",borderCollapse:"collapse",fontSize:"11px"});c.setAttribute("aria-label",t.title??`Die list (${n.length} dies)`);const u=[...t.extraColumn?[{label:t.extraColumn.label,get:e=>t.extraColumn.get(e)??""}]:[],{label:"Position",get:B},{label:"Site",get:e=>e.siteNum!==void 0?String(e.siteNum):""},{label:"Hard bin",get:e=>e.hbin!==void 0?String(e.hbin):""},{label:"Soft bin",get:e=>e.sbin!==void 0?String(e.sbin):""},...l.map(e=>({label:e.name,get:i=>{const p=i.testValues?.[e.testNumber];if(p!==void 0)return y(p,e.unit);const g=i.testPass?.[e.testNumber];return g===void 0?"":g?"PASS":"FAIL"}}))],b=o("thead"),x=o("tr");for(const e of u){const i=o("th",{textAlign:"left",padding:"4px 8px",position:"sticky",top:"0",background:r.panelBg,color:r.label,fontWeight:"600",borderBottom:`1px solid ${r.menuBorder}`,whiteSpace:"nowrap"},e.label);i.scope="col",x.appendChild(i)}b.appendChild(x),c.appendChild(b);const h=o("tbody");for(const e of n){const i=o("tr");for(const p of u)i.appendChild(o("td",{padding:"3px 8px",color:r.text,borderBottom:`1px solid ${r.menuBorder}`,whiteSpace:"nowrap"},p.get(e)));h.appendChild(i)}return c.appendChild(h),m.appendChild(c),a.appendChild(m),f.addEventListener("click",()=>{const e=[u.map(i=>C(i.label)).join(",")];for(const i of n)e.push(u.map(p=>C(p.get(i))).join(","));w(e.join(`
|
|
2
|
+
`),t.csvFilename??"dies.csv","text/csv",t.onSaveText)}),a}
|
|
@@ -5,6 +5,8 @@ export type { RenderOptions, WaferMapController, WaferViewOptions, WaferPreferen
|
|
|
5
5
|
export { renderWaferGallery } from './renderWaferGallery.js';
|
|
6
6
|
export type { WaferMapDisplayItem, WaferMapDisplayItemFactory, GalleryOptions, GalleryController } from './renderWaferGallery.js';
|
|
7
7
|
export type { SummaryPanelOptions } from './summaryPanel.js';
|
|
8
|
+
export { buildDieListSection } from './dieList.js';
|
|
9
|
+
export type { DieListOptions } from './dieList.js';
|
|
8
10
|
export { collectWarnings, severityOf } from './warnings.js';
|
|
9
11
|
export type { WarningsOptions } from './warnings.js';
|
|
10
12
|
export type { InsightsOptions, InsightsView } from './insightsTab.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{toCanvas}from"./toCanvas.js";export{renderWaferMap}from"./renderWaferMap.js";export{renderWaferGallery}from"./renderWaferGallery.js";export{collectWarnings,severityOf}from"./warnings.js";export{setDetachWindowOpener}from"./toolbar.js";
|
|
1
|
+
export{toCanvas}from"./toCanvas.js";export{renderWaferMap}from"./renderWaferMap.js";export{renderWaferGallery}from"./renderWaferGallery.js";export{buildDieListSection}from"./dieList.js";export{collectWarnings,severityOf}from"./warnings.js";export{setDetachWindowOpener}from"./toolbar.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Die } from '../core/dies.js';
|
|
2
|
+
import type { TestDef, BinDef } from '../renderer/buildWaferMap.js';
|
|
3
|
+
import type { PlotMode } from '../renderer/buildView.js';
|
|
4
|
+
export interface MaplessSummaryOptions {
|
|
5
|
+
plotMode: PlotMode;
|
|
6
|
+
activeTest?: number;
|
|
7
|
+
hbinDefs?: BinDef[];
|
|
8
|
+
sbinDefs?: BinDef[];
|
|
9
|
+
colorScheme?: string;
|
|
10
|
+
/** Effective log-scale setting for the active test — same resolution the
|
|
11
|
+
* map itself uses (explicit viewOpts.logScale, falling back to the
|
|
12
|
+
* TestDef's own default). */
|
|
13
|
+
logScale?: boolean;
|
|
14
|
+
/** 'spec' colours the full spec-limit window (like the map's colorbar
|
|
15
|
+
* range toggle); 'data' colours just the observed value range. Default 'spec'. */
|
|
16
|
+
colorbarRangeMode?: 'data' | 'spec';
|
|
17
|
+
/**
|
|
18
|
+
* Full wafer/lot die population for this test (positioned + unpositioned),
|
|
19
|
+
* used only to compute the value→colour range — NOT what gets bucketed
|
|
20
|
+
* into bars, which always stays scoped to `dies` (the caller's own
|
|
21
|
+
* argument, below). Falls back to `dies` when omitted. Passing the full
|
|
22
|
+
* population (e.g. a mixed wafer's footer passing every die, not just the
|
|
23
|
+
* unpositioned subset it's charting) keeps bar colours consistent with
|
|
24
|
+
* whatever range the wafer's own visible map is using for the same test.
|
|
25
|
+
*/
|
|
26
|
+
valueRangeDies?: Die[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compact, plot-mode-aware summary for dies with no position data. Always
|
|
30
|
+
* returns a mountable element — an unhandled mode/empty data still renders a
|
|
31
|
+
* one-line message rather than leaving the caller with nothing to show.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildMaplessSummary(dies: Die[], testDefs: TestDef[] | undefined, options: MaplessSummaryOptions): HTMLElement;
|
|
34
|
+
//# sourceMappingURL=maplessSummary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getDieTestValue as R}from"../renderer/buildWaferMap.js";import{resolveTestNumber as T,findTestDef as $}from"../renderer/buildView.js";import{buildBinSection as y}from"./summaryPanel.js";import{buildTestHistogramData as B}from"../stats/histogram.js";import{getColorScheme as z}from"../renderer/colorSchemes.js";import{CLR as u,getTooltip as E,positionTooltip as H,hideTooltip as V}from"./toolbar.js";import{fmt as b}from"../renderer/fmt.js";function c(i,n,e){const t=document.createElement(i);return n&&Object.assign(t.style,n),e!==void 0&&(t.textContent=e),t}function F(i,n){i.tabIndex=0,i.setAttribute("role","img"),i.setAttribute("aria-label",n);const e=m=>{const l=E(i.ownerDocument);if(l.textContent=n,l.style.display="block",m)H(l,i,m.clientX,m.clientY);else{const r=i.getBoundingClientRect();H(l,i,r.left+r.width/2,r.top)}},t=()=>V(i.ownerDocument);i.addEventListener("mousemove",e),i.addEventListener("mouseleave",t),i.addEventListener("focus",()=>e()),i.addEventListener("blur",t)}function I(i,n,e,t,m){let l=1/0,r=-1/0;for(const p of i){const a=R(p,n);a!==void 0&&(a<l&&(l=a),a>r&&(r=a))}const f=t==="spec"&&e&&(e.limitLow!==void 0||e.limitHigh!==void 0);let s,d;f?(s=e.limitLow!==void 0?e.limitLow:isFinite(l)?l:0,d=e.limitHigh!==void 0?e.limitHigh:isFinite(r)?r:1):(s=isFinite(l)?l:0,d=isFinite(r)?r:1),s===d&&(d=s+1);const x=p=>Math.min(1,Math.max(0,p));if(m&&s>0&&d>0){const p=Math.log10(s),a=Math.log10(d)-p;return h=>h<=0?0:x((Math.log10(h)-p)/a)}return p=>x((p-s)/(d-s))}function N(i,n,e,t,m,l,r){const f=c("div",{display:"flex",flexDirection:"column",gap:"2px",minWidth:"0",flex:"1",minHeight:"0"});f.appendChild(c("div",{fontSize:"10px",fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:u.label},n?.name?`${n.name} distribution`:"Value distribution"));const s=i[0].rangeLow,d=i[i.length-1].rangeHigh,x=d-s||1,p=o=>Math.min(1,Math.max(0,(o-s)/x)),a=z(t),h=I(m,e,n,l,r),S=Math.max(1,...i.map(o=>o.count)),L=c("div",{position:"relative",display:"flex",alignItems:"flex-end",gap:"2px",flex:"1",minHeight:"48px"});for(const o of i){const g=c("div",{flex:"1",minWidth:"2px",background:a.forValue(h((o.rangeLow+o.rangeHigh)/2)),height:`${o.count/S*100}%`,borderRadius:"1px 1px 0 0"});g.dataset.wmapBar="1",F(g,`${b(o.rangeLow,n?.unit)} \u2013 ${b(o.rangeHigh,n?.unit)}: ${o.count}`),L.appendChild(g)}const M=[];for(const[o,g]of[[n?.limitLow,"LSL"],[n?.limitHigh,"USL"]]){if(o===void 0||o<s||o>d)continue;const v=p(o)*100,w=c("div",{position:"absolute",top:"0",bottom:"0",left:`${v}%`,width:"2px",marginLeft:"-1px",background:u.value,boxShadow:`0 0 0 1px ${u.panelBg}`,pointerEvents:"none"});w.dataset.wmapLimitMarker="1",L.appendChild(w),M.push({pct:v,text:`${g} ${b(o,n?.unit)}`})}if(f.appendChild(L),M.length){const o=c("div",{position:"relative",height:"14px"});for(const{pct:g,text:v}of M){const w=Math.min(96,Math.max(4,g));o.appendChild(c("span",{position:"absolute",left:`${w}%`,top:"0",transform:"translateX(-50%)",whiteSpace:"nowrap",fontSize:"9px",fontWeight:"600",color:u.text,background:u.panelBg,border:`1px solid ${u.value}`,borderRadius:"2px",padding:"0 3px",lineHeight:"12px"},v))}f.appendChild(o)}const C=c("div",{display:"flex",justifyContent:"space-between",fontSize:"10px",color:u.label});return C.appendChild(c("span",{},b(i[0].rangeLow,n?.unit))),C.appendChild(c("span",{},b(i[i.length-1].rangeHigh,n?.unit))),f.appendChild(C),f}export function buildMaplessSummary(i,n,e){const{plotMode:t,activeTest:m,hbinDefs:l,sbinDefs:r,colorScheme:f,logScale:s=!1,colorbarRangeMode:d="spec",valueRangeDies:x}=e;if(t==="hardBin"||t==="softBin"){const a=y(i,t==="hardBin"?l:r,t==="hardBin"?"hard":"soft",f);if(a)return a}else if(t==="value"){const{testNumber:p}=T(m??0,n),a=$(n,m??0),h=B([{dies:i}],p,12,a?.limitLow,a?.limitHigh);if(h.length)return N(h,a,p,f,x??i,d,s)}return c("div",{padding:"12px",fontSize:"11px",color:u.label,textAlign:"center"},"No summary available for this view.")}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type SaveImageHandler, type SaveTextHandler, type UserGuideExtension } from './toolbar.js';
|
|
2
2
|
import type { Die } from '../core/dies.js';
|
|
3
3
|
import type { WaferViewOptions } from './renderWaferMap.js';
|
|
4
|
+
import type { RenderableWaferMap } from './renderWaferMap.js';
|
|
4
5
|
import type { LotStatsSummary } from '../stats/types.js';
|
|
5
6
|
import { type WarningsOptions } from './warnings.js';
|
|
6
7
|
import type { SummaryPanelOptions } from './summaryPanel.js';
|
|
@@ -18,24 +19,7 @@ import { type InsightsOptions } from './insightsTab.js';
|
|
|
18
19
|
* ]);
|
|
19
20
|
* ```
|
|
20
21
|
*/
|
|
21
|
-
export interface WaferMapDisplayItem {
|
|
22
|
-
wafer: import('../core/wafer.js').Wafer;
|
|
23
|
-
dies: Die[];
|
|
24
|
-
hbinDefs?: import('../renderer/buildWaferMap.js').BinDef[];
|
|
25
|
-
sbinDefs?: import('../renderer/buildWaferMap.js').BinDef[];
|
|
26
|
-
testDefs?: import('../renderer/buildWaferMap.js').TestDef[];
|
|
27
|
-
metadataFields?: import('../renderer/buildWaferMap.js').MetadataFieldDef[];
|
|
28
|
-
reticles?: import('../core/reticle.js').Reticle[];
|
|
29
|
-
/**
|
|
30
|
-
* Geometry advisories carried from `buildWaferMap`. Present automatically when
|
|
31
|
-
* an item is spread from a `WaferMapResult` (the usual `{ ...result, label }`
|
|
32
|
-
* shape); the gallery collects them across cards and surfaces them once in the
|
|
33
|
-
* lot bar's warning indicator.
|
|
34
|
-
*/
|
|
35
|
-
warnings?: import('../renderer/buildWaferMap.js').WaferWarning[];
|
|
36
|
-
isLotStack?: boolean;
|
|
37
|
-
aggrMethod?: string;
|
|
38
|
-
lotSize?: number;
|
|
22
|
+
export interface WaferMapDisplayItem extends RenderableWaferMap {
|
|
39
23
|
label?: string;
|
|
40
24
|
/** Merged on top of the shared gallery options for this card only. */
|
|
41
25
|
viewOptions?: Partial<WaferViewOptions>;
|