@wafertools/wafermap 0.22.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/AGENTS.md +16 -5
  2. package/CHANGELOG.md +130 -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/charts/histogram.js +1 -1
  6. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  7. package/dist/packages/canvas-adapter/dieList.d.ts +41 -0
  8. package/dist/packages/canvas-adapter/dieList.js +2 -0
  9. package/dist/packages/canvas-adapter/index.d.ts +2 -0
  10. package/dist/packages/canvas-adapter/index.js +1 -1
  11. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  12. package/dist/packages/canvas-adapter/maplessSummary.d.ts +34 -0
  13. package/dist/packages/canvas-adapter/maplessSummary.js +1 -0
  14. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +2 -18
  15. package/dist/packages/canvas-adapter/renderWaferGallery.js +8 -8
  16. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +17 -1
  17. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  18. package/dist/packages/canvas-adapter/summaryPanel.d.ts +2 -0
  19. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  20. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  21. package/dist/packages/canvas-adapter/userGuideHtml.js +1 -1
  22. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  23. package/dist/packages/canvas-adapter/version.js +1 -1
  24. package/dist/packages/canvas-adapter/warnings.d.ts +6 -1
  25. package/dist/packages/core/classify.d.ts +6 -2
  26. package/dist/packages/core/dies.d.ts +67 -7
  27. package/dist/packages/core/dies.js +1 -1
  28. package/dist/packages/core/inference/pitch.js +1 -1
  29. package/dist/packages/core/probe.d.ts +5 -0
  30. package/dist/packages/core/probe.js +1 -1
  31. package/dist/packages/core/transforms.d.ts +4 -4
  32. package/dist/packages/core/transforms.js +1 -1
  33. package/dist/packages/renderer/buildView.d.ts +3 -3
  34. package/dist/packages/renderer/buildView.js +1 -1
  35. package/dist/packages/renderer/buildWaferMap.d.ts +25 -8
  36. package/dist/packages/renderer/buildWaferMap.js +1 -1
  37. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  38. package/dist/packages/stats/clusterDetection.d.ts +2 -2
  39. package/dist/packages/stats/patternClassification.d.ts +2 -2
  40. package/dist/packages/stats/regions.d.ts +6 -6
  41. package/dist/packages/stats/regions.js +1 -1
  42. package/dist/packages/stats/renderSummaryReport.js +17 -17
  43. package/llms.txt +1 -1
  44. 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 are
57
- `WaferWarning[]` — `{ code, message, severity }`. Branch on `code`, never on the
58
- prose. Geometry advisories are severity `'error'`: they mean dies may be drawn in
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
- - **Container needs a real height.** A zero-height parent renders nothing. This is
64
- the most common "it didn't work" report.
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
@@ -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,132 @@ under `### Breaking`.
19
22
 
20
23
  ---
21
24
 
