@wafertools/wafermap 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +1 -1
  3. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +1 -0
  4. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  5. package/dist/packages/canvas-adapter/dieList.d.ts +83 -17
  6. package/dist/packages/canvas-adapter/dieList.js +13 -2
  7. package/dist/packages/canvas-adapter/index.d.ts +1 -1
  8. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  9. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +10 -0
  10. package/dist/packages/canvas-adapter/renderWaferGallery.js +2 -2
  11. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +13 -0
  12. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  13. package/dist/packages/canvas-adapter/summaryPanel.d.ts +36 -3
  14. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  15. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  16. package/dist/packages/canvas-adapter/userGuideHtml.js +1 -1
  17. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  18. package/dist/packages/canvas-adapter/version.js +1 -1
  19. package/dist/packages/core/inference/pitch.js +1 -1
  20. package/dist/packages/core/metadata.d.ts +29 -0
  21. package/dist/packages/core/metadata.js +1 -1
  22. package/dist/packages/renderer/buildView.d.ts +8 -0
  23. package/dist/packages/renderer/buildView.js +1 -1
  24. package/dist/packages/stats/facets.js +1 -1
  25. package/dist/packages/stats/index.d.ts +2 -0
  26. package/dist/packages/stats/index.js +1 -1
  27. package/dist/packages/stats/metadataColumns.d.ts +70 -0
  28. package/dist/packages/stats/metadataColumns.js +1 -0
  29. package/package.json +11 -6
package/CHANGELOG.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to `@wafertools/wafermap` are documented here.
3
+ All notable changes to `@wafertools/wafermap` are documented here. For a curated,
4
+ plain-language summary of what's actually changed for users and adopters, see
5
+ [What's New](https://wafertools.github.io/whats-new/) instead — this file is the complete
6
+ technical record, including internal changes.
4
7
 
5
8
  ## Versioning policy
6
9
 
@@ -19,6 +22,111 @@ under `### Breaking`.
19
22
 
20
23
  ---
21
24
 
25
+ ## [0.24.0] — 2026-08-24
26
+
27
+ ### Breaking
28
+
29
+ - **`buildDieListSection`/`RenderOptions.dieList` now caps rendered rows at `maxRows` (default
30
+ `50_000`)**, where the table previously rendered every die unconditionally. This table has
31
+ no virtualisation, so an uncapped multi-hundred-thousand-die lot was already a genuinely
32
+ heavy DOM build (~1.3M elements at 266k dies) before this release added metadata columns —
33
+ without the cap, adding columns would have made that worst case materially worse rather than
34
+ strictly better. The **CSV export is never capped**; it always contains every die, and a
35
+ footer states the truncation explicitly whenever it applies (`"Showing the first 50,000 of
36
+ 266,412 dies. The CSV export contains all 266,412."`). A host that needs every row on screen
37
+ regardless of scale can set `maxRows` to a larger value or `Infinity`.
38
+
39
+ ### Added
40
+
41
+ - **Metadata now reaches the die-list table and its CSV export**, and the two per-test CSVs
42
+ (`test-values.csv`, `functional-tests.csv`) gain wafer identity. Previously no export surface
43
+ in the library emitted `die.metadata` at all — only the hover tooltip and the `'metadata'`
44
+ plot mode did — and none of the three CSVs emitted wafer metadata either, despite the HTML
45
+ reports already doing so.
46
+ - **Die metadata** (`DieMetadata`) is on by default in `buildDieListSection`
47
+ (`DieListOptions.metadataColumns`, default `'auto'`) — every key found on any die,
48
+ deterministically ordered (`metadataFields` declaration order first, then natural sort).
49
+ Unlike `metadataFields` gating the `'metadata'` plot mode (a legend has a cardinality
50
+ limit), a table column has none, so auto-discovery here does not conflict with that rule —
51
+ it follows the same intent. Pass an explicit `string[]` to pin the set, or `'none'` to omit.
52
+ - **Wafer metadata** (`WaferMetadata`) is CSV-only by default
53
+ (`DieListOptions.waferMetadataColumns`, default `'csv'`) — constant down every row, so it's
54
+ noise on screen next to the always-visible metadata badge, but it's what makes a detached
55
+ CSV self-describing enough to concatenate several exports and still know which wafer each
56
+ row came from. Set `'both'` to also show it in the table, or `'none'` to omit it entirely.
57
+ - A key present on both a wafer and a die produces exactly **one** column, scope `die`,
58
+ carrying the die's value — the same shadowing rule the hover tooltip already applied.
59
+ - Column labels resolve `metadataFields[].label` → `prettyKey(key)` → the raw key, matching
60
+ every other column's human-readable header. A key colliding with a built-in column name
61
+ (e.g. a metadata key literally called `Site`) is never dropped — it becomes
62
+ `"Site (metadata)"`, with further deterministic fallbacks for the rare case that also
63
+ collides.
64
+ - `RenderOptions.dieList?: DieListDisplayOptions` reaches the built-in die-list view (the
65
+ coordinate-less map replacement, and the "+N dies without position" footer), which
66
+ previously had no host hook at all. The wafer metadata and `metadataFields` it needs are
67
+ always supplied by the library from the current build result, never from this option, so a
68
+ host mistake can't substitute the wrong identity data into an export.
69
+ - `buildTestSection`/`buildFunctionalTestSection`/`buildLotTestSection`/
70
+ `buildLotFunctionalSection` (internal to the Summary/Insights panels) gained a trailing
71
+ `CsvExportContext` — the two lot variants self-derive it from the `perWaferSummaries` they
72
+ already receive, so `renderWaferGallery`'s Insights tab and the lot Summary panel needed no
73
+ call-site changes. A mixed lot (no metadata field common to every wafer) emits no false
74
+ identity column at all, rather than guessing.
75
+ - **"View die list" — every die as a table, reachable from the Summary panel**, on both
76
+ `renderWaferMap` (this wafer's own dies) and `renderWaferGallery` (every wafer in the lot,
77
+ pooled, with a leading `Wafer` column). Deliberately **not** a new toolbar button — reached
78
+ as a link inside the already-open Summary panel, the same way "Summary report" opens the
79
+ HTML report without one either, since the toolbar already carries eleven buttons and this
80
+ reuses an existing entry point rather than adding a twelfth. **On by default** whenever a
81
+ Summary panel is reachable at all (`RenderOptions.dieList`/`GalleryOptions.dieList`, set
82
+ `{ enabled: false }` to hide it); opens wmap's own resizable modal, mounting
83
+ `buildDieListSection` with the same metadata columns and CSV export the coordinate-less
84
+ die-list view already has. The lot-pooled CSV's wafer-metadata columns use `commonMetadata`,
85
+ so a mixed lot emits no false shared identity, same as the pooled per-test CSVs above.
86
+ - **`prettyKey`-labelled hover tooltip metadata.** `buildHoverText` now resolves a metadata
87
+ key's label the same way the new table/CSV columns do (`metadataFields[].label` →
88
+ `prettyKey(key)`), instead of printing the raw key. Brings the tooltip in line with every
89
+ other metadata surface in the library rather than shipping the inconsistency for even one
90
+ release. `HoverTextOptions` gained `metadataFields?: MetadataFieldDef[]`.
91
+ - New `@wafertools/wafermap/core` exports: `metadataDisplayValue(raw)` / `metadataCategoricalValue(raw)` —
92
+ the single stringifier for a metadata value (display/export vs. categorical/colour-swatch
93
+ use), replacing four near-identical inline implementations that had drifted (`buildView.ts`'s
94
+ tooltip and `'metadata'`-mode read path, `summaryPanel.ts`'s `metadataEntries`, `facets.ts`'s
95
+ `facetValueOf`). A `String(v)`-on-an-object bug in two of those four collapsed distinct object
96
+ values into the indistinguishable `"[object Object]"`; all four now emit `JSON.stringify(v)`
97
+ instead, so genuinely different values facet and export as genuinely different values.
98
+ - New `@wafertools/wafermap/stats` exports: `resolveMetadataColumns(options)` and
99
+ `discoverDieMetadataKeys(dies, metadataFields?, limit?)` — the column resolver backing the
100
+ above, public because a host building its own table/export over `die.metadata` shouldn't have
101
+ to reimplement discovery, ordering, shadowing, and collision resolution from scratch.
102
+
103
+ ## [0.23.1] — 2026-08-18
104
+
105
+ ### Added
106
+
107
+ - **The Insights tab's chart cards, section grid, sub-tab bar, Group-by select, and toggle button now carry
108
+ stable `data-wmap-*` hooks**, for tooling (screenshot capture, e2e tests, visual regression, accessibility
109
+ audits) that needs to drive the tab from outside wmap's own code. Previously these nodes were bare —
110
+ distinguishable only by exact heading text or button `textContent`, which breaks silently on any copy
111
+ change. `cardShell()` sets `data-wmap-chart-card`/`data-wmap-chart-title` (the full title, so
112
+ e.g. "Yield by wafer" and a hypothetical "Yield by wafer (grouped)" stay unambiguous); `makeChartGridWrap()`
113
+ sets `data-wmap-chart-grid`; `makeLabeledSelect()` gained an `opts.hook` param (`select.dataset.wmapSelect`)
114
+ since the same factory also builds every per-panel "Group:" restrict dropdown and the histogram wafer
115
+ picker; `insightsTab.ts`'s sub-tab buttons set `data-wmap-insights-tab` to the view key
116
+ (`overview`/`distributions`/`correlation`) and the back button sets `data-wmap-insights-back`; the Insights
117
+ toggle button in both `renderWaferMap` and `renderWaferGallery` sets `data-wmap-insights-btn` — needed
118
+ because its `aria-label` itself toggles between `'Insights'` and `'Back to wafer/gallery view'`, so
119
+ `button[aria-label="Insights"]` only ever matched while closed.
120
+
121
+ ### Fixed
122
+
123
+ - **Grid pitch inference no longer stretches the die aspect ratio when only one axis has real spread.** The
124
+ circular-wafer constraint used to fill in an unknown aspect ratio whenever nearest-neighbour spacing looked
125
+ square, even when one axis had only a single distinct position observed. A handful of positioned dies
126
+ confined to one row (or column) isn't evidence the wafer is wider than it is tall — it just reflects which
127
+ subset of dies happen to carry positions — so the constraint is now skipped on a degenerate axis (dropping
128
+ confidence to 0.3), leaving the aspect ratio at the safer 1:1 default instead of guessing.
129
+
22
130
  ## [0.23.0] — 2026-08-16
23
131
 
24
132
  ### Breaking
package/README.md CHANGED
@@ -74,7 +74,7 @@ and stats layers.
74
74
 
75
75
  ## Built with wafermap
76
76
 
77
- - **[tsmap](https://github.com/wafertools/tsmap)** — cross-platform desktop app for loading STDF, ATDF, CSV, and JSON wafer data
77
+ - **[tsmap](https://github.com/wafertools/tsmap)** — desktop and browser app for loading STDF, ATDF, CSV, JSON, and Parquet wafer data
78
78
 
79
79
  ## Local preview
80
80
 
@@ -141,6 +141,7 @@ export declare function makeLabeledSelect(labelText: string, options: readonly {
141
141
  label: string;
142
142
  }[], selected: string, onChange: (value: string) => void, opts?: {
143
143
  maxWidth?: string;
144
+ hook?: string;
144
145
  }): HTMLLabelElement;
145
146
  export declare function makeChartGridWrap(): HTMLDivElement;
146
147
  /** Small positioned hover tooltip, matching the pattern each ported panel builds for itself. */
@@ -1 +1 @@
1
- import{CLR as a,Z_BASE as T,MENU_SEARCH_THRESHOLD as N,makeMenuSearchBox as M,markMenuTrigger as E,saveImageBlob as D,openReparentedModal as I}from"../toolbar.js";import{ICONS as L}from"../icons.js";import{fmt as z,fmtColorbarAxis as W}from"../../renderer/fmt.js";export function openChartExpandModal(e,n,r){const t=e.getAttribute("style")??"";if(Object.assign(e.style,{flex:"1",minHeight:"0",border:"none",borderRadius:"0"}),!I([e],{title:n,onClosed:()=>{e.setAttribute("style",t),r&&(r.style.display="flex")}})){e.setAttribute("style",t);return}r&&(r.style.display="none")}const F={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040"},O={text:"text",textMuted:"text-muted",border:"border",bg:"surface",bgHover:"bg-hover",track:"panel-bg",warnBorder:"warn-border"};export function resolveChartCanvasColors(e){const n=getComputedStyle(e),r={};for(const t of Object.keys(O)){const i=n.getPropertyValue(`--wmap-${O[t]}`).trim();r[t]=i||F[t]}return r}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,n){const r=new ResizeObserver(n);return r.observe(e),{disconnect:()=>r.disconnect()}}export function growCardToFitContent(e,n,r){const t=e.offsetHeight-n.clientHeight;e.style.minHeight=`${t+r}px`}export function chartFillHeight(e,n,r,t){let i=0;for(const s of Array.from(n.children))s!==r&&(i+=s.offsetHeight);return growCardToFitContent(e,n,i+t),Math.max(t,n.clientHeight-i)}export function applyCanvasFlow(e,n=0){e.style.position="absolute",e.style.left="0",e.style.right="0",e.style.top=`${n}px`,e.style.bottom="0"}export function formatValue(e){return Number.isFinite(e)?Number.isInteger(e)?`${e}`:z(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,n){if(!n)return{tick:formatValue,unitLabel:""};const{tickFmt:r,axisLabel:t}=W(e,null,n);return{tick:r,unitLabel:t}}export function drawAxisUnit(e,n,r,t,i){const s={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font="10px system-ui, sans-serif",e.textAlign="left",e.textBaseline="middle",e.fillStyle=i,e.fillText(`(${n})`,r,t),Object.assign(e,s)}export function saveCanvasPng(e,n,r){const t=document.createElement("canvas");t.width=e.width,t.height=e.height;const i=t.getContext("2d");i.fillStyle="#ffffff",i.fillRect(0,0,t.width,t.height),i.drawImage(e,0,0),t.toBlob(s=>{s&&D(s,n,r)},"image/png")}export function cardShell(e,n){const r=document.createElement("div");Object.assign(r.style,{display:"flex",flexDirection:"column",background:a.menuBg,border:`1px solid ${a.menuBorder}`,borderRadius:"6px",padding:"12px",minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const t=document.createElement("div");Object.assign(t.style,{display:"flex",alignItems:"center",gap:"6px",marginBottom:"6px"});const i=document.createElement("div");i.textContent=e,Object.assign(i.style,{color:a.value,fontSize:"13px",fontWeight:"600",flex:"1"}),t.appendChild(i);const s=document.createElement("button");s.type="button",s.title="Save as PNG",s.setAttribute("aria-label","Save as PNG"),s.innerHTML=L.download,Object.assign(s.style,{border:`1px solid ${a.menuBorder}`,borderRadius:"4px",background:"none",color:a.label,cursor:"pointer",width:"22px",height:"22px",lineHeight:"1",flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),s.addEventListener("click",()=>{const p=r.querySelector("canvas");p&&saveCanvasPng(p,e,n)}),t.appendChild(s);const o=document.createElement("button");o.type="button",o.title="Expand",o.setAttribute("aria-label","Expand"),o.innerHTML=L.expand,Object.assign(o.style,{border:`1px solid ${a.menuBorder}`,borderRadius:"4px",background:"none",color:a.label,cursor:"pointer",width:"22px",height:"22px",lineHeight:"1",flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),o.addEventListener("click",()=>openChartExpandModal(r,i.textContent??e,o)),t.appendChild(o),r.appendChild(t);const c=document.createElement("div");Object.assign(c.style,{display:"flex",gap:"6px",marginBottom:"6px",flexWrap:"wrap",alignItems:"center"}),r.appendChild(c);const d=document.createElement("div");return Object.assign(d.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),r.appendChild(d),{card:r,heading:i,controlsRow:c,body:d}}export function makeSegmented(e,n,r){const t=document.createElement("div");t.setAttribute("role","radiogroup"),Object.assign(t.style,{display:"inline-flex",border:`1px solid ${a.menuBorder}`,borderRadius:"4px",overflow:"hidden"});const i=`seg-${Math.random().toString(36).slice(2,9)}`,s=[];return e.forEach(([o,c],d)=>{const p=document.createElement("label");Object.assign(p.style,{display:"inline-flex",alignItems:"center",fontSize:"12px",padding:"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:d>0?`1px solid ${a.menuBorder}`:"none"});const l=document.createElement("input");l.type="radio",l.name=i,l.value=o,l.checked=o===n,Object.assign(l.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const g=()=>{p.style.background=l.checked?a.bgActive:a.menuBg,p.style.color=l.checked?a.iconActive:a.text};s.push(g),g(),l.addEventListener("change",()=>{if(l.checked){for(const h of s)h();r(o)}}),p.append(l,document.createTextNode(c)),t.appendChild(p)}),t}export function makeBackButton(e){const n=document.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{fontSize:"12px",padding:"3px 10px",border:`1px solid ${a.menuBorder}`,borderRadius:"4px",background:a.menuBg,color:a.label,cursor:"pointer"}),n.addEventListener("click",e),n}export function renderEmptyState(e,n,r){const t=document.createElement("div");t.textContent=n,Object.assign(t.style,{color:a.label,fontSize:"12px",padding:"8px 0"}),r&&Object.assign(t.style,r),e.appendChild(t)}export function makeTestSelect(e,n,r,t={}){const{maxWidth:i="200px",emptyText:s="No parametric tests"}=t;if(e.length>N)return P(e,n,r,i);const o=document.createElement("select");if(Object.assign(o.style,{fontSize:"12px",padding:"2px 6px",background:a.menuBg,color:a.text,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",maxWidth:i}),e.length===0){o.disabled=!0;const c=document.createElement("option");c.textContent=s,o.appendChild(c)}else{for(const c of e){const d=document.createElement("option");d.value=String(c.testNumber),d.textContent=c.name||`Test ${c.testNumber}`,c.testNumber===n&&(d.selected=!0),o.appendChild(d)}o.addEventListener("change",()=>r(Number(o.value)))}return o}function P(e,n,r,t){const i=f=>e.find(y=>y.testNumber===f)?.name||`Test ${f}`;let s=n;const o=document.createElement("button");o.type="button",Object.assign(o.style,{fontSize:"12px",padding:"2px 6px",background:a.menuBg,color:a.text,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",maxWidth:t,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:"6px",justifyContent:"space-between"}),E(o,!1);const c=document.createElement("span");Object.assign(c.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const d=document.createElement("span");d.textContent="\u25BE",d.style.flex="0 0 auto",o.append(c,d);const p=()=>{c.textContent=s!==null?i(s):"(none)"};p();let l=null;const g=()=>{l&&(l.remove(),l=null,E(o,!1))},h=()=>{g(),o.focus()};function j(){const f=o.getBoundingClientRect(),y=o.ownerDocument.defaultView??window,C=Math.max(f.width,220),R=320,$=Math.min(f.left,Math.max(4,(y.innerWidth??1/0)-C-4));l=document.createElement("div"),Object.assign(l.style,{position:"fixed",top:`${f.bottom+4}px`,left:`${$}px`,background:a.menuBg,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:T,minWidth:`${C}px`,maxHeight:`${R}px`,overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const v=[],S=()=>v.filter(u=>u.row.style.display!=="none").map(u=>u.row),k=M(u=>{for(const m of v)m.row.style.display=m.label.includes(u)?"":"none"},"Filter tests\u2026");Object.assign(k.style,{position:"sticky",top:"0",zIndex:"1",background:a.menuBg}),k.addEventListener("keydown",u=>{if(u.key==="Escape"){h();return}u.key==="ArrowDown"&&(u.preventDefault(),S()[0]?.focus())}),l.appendChild(k);for(const u of e){const m=u.name||`Test ${u.testNumber}`,x=u.testNumber===s,b=document.createElement("div");b.textContent=m,b.setAttribute("role","menuitemradio"),b.setAttribute("aria-checked",x?"true":"false"),b.tabIndex=-1,Object.assign(b.style,{padding:"6px 14px",fontSize:"12px",cursor:"pointer",color:x?a.iconActive:a.text,fontWeight:x?"700":"400",background:x?a.menuActive:"transparent",whiteSpace:"nowrap",outline:"none"});const B=()=>{x||(b.style.background=a.menuHover)},A=()=>{b.style.background=x?a.menuActive:"transparent"};b.addEventListener("mouseenter",B),b.addEventListener("mouseleave",A),b.addEventListener("focus",B),b.addEventListener("blur",A),b.addEventListener("click",H=>{H.stopPropagation(),s=u.testNumber,p(),h(),r(u.testNumber)}),l.appendChild(b),v.push({row:b,label:m.toLowerCase()})}l.addEventListener("keydown",u=>{const m=S();if(m.length===0)return;const x=m.indexOf(document.activeElement);switch(u.key){case"ArrowDown":u.preventDefault(),m[x<0||x===m.length-1?0:x+1].focus();break;case"ArrowUp":u.preventDefault(),m[x<=0?m.length-1:x-1].focus();break;case"Home":u.preventDefault(),m[0].focus();break;case"End":u.preventDefault(),m[m.length-1].focus();break;case"Enter":case" ":x>=0&&(u.preventDefault(),m[x].click());break;case"Escape":case"Tab":u.preventDefault(),h();break}}),l.setAttribute("role","menu"),l.setAttribute("aria-label","Test"),o.ownerDocument.body.appendChild(l),E(o,!0)}o.addEventListener("click",f=>{if(f.stopPropagation(),l){g();return}j()});const w=f=>{if(!o.isConnected){o.ownerDocument.removeEventListener("click",w);return}l&&!l.contains(f.target)&&!o.contains(f.target)&&g()};return o.ownerDocument.addEventListener("click",w),Object.defineProperty(o,"value",{get:()=>s!==null?String(s):"",set:f=>{s=f===""?null:Number(f),p()}})}export function makeWaferSelect(e,n,r,t={}){const{maxWidth:i="160px",allLabel:s="All wafers"}=t,o="\0all",c=document.createElement("select");Object.assign(c.style,{fontSize:"12px",padding:"2px 6px",background:a.menuBg,color:a.text,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",maxWidth:i});const d=document.createElement("option");return d.value=o,d.textContent=s,n===null&&(d.selected=!0),c.appendChild(d),e.forEach((p,l)=>{const g=document.createElement("option");g.value=String(l),g.textContent=p.label??`#${l}`,n===l&&(g.selected=!0),c.appendChild(g)}),c.addEventListener("change",()=>r(c.value===o?null:Number(c.value))),c}export function makeToggle(e,n,r){const t=document.createElement("label");Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:"4px",fontSize:"11px",color:a.label,cursor:"pointer",userSelect:"none"});const i=document.createElement("input");return i.type="checkbox",i.checked=n,i.style.cssText="margin:0;cursor:pointer;",i.addEventListener("change",()=>r(i.checked)),t.append(i,document.createTextNode(e)),t}export function makeLabeledSelect(e,n,r,t,i={}){const{maxWidth:s="160px"}=i,o=document.createElement("label");o.textContent=e,Object.assign(o.style,{color:a.label,fontSize:"12px",display:"flex",alignItems:"center",gap:"4px"});const c=document.createElement("select");Object.assign(c.style,{fontSize:"12px",padding:"2px 6px",background:a.menuBg,color:a.text,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",maxWidth:s});for(const d of n){const p=document.createElement("option");p.value=d.value,p.textContent=d.label,d.value===r&&(p.selected=!0),c.appendChild(p)}return c.addEventListener("change",()=>t(c.value)),o.appendChild(c),o}export function makeChartGridWrap(){const e=document.createElement("div");return Object.assign(e.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:"10px",flex:"0 0 auto"}),e}export function makeTooltip(e){const n=document.createElement("div");return Object.assign(n.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:a.menuBg,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",padding:"4px 8px",fontSize:"11px",fontFamily:"system-ui, sans-serif",color:a.text,whiteSpace:"nowrap",boxShadow:"0 2px 8px rgba(0,0,0,0.3)"}),e.appendChild(n),n}export function positionChartTooltip(e,n,r,t){const i=n.getBoundingClientRect(),s=14,o=4;e.style.left="0",e.style.top="0";const c=e.offsetWidth,d=e.offsetHeight;let p=r-i.left+s,l=t-i.top+s;p+c+o>i.width&&(p=r-i.left-s-c),l+d+o>i.height&&(l=t-i.top-s-d),p=Math.max(o,p),l=Math.max(o,l),e.style.left=`${p}px`,e.style.top=`${l}px`}
1
+ import{CLR as a,Z_BASE as T,MENU_SEARCH_THRESHOLD as N,makeMenuSearchBox as M,markMenuTrigger as E,saveImageBlob as D,openReparentedModal as I}from"../toolbar.js";import{ICONS as L}from"../icons.js";import{fmt as z,fmtColorbarAxis as W}from"../../renderer/fmt.js";export function openChartExpandModal(e,n,o){const t=e.getAttribute("style")??"";if(Object.assign(e.style,{flex:"1",minHeight:"0",border:"none",borderRadius:"0"}),!I([e],{title:n,onClosed:()=>{e.setAttribute("style",t),o&&(o.style.display="flex")}})){e.setAttribute("style",t);return}o&&(o.style.display="none")}const F={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040"},O={text:"text",textMuted:"text-muted",border:"border",bg:"surface",bgHover:"bg-hover",track:"panel-bg",warnBorder:"warn-border"};export function resolveChartCanvasColors(e){const n=getComputedStyle(e),o={};for(const t of Object.keys(O)){const i=n.getPropertyValue(`--wmap-${O[t]}`).trim();o[t]=i||F[t]}return o}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,n){const o=new ResizeObserver(n);return o.observe(e),{disconnect:()=>o.disconnect()}}export function growCardToFitContent(e,n,o){const t=e.offsetHeight-n.clientHeight;e.style.minHeight=`${t+o}px`}export function chartFillHeight(e,n,o,t){let i=0;for(const l of Array.from(n.children))l!==o&&(i+=l.offsetHeight);return growCardToFitContent(e,n,i+t),Math.max(t,n.clientHeight-i)}export function applyCanvasFlow(e,n=0){e.style.position="absolute",e.style.left="0",e.style.right="0",e.style.top=`${n}px`,e.style.bottom="0"}export function formatValue(e){return Number.isFinite(e)?Number.isInteger(e)?`${e}`:z(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,n){if(!n)return{tick:formatValue,unitLabel:""};const{tickFmt:o,axisLabel:t}=W(e,null,n);return{tick:o,unitLabel:t}}export function drawAxisUnit(e,n,o,t,i){const l={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font="10px system-ui, sans-serif",e.textAlign="left",e.textBaseline="middle",e.fillStyle=i,e.fillText(`(${n})`,o,t),Object.assign(e,l)}export function saveCanvasPng(e,n,o){const t=document.createElement("canvas");t.width=e.width,t.height=e.height;const i=t.getContext("2d");i.fillStyle="#ffffff",i.fillRect(0,0,t.width,t.height),i.drawImage(e,0,0),t.toBlob(l=>{l&&D(l,n,o)},"image/png")}export function cardShell(e,n){const o=document.createElement("div");o.dataset.wmapChartCard="1",o.dataset.wmapChartTitle=e,Object.assign(o.style,{display:"flex",flexDirection:"column",background:a.menuBg,border:`1px solid ${a.menuBorder}`,borderRadius:"6px",padding:"12px",minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const t=document.createElement("div");Object.assign(t.style,{display:"flex",alignItems:"center",gap:"6px",marginBottom:"6px"});const i=document.createElement("div");i.textContent=e,Object.assign(i.style,{color:a.value,fontSize:"13px",fontWeight:"600",flex:"1"}),t.appendChild(i);const l=document.createElement("button");l.type="button",l.title="Save as PNG",l.setAttribute("aria-label","Save as PNG"),l.innerHTML=L.download,Object.assign(l.style,{border:`1px solid ${a.menuBorder}`,borderRadius:"4px",background:"none",color:a.label,cursor:"pointer",width:"22px",height:"22px",lineHeight:"1",flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),l.addEventListener("click",()=>{const p=o.querySelector("canvas");p&&saveCanvasPng(p,e,n)}),t.appendChild(l);const r=document.createElement("button");r.type="button",r.title="Expand",r.setAttribute("aria-label","Expand"),r.innerHTML=L.expand,Object.assign(r.style,{border:`1px solid ${a.menuBorder}`,borderRadius:"4px",background:"none",color:a.label,cursor:"pointer",width:"22px",height:"22px",lineHeight:"1",flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),r.addEventListener("click",()=>openChartExpandModal(o,i.textContent??e,r)),t.appendChild(r),o.appendChild(t);const c=document.createElement("div");Object.assign(c.style,{display:"flex",gap:"6px",marginBottom:"6px",flexWrap:"wrap",alignItems:"center"}),o.appendChild(c);const d=document.createElement("div");return Object.assign(d.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),o.appendChild(d),{card:o,heading:i,controlsRow:c,body:d}}export function makeSegmented(e,n,o){const t=document.createElement("div");t.setAttribute("role","radiogroup"),Object.assign(t.style,{display:"inline-flex",border:`1px solid ${a.menuBorder}`,borderRadius:"4px",overflow:"hidden"});const i=`seg-${Math.random().toString(36).slice(2,9)}`,l=[];return e.forEach(([r,c],d)=>{const p=document.createElement("label");Object.assign(p.style,{display:"inline-flex",alignItems:"center",fontSize:"12px",padding:"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:d>0?`1px solid ${a.menuBorder}`:"none"});const s=document.createElement("input");s.type="radio",s.name=i,s.value=r,s.checked=r===n,Object.assign(s.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const g=()=>{p.style.background=s.checked?a.bgActive:a.menuBg,p.style.color=s.checked?a.iconActive:a.text};l.push(g),g(),s.addEventListener("change",()=>{if(s.checked){for(const h of l)h();o(r)}}),p.append(s,document.createTextNode(c)),t.appendChild(p)}),t}export function makeBackButton(e){const n=document.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{fontSize:"12px",padding:"3px 10px",border:`1px solid ${a.menuBorder}`,borderRadius:"4px",background:a.menuBg,color:a.label,cursor:"pointer"}),n.addEventListener("click",e),n}export function renderEmptyState(e,n,o){const t=document.createElement("div");t.textContent=n,Object.assign(t.style,{color:a.label,fontSize:"12px",padding:"8px 0"}),o&&Object.assign(t.style,o),e.appendChild(t)}export function makeTestSelect(e,n,o,t={}){const{maxWidth:i="200px",emptyText:l="No parametric tests"}=t;if(e.length>N)return P(e,n,o,i);const r=document.createElement("select");if(Object.assign(r.style,{fontSize:"12px",padding:"2px 6px",background:a.menuBg,color:a.text,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",maxWidth:i}),e.length===0){r.disabled=!0;const c=document.createElement("option");c.textContent=l,r.appendChild(c)}else{for(const c of e){const d=document.createElement("option");d.value=String(c.testNumber),d.textContent=c.name||`Test ${c.testNumber}`,c.testNumber===n&&(d.selected=!0),r.appendChild(d)}r.addEventListener("change",()=>o(Number(r.value)))}return r}function P(e,n,o,t){const i=f=>e.find(y=>y.testNumber===f)?.name||`Test ${f}`;let l=n;const r=document.createElement("button");r.type="button",Object.assign(r.style,{fontSize:"12px",padding:"2px 6px",background:a.menuBg,color:a.text,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",maxWidth:t,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:"6px",justifyContent:"space-between"}),E(r,!1);const c=document.createElement("span");Object.assign(c.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const d=document.createElement("span");d.textContent="\u25BE",d.style.flex="0 0 auto",r.append(c,d);const p=()=>{c.textContent=l!==null?i(l):"(none)"};p();let s=null;const g=()=>{s&&(s.remove(),s=null,E(r,!1))},h=()=>{g(),r.focus()};function j(){const f=r.getBoundingClientRect(),y=r.ownerDocument.defaultView??window,C=Math.max(f.width,220),R=320,$=Math.min(f.left,Math.max(4,(y.innerWidth??1/0)-C-4));s=document.createElement("div"),Object.assign(s.style,{position:"fixed",top:`${f.bottom+4}px`,left:`${$}px`,background:a.menuBg,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:T,minWidth:`${C}px`,maxHeight:`${R}px`,overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const v=[],S=()=>v.filter(u=>u.row.style.display!=="none").map(u=>u.row),k=M(u=>{for(const m of v)m.row.style.display=m.label.includes(u)?"":"none"},"Filter tests\u2026");Object.assign(k.style,{position:"sticky",top:"0",zIndex:"1",background:a.menuBg}),k.addEventListener("keydown",u=>{if(u.key==="Escape"){h();return}u.key==="ArrowDown"&&(u.preventDefault(),S()[0]?.focus())}),s.appendChild(k);for(const u of e){const m=u.name||`Test ${u.testNumber}`,x=u.testNumber===l,b=document.createElement("div");b.textContent=m,b.setAttribute("role","menuitemradio"),b.setAttribute("aria-checked",x?"true":"false"),b.tabIndex=-1,Object.assign(b.style,{padding:"6px 14px",fontSize:"12px",cursor:"pointer",color:x?a.iconActive:a.text,fontWeight:x?"700":"400",background:x?a.menuActive:"transparent",whiteSpace:"nowrap",outline:"none"});const B=()=>{x||(b.style.background=a.menuHover)},A=()=>{b.style.background=x?a.menuActive:"transparent"};b.addEventListener("mouseenter",B),b.addEventListener("mouseleave",A),b.addEventListener("focus",B),b.addEventListener("blur",A),b.addEventListener("click",H=>{H.stopPropagation(),l=u.testNumber,p(),h(),o(u.testNumber)}),s.appendChild(b),v.push({row:b,label:m.toLowerCase()})}s.addEventListener("keydown",u=>{const m=S();if(m.length===0)return;const x=m.indexOf(document.activeElement);switch(u.key){case"ArrowDown":u.preventDefault(),m[x<0||x===m.length-1?0:x+1].focus();break;case"ArrowUp":u.preventDefault(),m[x<=0?m.length-1:x-1].focus();break;case"Home":u.preventDefault(),m[0].focus();break;case"End":u.preventDefault(),m[m.length-1].focus();break;case"Enter":case" ":x>=0&&(u.preventDefault(),m[x].click());break;case"Escape":case"Tab":u.preventDefault(),h();break}}),s.setAttribute("role","menu"),s.setAttribute("aria-label","Test"),r.ownerDocument.body.appendChild(s),E(r,!0)}r.addEventListener("click",f=>{if(f.stopPropagation(),s){g();return}j()});const w=f=>{if(!r.isConnected){r.ownerDocument.removeEventListener("click",w);return}s&&!s.contains(f.target)&&!r.contains(f.target)&&g()};return r.ownerDocument.addEventListener("click",w),Object.defineProperty(r,"value",{get:()=>l!==null?String(l):"",set:f=>{l=f===""?null:Number(f),p()}})}export function makeWaferSelect(e,n,o,t={}){const{maxWidth:i="160px",allLabel:l="All wafers"}=t,r="\0all",c=document.createElement("select");Object.assign(c.style,{fontSize:"12px",padding:"2px 6px",background:a.menuBg,color:a.text,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",maxWidth:i});const d=document.createElement("option");return d.value=r,d.textContent=l,n===null&&(d.selected=!0),c.appendChild(d),e.forEach((p,s)=>{const g=document.createElement("option");g.value=String(s),g.textContent=p.label??`#${s}`,n===s&&(g.selected=!0),c.appendChild(g)}),c.addEventListener("change",()=>o(c.value===r?null:Number(c.value))),c}export function makeToggle(e,n,o){const t=document.createElement("label");Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:"4px",fontSize:"11px",color:a.label,cursor:"pointer",userSelect:"none"});const i=document.createElement("input");return i.type="checkbox",i.checked=n,i.style.cssText="margin:0;cursor:pointer;",i.addEventListener("change",()=>o(i.checked)),t.append(i,document.createTextNode(e)),t}export function makeLabeledSelect(e,n,o,t,i={}){const{maxWidth:l="160px",hook:r}=i,c=document.createElement("label");c.textContent=e,Object.assign(c.style,{color:a.label,fontSize:"12px",display:"flex",alignItems:"center",gap:"4px"});const d=document.createElement("select");r&&(d.dataset.wmapSelect=r),Object.assign(d.style,{fontSize:"12px",padding:"2px 6px",background:a.menuBg,color:a.text,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",maxWidth:l});for(const p of n){const s=document.createElement("option");s.value=p.value,s.textContent=p.label,p.value===o&&(s.selected=!0),d.appendChild(s)}return d.addEventListener("change",()=>t(d.value)),c.appendChild(d),c}export function makeChartGridWrap(){const e=document.createElement("div");return e.dataset.wmapChartGrid="1",Object.assign(e.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:"10px",flex:"0 0 auto"}),e}export function makeTooltip(e){const n=document.createElement("div");return Object.assign(n.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:a.menuBg,border:`1px solid ${a.menuBorder}`,borderRadius:"4px",padding:"4px 8px",fontSize:"11px",fontFamily:"system-ui, sans-serif",color:a.text,whiteSpace:"nowrap",boxShadow:"0 2px 8px rgba(0,0,0,0.3)"}),e.appendChild(n),n}export function positionChartTooltip(e,n,o,t){const i=n.getBoundingClientRect(),l=14,r=4;e.style.left="0",e.style.top="0";const c=e.offsetWidth,d=e.offsetHeight;let p=o-i.left+l,s=t-i.top+l;p+c+r>i.width&&(p=o-i.left-l-c),s+d+r>i.height&&(s=t-i.top-l-d),p=Math.max(r,p),s=Math.max(r,s),e.style.left=`${p}px`,e.style.top=`${s}px`}
@@ -1,24 +1,67 @@
1
1
  import type { Die } from '../core/dies.js';
2
- import { type TestDef } from '../renderer/buildWaferMap.js';
2
+ import { type MetadataFieldDef, type TestDef } from '../renderer/buildWaferMap.js';
3
+ import type { WaferMetadata } from '../core/metadata.js';
4
+ import { type MetadataKeySelection } from '../stats/metadataColumns.js';
3
5
  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;
6
+ /** Display preferences for the built-in die-list table. Everything here is a
7
+ * choice about what to show; the *data* it acts on (wafer metadata, metadata
8
+ * field definitions) is supplied by the library from its own build result
9
+ * when reached through `renderWaferMap`'s `RenderOptions.dieList` — never by
10
+ * the host substituting its own, so an export can't be handed the wrong
11
+ * identity data by mistake. */
12
+ export interface DieListDisplayOptions {
13
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.
14
+ * Show a "View die list" link inside the Summary panel (`renderWaferMap`)
15
+ * / lot Summary panel (`renderWaferGallery`) that opens this wafer's — or,
16
+ * in the gallery, the whole lot's — dies as a table in a modal, with the
17
+ * same "Export CSV" button `buildDieListSection` always has. **Default
18
+ * `true`** — set `enabled: false` to hide it. Requires a Summary panel to
19
+ * be reachable at all (`RenderOptions.summaryPanel` / `GalleryOptions.summaryPanel`),
20
+ * since that panel is this link's only home; irrelevant otherwise.
21
+ *
22
+ * Has no effect on the coordinate-less map replacement or the "+N dies
23
+ * without position" footer, both of which show a die list unconditionally
24
+ * regardless of this flag — those exist because there is literally nothing
25
+ * else to show, not as an opt-in extra. The rest of this options object
26
+ * (column selection, `maxRows`, …) is shared by both surfaces.
17
27
  */
18
- extraColumn?: {
19
- label: string;
20
- get: (die: Die) => string | undefined;
21
- };
28
+ enabled?: boolean;
29
+ /**
30
+ * `die.metadata` keys to show as columns. Default `'auto'` — every key
31
+ * present on any die, deterministically ordered. `'none'` omits them; an
32
+ * explicit array pins both the set and the order.
33
+ *
34
+ * Unlike `metadataFields` (which gates the `'metadata'` PLOT mode, where a
35
+ * key must be categorical enough for a legend), a table column has no
36
+ * cardinality limit — one cell per die, whatever the value — so this
37
+ * defaults to on. An export that silently drops host data is worse than a
38
+ * wide one.
39
+ */
40
+ metadataColumns?: MetadataKeySelection;
41
+ /**
42
+ * Where wafer-level metadata (lot, wafer id, product, …) appears.
43
+ * - `'csv'` (default): CSV only. It is constant down every row, so on
44
+ * screen it is pure noise beside the metadata badge that already shows
45
+ * it — but a CSV travels alone, and per-row identity is what lets
46
+ * several exports be concatenated and pivoted later.
47
+ * - `'both'`: also as table columns.
48
+ * - `'none'`: omitted entirely.
49
+ */
50
+ waferMetadataColumns?: 'csv' | 'both' | 'none';
51
+ /**
52
+ * Cap the number of rows **rendered** in the DOM. Default `50_000`. The CSV
53
+ * export is never capped — it always contains every die — and a footer
54
+ * states the truncation explicitly when it applies, since a silently
55
+ * truncated view is exactly the kind of unlabelled filtered population
56
+ * this library's own display rules forbid.
57
+ *
58
+ * Exists because this table has no virtualisation: each row is real DOM.
59
+ * At lot scale (a quarter-million dies is a realistic STDF batch) an
60
+ * unbounded table is tens of seconds and several hundred MB before any
61
+ * metadata column is added. Set `0` to skip the table entirely and offer
62
+ * only the CSV export.
63
+ */
64
+ maxRows?: number;
22
65
  /**
23
66
  * Cap the scrolling table at this CSS length instead of letting it fill.
24
67
  *
@@ -32,6 +75,29 @@ export interface DieListOptions {
32
75
  * directly into a long document).
33
76
  */
34
77
  maxHeight?: string;
78
+ /** CSV filename, including extension. Default: `'dies.csv'`. */
79
+ csvFilename?: string;
80
+ }
81
+ export interface DieListOptions extends DieListDisplayOptions {
82
+ /** Heading text. Default: `Die list (N)`. */
83
+ title?: string;
84
+ /** Optional note shown under the heading, e.g. explaining why this replaced the map. */
85
+ note?: string;
86
+ /** Host hook for "Export CSV" — see `saveTextFile` (toolbar.ts). */
87
+ onSaveText?: SaveTextHandler;
88
+ /**
89
+ * Extra leading column, e.g. a wafer-id label for a lot-level combined
90
+ * list spanning multiple wafers. `get(die)` returning `undefined` renders
91
+ * an empty cell for that row.
92
+ */
93
+ extraColumn?: {
94
+ label: string;
95
+ get: (die: Die) => string | undefined;
96
+ };
97
+ /** Wafer-level metadata for this population, e.g. `WaferMapResult.metadata`. */
98
+ waferMetadata?: WaferMetadata;
99
+ /** Label/order hints for die metadata columns, e.g. `WaferMapResult.metadataFields`. */
100
+ metadataFields?: MetadataFieldDef[];
35
101
  }
36
102
  /**
37
103
  * Build a scrollable die-list table + "Export CSV" button. Returns `null`
@@ -1,2 +1,13 @@
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}
1
+ import{hasPosition as k}from"../core/dies.js";import{isParametricTest as H}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as R}from"../stats/metadataColumns.js";import{CLR as a,saveTextFile as E}from"./toolbar.js";import{csvField as y}from"./summaryPanel.js";import{fmt as M}from"../renderer/fmt.js";const T=5e4;let $=!1;function B(){if($)return;$=!0;const n=document.createElement("style");n.textContent=`
2
+ .wmap-dielist-table { width: 100%; border-collapse: collapse; font-size: 11px; }
3
+ .wmap-dielist-th {
4
+ text-align: left; padding: 4px 8px; position: sticky; top: 0;
5
+ background: ${a.panelBg}; color: ${a.label}; font-weight: 600;
6
+ border-bottom: 1px solid ${a.menuBorder}; white-space: nowrap;
7
+ }
8
+ .wmap-dielist-td {
9
+ padding: 3px 8px; color: ${a.text};
10
+ border-bottom: 1px solid ${a.menuBorder}; white-space: nowrap;
11
+ }
12
+ `,document.head.appendChild(n)}function o(n,s,t){const i=document.createElement(n);return s&&Object.assign(i.style,s),t!==void 0&&(i.textContent=t),i}function F(n){return k(n)?`(${n.x}, ${n.y})`:"\u2014"}function P(n,s){if(s?.length)return s.filter(H);const t=new Map;for(const i of n)for(const d of Object.keys(i.testValues??{})){const r=Number(d);t.has(r)||t.set(r,{testNumber:r,name:`Test ${r}`})}return[...t.values()].sort((i,d)=>i.testNumber-d.testNumber)}export function buildDieListSection(n,s,t={}){if(!n.length)return null;B();const i=P(n,s),d=t.maxRows??T,r=Math.max(0,n.length-d),N=r>0?n.slice(0,d):n,u=o("div",{display:"flex",flexDirection:"column",gap:"8px",flex:"1",minHeight:"0"}),h=o("div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});h.appendChild(o("div",{fontSize:"10px",fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:a.label},t.title??`Die list (${n.length})`));const b=o("button",{fontSize:"11px",padding:"3px 8px",borderRadius:"4px",border:`1px solid ${a.menuBorder}`,background:a.menuBg,color:a.text,cursor:"pointer"},"Export CSV");b.type="button",h.appendChild(b),u.appendChild(h),t.note&&u.appendChild(o("div",{fontSize:"11px",color:a.label,lineHeight:"1.4",flexShrink:"0"},t.note));const L=[...t.extraColumn?[t.extraColumn.label]:[],"Position","Site","Hard bin","Soft bin",...i.map(e=>e.name)],{columns:j,truncatedKeys:f}=R({dies:n,waferMetadata:t.waferMetadata,metadataFields:t.metadataFields,dieKeys:t.metadataColumns,waferKeys:t.waferMetadataColumns==="none"?"none":void 0,waferPlacement:t.waferMetadataColumns??"csv",reservedLabels:L}),x=[...t.extraColumn?[{label:t.extraColumn.label,get:e=>t.extraColumn.get(e)??""}]:[],{label:"Position",get:F},{label:"Site",get:e=>e.siteNum!==void 0?String(e.siteNum):""},{label:"Hard bin",get:e=>e.hbin!==void 0?String(e.hbin):""},{label:"Soft bin",get:e=>e.sbin!==void 0?String(e.sbin):""},...i.map(e=>({label:e.name,get:l=>{const c=l.testValues?.[e.testNumber];if(c!==void 0)return M(c,e.unit);const m=l.testPass?.[e.testNumber];return m===void 0?"":m?"PASS":"FAIL"}})),...j.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],g=x.filter(e=>!e.csvOnly),C=o("div",{overflow:"auto",flex:"1",minHeight:"0",border:`1px solid ${a.menuBorder}`,borderRadius:"4px",...t.maxHeight?{maxHeight:t.maxHeight}:{}}),p=document.createElement("table");p.className="wmap-dielist-table",p.setAttribute("aria-label",t.title??`Die list (${n.length} dies)`);const w=o("thead"),S=o("tr");for(const e of g){const l=document.createElement("th");l.className="wmap-dielist-th",l.scope="col",l.textContent=e.label,S.appendChild(l)}w.appendChild(S),p.appendChild(w);const v=o("tbody");for(const e of N){const l=o("tr");for(const c of g){const m=document.createElement("td");m.className="wmap-dielist-td",m.textContent=c.get(e),l.appendChild(m)}v.appendChild(l)}if(p.appendChild(v),C.appendChild(p),u.appendChild(C),r>0||f.length>0){const e=[];r>0&&e.push(`Showing the first ${d.toLocaleString()} of ${n.length.toLocaleString()} dies. The CSV export contains all ${n.length.toLocaleString()}.`),f.length>0&&e.push(`${f.length} further metadata field${f.length===1?"":"s"} not shown.`),u.appendChild(o("div",{fontSize:"11px",color:a.label,lineHeight:"1.4",flexShrink:"0"},e.join(" ")))}return b.addEventListener("click",()=>{const e=[x.map(l=>y(l.label)).join(",")];for(const l of n)e.push(x.map(c=>y(c.get(l))).join(","));E(e.join(`
13
+ `),t.csvFilename??"dies.csv","text/csv",t.onSaveText)}),u}
@@ -6,7 +6,7 @@ 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
8
  export { buildDieListSection } from './dieList.js';
9
- export type { DieListOptions } from './dieList.js';
9
+ export type { DieListOptions, DieListDisplayOptions } from './dieList.js';
10
10
  export { collectWarnings, severityOf } from './warnings.js';
11
11
  export type { WarningsOptions } from './warnings.js';
12
12
  export type { InsightsOptions, InsightsView } from './insightsTab.js';
@@ -1 +1 @@
1
- import{buildFacetTable as de,facetValueOf as ce,FACET_NONE_VALUE as pe}from"../stats/facets.js";import{isParametricTest as ue}from"../renderer/buildWaferMap.js";import{getColorScheme as me}from"../renderer/colorSchemes.js";import{CLR as x}from"./toolbar.js";import{renderCapabilityPanel as fe}from"./charts/capability.js";import{renderBoxplotPanel as be}from"./charts/boxplot.js";import{renderHistogramPanel as ye}from"./charts/histogram.js";import{renderCorrelationPanel as he}from"./charts/correlation.js";import{renderScatterPanel as ge}from"./charts/scatter.js";import{renderBarPanel as q}from"./charts/barPanel.js";import{renderBinClusterPanel as xe}from"./charts/binCluster.js";import{QUANTITY as Ce}from"./charts/palette.js";import{makeChartGridWrap as j,makeLabeledSelect as Se}from"./charts/chartShell.js";import{buildYieldData as A,buildYieldDataCombined as ve}from"../stats/yield.js";import{buildBinParetoData as we}from"../stats/binPareto.js";import{buildLotTestSection as Be,buildLotFunctionalSection as ke,buildMetadataStripBox as Te}from"./summaryPanel.js";import{buildRegionYieldData as N,buildRingRegions as $e,buildQuadrantRegions as Ee}from"../stats/regions.js";import{renderRegionYieldDiagram as z}from"./charts/regionYieldDiagram.js";const Oe=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}];export function createInsightsTab(k){const{getItems:H,getLotStats:Q,getColorSchemeName:h,passBins:f,getRingCount:K,onSaveImage:y,onSaveText:M,openWafer:D}=k,U=k.showMetadataStrip??!0,S=document.createElement("div");Object.assign(S.style,{display:"none",flexDirection:"column",gap:"10px",width:"100%"});const L=document.createElement("div"),C=document.createElement("div");Object.assign(C.style,{display:"flex",gap:"4px",borderBottom:`1px solid ${x.menuBorder}`,marginBottom:"2px"}),C.setAttribute("role","tablist"),C.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from(C.querySelectorAll('[role="tab"]')),o=t.indexOf(document.activeElement);if(o===-1)return;e.preventDefault();const r=t[(o+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];r.focus(),r.click()});const T=document.createElement("div");Object.assign(T.style,{display:"flex",flexDirection:"column",gap:"10px"}),S.appendChild(L),S.appendChild(C),S.appendChild(T);let $=k.defaultView??"overview",v,O=[];function V(e,t,o){D?.(e,t,o)}function _(){return H().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:e.label??String(e.wafer.metadata?.waferId??""),waferIndex:t,statsSummary:e.statsSummary}).filter(e=>e!=null)}function F(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${x.iconActive}`:"2px solid transparent",color:t?x.iconActive:x.label,fontWeight:t?"700":"500",fontSize:"12px",padding:"6px 10px 8px",cursor:"pointer",marginBottom:"-1px"})}function X(e,t){const o=document.createElement("button");o.type="button",o.textContent=t;const r=e===$;return F(o,r),o.setAttribute("role","tab"),o.setAttribute("aria-selected",r?"true":"false"),o.tabIndex=r?0:-1,o.addEventListener("click",()=>{$!==e&&($=e,R())}),o}function J(e){const t=document.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,F(t,!1),t.addEventListener("click",e.onBack),t}function Z(e,t,o,r=!0){const l=j(),s=me(h());let p="label";const d=o??"group",n=f.length===1?`bin ${f[0]}`:`bins ${f.join(", ")}`,u=Q?.(),m=new Map(u?.lotYieldSeries.map(a=>[a.waferIndex,a.yieldPercent])),c=a=>({...a,label:a.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:m.get(a.waferIndex),key:a.waferIndex}),w=e.map(c),g=t?.map(a=>({key:a.key,items:a.items.map(c)})),B=()=>g?ve(g,f,p):A(w,f,p),i={title:t?`Yield by ${d} (pass: ${n})`:`Yield by wafer (pass: ${n})`,data:B(),selfControl:{current:p,options:[["yield","Sort: yield"],["label",t?`Sort: ${d}`:"Sort: wafer ID"]],onChange:a=>(p=a,{data:B()})},barColor:()=>Ce,valueLabel:a=>`${a.percent.toFixed(1)}%`,drill:g?{onOpenGroup:a=>{const P=g.find(E=>E.key===a.label)?.items??[];return{data:A(P,f,p),title:`Yield by wafer \u2014 ${d}: ${a.label} (pass: ${n})`}},onBack:()=>({data:B(),title:`Yield by ${d} (pass: ${n})`}),groupLabelText:d}:void 0,onOpen:D?a=>{typeof a.key=="number"&&V(a.key,`Wafer ${a.label}`)}:void 0};let b=null;if(r&&(b=q(i,y),b.card.style.minHeight="360px",l.appendChild(b.card)),t){const a=xe({title:"Hard bin pareto",groups:t.map(P=>({key:P.key,items:P.items.map(E=>({dies:E.dies,hardBinCounts:E.statsSummary?.stats.hardBinCounts,softBinCounts:E.statsSummary?.stats.softBinCounts}))})),colorScheme:h(),onSaveImage:y});return a.card.style.minHeight="360px",l.appendChild(a.card),{card:l,destroy:()=>{b?.destroy(),a.destroy()}}}let I="hbin";const ie=s.forBin,le=e.map(a=>({dies:a.dies,hardBinCounts:a.statsSummary?.stats.hardBinCounts,softBinCounts:a.statsSummary?.stats.softBinCounts})),G=()=>we(le,I),se={title:"Hard bin pareto",data:G(),selfControl:{current:I,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:a=>(I=a,{data:G(),title:`${I==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:a=>ie(a.binCode??0)},Y=q(se,y);return Y.card.style.minHeight="360px",l.appendChild(Y.card),{card:l,destroy:()=>{b?.destroy(),Y.destroy()}}}function W(){const e=document.createElement("div");return Object.assign(e.style,{display:"flex",flexDirection:"column",gap:"10px",minWidth:"0",background:x.menuBg,border:`1px solid ${x.menuBorder}`,borderRadius:"6px",padding:"12px"}),e}function ee(e){if(L.innerHTML="",!U)return;const t=Te(e.map(o=>({metadata:o.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",L.appendChild(t))}function te(e,t,o){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const r=e.map(i=>i.wafer),l=e.map(i=>i.dies),s=e.flatMap(i=>i.dies),p=e.map(i=>i.statsSummary),d=p.every(i=>i!==void 0)?p:void 0,n=K?.()??4,u=[],m=[],c=N(l,r,n,f,$e);if(c.length){const i=z({title:"Ring yield",mode:"ring",rows:c,colorScheme:h(),onSaveImage:y});u.push(i.card),m.push(i.destroy)}const w=N(l,r,n,f,Ee);if(w.length){const i=z({title:"Quadrant yield",mode:"quadrant",rows:w,colorScheme:h(),onSaveImage:y});u.push(i.card),m.push(i.destroy)}let g=null;if(t.length){const i=Be(s,t,void 0,d,M);if(i){const b=W();b.appendChild(i),g=b}}let B=null;if(o.length){const i=ke(s,o,d,M);if(i){const b=W();b.appendChild(i),B=b}}return{elements:u,testValuesCard:g,functionalCard:B,destroy:()=>{for(const i of m)i()}}}function ne(e){const t=f.length===1?`bin ${f[0]}`:`bins ${f.join(", ")}`,o=A([{...e,key:e.waferIndex}],f)[0]?.percent,r=W();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:"8px"});function l(s,p){const d=document.createElement("div");Object.assign(d.style,{border:`1px solid ${x.menuBorder}`,borderRadius:"6px",padding:"8px 16px",textAlign:"center",minWidth:"110px"});const n=document.createElement("div");n.textContent=s,Object.assign(n.style,{fontSize:"20px",fontWeight:"700",color:x.value,lineHeight:"1.2"});const u=document.createElement("div");return u.textContent=p,Object.assign(u.style,{fontSize:"10px",color:x.label,marginTop:"2px"}),d.append(n,u),d}return o!==void 0&&r.appendChild(l(`${o.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),r.appendChild(l(String(e.dies.length),"Total dies")),r}function oe(e,t,o,r,l){const s=document.createElement("div");Object.assign(s.style,{display:"flex",flexDirection:"column",gap:"10px"});const p=e.length===1&&!r;p&&s.appendChild(ne(e[0]));const d=Z(e,r,l,!p);s.appendChild(d.card);const n=te(e,t,o);for(const u of n.elements)d.card.appendChild(u);return n.testValuesCard&&s.appendChild(n.testValuesCard),n.functionalCard&&s.appendChild(n.functionalCard),{card:s,destroy:()=>{d.destroy(),n.destroy()}}}function ae(e,t,o,r){const l=j(),s=c=>({...c,testStats:c.statsSummary?.stats.perTestStats}),p=e.map(s),d=o?.map(c=>({key:c.key,items:c.items.map(s)})),n=be({title:"Test value distribution",items:p,testDefs:t,groups:d,groupLabelText:r,colorScheme:h(),onSaveImage:y,onOpen:D?(c,w)=>V(c,`Wafer ${e.find(g=>g.waferIndex===c)?.label??c}`,w):void 0}),u=ye({title:"Value histogram",items:e,testDefs:t,groups:o,colorScheme:h(),onSaveImage:y}),m=fe({title:"Process capability",items:e,testDefs:t,groups:o,colorScheme:h(),onSaveImage:y,onSelectTest:c=>{n.setTest(c),u.setTest(c)}});return l.append(m.card,n.card,u.card),{card:l,destroy:()=>{m.destroy(),n.destroy(),u.destroy()}}}function re(e,t,o){const r=j(),l=ge({title:"Test scatter",items:e,testDefs:t,groups:o,colorScheme:h(),onSaveImage:y}),s=he({title:"Test correlation matrix",items:e,testDefs:t,groups:o,colorScheme:h(),onSaveImage:y,onSelectPair:(p,d)=>l.setXY(p,d)});return r.append(s.card,l.card),{card:r,destroy:()=>{s.destroy(),l.destroy()}}}function R(){for(const n of O)n.destroy();O=[],T.innerHTML="",C.innerHTML="",k.backTab&&C.appendChild(J(k.backTab));for(const n of Oe)C.appendChild(X(n.key,n.label));const e=_();ee(e);const t=de(e,{facetableOnly:!0}).filter(n=>n.splittable),o=document.createElement("div");Object.assign(o.style,{display:"flex",gap:"8px",alignItems:"center"});let r;t.length>0?(o.appendChild(Se("Group by:",[{value:"",label:"None"},...t.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],v??"",n=>{v=n||void 0,R()})),r=t.find(n=>n.key===v)?.label):v=void 0,T.appendChild(o);const l=H().find(n=>n?.testDefs?.length)?.testDefs??[],s=l.filter(ue);let p;if(v){const n=new Map,u=[];for(const m of e){const c=ce(m.metadata,v)??pe;n.has(c)||(n.set(c,[]),u.push(c)),n.get(c).push(m)}p=u.map(m=>({key:m,items:n.get(m)}))}const d=$==="overview"?oe(e,s,l,p,r):$==="distributions"?ae(e,s,p,r):re(e,s,p);O.push(d),T.appendChild(d.card)}return{el:S,render:R,destroy:()=>{for(const e of O)e.destroy();S.remove()}}}
1
+ import{buildFacetTable as de,facetValueOf as ce,FACET_NONE_VALUE as pe}from"../stats/facets.js";import{isParametricTest as ue}from"../renderer/buildWaferMap.js";import{getColorScheme as me}from"../renderer/colorSchemes.js";import{CLR as C}from"./toolbar.js";import{renderCapabilityPanel as fe}from"./charts/capability.js";import{renderBoxplotPanel as be}from"./charts/boxplot.js";import{renderHistogramPanel as ye}from"./charts/histogram.js";import{renderCorrelationPanel as he}from"./charts/correlation.js";import{renderScatterPanel as ge}from"./charts/scatter.js";import{renderBarPanel as q}from"./charts/barPanel.js";import{renderBinClusterPanel as Ce}from"./charts/binCluster.js";import{QUANTITY as xe}from"./charts/palette.js";import{makeChartGridWrap as j,makeLabeledSelect as Se}from"./charts/chartShell.js";import{buildYieldData as A,buildYieldDataCombined as we}from"../stats/yield.js";import{buildBinParetoData as Be}from"../stats/binPareto.js";import{buildLotTestSection as ke,buildLotFunctionalSection as ve,buildMetadataStripBox as Te}from"./summaryPanel.js";import{buildRegionYieldData as N,buildRingRegions as $e,buildQuadrantRegions as Ee}from"../stats/regions.js";import{renderRegionYieldDiagram as z}from"./charts/regionYieldDiagram.js";const Oe=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}];export function createInsightsTab(v){const{getItems:H,getLotStats:Q,getColorSchemeName:h,passBins:f,getRingCount:K,onSaveImage:y,onSaveText:M,openWafer:D}=v,U=v.showMetadataStrip??!0,S=document.createElement("div");Object.assign(S.style,{display:"none",flexDirection:"column",gap:"10px",width:"100%"});const L=document.createElement("div"),x=document.createElement("div");Object.assign(x.style,{display:"flex",gap:"4px",borderBottom:`1px solid ${C.menuBorder}`,marginBottom:"2px"}),x.setAttribute("role","tablist"),x.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from(x.querySelectorAll('[role="tab"]')),a=t.indexOf(document.activeElement);if(a===-1)return;e.preventDefault();const r=t[(a+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];r.focus(),r.click()});const T=document.createElement("div");Object.assign(T.style,{display:"flex",flexDirection:"column",gap:"10px"}),S.appendChild(L),S.appendChild(x),S.appendChild(T);let $=v.defaultView??"overview",w,O=[];function V(e,t,a){D?.(e,t,a)}function _(){return H().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:e.label??String(e.wafer.metadata?.waferId??""),waferIndex:t,statsSummary:e.statsSummary}).filter(e=>e!=null)}function F(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${C.iconActive}`:"2px solid transparent",color:t?C.iconActive:C.label,fontWeight:t?"700":"500",fontSize:"12px",padding:"6px 10px 8px",cursor:"pointer",marginBottom:"-1px"})}function X(e,t){const a=document.createElement("button");a.type="button",a.textContent=t;const r=e===$;return F(a,r),a.dataset.wmapInsightsTab=e,a.setAttribute("role","tab"),a.setAttribute("aria-selected",r?"true":"false"),a.tabIndex=r?0:-1,a.addEventListener("click",()=>{$!==e&&($=e,R())}),a}function J(e){const t=document.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,F(t,!1),t.dataset.wmapInsightsBack="1",t.addEventListener("click",e.onBack),t}function Z(e,t,a,r=!0){const l=j(),s=me(h());let p="label";const d=a??"group",n=f.length===1?`bin ${f[0]}`:`bins ${f.join(", ")}`,u=Q?.(),m=new Map(u?.lotYieldSeries.map(o=>[o.waferIndex,o.yieldPercent])),c=o=>({...o,label:o.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:m.get(o.waferIndex),key:o.waferIndex}),B=e.map(c),g=t?.map(o=>({key:o.key,items:o.items.map(c)})),k=()=>g?we(g,f,p):A(B,f,p),i={title:t?`Yield by ${d} (pass: ${n})`:`Yield by wafer (pass: ${n})`,data:k(),selfControl:{current:p,options:[["yield","Sort: yield"],["label",t?`Sort: ${d}`:"Sort: wafer ID"]],onChange:o=>(p=o,{data:k()})},barColor:()=>xe,valueLabel:o=>`${o.percent.toFixed(1)}%`,drill:g?{onOpenGroup:o=>{const P=g.find(E=>E.key===o.label)?.items??[];return{data:A(P,f,p),title:`Yield by wafer \u2014 ${d}: ${o.label} (pass: ${n})`}},onBack:()=>({data:k(),title:`Yield by ${d} (pass: ${n})`}),groupLabelText:d}:void 0,onOpen:D?o=>{typeof o.key=="number"&&V(o.key,`Wafer ${o.label}`)}:void 0};let b=null;if(r&&(b=q(i,y),b.card.style.minHeight="360px",l.appendChild(b.card)),t){const o=Ce({title:"Hard bin pareto",groups:t.map(P=>({key:P.key,items:P.items.map(E=>({dies:E.dies,hardBinCounts:E.statsSummary?.stats.hardBinCounts,softBinCounts:E.statsSummary?.stats.softBinCounts}))})),colorScheme:h(),onSaveImage:y});return o.card.style.minHeight="360px",l.appendChild(o.card),{card:l,destroy:()=>{b?.destroy(),o.destroy()}}}let I="hbin";const ie=s.forBin,le=e.map(o=>({dies:o.dies,hardBinCounts:o.statsSummary?.stats.hardBinCounts,softBinCounts:o.statsSummary?.stats.softBinCounts})),G=()=>Be(le,I),se={title:"Hard bin pareto",data:G(),selfControl:{current:I,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:o=>(I=o,{data:G(),title:`${I==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:o=>ie(o.binCode??0)},Y=q(se,y);return Y.card.style.minHeight="360px",l.appendChild(Y.card),{card:l,destroy:()=>{b?.destroy(),Y.destroy()}}}function W(){const e=document.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{display:"flex",flexDirection:"column",gap:"10px",minWidth:"0",background:C.menuBg,border:`1px solid ${C.menuBorder}`,borderRadius:"6px",padding:"12px"}),e}function ee(e){if(L.innerHTML="",!U)return;const t=Te(e.map(a=>({metadata:a.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",L.appendChild(t))}function te(e,t,a){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const r=e.map(i=>i.wafer),l=e.map(i=>i.dies),s=e.flatMap(i=>i.dies),p=e.map(i=>i.statsSummary),d=p.every(i=>i!==void 0)?p:void 0,n=K?.()??4,u=[],m=[],c=N(l,r,n,f,$e);if(c.length){const i=z({title:"Ring yield",mode:"ring",rows:c,colorScheme:h(),onSaveImage:y});u.push(i.card),m.push(i.destroy)}const B=N(l,r,n,f,Ee);if(B.length){const i=z({title:"Quadrant yield",mode:"quadrant",rows:B,colorScheme:h(),onSaveImage:y});u.push(i.card),m.push(i.destroy)}let g=null;if(t.length){const i=ke(s,t,void 0,d,M);if(i){const b=W();b.appendChild(i),g=b}}let k=null;if(a.length){const i=ve(s,a,d,M);if(i){const b=W();b.appendChild(i),k=b}}return{elements:u,testValuesCard:g,functionalCard:k,destroy:()=>{for(const i of m)i()}}}function ne(e){const t=f.length===1?`bin ${f[0]}`:`bins ${f.join(", ")}`,a=A([{...e,key:e.waferIndex}],f)[0]?.percent,r=W();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:"8px"});function l(s,p){const d=document.createElement("div");Object.assign(d.style,{border:`1px solid ${C.menuBorder}`,borderRadius:"6px",padding:"8px 16px",textAlign:"center",minWidth:"110px"});const n=document.createElement("div");n.textContent=s,Object.assign(n.style,{fontSize:"20px",fontWeight:"700",color:C.value,lineHeight:"1.2"});const u=document.createElement("div");return u.textContent=p,Object.assign(u.style,{fontSize:"10px",color:C.label,marginTop:"2px"}),d.append(n,u),d}return a!==void 0&&r.appendChild(l(`${a.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),r.appendChild(l(String(e.dies.length),"Total dies")),r}function ae(e,t,a,r,l){const s=document.createElement("div");Object.assign(s.style,{display:"flex",flexDirection:"column",gap:"10px"});const p=e.length===1&&!r;p&&s.appendChild(ne(e[0]));const d=Z(e,r,l,!p);s.appendChild(d.card);const n=te(e,t,a);for(const u of n.elements)d.card.appendChild(u);return n.testValuesCard&&s.appendChild(n.testValuesCard),n.functionalCard&&s.appendChild(n.functionalCard),{card:s,destroy:()=>{d.destroy(),n.destroy()}}}function oe(e,t,a,r){const l=j(),s=c=>({...c,testStats:c.statsSummary?.stats.perTestStats}),p=e.map(s),d=a?.map(c=>({key:c.key,items:c.items.map(s)})),n=be({title:"Test value distribution",items:p,testDefs:t,groups:d,groupLabelText:r,colorScheme:h(),onSaveImage:y,onOpen:D?(c,B)=>V(c,`Wafer ${e.find(g=>g.waferIndex===c)?.label??c}`,B):void 0}),u=ye({title:"Value histogram",items:e,testDefs:t,groups:a,colorScheme:h(),onSaveImage:y}),m=fe({title:"Process capability",items:e,testDefs:t,groups:a,colorScheme:h(),onSaveImage:y,onSelectTest:c=>{n.setTest(c),u.setTest(c)}});return l.append(m.card,n.card,u.card),{card:l,destroy:()=>{m.destroy(),n.destroy(),u.destroy()}}}function re(e,t,a){const r=j(),l=ge({title:"Test scatter",items:e,testDefs:t,groups:a,colorScheme:h(),onSaveImage:y}),s=he({title:"Test correlation matrix",items:e,testDefs:t,groups:a,colorScheme:h(),onSaveImage:y,onSelectPair:(p,d)=>l.setXY(p,d)});return r.append(s.card,l.card),{card:r,destroy:()=>{s.destroy(),l.destroy()}}}function R(){for(const n of O)n.destroy();O=[],T.innerHTML="",x.innerHTML="",v.backTab&&x.appendChild(J(v.backTab));for(const n of Oe)x.appendChild(X(n.key,n.label));const e=_();ee(e);const t=de(e,{facetableOnly:!0}).filter(n=>n.splittable),a=document.createElement("div");Object.assign(a.style,{display:"flex",gap:"8px",alignItems:"center"});let r;t.length>0?(a.appendChild(Se("Group by:",[{value:"",label:"None"},...t.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],w??"",n=>{w=n||void 0,R()},{hook:"group-by"})),r=t.find(n=>n.key===w)?.label):w=void 0,T.appendChild(a);const l=H().find(n=>n?.testDefs?.length)?.testDefs??[],s=l.filter(ue);let p;if(w){const n=new Map,u=[];for(const m of e){const c=ce(m.metadata,w)??pe;n.has(c)||(n.set(c,[]),u.push(c)),n.get(c).push(m)}p=u.map(m=>({key:m,items:n.get(m)}))}const d=$==="overview"?ae(e,s,l,p,r):$==="distributions"?oe(e,s,p,r):re(e,s,p);O.push(d),T.appendChild(d.card)}return{el:S,render:R,destroy:()=>{for(const e of O)e.destroy();S.remove()}}}
@@ -6,6 +6,7 @@ import type { LotStatsSummary } from '../stats/types.js';
6
6
  import { type WarningsOptions } from './warnings.js';
7
7
  import type { SummaryPanelOptions } from './summaryPanel.js';
8
8
  import { type InsightsOptions } from './insightsTab.js';
9
+ import type { DieListDisplayOptions } from './dieList.js';
9
10
  /**
10
11
  * The data and display overrides for a single gallery card.
11
12
  *
@@ -87,6 +88,15 @@ export interface GalleryOptions {
87
88
  * map for a finding to highlight against.
88
89
  */
89
90
  summaryPanel?: SummaryPanelOptions;
91
+ /**
92
+ * Display preferences for the lot-wide "View die list" link inside the
93
+ * Summary panel — every die across every wafer, pooled, with a Wafer
94
+ * column and CSV export. **On by default** whenever `summaryPanel` is
95
+ * reachable at all, since that panel is the link's only home; pass
96
+ * `{ enabled: false }` to hide it. See `DieListDisplayOptions` and wmap's
97
+ * own coordinate-less die-list table, which this reuses.
98
+ */
99
+ dieList?: DieListDisplayOptions;
90
100
  /**
91
101
  * Bin numbers treated as pass for yield calculation in the summary panel.
92
102
  * Defaults to `[1]`. Must match the `passBins` passed to `analyzeWaferLot` / `buildWaferMap`