25
+ ## [0.23.1] — 2026-08-18
26
+
27
+ ### Added
28
+
29
+ - **The Insights tab's chart cards, section grid, sub-tab bar, Group-by select, and toggle button now carry
30
+ stable `data-wmap-*` hooks**, for tooling (screenshot capture, e2e tests, visual regression, accessibility
31
+ audits) that needs to drive the tab from outside wmap's own code. Previously these nodes were bare —
32
+ distinguishable only by exact heading text or button `textContent`, which breaks silently on any copy
33
+ change. `cardShell()` sets `data-wmap-chart-card`/`data-wmap-chart-title` (the full title, so
34
+ e.g. "Yield by wafer" and a hypothetical "Yield by wafer (grouped)" stay unambiguous); `makeChartGridWrap()`
35
+ sets `data-wmap-chart-grid`; `makeLabeledSelect()` gained an `opts.hook` param (`select.dataset.wmapSelect`)
36
+ since the same factory also builds every per-panel "Group:" restrict dropdown and the histogram wafer
37
+ picker; `insightsTab.ts`'s sub-tab buttons set `data-wmap-insights-tab` to the view key
38
+ (`overview`/`distributions`/`correlation`) and the back button sets `data-wmap-insights-back`; the Insights
39
+ toggle button in both `renderWaferMap` and `renderWaferGallery` sets `data-wmap-insights-btn` — needed
40
+ because its `aria-label` itself toggles between `'Insights'` and `'Back to wafer/gallery view'`, so
41
+ `button[aria-label="Insights"]` only ever matched while closed.
42
+
43
+ ### Fixed
44
+
45
+ - **Grid pitch inference no longer stretches the die aspect ratio when only one axis has real spread.** The
46
+ circular-wafer constraint used to fill in an unknown aspect ratio whenever nearest-neighbour spacing looked
47
+ square, even when one axis had only a single distinct position observed. A handful of positioned dies
48
+ confined to one row (or column) isn't evidence the wafer is wider than it is tall — it just reflects which
49
+ subset of dies happen to carry positions — so the constraint is now skipped on a degenerate axis (dropping
50
+ confidence to 0.3), leaving the aspect ratio at the safer 1:1 default instead of guessing.
51
+
52
+ ### Breaking
53
+
54
+ - **`Die.x`/`y`/`physX`/`physY` are now optional**, not `number`. Existing code
55
+ that reads `die.x` as a bare number (e.g. arithmetic without a guard) now gets
56
+ a type error under strict TS — the runtime value for already-positioned data
57
+ is unchanged, only the type widened. New `isPositionedDie(die): die is PositionedDie`
58
+ (`@wafertools/wafermap/core`) narrows it back; every spatial function in the
59
+ library (region builders, cluster/pattern detection, `buildView`) now takes
60
+ `PositionedDie[]` rather than `Die[]`, so a caller passing unfiltered `Die[]`
61
+ into one of these will see the type error at the call site rather than a
62
+ runtime surprise. Its sibling `hasPosition(die)` is the looser guard for
63
+ inputs that carry `x`/`y` but no `physX`/`physY` (a raw `DieResult`, before
64
+ layout); use `isPositionedDie` for anything holding built `Die`s.
65
+
66
+ ### Added
67
+
68
+ - **`renderWaferMap` accepts a `RenderableWaferMap`** — `{ wafer, dies }` plus
69
+ every other `WaferMapResult` field as optional — rather than requiring a full
70
+ `WaferMapResult`. A `WaferMapResult` still satisfies it, so no caller needs to
71
+ change. This makes the gallery's own use honest: it renders each card through
72
+ `renderWaferMap`, but a card is a `WaferMapDisplayItem` carrying no
73
+ `dataCoverage`/`viewport`/`legendBox`/`binLegendRows`/`reticleConfig`, and it
74
+ used to be passed via `item as WaferMapResult`. That cast asserted fields
75
+ which were absent at runtime; reading one unguarded threw, and each such read
76
+ had to be found by hitting it. `collectWarnings` likewise now types
77
+ `result.warnings` as optional, which its body already handled.
78
+
79
+ - **Support for dies/wafers with no reported X/Y position.** Real-world data
80
+ sometimes has no spatial layout at all (wafer-number-only test logs), or a
81
+ lot/wafer where some dies report a position and others don't — previously
82
+ such a die was silently dropped by every parser upstream of this library, and
83
+ wmap itself had no representation for "no position" beyond crashing or
84
+ drawing garbage.
85
+ - `hasPosition(die)` / `isPositionedDie(die)` (`packages/core/dies.ts`) are
86
+ the predicates every spatial code path filters on — the first narrows
87
+ `x`/`y`, the second narrows `physX`/`physY` with them and so yields a
88
+ `PositionedDie`. `getDieKey` falls back to `` `id:${id}` `` for an
89
+ unpositioned die so two of them never collide.
90
+ - `buildWaferMap` partitions positioned/unpositioned dies before geometry
91
+ inference runs; unpositioned dies are folded back into the returned
92
+ `dies`/`dataCoverage`/`yield` afterward. `dataCoverage` gained
93
+ `unpositionedDies: number` (always present); `totalDies`/`filledDies`/
94
+ `ratio` stay scoped to positioned dies only. Yield is deliberately
95
+ **not** spatial — a coordinate-less die with bin data still counts toward
96
+ it, same as `isYieldEligibleDie` always ignored position.
97
+ - `analyzeWaferMap`/`regions.ts` exclude unpositioned dies from spatial
98
+ region families only (ring/quadrant/sector/reticle-position, cluster and
99
+ pattern detection). Everything value-based (yield, bin counts, per-test
100
+ stats, spec-limit yield) is unaffected — coordinate-less dies still count.
101
+ - A wafer with **zero** positioned dies never renders as a wafer-shaped
102
+ visual — a fabricated mosaic risks being misread as real spatial data.
103
+ `renderWaferMap`/`renderWaferGallery` instead show a compact,
104
+ plot-mode-aware summary in its place: a bin breakdown (reusing the
105
+ existing summary-panel component, so colours match the map's own bin
106
+ legend) for hard/soft-bin modes, or a small histogram for value mode
107
+ (coloured through the same colour-scheme/log-scale/spec-range resolution
108
+ the map's own colorbar uses). A **View die list** toggle reaches the full
109
+ per-die table (`buildDieListSection`, new, exported from `/render`) for
110
+ CSV export and per-die inspection, and **View chart** returns. A mixed
111
+ wafer keeps its normal map for the positioned dies, plus an expandable
112
+ "+N dies without position data" footer giving the same chart/die-list
113
+ toggle for the rest.
114
+ - The toolbar's spatial-only controls (zoom/pan/select/download,
115
+ orientation, overlays, legend position) are hidden on a fully
116
+ coordinate-less card — nothing to act on with no map drawn. Plot mode and
117
+ colour scheme stay, since both drive the summary's own appearance.
118
+ - A gallery card's own "expand" button now preserves that card's live view
119
+ state (plot mode, active test, colour scheme, …) when opening the
120
+ detached window/popup, rather than always reverting to the gallery's
121
+ shared default — a pre-existing gap for any card (not specific to
122
+ coordinate-less data) that became far more visible once a card's
123
+ representation could change shape between modes (bin breakdown vs.
124
+ histogram vs. map).
125
+
126
+ ### Fixed
127
+
128
+ - **`buildDieListSection`'s table was capped at a fixed 360px**, regardless of
129
+ how much room its container actually had — a short table floated in a
130
+ half-empty card, and a host mounting it inside a scrolling container of its
131
+ own got two nested scrollbars. It now fills its container by default
132
+ (`flex: 1; min-height: 0`); a new optional `maxHeight` re-imposes a cap for a
133
+ host with no definite-height container to fill.
134
+
135
+ ### Known gaps (not fixed this pass, tracked for a follow-up)
136
+
137
+ - Stacked plot modes (`stackedValues`/`stackedBins`/`stackedSoftBins`) discard
138
+ every individual wafer card from the gallery grid and pool across the lot —
139
+ a coordinate-less wafer's dies are correctly excluded from those pooled
140
+ aggregates (stacking is inherently position-based), but nothing in the UI
141
+ currently indicates a wafer was excluded; it's indistinguishable from that
142
+ wafer not existing in the lot at all.
143
+ - `hasPosition`, `PositionedDie`, `dataCoverage.unpositionedDies`, and
144
+ `buildDieListSection`/`DieListOptions` are documented in `docs/api.md`
145
+ (§12.1, §7); a dedicated numbered reference entry for `hasPosition` and
146
+ `buildDieListSection` alongside the library's other numbered API entries
147
+ has not been added yet.
148
+
149
+ ---
150
+
22
151
  ## [0.22.0] — 2026-08-04
23
152
 
24
153
  ### Breaking
@@ -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 +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,M=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((y,C)=>{const k=y.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(`${y.count}`,r+S/2,L-2))});const V=y=>c+(y-B)/ot*t;e.font="10px system-ui, sans-serif";for(const[y,C]of[[nt,"LSL"],[x,"USL"]]){if(y===void 0)continue;const k=V(y);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 y=0;y<=i.length;y+=ht){const C=y<i.length?i[y].rangeLow:i[i.length-1].rangeHigh,k=c+y*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;M&&!i.some(n=>n.groupKey===M)&&(M=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=M!==null&&M!==n.groupKey;t.style.opacity=l?"0.45":"1",M===n.groupKey&&(t.style.background=lt.bgHover),t.addEventListener("click",()=>{M=M===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===M?1:0)-(L.s.groupKey===M?1:0));for(const{s:r,i:L}of dt){const E=M===r.groupKey,ut=M!==null&&!E,ft=D(L);t.strokeStyle=ft,t.fillStyle=ft,t.lineWidth=E?2:1,t.globalAlpha=ut?.12:M?.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
+ 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 s=52,Ot=16,i=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 c=document.createElement("label");Object.assign(c.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 c.append(d,p),{wrap:c,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-s-Ot),plotH:Math.max(10,o-i-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",e.style.boxShadow=g.has(n)?`0 0 0 2px ${M.text}`:"none"}}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 c=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:c,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:c,h:d,plotW:p,plotH:r}=xt();u.width=Math.max(1,Math.floor(c*F)),u.height=Math.max(1,Math.floor(d*F)),u.style.width=`${c}px`,u.style.height=`${d}px`;const t=u.getContext("2d");if(t.setTransform(F,0,0,F,0,0),t.clearRect(0,0,c,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.",c/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 l=0;l<=y;l++){const S=w+n*l/y,a=s+l/y*p;t.beginPath(),t.moveTo(a,i),t.lineTo(a,i+r),t.stroke(),t.textAlign="center",t.textBaseline="top",t.fillText(v.tick(S),a,i+r+4);const L=k+o*l/y,x=i+(1-l/y)*r;t.beginPath(),t.moveTo(s,x),t.lineTo(s+p,x),t.stroke(),t.textAlign="right",t.textBaseline="middle",t.fillText(U.tick(L),s-4,x)}v.unitLabel&&At(t,v.unitLabel,s+p/2,i+r+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,i+r/2),t.rotate(-Math.PI/2),t.fillText(`(${U.unitLabel})`,0,0),t.restore());const W=g.size===0?h:h.filter(l=>g.has(G(l))),et=W.length>5e3?Math.ceil(W.length/5e3):1;t.globalAlpha=Math.max(.15,Math.min(.7,200/(W.length/et)));for(let l=0;l<W.length;l+=et){const S=W[l],a=s+(S.x-w)/n*p,L=i+(1-(S.y-k)/o)*r;t.beginPath(),t.arc(a,L,2.5,0,Math.PI*2),t.fillStyle=Q(G(S)),t.fill()}if(t.globalAlpha=1,m!==null&&f!==null){const l=$(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[a,L]of[[l.limitLow,"LSL"],[l.limitHigh,"USL"]]){if(a===void 0||a<w||a>I)continue;const x=s+(a-w)/n*p;t.beginPath(),t.moveTo(x,i),t.lineTo(x,i+r),t.stroke(),t.textAlign="left",t.fillText(L,x+2,i+2)}for(const[a,L]of[[S.limitLow,"LSL"],[S.limitHigh,"USL"]]){if(a===void 0||a<k||a>N)continue;const x=i+(1-(a-k)/o)*r;t.beginPath(),t.moveTo(s,x),t.lineTo(s+p,x),t.stroke(),t.textAlign="left",t.fillText(L,s+3,x+2)}t.setLineDash([])}t.strokeStyle=e.border,t.lineWidth=1,t.beginPath(),t.moveTo(s,i),t.lineTo(s,i+r),t.moveTo(s,i+r),t.lineTo(s+p,i+r),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),c=Math.min(...n),d=Math.max(...n),p=Math.min(...o),r=Math.max(...o),t=(d-c)*.05||1,y=(r-p)*.05||1;w=c-t,I=d+t,k=p-y,N=r+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()}}
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";
@@ -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()}}}
@@ -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>;