@wafertools/wafermap 0.30.3 → 0.30.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +2 -2
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +5 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +11 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.js +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +38 -36
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/dies.d.ts +1 -1
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +40 -12
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/derivedTests/parser.js +1 -1
- package/dist/packages/renderer/spec.d.ts +17 -0
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +4 -2
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/correlation.d.ts +1 -1
- package/dist/packages/stats/deprecated.d.ts +1 -1
- package/dist/packages/stats/deprecated.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/renderSummaryReport.js +11 -11
- package/dist/packages/stats/sweep.d.ts +8 -0
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/types.d.ts +9 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,54 @@ under `### Breaking`.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## [Unreleased]
|
|
26
|
+
|
|
27
|
+
## [0.30.4] — 2026-09-25
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`input-values-outside-stdf` warning** from `buildWaferMap`: bins, coordinates, test
|
|
32
|
+
numbers or site numbers outside the STDF V4 ranges, test values that are not finite, and a
|
|
33
|
+
`waferConfig.orientation` other than 0, 90, 180 or 270. The values are used as given; a
|
|
34
|
+
future release treats them as missing.
|
|
35
|
+
- **`DieResult.supersedes`** (`'partId' | 'position'`): the tester marked the record as
|
|
36
|
+
replacing an earlier one. Such a record always wins, whatever `retestPolicy` says.
|
|
37
|
+
- **Retests of dies with no position are matched by part ID** within a wafer, resolved by
|
|
38
|
+
`retestPolicy` and reported with an `info` warning, `retests-by-part-id`. Blank part IDs
|
|
39
|
+
never match, and part IDs are not used on a wafer where one value covers more than 20% of
|
|
40
|
+
the unpositioned records.
|
|
41
|
+
- **`TestDef.specLow` / `TestDef.specHigh`**: a test's spec limits, separate from its test
|
|
42
|
+
limits (`limitLow`/`limitHigh`), as STDF records them. Process capability uses the spec
|
|
43
|
+
limits when both are given and the test limits otherwise, and reports which as
|
|
44
|
+
`TestCapability.limitBasis`. Wafers that disagree on a test's spec limits raise a
|
|
45
|
+
`specLimits` test-definition conflict.
|
|
46
|
+
- **Sweeps tab notice for sweeps that name no test in the data**, and
|
|
47
|
+
**`InsightsOptions.onRemoveSweeps`**, which adds a Remove button to it. The sweep's own card
|
|
48
|
+
says the same in place of an empty chart.
|
|
49
|
+
- **`TestDef.limitLowInclusive` / `TestDef.limitHighInclusive`**: whether a value exactly
|
|
50
|
+
equal to a test limit passes (default `true`), as STDF and ATDF state per test. Every
|
|
51
|
+
pass/fail judgement, yield and out-of-limit marker honours them. Wafers that disagree on
|
|
52
|
+
them raise a `limits` test-definition conflict.
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- **`DieResult.partId` and `Die.partId` accept text** (`number | string`), as STDF defines
|
|
57
|
+
part IDs; a number is still accepted.
|
|
58
|
+
- **Scatter chart legend:** hard bin 0 has its own entry and colour, and dies without a bin
|
|
59
|
+
are listed last as "No bin data".
|
|
60
|
+
- **A `NaN` bin is no bin**: it is neither a bin number nor a fail, and is counted in the new
|
|
61
|
+
warning.
|
|
62
|
+
- **One spec-limit judgement** (`classifySpec`) serves every surface, including the per-test
|
|
63
|
+
spec yield, lot-stack fail filtering, regional spec-fail findings and the Summary panel.
|
|
64
|
+
A value that is not a finite number has no verdict everywhere, rather than counting as a
|
|
65
|
+
pass on some surfaces.
|
|
66
|
+
- **Bin colours** for a bin that is not a whole number are taken from the whole bin below it.
|
|
67
|
+
- **Test limits and spec limits are labelled apart.** `limitLow`/`limitHigh` are test limits,
|
|
68
|
+
labelled "Lo limit"/"Hi limit" on charts; LSL/USL appear only where a test's spec limits
|
|
69
|
+
are shown. The pass/fail option is "Limit pass/fail", the Summary panel's yield column is
|
|
70
|
+
"Limit yield", and the capability chart, tooltip and report name the limits each test was
|
|
71
|
+
judged against.
|
|
72
|
+
|
|
25
73
|
## [0.30.3] — 2026-09-23
|
|
26
74
|
|
|
27
75
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/wafertools/wafermap/actions/workflows/deploy.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/@wafertools/wafermap)
|
|
7
7
|

|
|
8
|
-

|
|
9
9
|
[](./LICENSE)
|
|
10
10
|
|
|
11
11
|
<img src="docs/images/hero-test-values.png" alt="wafermap demo" style="max-width:640px; display:block; margin:8px 0;" />
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
Browser-first wafer map visualization for semiconductor test data.
|
|
14
14
|
|
|
15
15
|
**Zero runtime dependencies.** Pure ES modules with TypeScript types — works in React,
|
|
16
|
-
Svelte, Vue, plain HTML, or a Web Worker. The DOM-free data-and-stats layer is ~
|
|
16
|
+
Svelte, Vue, plain HTML, or a Web Worker. The DOM-free data-and-stats layer is ~61 kB
|
|
17
17
|
min+gz; the interactive renderer is larger, and its chart suite and in-app guide are
|
|
18
18
|
loaded on demand rather than shipped up front — [measured sizes](docs/performance.md).
|
|
19
19
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestBoxplotData as nt}from"../../stats/boxplot.js";import{SPACE as Be,fontPx as Y,FONT as it,CLR as ot}from"../toolbar.js";import{fmt as he}from"../../renderer/fmt.js";import{fitTicks as lt}from"../../renderer/axisTicks.js";import{QUANTITY as He}from"./palette.js";import{cardShell as st,observeResize as at,makeTooltip as rt,positionChartTooltip as ct,makeBackButton as ft,makeLinkedTestSelect as ut,makeToggle as mt,makeLinkedAxisPrefs as dt,renderEmptyState as Re,fitRowsHeight as xt,setChartGrow as pt,resolveChartCanvasColors as ht,makeAxisFormat as De,horizontalTickSpacing as gt,resolveAxisRange as bt,shouldIncludeLimitsByDefault as Tt,drawOffAxisLimits as yt,limitLabelSide as wt,PADDING as
|
|
1
|
+
import{buildTestBoxplotData as nt}from"../../stats/boxplot.js";import{SPACE as Be,fontPx as Y,FONT as it,CLR as ot}from"../toolbar.js";import{fmt as he}from"../../renderer/fmt.js";import{fitTicks as lt}from"../../renderer/axisTicks.js";import{QUANTITY as He}from"./palette.js";import{cardShell as st,observeResize as at,makeTooltip as rt,positionChartTooltip as ct,makeBackButton as ft,makeLinkedTestSelect as ut,makeToggle as mt,makeLinkedAxisPrefs as dt,renderEmptyState as Re,fitRowsHeight as xt,setChartGrow as pt,resolveChartCanvasColors as ht,makeAxisFormat as De,horizontalTickSpacing as gt,resolveAxisRange as bt,shouldIncludeLimitsByDefault as Tt,drawOffAxisLimits as yt,limitLabelSide as wt,PADDING as M,VALUE_WIDTH as vt,WAFER_MENU_HINT as kt,prepareCanvas as Mt}from"./chartShell.js";import{escHtml as L,maxOf as Lt,minOf as Ct}from"../../core/utils.js";const C=24,A=5,We=110,At=12,Oe=20;export function renderBoxplotPanel(u){const{title:ee="Test value distribution",items:Ee,testDefs:ge,onSaveImage:Pe,groups:g,groupLabelText:te="group",onOpen:z}=u,_e=u.openActionLabel??"open that wafer",Ge=u.openActionLabel??"open this wafer",{card:a,heading:Ne,body:x,controlsRow:Q}=st(ee,Pe,u.ownerDocument);pt(a,"rows"),a.style.flex="0 0 auto",x.style.flex="0 0 auto",a.style.alignSelf="start";const ne=ge.filter(i=>i.testNumber!==void 0);let y=u.selectedTestNumber??ne[0]?.testNumber??null,ie=!1,U=0,b=null,R=null,oe=null,be=null;function Xe(){const i=!g||g.length===0?"":b??"\0overview";if(oe&&be===i)return oe;const p=!g||g.length===0?Ee:b!==null?g.find(h=>h.key===b)?.items??[]:g.map(h=>({label:h.key,dies:h.items.flatMap(f=>f.dies??[])}));return oe=p,be=i,p}function Te(){b!==null&&!R?(R=ft(()=>{ye(null),u.onGroupChange?.(null)},a.ownerDocument),Q.appendChild(R)):b===null&&R&&(R.remove(),R=null),Ne.textContent=b!==null?`${ee} \u2014 ${te}: ${b}`:ee}function ye(i){return b===i||i!==null&&!(g??[]).some(p=>p.key===i)?!1:(b=i,Te(),F(),$(),!0)}const we=ut(ne,y,i=>{y=i,$(),u.onTestChange?.(i)},{maxWidth:"240px",emptyText:"No parametric tests",ownerDocument:a.ownerDocument}),qe=we.el;Q.appendChild(qe),Q.appendChild(mt("Log scale",ie,i=>{ie=i,$()},a.ownerDocument));const le=a.ownerDocument.createElement("span");Object.assign(le.style,{display:"inline-flex",gap:Be.lg,alignItems:"center"}),Q.appendChild(le);const se=dt(le,u.axisPrefs,i=>{u.onAxisPrefsChange?.(i),$()},a.ownerDocument),ae=a.ownerDocument.createElement("div");Object.assign(ae.style,{color:ot.label,fontSize:it.body,marginBottom:Be.sm}),a.insertBefore(ae,x);function F(){const i=!!g&&g.length>0&&b===null,p=[];i?p.push(`click a ${te}'s box to see it by wafer`):z&&p.push(`click a box to ${_e}`);const h=p.length?`${p[0][0].toUpperCase()}${p[0].slice(1)} \xB7 `:"";ae.textContent=`${h}box = Q1\u2013Q3, line = median, whiskers = min/max \xB7 value shown is the median`+(U?` \xB7 axis clipped, ${U} value${U===1?"":"s"} outside`:"")}F(),Te();const D=rt(a);let re=null;function $(){const{includeLimits:i,clipOutliers:p}=se.get();if(F(),x.innerHTML="",ne.length===0||y===null){Re(x,"No parametric test data available for box plots.");return}const h=Xe(),f=nt(h,y),ce=ge.find(t=>t.testNumber===y),W=ce?.unit,V=ce?.limitLow,j=ce?.limitHigh;if(f.every(t=>t.count===0)){Re(x,"No parametric test data available for box plots.");return}const m=a.ownerDocument.createElement("canvas");m.style.display="block",m.style.cursor="default",x.appendChild(m);const ve=M*2+Math.min(f.length,At)*(C+A)+Oe;x.style.maxHeight=`${ve}px`,x.style.overflowY="auto",x.style.scrollbarGutter="stable";let P=-1;const fe=f.filter(t=>t.count>0),ke=Ct(fe.map(t=>t.min)),Me=Lt(fe.map(t=>t.max)),Le=i??Tt(ke,Me,V,j);se.sync(Le,V!==void 0||j!==void 0);const _=bt({dataMin:ke,dataMax:Me,limitLow:V,limitHigh:j,includeLimits:Le,clipOutliers:p,values:fe.flatMap(t=>[t.min,t.q1,t.median,t.q3,t.max])});U=_.clippedCount,F();const O=_.lo,K=_.hi,Qe=K-O||1,J=ie&&O>0,ue=J?Math.log10(O):0,Ce=(J?Math.log10(K):0)-ue||1,me=Math.max(Math.abs(O),Math.abs(K));function Ue(){const t=M+We,s=m.clientWidth-t-vt-M;return{plotX:t,plotMaxWidth:Math.max(10,s)}}function S(t,s,n){return J?s+(Math.log10(t)-ue)/Ce*n:s+(t-O)/Qe*n}function Fe(t,s){if(J){const d=[];for(let e=0;e<=4;e++)d.push(Math.pow(10,ue+Ce*e/4));return{ticks:d,axis:De(me,W)}}const n=lt(O,K,s,gt(t,me,W));return{ticks:n.ticks,axis:De(me,W,n.step||void 0)}}function Z(){const t=ht(a),s=M*2+f.length*(C+A)+Oe;x.style.maxHeight=`${xt(a,x,ve,s)}px`;const n=x.clientWidth,d=Mt(m,a,n,s);if(!d)return;const{ctx:e}=d;e.font=`${Y(-1)}px system-ui, sans-serif`,e.textBaseline="middle";const{plotX:l,plotMaxWidth:r}=Ue();f.forEach((o,T)=>{const v=M+T*(C+A),c=v+C/2;T===P&&(e.fillStyle=t.bgHover,e.fillRect(0,v-A/2,n,C+A)),e.fillStyle=t.text,e.textAlign="right";const Ke=o.label.length>16?`${o.label.slice(0,15)}\u2026`:o.label;if(e.fillText(Ke,M+We-8,c),o.count===0){e.fillStyle=t.textMuted,e.textAlign="left",e.fillText("no data",l,c);return}const Je=l+r,N=tt=>Math.min(Je,Math.max(l,tt)),$e=S(o.min,l,r),Se=S(o.max,l,r),I=N($e),X=N(S(o.q1,l,r)),Ie=N(S(o.median,l,r)),pe=N(S(o.q3,l,r)),B=N(Se),Ze=$e<I,et=Se>B,H=v+3,q=v+C-3;e.strokeStyle=t.textMuted,e.lineWidth=1,e.beginPath(),e.moveTo(I,c),e.lineTo(X,c),e.moveTo(pe,c),e.lineTo(B,c);const k=3;Ze?(e.moveTo(I+k,c-k),e.lineTo(I,c),e.lineTo(I+k,c+k)):(e.moveTo(I,H),e.lineTo(I,q)),et?(e.moveTo(B-k,c-k),e.lineTo(B,c),e.lineTo(B-k,c+k)):(e.moveTo(B,H),e.lineTo(B,q)),e.stroke(),e.fillStyle=He,e.globalAlpha=.35,e.fillRect(X,H,Math.max(1,pe-X),q-H),e.globalAlpha=1,e.strokeStyle=He,e.strokeRect(X,H,Math.max(1,pe-X),q-H),e.strokeStyle=t.text,e.lineWidth=2,e.beginPath(),e.moveTo(Ie,H),e.lineTo(Ie,q),e.stroke(),e.lineWidth=1,e.fillStyle=t.textMuted,e.textAlign="left",e.fillText(he(o.median,W,"engineering"),l+r+10,c)});const w=M+f.length*(C+A);e.font=`${Y(-1)}px system-ui, sans-serif`,yt(e,_.offAxis,{left:l,right:l+r,top:0,bottom:w},"horizontal",t.limitLine,o=>he(o,W,"engineering"));const Ve=new Set(_.offAxis.map(o=>o.value));for(const[o,T]of[[V,"Lo limit"],[j,"Hi limit"]]){if(o===void 0||Ve.has(o))continue;const v=S(o,l,r);e.strokeStyle=t.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(v,0),e.lineTo(v,w),e.stroke(),e.setLineDash([]),e.fillStyle=t.limitLine;const c=wt(v,e.measureText(T).width,l,l+r,T==="Lo limit");e.textAlign=c<0?"right":"left",e.textBaseline="bottom",e.fillText(T,v+c*3,w-1)}e.font=`${Y(-1)}px system-ui, sans-serif`,e.strokeStyle=t.border,e.lineWidth=1,e.beginPath(),e.moveTo(l,w),e.lineTo(l+r,w),e.stroke(),e.fillStyle=t.textMuted,e.textAlign="center",e.textBaseline="top";const{ticks:je,axis:xe}=Fe(e,r);for(const o of je){const T=S(o,l,r);e.beginPath(),e.moveTo(T,w),e.lineTo(T,w+4),e.stroke(),e.fillText(xe.tick(o),T,w+6)}xe.unitLabel&&(e.textAlign="left",e.font=`${Y(-1)}px system-ui, sans-serif`,e.fillText(`(${xe.unitLabel})`,l+r+22,w+6),e.font=`${Y(-1)}px system-ui, sans-serif`),e.textBaseline="middle"}function de(t){const s=Math.floor((t-M+A/2)/(C+A));return s>=0&&s<f.length?s:-1}function G(t){return he(t,W,"engineering")}const E=!!g&&g.length>0&&b===null,Ae=t=>!E&&!!z&&h[t]?.waferIndex!==void 0;m.addEventListener("mousemove",t=>{const s=m.getBoundingClientRect(),n=de(t.clientY-s.top),d=n>=0&&f[n].count>0&&(E||Ae(n));if(n!==P&&(P=n,m.style.cursor=d?"pointer":"default",Z()),n>=0&&f[n].count>0){const e=f[n],l=E?`<br><em>click to see this ${L(te)} by wafer</em>`:Ae(n)?`<br><em>click to ${L(Ge)}</em>`:"",r=!E&&u.onWaferContextMenu&&h[n]?.waferIndex!==void 0?kt:"";D.innerHTML=`<strong>${L(e.label)}</strong> (${e.count} dies)<br>max ${L(G(e.max))}<br>q3 ${L(G(e.q3))}<br>median ${L(G(e.median))}<br>q1 ${L(G(e.q1))}<br>min ${L(G(e.min))}${l}${r}`,D.style.display="block",ct(D,a,t.clientX,t.clientY)}else D.style.display="none"}),m.addEventListener("mouseleave",()=>{P!==-1&&(P=-1,Z()),D.style.display="none"}),m.addEventListener("click",t=>{const s=m.getBoundingClientRect(),n=de(t.clientY-s.top);if(n===-1||f[n].count===0)return;if(E){const e=f[n].label;ye(e),u.onGroupChange?.(e);return}const d=h[n]?.waferIndex;z&&d!==void 0&&y!==null&&z(d,y)}),m.addEventListener("contextmenu",t=>{const s=m.getBoundingClientRect(),n=de(t.clientY-s.top),d=n===-1?void 0:h[n]?.waferIndex;E||d===void 0||!u.onWaferContextMenu||(D.style.display="none",u.onWaferContextMenu(d,y??void 0,t))}),re?.disconnect(),re=at(a,()=>Z()),Z()}$();function Ye(i){we.set(i)&&(y=i,$())}function ze(i){se.set(i)&&$()}return{card:a,setTest:Ye,setAxisPrefs:ze,destroy:()=>re?.disconnect()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildCapabilityData as
|
|
1
|
+
import{buildCapabilityData as ve}from"../../stats/capability.js";import{capabilityColor as U}from"./palette.js";import{LEADING as Se,SPACE as T,fontPx as J,FONT as Z,CLR as _}from"../toolbar.js";import{cardShell as Me,isExpandedCard as Te,chartFillHeight as $e,applyCanvasFlow as Ce,observeResize as Le,makeTooltip as ke,positionChartTooltip as Pe,renderEmptyState as Ee,resolveChartCanvasColors as De,chartSwatchCss as Ae,prepareCanvas as We,chartDpr as we}from"./chartShell.js";import{fmt as Ne}from"../../renderer/fmt.js";import{escHtml as V}from"../../core/utils.js";import{DERIVED_MARK as ee,DERIVED_KEY as le}from"../../renderer/testLabel.js";function N(v){return v==="spec"?{lo:"LSL",hi:"USL",noun:"spec limits"}:{lo:"Lo limit",hi:"Hi limit",noun:"test limits"}}function ae(v){let D;for(const z of v){if(!z.hasSpec)continue;const A=z.limitBasis??"test";if(D===void 0)D=A;else if(D!==A)return"mixed"}return D}const Ie=30,re=160,ce=90,me=28,C=42;function j(v){return v===null?"\u2014":v.toFixed(2)}export function renderCapabilityPanel(v){const{title:D="Process capability",items:z,testDefs:A,onSaveImage:de,onSelectTest:X}=v;let q=v.selectedTestNumber??null;const{card:m,body:L}=Me(D,de,v.ownerDocument),pe=A.some(t=>t.testNumber!==void 0);L.style.overflowX="auto";function he(){return z}const W=m.ownerDocument.createElement("div");Object.assign(W.style,{display:"flex",flexDirection:"column",gap:T.xs,marginBottom:T.sm}),m.insertBefore(W,L);const I=ke(m);let F=()=>{};function fe(t,l,S,f,u){W.innerHTML="";const $=m.ownerDocument.createElement("span");Object.assign($.style,{display:"inline-flex",alignItems:"center",gap:T.sm,color:_.value,fontSize:Z.body,fontWeight:"500"});const d=S-t,y=l>0?` \xB7 ${l} without both limits`:"",M=m.ownerDocument.createElement("span");M.textContent=d>0?`${t} of ${S} tests shown${y} \xB7 ${d} excluded (no recorded values)`:`${t} test${t!==1?"s":""} shown${y}`,$.appendChild(M),W.appendChild($);const k=l>=t,b=m.ownerDocument.createElement("div");b.textContent=k?"No test has both limits, so each is normalised to its own observed range (min = 0, max = 1) and sorted most-variable first. Ppk needs limits, so none is shown.":(u==="mixed"?"Normalised to each test's spec limits (LSL = 0, USL = 1), or its test limits (Lo limit = 0, Hi limit = 1) where it has no spec limits, worst Ppk first. ":`Normalised to ${N(u).noun} (${N(u).lo} = 0, ${N(u).hi} = 1), worst Ppk first. `)+"Tests without both limits are normalised to their own observed range and drawn muted/dashed.",Object.assign(b.style,{color:_.label,fontSize:Z.body,lineHeight:Se.base,marginTop:T.xs,maxWidth:"78ch"}),W.appendChild(b),W.appendChild(ue(m.ownerDocument,l>0,k,f>0))}function ue(t,l,S=!1,f=!1){const u=t.createElement("div");Object.assign(u.style,{display:"flex",flexWrap:"wrap",alignItems:"center",gap:`${T.xs} ${T.xl}`,marginTop:T.xl,marginBottom:T.xs,fontSize:Z.body,color:_.label});const $=S?[]:[{color:U(2),label:"Capable \xB7 Ppk \u2265 1.33"},{color:U(1.1),label:"Marginal \xB7 Ppk \u2265 1.0"},{color:U(.5),label:"Poor \xB7 Ppk < 1.0"}];l&&$.push({color:_.label,label:"No limits \xB7 no Ppk",dashed:!0});for(const d of $){const y=t.createElement("span");Object.assign(y.style,{display:"inline-flex",alignItems:"center",gap:T.sm});const M=t.createElement("span");M.style.cssText=Ae(d.color,d.dashed?"outline":"solid");const k=t.createElement("span");k.textContent=d.label,y.append(M,k),u.appendChild(y)}if(f){const d=t.createElement("span");Object.assign(d.style,{display:"inline-flex",alignItems:"center",gap:T.sm});const y=t.createElement("span");y.textContent=ee,Object.assign(y.style,{color:_.value,fontWeight:"600"});const M=t.createElement("span");M.textContent=le,d.append(y,M),u.appendChild(d)}return u}function be(t){if(L.innerHTML="",t.length===0)return Ee(L,"No parametric tests have any recorded values.",{maxWidth:"480px"}),()=>{};const l=m.ownerDocument.createElement("canvas");l.style.display="block",l.style.cursor="default",L.appendChild(l);const S=t.length,f=Math.min(0,...t.map(n=>n.min)),u=Math.max(1,...t.map(n=>n.max)),$=(u-f)*.05||.1,d=f-$,M=u+$-d||1;function k(n){const o=Math.floor(n/S);return Math.max(Ie,Te(m)?o:Math.min(re,o))}function b(n,o,i){return o+(1-(n-d)/M)*i}function ge(n,o,i,s,P){const x=t.filter(g=>g.hasSpec).length,w=x===t.length,p=x===0,E=ae(t),e=E==="mixed"?{lo:"Lo",hi:"Hi",noun:"limits"}:N(E),h=p?"max":E==="spec"?e.hi:"Hi",a=p?"min":E==="spec"?e.lo:"Lo",H=w?`normalised to ${e.noun}`:p?"normalised to range":"normalised (per test)";n.font=`${J(-1)}px system-ui, sans-serif`,n.textAlign="right",n.textBaseline="middle";for(const[g,r]of[[1,h],[0,a]]){const c=b(g,i,s);c<i-1||c>i+s+1||(n.strokeStyle=o.limitLine,n.lineWidth=1,n.beginPath(),n.moveTo(C-5,c),n.lineTo(C,c),n.stroke(),n.fillStyle=o.textMuted,n.fillText(r,C-8,c))}n.save(),n.translate(9,i+s/2),n.rotate(-Math.PI/2),n.textAlign="center",n.fillStyle=o.textMuted,n.fillText(H,0,0),n.restore()}let B=-1;function G(){Ce(l);const n=L.clientWidth,o=k(n-C),i=o*S,s=me,P=C+i,x=$e(m,L,l,me+200+ce),w=Math.max(s+60,x-ce),p=w-s,E=We(l,m,P,x);if(!E)return;const{ctx:e}=E,h=De(m);t.forEach((a,H)=>{const g=C+H*o,r=g+o/2,c=Math.max(4,Math.min(o,re)*.55);if(H===B&&(e.fillStyle=h.bgHover,e.fillRect(g,s,o,w-s)),t[H].testNumber===q&&(e.fillStyle=h.accent,e.fillRect(g,w-2,o,2)),a.hasSpec){const oe=b(0,s,p),se=b(1,s,p);e.strokeStyle=h.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(g,oe),e.lineTo(g+o,oe),e.moveTo(g,se),e.lineTo(g+o,se),e.stroke(),e.setLineDash([])}const K=a.hasSpec?U(a.ppk):h.textMuted,Q=b(a.min,s,p),R=b(a.q1,s,p),ne=b(a.median,s,p),O=b(a.q3,s,p),Y=b(a.max,s,p);e.strokeStyle=h.textMuted,e.lineWidth=1,e.beginPath(),e.moveTo(r,Q),e.lineTo(r,R),e.moveTo(r,O),e.lineTo(r,Y),e.moveTo(r-c/4,Q),e.lineTo(r+c/4,Q),e.moveTo(r-c/4,Y),e.lineTo(r+c/4,Y),e.stroke(),e.fillStyle=K,e.globalAlpha=.5,e.fillRect(r-c/2,Math.min(R,O),c,Math.max(1,Math.abs(O-R))),e.globalAlpha=1,e.strokeStyle=K,a.hasSpec||e.setLineDash([4,2]),e.strokeRect(r-c/2,Math.min(R,O),c,Math.max(1,Math.abs(O-R))),e.setLineDash([]),e.strokeStyle=h.text,e.lineWidth=2,e.beginPath(),e.moveTo(r-c/2,ne),e.lineTo(r+c/2,ne),e.stroke(),e.lineWidth=1,e.font=`${J(-1)}px system-ui, sans-serif`,e.textAlign="center",e.textBaseline="alphabetic",e.fillStyle=a.hasSpec?K:h.textMuted,e.fillText(a.hasSpec?j(a.ppk):"\u2014",r,s-8);const ie=a.label.length>12?`${a.label.slice(0,11)}\u2026`:a.label;e.save(),e.translate(r,w+6),e.rotate(-Math.PI/4),e.textAlign="right",e.textBaseline="middle",e.font=`${J(-1)}px system-ui, sans-serif`,e.fillStyle=h.textMuted,e.fillText(ie,0,0),a.derived&&(e.fillStyle=h.text,e.fillText(`${ee} `,-e.measureText(ie).width,0)),e.restore()}),e.strokeStyle=h.border,e.lineWidth=1,e.strokeRect(C,s,i,p),ge(e,h,s,p,i)}function te(n){const o=l.getBoundingClientRect(),i=L.clientWidth,s=k(i-C),P=(n.clientX-o.left)*(l.width/we(l)/o.width)-C,x=Math.floor(P/s);return x>=0&&x<S?x:-1}return l.addEventListener("mousemove",n=>{const o=te(n);if(o!==B&&(B=o,G()),o===-1){I.style.display="none";return}const i=t[o],s=x=>V(Ne(x,i.unit)),P=i.derived?`<em>${ee} ${V(le)}</em>`+(i.expression?`<br><code>${V(i.expression)}</code>`:"")+"<br>":"";I.innerHTML=`<strong>${V(i.label)}</strong> (n=${i.n})<br>${P}`+(i.hasSpec?`${N(i.limitBasis).lo} ${s(i.lsl)} \xB7 ${N(i.limitBasis).hi} ${s(i.usl)}<br>mean ${s(i.mean)}<br>Cp ${j(i.cp)} \xB7 Cpk ${j(i.cpk)}<br>Pp ${j(i.pp)} \xB7 Ppk ${j(i.ppk)}`:`<em>No limits \u2014 sorted by variability</em><br>mean ${s(i.mean)} \xB7 stddev ${s(i.stdOverall)}`)+(X?"<br><em>click to view in boxplot</em>":""),I.style.display="block",Pe(I,m,n.clientX,n.clientY),l.style.cursor=X?"pointer":"default"}),l.addEventListener("mouseleave",()=>{B!==-1&&(B=-1,G()),I.style.display="none"}),l.addEventListener("click",n=>{if(!X)return;const o=te(n);o!==-1&&X(t[o].testNumber)}),G}function ye(){const t=ve(he(),A),l=A.filter(f=>f.testNumber!==void 0).length,S=t.filter(f=>!f.hasSpec).length;fe(t.length,S,l,t.filter(f=>f.derived).length,ae(t)),F=be(t),F()}const xe=Le(m,()=>F());return ye(),{card:m,hasData:pe,setTest:t=>{q!==t&&(q=t,F())},destroy:()=>xe.disconnect()}}
|
|
@@ -524,7 +524,7 @@ export interface AxisRange {
|
|
|
524
524
|
* is merely off-screen is indistinguishable from a test having no limit. */
|
|
525
525
|
offAxis: Array<{
|
|
526
526
|
value: number;
|
|
527
|
-
label: '
|
|
527
|
+
label: 'Lo limit' | 'Hi limit';
|
|
528
528
|
side: 'lo' | 'hi';
|
|
529
529
|
}>;
|
|
530
530
|
/** Values excluded by the robust fence, when clipping is on. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SHADOW as j,LEADING as H,wireControlHover as L,controlStyle as T,SPACE as m,RADIUS as E,fontPx as F,FONT as v,CLR as p,Z_BASE as U,menuLayerFor as X,wireListNavigation as Y,MENU_SEARCH_THRESHOLD as N,makeMenuSearchBox as K,markMenuTrigger as M,saveImageBlob as Z,openReparentedModal as J}from"../toolbar.js";import{ICONS as P}from"../icons.js";import{fmt as Q,fmtColorbarAxis as ee}from"../../renderer/fmt.js";import{markedTestLabel as te,DERIVED_LANE_PAD as ne}from"../../renderer/testLabel.js";export function openChartExpandModal(e,i,n={}){for(const a of e.querySelectorAll('div[style*="position: absolute"]'))a.style.display==="block"&&a.style.pointerEvents==="none"&&(a.style.display="none");const t=e.querySelector("[data-wmap-chart-expand]"),o=e.getAttribute("style")??"",r=e.dataset.wmapChartGrow??"plot";Object.assign(e.style,{flex:"1",minHeight:"0",alignSelf:"stretch",border:"none",borderRadius:"0"}),e.dataset.wmapExpanded="1";const l=J([e],{title:i,anchor:n.anchor,ownerDocument:e.ownerDocument,boxSize:oe[r],onClosed:()=>{delete e.dataset.wmapExpanded,e.setAttribute("style",o),t&&(t.style.display="flex"),n.onClosed?.()}});if(!l){delete e.dataset.wmapExpanded,e.setAttribute("style",o);return}if(t&&(t.style.display="none"),r==="rows"){const a=()=>{const s=Number(e.dataset.wmapChartFullH);if(!s)return;const c=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(c+s)}px)`)};a(),(e.ownerDocument.defaultView??window).requestAnimationFrame(a)}}const oe={plot:{width:"min(94vw, 1100px)",height:"min(90vh, 760px)"},rows:{width:"min(94vw, 960px)",height:"min(90vh, 700px)"},square:{width:"min(94vw, 820px)",height:"min(90vh, 860px)"}},ie={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},W={text:"text",textMuted:"text-muted",border:"border",bg:"surface",bgHover:"bg-hover",track:"panel-bg",warnBorder:"warn-border",accent:"icon-active",limitLine:"warn-text"};export function resolveChartCanvasColors(e){const i=getComputedStyle(e),n={};for(const t of Object.keys(W)){const o=i.getPropertyValue(`--wmap-${W[t]}`).trim();n[t]=o||ie[t]}return n}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,i){const n=e.ownerDocument.defaultView??window;let t=0;const o=new n.ResizeObserver(()=>{t||(t=n.requestAnimationFrame(()=>{t=0,i()}))});return o.observe(e),{disconnect:()=>{t&&n.cancelAnimationFrame(t),t=0,o.disconnect()}}}export function growCardToFitContent(e,i,n){const t=`${R(e,i)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function R(e,i){const n=e.ownerDocument.defaultView??window,t=n.getComputedStyle(e);let o=0;for(const r of Array.from(e.children)){if(r===i)continue;const l=r,a=n.getComputedStyle(l);a.display==="none"||a.position==="absolute"||a.position==="fixed"||(o+=l.offsetHeight+(parseFloat(a.marginTop)||0)+(parseFloat(a.marginBottom)||0))}return t.boxSizing==="border-box"&&(o+=(parseFloat(t.paddingTop)||0)+(parseFloat(t.paddingBottom)||0)+(parseFloat(t.borderTopWidth)||0)+(parseFloat(t.borderBottomWidth)||0)),o}function _(e,i){const n=e.ownerDocument.defaultView??window;let t=0;for(const o of Array.from(e.children)){if(o===i)continue;const r=o,l=n.getComputedStyle(r);l.display==="none"||l.position==="absolute"||l.position==="fixed"||(t+=r.offsetHeight+(parseFloat(l.marginTop)||0)+(parseFloat(l.marginBottom)||0))}return t}export const WAFER_MENU_HINT="<br><em>right-click to chart this wafer</em>";export function populationPhrase(e,i,n){const t=e.toLocaleString(),o=e===1?"die":"dies";return n?e===i?`${t} ${o} ${n}`:`${t} of ${i.toLocaleString()} dies ${n} (partial and edge-excluded dies left out)`:`${t} ${o}`}export function setChartGrow(e,i){e.dataset.wmapChartGrow=i}export function isExpandedCard(e){return e.dataset.wmapExpanded==="1"}export function bodyRoom(e,i,n){const o=(e.ownerDocument.defaultView??window).getComputedStyle(e),r=e.clientHeight-(parseFloat(o.paddingTop)||0)-(parseFloat(o.paddingBottom)||0),l=o.boxSizing==="border-box"?(parseFloat(o.paddingTop)||0)+(parseFloat(o.paddingBottom)||0)+(parseFloat(o.borderTopWidth)||0)+(parseFloat(o.borderBottomWidth)||0):0;return r-(R(e,i)-l)-(n?_(i,n):0)}export function fitRowsHeight(e,i,n,t,o=0){if(growCardToFitContent(e,i,o+n),e.dataset.wmapChartFullH=String(Math.ceil(R(e,i)+o+t)),!isExpandedCard(e))return n;const r=bodyRoom(e,i)-o;return Math.max(n,Math.min(t,Math.floor(r)))}export function chartFillHeight(e,i,n,t){const o=_(i,n);return growCardToFitContent(e,i,o+t),Math.max(t,i.clientHeight-o)}export function chartDpr(e){return(e.ownerDocument.defaultView??window).devicePixelRatio||1}export function prepareCanvas(e,i,n,t){const o=chartDpr(e);e.width=Math.max(1,Math.floor(n*o)),e.height=Math.max(1,Math.floor(t*o)),e.style.width=`${n}px`,e.style.height=`${t}px`;const r=e.getContext("2d");return r?(r.setTransform(o,0,0,o,0,0),r.clearRect(0,0,n,t),r.font=`${F(-1)}px system-ui, sans-serif`,r.textBaseline="middle",{ctx:r,theme:resolveChartCanvasColors(i)}):null}export function applyCanvasFlow(e,i=0){let n;if(typeof i=="number")n=i;else{const t=(i.ownerDocument.defaultView??window).getComputedStyle(i);n=i.offsetTop+i.offsetHeight+(parseFloat(t.marginBottom)||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}`:Q(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,i,n){if(!i&&n===void 0)return{tick:formatValue,tickWithUnit:formatValue,unitLabel:""};const{tickFmt:t,axisLabel:o,scaledUnit:r}=ee(e,null,i,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:o}}const re=8;export const VERTICAL_TICK_SPACING_PX=28;export function horizontalTickSpacing(e,i,n){return(t,o)=>{const r=makeAxisFormat(i,n,t).tick;return o.reduce((l,a)=>Math.max(l,e.measureText(r(a)).width),0)+re}}export function drawAxisUnit(e,i,n,t,o){const r={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font=`${F(-1)}px system-ui, sans-serif`,e.textAlign="left",e.textBaseline="middle",e.fillStyle=o,e.fillText(`(${i})`,n,t),Object.assign(e,r)}export function saveCanvasPng(e,i,n){const t=e.ownerDocument.createElement("canvas");t.width=e.width,t.height=e.height;const o=t.getContext("2d");o.fillStyle="#ffffff",o.fillRect(0,0,t.width,t.height),o.drawImage(e,0,0),t.toBlob(r=>{r&&Z(r,i,n)},"image/png")}const z="10px";export function chartSwatchCss(e,i="solid"){return["display:inline-block",`width:${z}`,`height:${z}`,`border-radius:${E.pill}`,`background:${i==="outline"?"transparent":e}`,`border:1px ${i==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const le="0.35";export function makeSeriesLegendItem(e,i,n=document){const t=n.createElement("button");t.type="button",Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:m.xs,padding:`${m.xs} ${m.md}`,borderRadius:E.pill,border:`1px solid ${p.menuBorder}`,background:"none",cursor:"pointer",fontSize:v.body,color:p.text,whiteSpace:"nowrap"}),L(t,"bare");const o=n.createElement("span");return o.style.cssText=chartSwatchCss(i),t.append(o,n.createTextNode(e)),{el:t,setState({selected:r,dimmed:l}){t.style.borderColor=r?p.iconActive:p.menuBorder,t.style.fontWeight=r?"600":"400",t.style.opacity=l?le:"1",t.setAttribute("aria-pressed",r?"true":"false")}}}export function cardFrameStyle(){return{background:p.menuBg,border:`1px solid ${p.menuBorder}`,borderRadius:E.container,padding:m.xl,fontVariantNumeric:"tabular-nums"}}export function cardShell(e,i,n=document){const t=n.createElement("div");t.dataset.wmapChartCard="1",t.dataset.wmapChartTitle=e,Object.assign(t.style,{display:"flex",flexDirection:"column",...cardFrameStyle(),minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const o=t.ownerDocument.createElement("div");Object.assign(o.style,{display:"flex",alignItems:"center",gap:m.sm,paddingBottom:"6px",marginBottom:m.md,borderBottom:`1px solid ${p.menuBorder}`});const r=t.ownerDocument.createElement("div");r.textContent=e,Object.assign(r.style,{color:p.value,fontSize:v.heading,fontWeight:"600",flex:"1"}),o.appendChild(r);const l=makeTooltip(t),a=t.ownerDocument.createElement("button");a.type="button",a.setAttribute("aria-label","Save as PNG"),a.innerHTML=P.download,Object.assign(a.style,{...T("outlined"),color:p.label,padding:"0",width:"22px",height:"22px",lineHeight:H.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(a),attachChartTip(a,t,l,"Save as PNG"),a.addEventListener("click",()=>{const f=t.querySelector("canvas");f&&saveCanvasPng(f,e,i)}),o.appendChild(a);const s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Expand"),s.innerHTML=P.expand,Object.assign(s.style,{...T("outlined"),color:p.label,padding:"0",width:"22px",height:"22px",lineHeight:H.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(s),attachChartTip(s,t,l,"Expand"),s.dataset.wmapChartExpand="1",s.addEventListener("click",()=>openChartExpandModal(t,r.textContent??e)),o.appendChild(s),t.appendChild(o);const c=t.ownerDocument.createElement("div");Object.assign(c.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(c);const d=t.ownerDocument.createElement("div");return Object.assign(d.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),t.appendChild(d),{card:t,heading:r,controlsRow:c,body:d}}export function makeSegmented(e,i,n,t=document,o=!1){const r=t.createElement("div");r.setAttribute("role","radiogroup"),Object.assign(r.style,{display:"inline-flex",border:`1px solid ${p.menuBorder}`,borderRadius:E.control,overflow:"hidden"});const l=`seg-${Math.random().toString(36).slice(2,9)}`,a=[];return e.forEach(([s,c],d)=>{const f=t.createElement("label");Object.assign(f.style,{display:"inline-flex",alignItems:"center",fontSize:v.body,padding:o?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:d>0?`1px solid ${p.menuBorder}`:"none"}),L(f,"bare");const u=t.createElement("input");u.type="radio",u.name=l,u.value=s,u.checked=s===i,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{f.style.background=u.checked?p.bgActive:p.menuBg,f.style.color=u.checked?p.iconActive:p.text,f.dataset.on=u.checked?"true":"false"};a.push(w),w(),u.addEventListener("change",()=>{if(u.checked){for(const C of a)C();n(s)}}),f.append(u,t.createTextNode(c)),r.appendChild(f)}),r}export function makeBackButton(e,i=document){const n=i.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...T("outlined"),background:p.menuBg,color:p.label}),L(n),n.addEventListener("click",e),n}export function renderEmptyState(e,i,n){const t=e.ownerDocument.createElement("div");t.textContent=i,Object.assign(t.style,{color:p.label,fontSize:v.body,padding:"8px 0"}),n&&Object.assign(t.style,n),e.appendChild(t)}export function makeListSelect(e,i,n,t={}){const{maxWidth:o="200px",ownerDocument:r=document,ariaLabel:l,emptyText:a,searchPlaceholder:s="Filter\u2026",hook:c}=t;let d=i;const f=b=>e.find($=>$.value===b)?.label??"",u=r.createElement("button");u.type="button",c&&(u.dataset.wmapSelect=c),l&&u.setAttribute("aria-label",l),Object.assign(u.style,{fontSize:v.body,padding:`${m.xxs} ${m.sm}`,background:p.menuBg,color:p.text,border:`1px solid ${p.menuBorder}`,borderRadius:E.control,maxWidth:o,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:m.sm,justifyContent:"space-between"}),L(u),M(u,!1);const w=r.createElement("span");Object.assign(w.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const C=r.createElement("span");C.textContent="\u25BE",C.style.flex="0 0 auto",u.append(w,C);const S=()=>{w.textContent=e.length===0?a??"(none)":f(d)||a||"(none)"};if(S(),e.length===0)return u.disabled=!0,u.style.opacity="0.6",u.style.cursor="default",Object.defineProperty(u,"value",{get:()=>d,set:b=>{d=b,S()}});let x=null;const k=()=>{x&&(x.remove(),x=null,M(u,!1))},A=()=>{k(),u.focus()};function V(){const b=u.getBoundingClientRect(),$=r.defaultView??window,D=Math.max(b.width,220),G=Math.min(b.left,Math.max(4,($.innerWidth??1/0)-D-4));x=r.createElement("div"),x.setAttribute("role","listbox"),x.setAttribute("aria-label",l??"Options"),Object.assign(x.style,{position:"fixed",top:`${b.bottom+4}px`,left:`${G}px`,background:p.menuBg,border:`1px solid ${p.menuBorder}`,borderRadius:E.control,boxShadow:j.menu,zIndex:U,minWidth:`${D}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const B=[],O=()=>B.filter(h=>h.row.style.display!=="none").map(h=>h.row);if(e.length>N){const h=K(y=>{for(const g of B)g.row.style.display=g.label.includes(y)?"":"none"},s,r);Object.assign(h.style,{position:"sticky",top:"0",zIndex:"1",background:p.menuBg}),h.addEventListener("keydown",y=>{if(y.key==="Escape"){A();return}y.key==="ArrowDown"&&(y.preventDefault(),O()[0]?.focus())}),x.appendChild(h)}for(const h of e){const y=h.value===d,g=r.createElement("div");g.textContent=h.label,g.setAttribute("role","option"),g.setAttribute("aria-selected",y?"true":"false"),g.tabIndex=-1,Object.assign(g.style,{padding:`${m.sm} ${m.xl}`,fontSize:v.body,cursor:"pointer",color:y?p.iconActive:p.text,fontWeight:y?"700":"400",background:y?p.menuActive:"transparent",whiteSpace:"nowrap"}),g.addEventListener("mouseenter",()=>{y||(g.style.background=p.menuHover)}),g.addEventListener("mouseleave",()=>{y||(g.style.background="transparent")}),g.addEventListener("click",q=>{q.stopPropagation(),d=h.value,S(),A(),n(h.value)}),x.appendChild(g),B.push({row:g,label:h.label.toLowerCase()})}if(Y(x,O,A),X(u).appendChild(x),M(u,!0),e.length<=N){const h=e.findIndex(y=>y.value===d);O()[h>=0?h:0]?.focus()}}u.addEventListener("click",b=>{if(b.stopPropagation(),x){k();return}V()});const I=b=>{if(!u.isConnected){r.removeEventListener("click",I);return}x&&!x.contains(b.target)&&!u.contains(b.target)&&k()};return r.addEventListener("click",I),Object.defineProperty(u,"value",{get:()=>d,set:b=>{d=b,S()}})}export function testOptionLabels(e){const i=e.some(n=>n.derived);return e.map(n=>(i&&!n.derived?ne:"")+te(n,n.testNumber))}export function makeTestSelect(e,i,n,t={}){const{maxWidth:o="200px",emptyText:r="No parametric tests",ownerDocument:l=document}=t;return makeListSelect(testOptionLabels(e).map((a,s)=>({value:String(e[s].testNumber),label:a})),i!==null?String(i):"",a=>n(Number(a)),{maxWidth:o,ownerDocument:l,ariaLabel:"Test",emptyText:r,searchPlaceholder:"Filter tests\u2026"})}export function makeLinkedAxisPrefs(e,i,n,t=document){let o=i?.includeLimits,r=i?.clipOutliers??!1;const l=()=>n({includeLimits:o,clipOutliers:r});return{get:()=>({includeLimits:o,clipOutliers:r}),sync(a,s){e.innerHTML="",s&&e.appendChild(makeToggle("Axis includes limits",a,c=>{o=c,l()},t)),e.appendChild(makeToggle("Clip outliers",r,c=>{r=c,l()},t))},set(a){return a.includeLimits===o&&a.clipOutliers===r?!1:(o=a.includeLimits,r=a.clipOutliers,!0)}}}export function makeLinkedTestSelect(e,i,n,t={}){let o=i;const r=makeTestSelect(e,o,l=>{l!==o&&(o=l,n(l))},t);return{el:r,get:()=>o,set(l){return o===l||!e.some(a=>a.testNumber===l)?!1:(o=l,r.value=String(l),!0)}}}export const ALL_GROUPS="\0all";export function makeLinkedGroupSelect(e,i,n,t,o={}){let r=n;const l=d=>d??ALL_GROUPS,a=d=>d===ALL_GROUPS?null:d,s=makeLabeledSelect(i,[{value:ALL_GROUPS,label:"All groups"},...e.map(d=>({value:d,label:d}))],l(r),d=>{const f=a(d);f!==r&&(r=f,t(f))},{...o,hook:"group-scope"}),c=s.querySelector('[data-wmap-select="group-scope"]');return{el:s,get:()=>r,set(d){return r===d||d!==null&&!e.includes(d)?!1:(r=d,c&&(c.value=l(d)),!0)}}}export function makeWaferSelect(e,i,n,t={}){const{maxWidth:o="160px",allLabel:r="All wafers",ownerDocument:l=document}=t,a="\0all";return makeListSelect([{value:a,label:r},...e.map((s,c)=>({value:String(c),label:s.label??`#${c}`}))],i===null?a:String(i),s=>n(s===a?null:Number(s)),{maxWidth:o,ownerDocument:l,ariaLabel:r,searchPlaceholder:"Filter wafers\u2026"})}export function makeToggle(e,i,n,t=document){const o=t.createElement("label");Object.assign(o.style,{display:"inline-flex",alignItems:"center",gap:m.xs,fontSize:v.body,color:p.label,cursor:"pointer",userSelect:"none"}),L(o,"bare");const r=t.createElement("input");return r.type="checkbox",r.checked=i,r.style.cssText="margin:0;cursor:pointer;",r.addEventListener("change",()=>n(r.checked)),o.append(r,t.createTextNode(e)),o}export function makeLabeledSelect(e,i,n,t,o={}){const{maxWidth:r="160px",hook:l,ownerDocument:a=document}=o,s=a.createElement("label");s.textContent=e,Object.assign(s.style,{color:p.label,fontSize:v.body,display:"flex",alignItems:"center",gap:m.xs});const c=makeListSelect(i,n,t,{maxWidth:r,ownerDocument:a,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return s.appendChild(c),s}export function makeChartGridWrap(e=document){const i=e.createElement("div");return i.dataset.wmapChartGrid="1",Object.assign(i.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:m.lg,flex:"0 0 auto"}),i}export function makeTooltip(e){const i=e.ownerDocument.createElement("div");return Object.assign(i.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:"rgba(30, 32, 40, 0.93)",color:"#f0f0f2",border:"1px solid rgba(255,255,255,0.10)",borderRadius:E.control,padding:`${m.sm} ${m.xl}`,fontSize:v.sub,fontFamily:v.family,maxWidth:"320px",whiteSpace:"normal",overflowWrap:"break-word",boxShadow:j.menu}),e.appendChild(i),i}export function attachChartTip(e,i,n,t){e.removeAttribute("title"),e.textContent?.trim()||(e.ariaLabel=t),e.addEventListener("mouseenter",o=>{n.textContent=t,n.style.display="block",positionChartTooltip(n,i,o.clientX,o.clientY)}),e.addEventListener("mousemove",o=>{n.style.display==="block"&&positionChartTooltip(n,i,o.clientX,o.clientY)}),e.addEventListener("mouseleave",()=>{n.style.display="none"}),e.addEventListener("click",()=>{n.style.display="none"}),e.addEventListener("focus",()=>{n.textContent=t,n.style.display="block";const o=e.getBoundingClientRect();positionChartTooltip(n,i,o.left+o.width/2,o.bottom)}),e.addEventListener("blur",()=>{n.style.display="none"})}export function positionChartTooltip(e,i,n,t){const o=i.getBoundingClientRect(),r=14,l=4;e.style.left="0",e.style.top="0";const a=e.offsetWidth,s=e.offsetHeight;let c=n-o.left+r,d=t-o.top+r;c+a+l>o.width&&(c=n-o.left-r-a),d+s+l>o.height&&(d=t-o.top-r-s),c=Math.max(l,c),d=Math.max(l,d),e.style.left=`${c}px`,e.style.top=`${d}px`}export function robustFence(e,i=1.5){const n=Float64Array.from(e.filter(a=>Number.isFinite(a)));if(n.sort(),n.length<8)return null;const t=a=>{const s=(n.length-1)*a,c=Math.floor(s),d=Math.ceil(s);return c===d?n[c]:n[c]+(n[d]-n[c])*(s-c)},o=t(.25),r=t(.75),l=r-o;return l===0?null:{lo:o-i*l,hi:r+i*l}}export function shouldIncludeLimitsByDefault(e,i,n,t,o=1/3){if(n===void 0&&t===void 0||!Number.isFinite(e)||!Number.isFinite(i))return!1;const r=Math.min(e,n??e),a=Math.max(i,t??i)-r;if(a<=0)return!0;const s=i-e;return s===0?!0:s/a>=o}export function resolveAxisRange(e){const{dataMin:i,dataMax:n,limitLow:t,limitHigh:o,includeLimits:r,clipOutliers:l,values:a}=e;let s=i,c=n,d=0;if(l&&a?.length){const u=robustFence(a);if(u){const w=Math.max(s,u.lo),C=Math.min(c,u.hi);C>w&&(d=a.filter(S=>Number.isFinite(S)&&(S<w||S>C)).length,s=w,c=C)}}r&&(t!==void 0&&(s=Math.min(s,t)),o!==void 0&&(c=Math.max(c,o))),s===c&&(s-=1,c+=1);const f=[];return t!==void 0&&t<s&&f.push({value:t,label:"LSL",side:"lo"}),t!==void 0&&t>c&&f.push({value:t,label:"LSL",side:"hi"}),o!==void 0&&o>c&&f.push({value:o,label:"USL",side:"hi"}),o!==void 0&&o<s&&f.push({value:o,label:"USL",side:"lo"}),{lo:s,hi:c,offAxis:f,clippedCount:d}}export function limitLabelSide(e,i,n,t,o,r=3){const l=o?-1:1;return(l<0?e-r-i>=n:e+r+i<=t)?l:-l}export function drawOffAxisLimits(e,i,n,t,o,r){if(i.length){e.save(),e.fillStyle=o,e.font=`${F(-1)}px system-ui, sans-serif`;for(const{value:l,label:a,side:s}of i){const c=t==="horizontal"?s==="lo"?"\u2190":"\u2192":s==="lo"?"\u2193":"\u2191",d=s==="lo"?`${c} ${a} ${r(l)}`:`${a} ${r(l)} ${c}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=s==="lo"?"left":"right",e.fillText(d,s==="lo"?n.left+2:n.right-2,n.top+2)):(e.textBaseline=s==="lo"?"bottom":"top",e.textAlign="left",e.fillText(d,n.left+4,s==="lo"?n.bottom-2:n.top+2))}e.restore()}}
|
|
1
|
+
import{SHADOW as D,LEADING as j,wireControlHover as E,controlStyle as T,SPACE as m,RADIUS as k,fontPx as F,FONT as v,CLR as p,Z_BASE as U,menuLayerFor as X,wireListNavigation as Y,MENU_SEARCH_THRESHOLD as N,makeMenuSearchBox as K,markMenuTrigger as M,saveImageBlob as Z,openReparentedModal as J}from"../toolbar.js";import{ICONS as P}from"../icons.js";import{fmt as Q,fmtColorbarAxis as ee}from"../../renderer/fmt.js";import{markedTestLabel as te,DERIVED_LANE_PAD as ne}from"../../renderer/testLabel.js";export function openChartExpandModal(e,i,n={}){for(const a of e.querySelectorAll('div[style*="position: absolute"]'))a.style.display==="block"&&a.style.pointerEvents==="none"&&(a.style.display="none");const t=e.querySelector("[data-wmap-chart-expand]"),o=e.getAttribute("style")??"",r=e.dataset.wmapChartGrow??"plot";Object.assign(e.style,{flex:"1",minHeight:"0",alignSelf:"stretch",border:"none",borderRadius:"0"}),e.dataset.wmapExpanded="1";const l=J([e],{title:i,anchor:n.anchor,ownerDocument:e.ownerDocument,boxSize:oe[r],onClosed:()=>{delete e.dataset.wmapExpanded,e.setAttribute("style",o),t&&(t.style.display="flex"),n.onClosed?.()}});if(!l){delete e.dataset.wmapExpanded,e.setAttribute("style",o);return}if(t&&(t.style.display="none"),r==="rows"){const a=()=>{const s=Number(e.dataset.wmapChartFullH);if(!s)return;const c=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(c+s)}px)`)};a(),(e.ownerDocument.defaultView??window).requestAnimationFrame(a)}}const oe={plot:{width:"min(94vw, 1100px)",height:"min(90vh, 760px)"},rows:{width:"min(94vw, 960px)",height:"min(90vh, 700px)"},square:{width:"min(94vw, 820px)",height:"min(90vh, 860px)"}},ie={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},W={text:"text",textMuted:"text-muted",border:"border",bg:"surface",bgHover:"bg-hover",track:"panel-bg",warnBorder:"warn-border",accent:"icon-active",limitLine:"warn-text"};export function resolveChartCanvasColors(e){const i=getComputedStyle(e),n={};for(const t of Object.keys(W)){const o=i.getPropertyValue(`--wmap-${W[t]}`).trim();n[t]=o||ie[t]}return n}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,i){const n=e.ownerDocument.defaultView??window;let t=0;const o=new n.ResizeObserver(()=>{t||(t=n.requestAnimationFrame(()=>{t=0,i()}))});return o.observe(e),{disconnect:()=>{t&&n.cancelAnimationFrame(t),t=0,o.disconnect()}}}export function growCardToFitContent(e,i,n){const t=`${R(e,i)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function R(e,i){const n=e.ownerDocument.defaultView??window,t=n.getComputedStyle(e);let o=0;for(const r of Array.from(e.children)){if(r===i)continue;const l=r,a=n.getComputedStyle(l);a.display==="none"||a.position==="absolute"||a.position==="fixed"||(o+=l.offsetHeight+(parseFloat(a.marginTop)||0)+(parseFloat(a.marginBottom)||0))}return t.boxSizing==="border-box"&&(o+=(parseFloat(t.paddingTop)||0)+(parseFloat(t.paddingBottom)||0)+(parseFloat(t.borderTopWidth)||0)+(parseFloat(t.borderBottomWidth)||0)),o}function _(e,i){const n=e.ownerDocument.defaultView??window;let t=0;for(const o of Array.from(e.children)){if(o===i)continue;const r=o,l=n.getComputedStyle(r);l.display==="none"||l.position==="absolute"||l.position==="fixed"||(t+=r.offsetHeight+(parseFloat(l.marginTop)||0)+(parseFloat(l.marginBottom)||0))}return t}export const WAFER_MENU_HINT="<br><em>right-click to chart this wafer</em>";export function populationPhrase(e,i,n){const t=e.toLocaleString(),o=e===1?"die":"dies";return n?e===i?`${t} ${o} ${n}`:`${t} of ${i.toLocaleString()} dies ${n} (partial and edge-excluded dies left out)`:`${t} ${o}`}export function setChartGrow(e,i){e.dataset.wmapChartGrow=i}export function isExpandedCard(e){return e.dataset.wmapExpanded==="1"}export function bodyRoom(e,i,n){const o=(e.ownerDocument.defaultView??window).getComputedStyle(e),r=e.clientHeight-(parseFloat(o.paddingTop)||0)-(parseFloat(o.paddingBottom)||0),l=o.boxSizing==="border-box"?(parseFloat(o.paddingTop)||0)+(parseFloat(o.paddingBottom)||0)+(parseFloat(o.borderTopWidth)||0)+(parseFloat(o.borderBottomWidth)||0):0;return r-(R(e,i)-l)-(n?_(i,n):0)}export function fitRowsHeight(e,i,n,t,o=0){if(growCardToFitContent(e,i,o+n),e.dataset.wmapChartFullH=String(Math.ceil(R(e,i)+o+t)),!isExpandedCard(e))return n;const r=bodyRoom(e,i)-o;return Math.max(n,Math.min(t,Math.floor(r)))}export function chartFillHeight(e,i,n,t){const o=_(i,n);return growCardToFitContent(e,i,o+t),Math.max(t,i.clientHeight-o)}export function chartDpr(e){return(e.ownerDocument.defaultView??window).devicePixelRatio||1}export function prepareCanvas(e,i,n,t){const o=chartDpr(e);e.width=Math.max(1,Math.floor(n*o)),e.height=Math.max(1,Math.floor(t*o)),e.style.width=`${n}px`,e.style.height=`${t}px`;const r=e.getContext("2d");return r?(r.setTransform(o,0,0,o,0,0),r.clearRect(0,0,n,t),r.font=`${F(-1)}px system-ui, sans-serif`,r.textBaseline="middle",{ctx:r,theme:resolveChartCanvasColors(i)}):null}export function applyCanvasFlow(e,i=0){let n;if(typeof i=="number")n=i;else{const t=(i.ownerDocument.defaultView??window).getComputedStyle(i);n=i.offsetTop+i.offsetHeight+(parseFloat(t.marginBottom)||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}`:Q(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,i,n){if(!i&&n===void 0)return{tick:formatValue,tickWithUnit:formatValue,unitLabel:""};const{tickFmt:t,axisLabel:o,scaledUnit:r}=ee(e,null,i,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:o}}const re=8;export const VERTICAL_TICK_SPACING_PX=28;export function horizontalTickSpacing(e,i,n){return(t,o)=>{const r=makeAxisFormat(i,n,t).tick;return o.reduce((l,a)=>Math.max(l,e.measureText(r(a)).width),0)+re}}export function drawAxisUnit(e,i,n,t,o){const r={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font=`${F(-1)}px system-ui, sans-serif`,e.textAlign="left",e.textBaseline="middle",e.fillStyle=o,e.fillText(`(${i})`,n,t),Object.assign(e,r)}export function saveCanvasPng(e,i,n){const t=e.ownerDocument.createElement("canvas");t.width=e.width,t.height=e.height;const o=t.getContext("2d");o.fillStyle="#ffffff",o.fillRect(0,0,t.width,t.height),o.drawImage(e,0,0),t.toBlob(r=>{r&&Z(r,i,n)},"image/png")}const z="10px";export function chartSwatchCss(e,i="solid"){return["display:inline-block",`width:${z}`,`height:${z}`,`border-radius:${k.pill}`,`background:${i==="outline"?"transparent":e}`,`border:1px ${i==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const le="0.35";export function makeSeriesLegendItem(e,i,n=document){const t=n.createElement("button");t.type="button",Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:m.xs,padding:`${m.xs} ${m.md}`,borderRadius:k.pill,border:`1px solid ${p.menuBorder}`,background:"none",cursor:"pointer",fontSize:v.body,color:p.text,whiteSpace:"nowrap"}),E(t,"bare");const o=n.createElement("span");return o.style.cssText=chartSwatchCss(i),t.append(o,n.createTextNode(e)),{el:t,setState({selected:r,dimmed:l}){t.style.borderColor=r?p.iconActive:p.menuBorder,t.style.fontWeight=r?"600":"400",t.style.opacity=l?le:"1",t.setAttribute("aria-pressed",r?"true":"false")}}}export function cardFrameStyle(){return{background:p.menuBg,border:`1px solid ${p.menuBorder}`,borderRadius:k.container,padding:m.xl,fontVariantNumeric:"tabular-nums"}}export function cardShell(e,i,n=document){const t=n.createElement("div");t.dataset.wmapChartCard="1",t.dataset.wmapChartTitle=e,Object.assign(t.style,{display:"flex",flexDirection:"column",...cardFrameStyle(),minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const o=t.ownerDocument.createElement("div");Object.assign(o.style,{display:"flex",alignItems:"center",gap:m.sm,paddingBottom:"6px",marginBottom:m.md,borderBottom:`1px solid ${p.menuBorder}`});const r=t.ownerDocument.createElement("div");r.textContent=e,Object.assign(r.style,{color:p.value,fontSize:v.heading,fontWeight:"600",flex:"1"}),o.appendChild(r);const l=makeTooltip(t),a=t.ownerDocument.createElement("button");a.type="button",a.setAttribute("aria-label","Save as PNG"),a.innerHTML=P.download,Object.assign(a.style,{...T("outlined"),color:p.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),E(a),attachChartTip(a,t,l,"Save as PNG"),a.addEventListener("click",()=>{const f=t.querySelector("canvas");f&&saveCanvasPng(f,e,i)}),o.appendChild(a);const s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Expand"),s.innerHTML=P.expand,Object.assign(s.style,{...T("outlined"),color:p.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),E(s),attachChartTip(s,t,l,"Expand"),s.dataset.wmapChartExpand="1",s.addEventListener("click",()=>openChartExpandModal(t,r.textContent??e)),o.appendChild(s),t.appendChild(o);const c=t.ownerDocument.createElement("div");Object.assign(c.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(c);const d=t.ownerDocument.createElement("div");return Object.assign(d.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),t.appendChild(d),{card:t,heading:r,controlsRow:c,body:d}}export function makeSegmented(e,i,n,t=document,o=!1){const r=t.createElement("div");r.setAttribute("role","radiogroup"),Object.assign(r.style,{display:"inline-flex",border:`1px solid ${p.menuBorder}`,borderRadius:k.control,overflow:"hidden"});const l=`seg-${Math.random().toString(36).slice(2,9)}`,a=[];return e.forEach(([s,c],d)=>{const f=t.createElement("label");Object.assign(f.style,{display:"inline-flex",alignItems:"center",fontSize:v.body,padding:o?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:d>0?`1px solid ${p.menuBorder}`:"none"}),E(f,"bare");const u=t.createElement("input");u.type="radio",u.name=l,u.value=s,u.checked=s===i,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{f.style.background=u.checked?p.bgActive:p.menuBg,f.style.color=u.checked?p.iconActive:p.text,f.dataset.on=u.checked?"true":"false"};a.push(w),w(),u.addEventListener("change",()=>{if(u.checked){for(const C of a)C();n(s)}}),f.append(u,t.createTextNode(c)),r.appendChild(f)}),r}export function makeBackButton(e,i=document){const n=i.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...T("outlined"),background:p.menuBg,color:p.label}),E(n),n.addEventListener("click",e),n}export function renderEmptyState(e,i,n){const t=e.ownerDocument.createElement("div");t.textContent=i,Object.assign(t.style,{color:p.label,fontSize:v.body,padding:"8px 0"}),n&&Object.assign(t.style,n),e.appendChild(t)}export function makeListSelect(e,i,n,t={}){const{maxWidth:o="200px",ownerDocument:r=document,ariaLabel:l,emptyText:a,searchPlaceholder:s="Filter\u2026",hook:c}=t;let d=i;const f=b=>e.find($=>$.value===b)?.label??"",u=r.createElement("button");u.type="button",c&&(u.dataset.wmapSelect=c),l&&u.setAttribute("aria-label",l),Object.assign(u.style,{fontSize:v.body,padding:`${m.xxs} ${m.sm}`,background:p.menuBg,color:p.text,border:`1px solid ${p.menuBorder}`,borderRadius:k.control,maxWidth:o,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:m.sm,justifyContent:"space-between"}),E(u),M(u,!1);const w=r.createElement("span");Object.assign(w.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const C=r.createElement("span");C.textContent="\u25BE",C.style.flex="0 0 auto",u.append(w,C);const S=()=>{w.textContent=e.length===0?a??"(none)":f(d)||a||"(none)"};if(S(),e.length===0)return u.disabled=!0,u.style.opacity="0.6",u.style.cursor="default",Object.defineProperty(u,"value",{get:()=>d,set:b=>{d=b,S()}});let x=null;const L=()=>{x&&(x.remove(),x=null,M(u,!1))},A=()=>{L(),u.focus()};function V(){const b=u.getBoundingClientRect(),$=r.defaultView??window,I=Math.max(b.width,220),G=Math.min(b.left,Math.max(4,($.innerWidth??1/0)-I-4));x=r.createElement("div"),x.setAttribute("role","listbox"),x.setAttribute("aria-label",l??"Options"),Object.assign(x.style,{position:"fixed",top:`${b.bottom+4}px`,left:`${G}px`,background:p.menuBg,border:`1px solid ${p.menuBorder}`,borderRadius:k.control,boxShadow:D.menu,zIndex:U,minWidth:`${I}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const B=[],O=()=>B.filter(h=>h.row.style.display!=="none").map(h=>h.row);if(e.length>N){const h=K(y=>{for(const g of B)g.row.style.display=g.label.includes(y)?"":"none"},s,r);Object.assign(h.style,{position:"sticky",top:"0",zIndex:"1",background:p.menuBg}),h.addEventListener("keydown",y=>{if(y.key==="Escape"){A();return}y.key==="ArrowDown"&&(y.preventDefault(),O()[0]?.focus())}),x.appendChild(h)}for(const h of e){const y=h.value===d,g=r.createElement("div");g.textContent=h.label,g.setAttribute("role","option"),g.setAttribute("aria-selected",y?"true":"false"),g.tabIndex=-1,Object.assign(g.style,{padding:`${m.sm} ${m.xl}`,fontSize:v.body,cursor:"pointer",color:y?p.iconActive:p.text,fontWeight:y?"700":"400",background:y?p.menuActive:"transparent",whiteSpace:"nowrap"}),g.addEventListener("mouseenter",()=>{y||(g.style.background=p.menuHover)}),g.addEventListener("mouseleave",()=>{y||(g.style.background="transparent")}),g.addEventListener("click",q=>{q.stopPropagation(),d=h.value,S(),A(),n(h.value)}),x.appendChild(g),B.push({row:g,label:h.label.toLowerCase()})}if(Y(x,O,A),X(u).appendChild(x),M(u,!0),e.length<=N){const h=e.findIndex(y=>y.value===d);O()[h>=0?h:0]?.focus()}}u.addEventListener("click",b=>{if(b.stopPropagation(),x){L();return}V()});const H=b=>{if(!u.isConnected){r.removeEventListener("click",H);return}x&&!x.contains(b.target)&&!u.contains(b.target)&&L()};return r.addEventListener("click",H),Object.defineProperty(u,"value",{get:()=>d,set:b=>{d=b,S()}})}export function testOptionLabels(e){const i=e.some(n=>n.derived);return e.map(n=>(i&&!n.derived?ne:"")+te(n,n.testNumber))}export function makeTestSelect(e,i,n,t={}){const{maxWidth:o="200px",emptyText:r="No parametric tests",ownerDocument:l=document}=t;return makeListSelect(testOptionLabels(e).map((a,s)=>({value:String(e[s].testNumber),label:a})),i!==null?String(i):"",a=>n(Number(a)),{maxWidth:o,ownerDocument:l,ariaLabel:"Test",emptyText:r,searchPlaceholder:"Filter tests\u2026"})}export function makeLinkedAxisPrefs(e,i,n,t=document){let o=i?.includeLimits,r=i?.clipOutliers??!1;const l=()=>n({includeLimits:o,clipOutliers:r});return{get:()=>({includeLimits:o,clipOutliers:r}),sync(a,s){e.innerHTML="",s&&e.appendChild(makeToggle("Axis includes limits",a,c=>{o=c,l()},t)),e.appendChild(makeToggle("Clip outliers",r,c=>{r=c,l()},t))},set(a){return a.includeLimits===o&&a.clipOutliers===r?!1:(o=a.includeLimits,r=a.clipOutliers,!0)}}}export function makeLinkedTestSelect(e,i,n,t={}){let o=i;const r=makeTestSelect(e,o,l=>{l!==o&&(o=l,n(l))},t);return{el:r,get:()=>o,set(l){return o===l||!e.some(a=>a.testNumber===l)?!1:(o=l,r.value=String(l),!0)}}}export const ALL_GROUPS="\0all";export function makeLinkedGroupSelect(e,i,n,t,o={}){let r=n;const l=d=>d??ALL_GROUPS,a=d=>d===ALL_GROUPS?null:d,s=makeLabeledSelect(i,[{value:ALL_GROUPS,label:"All groups"},...e.map(d=>({value:d,label:d}))],l(r),d=>{const f=a(d);f!==r&&(r=f,t(f))},{...o,hook:"group-scope"}),c=s.querySelector('[data-wmap-select="group-scope"]');return{el:s,get:()=>r,set(d){return r===d||d!==null&&!e.includes(d)?!1:(r=d,c&&(c.value=l(d)),!0)}}}export function makeWaferSelect(e,i,n,t={}){const{maxWidth:o="160px",allLabel:r="All wafers",ownerDocument:l=document}=t,a="\0all";return makeListSelect([{value:a,label:r},...e.map((s,c)=>({value:String(c),label:s.label??`#${c}`}))],i===null?a:String(i),s=>n(s===a?null:Number(s)),{maxWidth:o,ownerDocument:l,ariaLabel:r,searchPlaceholder:"Filter wafers\u2026"})}export function makeToggle(e,i,n,t=document){const o=t.createElement("label");Object.assign(o.style,{display:"inline-flex",alignItems:"center",gap:m.xs,fontSize:v.body,color:p.label,cursor:"pointer",userSelect:"none"}),E(o,"bare");const r=t.createElement("input");return r.type="checkbox",r.checked=i,r.style.cssText="margin:0;cursor:pointer;",r.addEventListener("change",()=>n(r.checked)),o.append(r,t.createTextNode(e)),o}export function makeLabeledSelect(e,i,n,t,o={}){const{maxWidth:r="160px",hook:l,ownerDocument:a=document}=o,s=a.createElement("label");s.textContent=e,Object.assign(s.style,{color:p.label,fontSize:v.body,display:"flex",alignItems:"center",gap:m.xs});const c=makeListSelect(i,n,t,{maxWidth:r,ownerDocument:a,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return s.appendChild(c),s}export function makeChartGridWrap(e=document){const i=e.createElement("div");return i.dataset.wmapChartGrid="1",Object.assign(i.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:m.lg,flex:"0 0 auto"}),i}export function makeTooltip(e){const i=e.ownerDocument.createElement("div");return Object.assign(i.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:"rgba(30, 32, 40, 0.93)",color:"#f0f0f2",border:"1px solid rgba(255,255,255,0.10)",borderRadius:k.control,padding:`${m.sm} ${m.xl}`,fontSize:v.sub,fontFamily:v.family,maxWidth:"320px",whiteSpace:"normal",overflowWrap:"break-word",boxShadow:D.menu}),e.appendChild(i),i}export function attachChartTip(e,i,n,t){e.removeAttribute("title"),e.textContent?.trim()||(e.ariaLabel=t),e.addEventListener("mouseenter",o=>{n.textContent=t,n.style.display="block",positionChartTooltip(n,i,o.clientX,o.clientY)}),e.addEventListener("mousemove",o=>{n.style.display==="block"&&positionChartTooltip(n,i,o.clientX,o.clientY)}),e.addEventListener("mouseleave",()=>{n.style.display="none"}),e.addEventListener("click",()=>{n.style.display="none"}),e.addEventListener("focus",()=>{n.textContent=t,n.style.display="block";const o=e.getBoundingClientRect();positionChartTooltip(n,i,o.left+o.width/2,o.bottom)}),e.addEventListener("blur",()=>{n.style.display="none"})}export function positionChartTooltip(e,i,n,t){const o=i.getBoundingClientRect(),r=14,l=4;e.style.left="0",e.style.top="0";const a=e.offsetWidth,s=e.offsetHeight;let c=n-o.left+r,d=t-o.top+r;c+a+l>o.width&&(c=n-o.left-r-a),d+s+l>o.height&&(d=t-o.top-r-s),c=Math.max(l,c),d=Math.max(l,d),e.style.left=`${c}px`,e.style.top=`${d}px`}export function robustFence(e,i=1.5){const n=Float64Array.from(e.filter(a=>Number.isFinite(a)));if(n.sort(),n.length<8)return null;const t=a=>{const s=(n.length-1)*a,c=Math.floor(s),d=Math.ceil(s);return c===d?n[c]:n[c]+(n[d]-n[c])*(s-c)},o=t(.25),r=t(.75),l=r-o;return l===0?null:{lo:o-i*l,hi:r+i*l}}export function shouldIncludeLimitsByDefault(e,i,n,t,o=1/3){if(n===void 0&&t===void 0||!Number.isFinite(e)||!Number.isFinite(i))return!1;const r=Math.min(e,n??e),a=Math.max(i,t??i)-r;if(a<=0)return!0;const s=i-e;return s===0?!0:s/a>=o}export function resolveAxisRange(e){const{dataMin:i,dataMax:n,limitLow:t,limitHigh:o,includeLimits:r,clipOutliers:l,values:a}=e;let s=i,c=n,d=0;if(l&&a?.length){const u=robustFence(a);if(u){const w=Math.max(s,u.lo),C=Math.min(c,u.hi);C>w&&(d=a.filter(S=>Number.isFinite(S)&&(S<w||S>C)).length,s=w,c=C)}}r&&(t!==void 0&&(s=Math.min(s,t)),o!==void 0&&(c=Math.max(c,o))),s===c&&(s-=1,c+=1);const f=[];return t!==void 0&&t<s&&f.push({value:t,label:"Lo limit",side:"lo"}),t!==void 0&&t>c&&f.push({value:t,label:"Lo limit",side:"hi"}),o!==void 0&&o>c&&f.push({value:o,label:"Hi limit",side:"hi"}),o!==void 0&&o<s&&f.push({value:o,label:"Hi limit",side:"lo"}),{lo:s,hi:c,offAxis:f,clippedCount:d}}export function limitLabelSide(e,i,n,t,o,r=3){const l=o?-1:1;return(l<0?e-r-i>=n:e+r+i<=t)?l:-l}export function drawOffAxisLimits(e,i,n,t,o,r){if(i.length){e.save(),e.fillStyle=o,e.font=`${F(-1)}px system-ui, sans-serif`;for(const{value:l,label:a,side:s}of i){const c=t==="horizontal"?s==="lo"?"\u2190":"\u2192":s==="lo"?"\u2193":"\u2191",d=s==="lo"?`${c} ${a} ${r(l)}`:`${a} ${r(l)} ${c}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=s==="lo"?"left":"right",e.fillText(d,s==="lo"?n.left+2:n.right-2,n.top+2)):(e.textBaseline=s==="lo"?"bottom":"top",e.textAlign="left",e.fillText(d,n.left+4,s==="lo"?n.bottom-2:n.top+2))}e.restore()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestHistogramData as Ue,collectTestValues as He,buildTestHistogramSeries as Xe,testValueExtent as Ae}from"../../stats/histogram.js";import{SPACE as se,fontPx as X,FONT as Ce,CLR as $e}from"../toolbar.js";import{fmt as pe}from"../../renderer/fmt.js";import{niceStep as Ye,fitTicks as We}from"../../renderer/axisTicks.js";import{QUANTITY as Qe,categorical as qe}from"./palette.js";import{cardShell as Je,observeResize as De,makeTooltip as Ze,attachChartTip as et,positionChartTooltip as Ee,makeLinkedTestSelect as tt,makeWaferSelect as nt,makeLinkedAxisPrefs as it,renderEmptyState as Te,chartFillHeight as Pe,applyCanvasFlow as Ie,makeAxisFormat as Le,horizontalTickSpacing as Be,PADDING as ue,robustFence as Re,shouldIncludeLimitsByDefault as Oe,drawOffAxisLimits as ot,resolveAxisRange as lt,chartSwatchCss as st,makeSeriesLegendItem as at,prepareCanvas as Fe}from"./chartShell.js";import{escHtml as ve,maxOf as rt}from"../../core/utils.js";const Me=230,Ke=36,xe=18;function Ne(o,z,G,Y,be,R,r,y=4){const ae=Math.max(1,Ye(R/y)),me=Y-G;o.save(),o.font=`${X(-1)}px system-ui, sans-serif`,o.textAlign="right",o.textBaseline="middle";for(let c=0;c<=R+1e-9;c+=ae){const F=Y-c/(R||1)*me;o.strokeStyle=r.grid,o.lineWidth=1,o.beginPath(),o.moveTo(z,F),o.lineTo(z+be,F),o.stroke(),o.fillStyle=r.text,o.fillText(String(Math.round(c)),z-4,F)}o.strokeStyle=r.axis,o.lineWidth=1,o.beginPath(),o.moveTo(z,G),o.lineTo(z,Y),o.stroke(),o.restore()}export function renderHistogramPanel(o){const{title:z="Test value distribution",items:G,testDefs:Y,onSaveImage:be,groups:R}=o,{card:r,body:y,controlsRow:ae}=Je(z,be,o.ownerDocument),me=Y.filter(l=>l.testNumber!==void 0);let c=o.selectedTestNumber??me[0]?.testNumber??null,F=null,Q=0;const Se=tt(me,c,l=>{c=l,V(),o.onTestChange?.(l)},{maxWidth:"200px",ownerDocument:r.ownerDocument}),ze=Se.el;ae.appendChild(ze);const fe=G.length>1?nt(G,F,l=>{F=l,V()},{ownerDocument:r.ownerDocument}):null;fe&&ae.appendChild(fe);function Ge(l){return v===l||l!==null&&!(R??[]).some(S=>S.key===l)?!1:(v=l,V(),!0)}const ye=r.ownerDocument.createElement("span");Object.assign(ye.style,{display:"inline-flex",gap:se.lg,alignItems:"center"}),ae.appendChild(ye);const he=it(ye,o.axisPrefs,l=>{o.onAxisPrefsChange?.(l),V()},r.ownerDocument),C=Ze(r);let re=null,v=null;function q(l){const S=Y.find(J=>J.testNumber===l);return{unit:S?.unit,limitLow:S?.limitLow,limitHigh:S?.limitHigh}}function V(){const{includeLimits:l,clipOutliers:S}=he.get();if(re?.disconnect(),re=null,y.innerHTML="",c===null){Te(y,"No parametric test data available for a histogram.");return}const J=!!R&&R.length>0;let Z=!1,k;if(J){const{limitLow:s,limitHigh:u}=q(c),i=R.flatMap(b=>b.items),{min:t,max:x}=Ae(i,c);if(Z=l??Oe(t,x,s,u),S){const b=He(i,c),M=Re(b);M&&(k={lo:Math.max(t,M.lo),hi:Math.min(x,M.hi)},Q=b.reduce((ie,A)=>ie+(A<k.lo||A>k.hi?1:0),0))}k||(Q=0)}const $=J?Xe(R,c,16,Z?q(c).limitLow:void 0,Z?q(c).limitHigh:void 0,k):null;if(fe&&(fe.style.display=$?"none":""),$){const{limitLow:s,limitHigh:u}=q(c);if(he.sync(Z,s!==void 0||u!==void 0),$.series.length===0){Te(y,"No parametric test data available for a histogram.");return}Ve($);return}const j=F!==null?[G[F]]:G,{unit:W,limitLow:D,limitHigh:E}=q(c),w=He(j,c),{min:K,max:ee}=Ae(j,c),te=l??Oe(K,ee,D,E);he.sync(te,D!==void 0||E!==void 0);const ne=S?Re(w):null,_=ne?{lo:Math.max(K,ne.lo),hi:Math.min(ee,ne.hi)}:void 0;Q=_?w.filter(s=>s<_.lo||s>_.hi).length:0;const H=Ue(j,c,16,te?D:void 0,te?E:void 0,_);if(H.length===0){Te(y,"No parametric test data available for a histogram.");return}const P=Math.max(rt(H.map(s=>s.count)),1),U=r.ownerDocument.createElement("div");Object.assign(U.style,{fontSize:Ce.body,color:$e.label,marginBottom:se.xxs}),U.textContent=`max ${P} dies/bucket`+(Q?` \xB7 ${Q} value${Q===1?"":"s"} outside clipped range`:""),y.appendChild(U);const T=r.ownerDocument.createElement("canvas");T.style.display="block",T.style.cursor="default",y.appendChild(T);let O=-1;const n=H[0].rangeLow,f=H[H.length-1].rangeHigh,g=f-n||1,e=Math.max(Math.abs(n),Math.abs(f));function d(s){const u=ue+36,i=T.clientWidth-u-ue,t=s-Ke-xe;return{plotX:u,plotMaxWidth:Math.max(10,i),plotMaxHeight:t,plotTop:xe}}function h(s){const{plotX:u,plotMaxWidth:i}=d(Me);if(s<u||s>u+i)return-1;const t=Math.floor((s-u)/i*H.length);return t>=0&&t<H.length?t:-1}function p(){Ie(T,U);const s=y.clientWidth,u=Pe(r,y,T,Me),i=Fe(T,r,s,u);if(!i)return;const{ctx:t,theme:x}=i,{plotX:b,plotMaxWidth:M,plotMaxHeight:ie,plotTop:A}=d(u),a=A+ie,L=M/H.length;Ne(t,b,A,a,M,P,{text:x.textMuted,axis:x.border,grid:x.border}),H.forEach((m,B)=>{const le=m.count/P*ie,ke=b+B*L,we=a-le;t.fillStyle=B===O?x.text:Qe,t.globalAlpha=B===O?1:.7,t.fillRect(ke+1,we,Math.max(1,L-2),le),t.globalAlpha=1,B===O&&(t.fillStyle=x.text,t.textAlign="center",t.textBaseline="bottom",t.fillText(`${m.count}`,ke+L/2,we-2))});const N=m=>b+(m-n)/g*M;t.font=`${X(-1)}px system-ui, sans-serif`;const{offAxis:ce}=lt({dataMin:n,dataMax:f,limitLow:D,limitHigh:E,includeLimits:!1});ot(t,ce,{left:b,right:b+M,top:A,bottom:a},"horizontal",x.limitLine,Le(e,W).tick);const ge=new Set(ce.map(m=>m.value));for(const[m,B]of[[D,"LSL"],[E,"USL"]]){if(m===void 0||ge.has(m))continue;const le=N(m);t.strokeStyle=x.limitLine,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(le,A),t.lineTo(le,a),t.stroke(),t.setLineDash([]),t.fillStyle=x.limitLine,t.textAlign="center",t.textBaseline="bottom",t.fillText(B,le,A-2)}t.font=`${X(-1)}px system-ui, sans-serif`,t.strokeStyle=x.border,t.lineWidth=1,t.beginPath(),t.moveTo(b,a),t.lineTo(b+M,a),t.stroke(),t.fillStyle=x.textMuted,t.textAlign="center",t.textBaseline="top";const{ticks:de,step:oe}=We(n,f,M,Be(t,e,W)),I=Le(e,W,oe||void 0);for(const m of de){const B=b+(m-n)/g*M;t.beginPath(),t.moveTo(B,a),t.lineTo(B,a+4),t.stroke(),t.fillText(I.tick(m),B,a+6)}I.unitLabel&&(t.save(),t.font=`${X(-1)}px system-ui, sans-serif`,t.fillStyle=x.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${I.unitLabel})`,b+M,a+20),t.restore())}T.addEventListener("mousemove",s=>{const u=T.getBoundingClientRect(),i=h(s.clientX-u.left);if(i!==O&&(O=i,p()),i>=0){const t=H[i];C.innerHTML=`<strong>${ve(`${pe(t.rangeLow,W,"engineering")} \u2013 ${pe(t.rangeHigh,W,"engineering")}`)}</strong><br>${t.count} dies`,C.style.display="block",Ee(C,r,s.clientX,s.clientY)}else C.style.display="none"}),T.addEventListener("mouseleave",()=>{O!==-1&&(O=-1,p()),C.style.display="none"}),re=De(r,()=>p()),p()}function Ve(l){const{unit:S,limitLow:J,limitHigh:Z}=q(c),k=l.ranges,$=l.series,j=qe;v&&!$.some(n=>n.groupKey===v)&&(v=null);const W=Math.max(1,...$.flatMap(n=>n.counts)),D=r.ownerDocument.createElement("div");Object.assign(D.style,{fontSize:Ce.body,color:$e.label,marginBottom:se.xxs}),D.textContent=`${$.length} groups \xB7 max ${W} dies/bucket`,y.appendChild(D);const E=r.ownerDocument.createElement("div");Object.assign(E.style,{display:"flex",flexWrap:"wrap",gap:se.sm,marginTop:se.md,marginBottom:se.xl}),$.forEach((n,f)=>{const g=at(n.groupKey,j(f),r.ownerDocument),e=g.el;et(e,r,C,`${n.groupKey} \u2014 click to emphasize (dim the rest)`),g.setState({selected:v===n.groupKey,dimmed:v!==null&&v!==n.groupKey}),e.addEventListener("click",()=>{const d=v===n.groupKey?null:n.groupKey;Ge(d),o.onGroupChange?.(d)}),E.appendChild(e)}),y.appendChild(E);const w=r.ownerDocument.createElement("canvas");w.style.display="block",w.style.cursor="default",y.appendChild(w);const K=k[0].rangeLow,ee=k[k.length-1].rangeHigh,te=ee-K||1,ne=Math.max(Math.abs(K),Math.abs(ee)),_=n=>{const f=(n.ownerDocument.defaultView??window).getComputedStyle(n);return n.offsetHeight+(parseFloat(f.marginTop)||0)+(parseFloat(f.marginBottom)||0)},H=()=>_(D)+_(E);let P=-1,U={plotX:ue+36,plotMaxWidth:10,barWidth:10};function T(){Ie(w,H());const n=y.clientWidth,f=Pe(r,y,w,Me),g=Fe(w,r,n,f);if(!g)return;const{ctx:e,theme:d}=g,h=ue+36,p=Math.max(10,n-h-ue),s=xe,u=f-Ke-xe,i=s+u,t=p/k.length;U={plotX:h,plotMaxWidth:p,barWidth:t},Ne(e,h,s,i,p,W,{text:d.textMuted,axis:d.border,grid:d.border}),P>=0&&(e.fillStyle=d.bgHover,e.globalAlpha=.5,e.fillRect(h+P*t,s,t,u),e.globalAlpha=1);const x=$.map((a,L)=>({s:a,i:L})).sort((a,L)=>(a.s.groupKey===v?1:0)-(L.s.groupKey===v?1:0));for(const{s:a,i:L}of x){const N=v===a.groupKey,ce=v!==null&&!N,ge=j(L);e.strokeStyle=ge,e.fillStyle=ge,e.lineWidth=N?2:1,e.globalAlpha=ce?.12:v?.32:.22,e.beginPath(),e.moveTo(h,i),a.counts.forEach((de,oe)=>{const I=h+oe*t,m=i-de/W*u;e.lineTo(I,m),e.lineTo(I+t,m)}),e.lineTo(h+p,i),e.closePath(),e.fill(),e.globalAlpha=ce?.3:1,e.beginPath(),a.counts.forEach((de,oe)=>{const I=h+oe*t,m=i-de/W*u;oe===0?e.moveTo(I,m):e.lineTo(I,m),e.lineTo(I+t,m)}),e.stroke()}e.globalAlpha=1;const b=a=>h+(a-K)/te*p;e.font=`${X(-1)}px system-ui, sans-serif`;for(const[a,L]of[[J,"LSL"],[Z,"USL"]]){if(a===void 0)continue;const N=b(a);e.strokeStyle=d.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(N,s),e.lineTo(N,i),e.stroke(),e.setLineDash([]),e.fillStyle=d.limitLine,e.textAlign="center",e.textBaseline="bottom",e.fillText(L,N,s-2)}e.font=`${X(-1)}px system-ui, sans-serif`,e.strokeStyle=d.border,e.lineWidth=1,e.beginPath(),e.moveTo(h,i),e.lineTo(h+p,i),e.stroke(),e.fillStyle=d.textMuted,e.textAlign="center",e.textBaseline="top";const{ticks:M,step:ie}=We(K,ee,p,Be(e,ne,S)),A=Le(ne,S,ie||void 0);for(const a of M){const L=h+(a-K)/te*p;e.beginPath(),e.moveTo(L,i),e.lineTo(L,i+4),e.stroke(),e.fillText(A.tick(a),L,i+6)}A.unitLabel&&(e.save(),e.font=`${X(-1)}px system-ui, sans-serif`,e.fillStyle=d.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${A.unitLabel})`,h+p,i+20),e.restore())}function O(n){const{plotX:f,plotMaxWidth:g,barWidth:e}=U;if(n<f||n>f+g)return-1;const d=Math.floor((n-f)/e);return d>=0&&d<k.length?d:-1}w.addEventListener("mousemove",n=>{const f=w.getBoundingClientRect(),g=O(n.clientX-f.left);if(g!==P&&(P=g,T()),g>=0){const e=k[g],d=$.map((h,p)=>`<span style="${st(j(p))};margin-right:4px"></span>${ve(`${h.groupKey}: ${h.counts[g]}`)}`).join("<br>");C.innerHTML=`<strong>${ve(`${pe(e.rangeLow,S,"engineering")} \u2013 ${pe(e.rangeHigh,S,"engineering")}`)}</strong><br>${d}`,C.style.display="block",Ee(C,r,n.clientX,n.clientY)}else C.style.display="none"}),w.addEventListener("mouseleave",()=>{P!==-1&&(P=-1,T()),C.style.display="none"}),re=De(r,()=>T()),T()}V();function je(l){Se.set(l)&&(c=l,V())}function _e(l){he.set(l)&&V()}return{card:r,setTest:je,setAxisPrefs:_e,destroy:()=>re?.disconnect()}}
|
|
1
|
+
import{buildTestHistogramData as Xt,collectTestValues as Ht,buildTestHistogramSeries as Yt,testValueExtent as At}from"../../stats/histogram.js";import{SPACE as st,fontPx as Y,FONT as Ct,CLR as $t}from"../toolbar.js";import{fmt as pt}from"../../renderer/fmt.js";import{niceStep as Qt,fitTicks as Wt}from"../../renderer/axisTicks.js";import{QUANTITY as Ut,categorical as qt}from"./palette.js";import{cardShell as Jt,observeResize as Dt,makeTooltip as Zt,attachChartTip as te,positionChartTooltip as Et,makeLinkedTestSelect as ee,makeWaferSelect as ne,makeLinkedAxisPrefs as ie,renderEmptyState as Tt,chartFillHeight as Pt,applyCanvasFlow as It,makeAxisFormat as vt,horizontalTickSpacing as Bt,PADDING as ut,robustFence as Rt,shouldIncludeLimitsByDefault as Ot,drawOffAxisLimits as oe,resolveAxisRange as le,chartSwatchCss as se,makeSeriesLegendItem as ae,prepareCanvas as Ft}from"./chartShell.js";import{escHtml as Mt,maxOf as re}from"../../core/utils.js";const Lt=230,Kt=36,xt=18;function Nt(o,z,G,Q,bt,R,r,y=4){const at=Math.max(1,Qt(R/y)),mt=Q-G;o.save(),o.font=`${Y(-1)}px system-ui, sans-serif`,o.textAlign="right",o.textBaseline="middle";for(let c=0;c<=R+1e-9;c+=at){const F=Q-c/(R||1)*mt;o.strokeStyle=r.grid,o.lineWidth=1,o.beginPath(),o.moveTo(z,F),o.lineTo(z+bt,F),o.stroke(),o.fillStyle=r.text,o.fillText(String(Math.round(c)),z-4,F)}o.strokeStyle=r.axis,o.lineWidth=1,o.beginPath(),o.moveTo(z,G),o.lineTo(z,Q),o.stroke(),o.restore()}export function renderHistogramPanel(o){const{title:z="Test value distribution",items:G,testDefs:Q,onSaveImage:bt,groups:R}=o,{card:r,body:y,controlsRow:at}=Jt(z,bt,o.ownerDocument),mt=Q.filter(l=>l.testNumber!==void 0);let c=o.selectedTestNumber??mt[0]?.testNumber??null,F=null,U=0;const kt=ee(mt,c,l=>{c=l,V(),o.onTestChange?.(l)},{maxWidth:"200px",ownerDocument:r.ownerDocument}),zt=kt.el;at.appendChild(zt);const ft=G.length>1?ne(G,F,l=>{F=l,V()},{ownerDocument:r.ownerDocument}):null;ft&&at.appendChild(ft);function Gt(l){return M===l||l!==null&&!(R??[]).some(k=>k.key===l)?!1:(M=l,V(),!0)}const yt=r.ownerDocument.createElement("span");Object.assign(yt.style,{display:"inline-flex",gap:st.lg,alignItems:"center"}),at.appendChild(yt);const ht=ie(yt,o.axisPrefs,l=>{o.onAxisPrefsChange?.(l),V()},r.ownerDocument),C=Zt(r);let rt=null,M=null;function q(l){const k=Q.find(J=>J.testNumber===l);return{unit:k?.unit,limitLow:k?.limitLow,limitHigh:k?.limitHigh}}function V(){const{includeLimits:l,clipOutliers:k}=ht.get();if(rt?.disconnect(),rt=null,y.innerHTML="",c===null){Tt(y,"No parametric test data available for a histogram.");return}const J=!!R&&R.length>0;let Z=!1,S;if(J){const{limitLow:s,limitHigh:u}=q(c),i=R.flatMap(b=>b.items),{min:e,max:x}=At(i,c);if(Z=l??Ot(e,x,s,u),k){const b=Ht(i,c),L=Rt(b);L&&(S={lo:Math.max(e,L.lo),hi:Math.min(x,L.hi)},U=b.reduce((it,A)=>it+(A<S.lo||A>S.hi?1:0),0))}S||(U=0)}const $=J?Yt(R,c,16,Z?q(c).limitLow:void 0,Z?q(c).limitHigh:void 0,S):null;if(ft&&(ft.style.display=$?"none":""),$){const{limitLow:s,limitHigh:u}=q(c);if(ht.sync(Z,s!==void 0||u!==void 0),$.series.length===0){Tt(y,"No parametric test data available for a histogram.");return}Vt($);return}const j=F!==null?[G[F]]:G,{unit:W,limitLow:D,limitHigh:E}=q(c),w=Ht(j,c),{min:K,max:tt}=At(j,c),et=l??Ot(K,tt,D,E);ht.sync(et,D!==void 0||E!==void 0);const nt=k?Rt(w):null,_=nt?{lo:Math.max(K,nt.lo),hi:Math.min(tt,nt.hi)}:void 0;U=_?w.filter(s=>s<_.lo||s>_.hi).length:0;const H=Xt(j,c,16,et?D:void 0,et?E:void 0,_);if(H.length===0){Tt(y,"No parametric test data available for a histogram.");return}const P=Math.max(re(H.map(s=>s.count)),1),X=r.ownerDocument.createElement("div");Object.assign(X.style,{fontSize:Ct.body,color:$t.label,marginBottom:st.xxs}),X.textContent=`max ${P} dies/bucket`+(U?` \xB7 ${U} value${U===1?"":"s"} outside clipped range`:""),y.appendChild(X);const T=r.ownerDocument.createElement("canvas");T.style.display="block",T.style.cursor="default",y.appendChild(T);let O=-1;const n=H[0].rangeLow,f=H[H.length-1].rangeHigh,g=f-n||1,t=Math.max(Math.abs(n),Math.abs(f));function d(s){const u=ut+36,i=T.clientWidth-u-ut,e=s-Kt-xt;return{plotX:u,plotMaxWidth:Math.max(10,i),plotMaxHeight:e,plotTop:xt}}function h(s){const{plotX:u,plotMaxWidth:i}=d(Lt);if(s<u||s>u+i)return-1;const e=Math.floor((s-u)/i*H.length);return e>=0&&e<H.length?e:-1}function p(){It(T,X);const s=y.clientWidth,u=Pt(r,y,T,Lt),i=Ft(T,r,s,u);if(!i)return;const{ctx:e,theme:x}=i,{plotX:b,plotMaxWidth:L,plotMaxHeight:it,plotTop:A}=d(u),a=A+it,v=L/H.length;Nt(e,b,A,a,L,P,{text:x.textMuted,axis:x.border,grid:x.border}),H.forEach((m,B)=>{const lt=m.count/P*it,St=b+B*v,wt=a-lt;e.fillStyle=B===O?x.text:Ut,e.globalAlpha=B===O?1:.7,e.fillRect(St+1,wt,Math.max(1,v-2),lt),e.globalAlpha=1,B===O&&(e.fillStyle=x.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${m.count}`,St+v/2,wt-2))});const N=m=>b+(m-n)/g*L;e.font=`${Y(-1)}px system-ui, sans-serif`;const{offAxis:ct}=le({dataMin:n,dataMax:f,limitLow:D,limitHigh:E,includeLimits:!1});oe(e,ct,{left:b,right:b+L,top:A,bottom:a},"horizontal",x.limitLine,vt(t,W).tick);const gt=new Set(ct.map(m=>m.value));for(const[m,B]of[[D,"Lo limit"],[E,"Hi limit"]]){if(m===void 0||gt.has(m))continue;const lt=N(m);e.strokeStyle=x.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(lt,A),e.lineTo(lt,a),e.stroke(),e.setLineDash([]),e.fillStyle=x.limitLine,e.textAlign="center",e.textBaseline="bottom",e.fillText(B,lt,A-2)}e.font=`${Y(-1)}px system-ui, sans-serif`,e.strokeStyle=x.border,e.lineWidth=1,e.beginPath(),e.moveTo(b,a),e.lineTo(b+L,a),e.stroke(),e.fillStyle=x.textMuted,e.textAlign="center",e.textBaseline="top";const{ticks:dt,step:ot}=Wt(n,f,L,Bt(e,t,W)),I=vt(t,W,ot||void 0);for(const m of dt){const B=b+(m-n)/g*L;e.beginPath(),e.moveTo(B,a),e.lineTo(B,a+4),e.stroke(),e.fillText(I.tick(m),B,a+6)}I.unitLabel&&(e.save(),e.font=`${Y(-1)}px system-ui, sans-serif`,e.fillStyle=x.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${I.unitLabel})`,b+L,a+20),e.restore())}T.addEventListener("mousemove",s=>{const u=T.getBoundingClientRect(),i=h(s.clientX-u.left);if(i!==O&&(O=i,p()),i>=0){const e=H[i];C.innerHTML=`<strong>${Mt(`${pt(e.rangeLow,W,"engineering")} \u2013 ${pt(e.rangeHigh,W,"engineering")}`)}</strong><br>${e.count} dies`,C.style.display="block",Et(C,r,s.clientX,s.clientY)}else C.style.display="none"}),T.addEventListener("mouseleave",()=>{O!==-1&&(O=-1,p()),C.style.display="none"}),rt=Dt(r,()=>p()),p()}function Vt(l){const{unit:k,limitLow:J,limitHigh:Z}=q(c),S=l.ranges,$=l.series,j=qt;M&&!$.some(n=>n.groupKey===M)&&(M=null);const W=Math.max(1,...$.flatMap(n=>n.counts)),D=r.ownerDocument.createElement("div");Object.assign(D.style,{fontSize:Ct.body,color:$t.label,marginBottom:st.xxs}),D.textContent=`${$.length} groups \xB7 max ${W} dies/bucket`,y.appendChild(D);const E=r.ownerDocument.createElement("div");Object.assign(E.style,{display:"flex",flexWrap:"wrap",gap:st.sm,marginTop:st.md,marginBottom:st.xl}),$.forEach((n,f)=>{const g=ae(n.groupKey,j(f),r.ownerDocument),t=g.el;te(t,r,C,`${n.groupKey} \u2014 click to emphasize (dim the rest)`),g.setState({selected:M===n.groupKey,dimmed:M!==null&&M!==n.groupKey}),t.addEventListener("click",()=>{const d=M===n.groupKey?null:n.groupKey;Gt(d),o.onGroupChange?.(d)}),E.appendChild(t)}),y.appendChild(E);const w=r.ownerDocument.createElement("canvas");w.style.display="block",w.style.cursor="default",y.appendChild(w);const K=S[0].rangeLow,tt=S[S.length-1].rangeHigh,et=tt-K||1,nt=Math.max(Math.abs(K),Math.abs(tt)),_=n=>{const f=(n.ownerDocument.defaultView??window).getComputedStyle(n);return n.offsetHeight+(parseFloat(f.marginTop)||0)+(parseFloat(f.marginBottom)||0)},H=()=>_(D)+_(E);let P=-1,X={plotX:ut+36,plotMaxWidth:10,barWidth:10};function T(){It(w,H());const n=y.clientWidth,f=Pt(r,y,w,Lt),g=Ft(w,r,n,f);if(!g)return;const{ctx:t,theme:d}=g,h=ut+36,p=Math.max(10,n-h-ut),s=xt,u=f-Kt-xt,i=s+u,e=p/S.length;X={plotX:h,plotMaxWidth:p,barWidth:e},Nt(t,h,s,i,p,W,{text:d.textMuted,axis:d.border,grid:d.border}),P>=0&&(t.fillStyle=d.bgHover,t.globalAlpha=.5,t.fillRect(h+P*e,s,e,u),t.globalAlpha=1);const x=$.map((a,v)=>({s:a,i:v})).sort((a,v)=>(a.s.groupKey===M?1:0)-(v.s.groupKey===M?1:0));for(const{s:a,i:v}of x){const N=M===a.groupKey,ct=M!==null&&!N,gt=j(v);t.strokeStyle=gt,t.fillStyle=gt,t.lineWidth=N?2:1,t.globalAlpha=ct?.12:M?.32:.22,t.beginPath(),t.moveTo(h,i),a.counts.forEach((dt,ot)=>{const I=h+ot*e,m=i-dt/W*u;t.lineTo(I,m),t.lineTo(I+e,m)}),t.lineTo(h+p,i),t.closePath(),t.fill(),t.globalAlpha=ct?.3:1,t.beginPath(),a.counts.forEach((dt,ot)=>{const I=h+ot*e,m=i-dt/W*u;ot===0?t.moveTo(I,m):t.lineTo(I,m),t.lineTo(I+e,m)}),t.stroke()}t.globalAlpha=1;const b=a=>h+(a-K)/et*p;t.font=`${Y(-1)}px system-ui, sans-serif`;for(const[a,v]of[[J,"Lo limit"],[Z,"Hi limit"]]){if(a===void 0)continue;const N=b(a);t.strokeStyle=d.limitLine,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(N,s),t.lineTo(N,i),t.stroke(),t.setLineDash([]),t.fillStyle=d.limitLine,t.textAlign="center",t.textBaseline="bottom",t.fillText(v,N,s-2)}t.font=`${Y(-1)}px system-ui, sans-serif`,t.strokeStyle=d.border,t.lineWidth=1,t.beginPath(),t.moveTo(h,i),t.lineTo(h+p,i),t.stroke(),t.fillStyle=d.textMuted,t.textAlign="center",t.textBaseline="top";const{ticks:L,step:it}=Wt(K,tt,p,Bt(t,nt,k)),A=vt(nt,k,it||void 0);for(const a of L){const v=h+(a-K)/et*p;t.beginPath(),t.moveTo(v,i),t.lineTo(v,i+4),t.stroke(),t.fillText(A.tick(a),v,i+6)}A.unitLabel&&(t.save(),t.font=`${Y(-1)}px system-ui, sans-serif`,t.fillStyle=d.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${A.unitLabel})`,h+p,i+20),t.restore())}function O(n){const{plotX:f,plotMaxWidth:g,barWidth:t}=X;if(n<f||n>f+g)return-1;const d=Math.floor((n-f)/t);return d>=0&&d<S.length?d:-1}w.addEventListener("mousemove",n=>{const f=w.getBoundingClientRect(),g=O(n.clientX-f.left);if(g!==P&&(P=g,T()),g>=0){const t=S[g],d=$.map((h,p)=>`<span style="${se(j(p))};margin-right:4px"></span>${Mt(`${h.groupKey}: ${h.counts[g]}`)}`).join("<br>");C.innerHTML=`<strong>${Mt(`${pt(t.rangeLow,k,"engineering")} \u2013 ${pt(t.rangeHigh,k,"engineering")}`)}</strong><br>${d}`,C.style.display="block",Et(C,r,n.clientX,n.clientY)}else C.style.display="none"}),w.addEventListener("mouseleave",()=>{P!==-1&&(P=-1,T()),C.style.display="none"}),rt=Dt(r,()=>T()),T()}V();function jt(l){kt.set(l)&&(c=l,V())}function _t(l){ht.set(l)&&V()}return{card:r,setTest:jt,setAxisPrefs:_t,destroy:()=>rt?.disconnect()}}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { type ScatterItem } from '../../stats/scatter.js';
|
|
1
|
+
import { type ScatterItem, type ScatterPoint } from '../../stats/scatter.js';
|
|
2
2
|
import { type FacetItem } from '../../stats/facets.js';
|
|
3
3
|
import type { TestDef } from '../../renderer/buildWaferMap.js';
|
|
4
4
|
import { type SaveImageHandler } from './chartShell.js';
|
|
5
5
|
type ScatterPanelItem = FacetItem & ScatterItem & {
|
|
6
6
|
label?: string;
|
|
7
7
|
};
|
|
8
|
+
export declare const NO_BIN_CATEGORY = "none";
|
|
9
|
+
export declare function binCategoryOf(hbin: number | undefined): string;
|
|
10
|
+
/** Legend order: bins ascending, then "No bin data" last. */
|
|
11
|
+
export declare function binCategories(points: readonly Pick<ScatterPoint, 'hbin'>[]): string[];
|
|
8
12
|
export interface ScatterPanelOptions {
|
|
9
13
|
title?: string;
|
|
10
14
|
items: ScatterPanelItem[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{NO_DATA_FILL as
|
|
1
|
+
import{NO_DATA_FILL as mt}from"../../renderer/colorMap.js";import{maxOf as ut,minOf as pt}from"../../core/utils.js";import{categorical as Ct}from"./palette.js";import{buildScatterData as Lt,buildScatterDataGrouped as At}from"../../stats/scatter.js";import{pearsonOfPairs as Ot}from"../../stats/correlation.js";import{buildFacetTable as Dt}from"../../stats/facets.js";import{SPACE as M,RADIUS as Bt,fontPx as R,FONT as Z,CLR as _}from"../toolbar.js";import{fitTicks as xt}from"../../renderer/axisTicks.js";import{cardShell as Pt,observeResize as $t,makeTooltip as Ht,attachChartTip as Et,makeTestSelect as It,makeWaferSelect as Wt,chartFillHeight as Nt,applyCanvasFlow as Ft,drawAxisUnit as Rt,resolveChartCanvasColors as _t,makeAxisFormat as ht,horizontalTickSpacing as zt,VERTICAL_TICK_SPACING_PX as Gt,makeSeriesLegendItem as jt,prepareCanvas as Ut}from"./chartShell.js";const r=52,Xt=16,l=16,Yt=44;export const NO_BIN_CATEGORY="none";export function binCategoryOf(b){return b===void 0?NO_BIN_CATEGORY:String(b)}export function binCategories(b){const z=new Set;let T=!1;for(const v of b)v.hbin===void 0?T=!0:z.add(v.hbin);const O=Array.from(z).sort((v,Y)=>v-Y).map(String);return T&&O.push(NO_BIN_CATEGORY),O}export function renderScatterPanel(b){const{title:z="Test scatter",items:T,testDefs:O,binColors:v,onSaveImage:Y,groups:G}=b,{card:i,body:C,controlsRow:tt}=Pt(z,Y,b.ownerDocument),j=O.filter(e=>e.testNumber!==void 0),S=!!(G&&G.length>0),et=(G??[]).map(e=>e.key),gt=new Map(et.map((e,n)=>[e,n]));let m=b.xTestNumber??j[0]?.testNumber??null,x=b.yTestNumber??j[1]?.testNumber??m,D=null;const nt=()=>D!==null?T[D]?[T[D]]:[]:T;function ot(e,n,o){const s=i.ownerDocument.createElement("label");Object.assign(s.style,{display:"inline-flex",alignItems:"center",gap:M.xs,fontSize:Z.body,color:_.label});const g=i.ownerDocument.createElement("span");g.textContent=e;const c=It(j,n,o,{maxWidth:"180px",emptyText:"No tests",ownerDocument:i.ownerDocument});return s.append(g,c),{wrap:s,select:c}}const{wrap:bt,select:yt}=ot("X:",m,e=>{m=e,E()}),{wrap:Tt,select:St}=ot("Y:",x,e=>{x=e,E()});tt.append(bt,Tt),!S&&T.length>1&&tt.appendChild(Wt(T,D,e=>{D=e,E()},{ownerDocument:i.ownerDocument}));const B=i.ownerDocument.createElement("div");Object.assign(B.style,{color:_.warnText,background:_.warnBg,border:`1px solid ${_.warnBorder}`,borderRadius:Bt.control,padding:`${M.xs} ${M.md}`,fontSize:Z.body,marginBottom:M.xs,display:"none"}),i.insertBefore(B,C);function it(){const e=S?[]:Dt(nt(),{facetableOnly:!0}).filter(n=>n.splittable).map(n=>n.label);e.length>0?(B.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.`,B.style.display=""):B.style.display="none"}it();const K=i.ownerDocument.createElement("div");Object.assign(K.style,{color:_.label,fontSize:Z.body,marginBottom:M.xs}),i.insertBefore(K,C);function V(){const e=S?"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",n=h.size===0?u:u.filter(g=>h.has(U(g))),{r:o,n:s}=Ot(n);K.textContent=o===null?`${e} \xB7 n = ${s.toLocaleString()}`:`${e} \xB7 r = ${o.toFixed(3)} \xB7 n = ${s.toLocaleString()}`}const U=e=>S?e.group??"\u2014":binCategoryOf(e.hbin),st=e=>S?Ct(gt.get(e)??0):e===NO_BIN_CATEGORY?mt:v?.get(Number(e))??mt,lt=e=>S?e:e===NO_BIN_CATEGORY?"No bin data":`Bin ${e}`,h=new Set,P=i.ownerDocument.createElement("div");Object.assign(P.style,{display:"flex",flexWrap:"wrap",gap:M.sm,marginTop:M.md,marginBottom:M.xl}),C.appendChild(P);const L=i.ownerDocument.createElement("canvas");L.style.display="block",L.style.cursor="crosshair",C.appendChild(L);const wt=Ht(i);let u=[],y=0,A=1,w=0,$=1;function H(e){const n=O.find(o=>o.testNumber===e);return{unit:n?.unit,limitLow:n?.limitLow,limitHigh:n?.limitHigh}}function Mt(){const e=C.clientWidth,n=Math.max(200,Math.min(400,e*.65)),o=Nt(i,C,L,n);return{w:e,h:o,plotW:Math.max(10,e-r-Xt),plotH:Math.max(10,o-l-Yt)}}const q=new Map;function at(){for(const[e,n]of q){const o=h.has(e);n.setState({selected:o,dimmed:h.size>0&&!o})}}function rt(e){P.innerHTML="",q.clear(),h.clear();for(const n of e){const o=jt(lt(n),st(n),i.ownerDocument),s=o.el;s.dataset.cat=n,q.set(n,o),Et(s,i,wt,`${lt(n)} \u2014 click to filter`),s.addEventListener("click",()=>{h.has(n)?h.delete(n):h.add(n),at(),V(),X()}),P.appendChild(s)}at()}function X(){Ft(L,P);const e=_t(i),n=A-y,o=$-w,{w:s,h:g,plotW:c,plotH:a}=Mt(),I=Ut(L,i,s,g);if(!I)return;const{ctx:t}=I;if(u.length===0){t.font=`${R()}px 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.",s/2,g/2);return}t.font=`${R(-1)}px system-ui, sans-serif`;const W=m!==null?H(m).unit:void 0,ct=xt(y,A,c,zt(t,Math.max(Math.abs(y),Math.abs(A)),W)),dt=xt(w,$,a,()=>Gt),J=ht(Math.max(Math.abs(y),Math.abs(A)),m!==null?H(m).unit:void 0,ct.step||void 0),Q=ht(Math.max(Math.abs(w),Math.abs($)),x!==null?H(x).unit:void 0,dt.step||void 0);t.font=`${R(-1)}px system-ui, sans-serif`,t.strokeStyle=e.border,t.lineWidth=.5,t.fillStyle=e.textMuted;for(const d of ct.ticks){const f=r+(d-y)/n*c;t.beginPath(),t.moveTo(f,l),t.lineTo(f,l+a),t.stroke(),t.textAlign="center",t.textBaseline="top",t.fillText(J.tick(d),f,l+a+4)}for(const d of dt.ticks){const f=l+(1-(d-w)/o)*a;t.beginPath(),t.moveTo(r,f),t.lineTo(r+c,f),t.stroke(),t.textAlign="right",t.textBaseline="middle",t.fillText(Q.tick(d),r-4,f)}J.unitLabel&&Rt(t,J.unitLabel,r+c/2,l+a+24,e.textMuted),Q.unitLabel&&(t.save(),t.font=`${R(-1)}px system-ui, sans-serif`,t.fillStyle=e.textMuted,t.textAlign="center",t.textBaseline="middle",t.translate(6,l+a/2),t.rotate(-Math.PI/2),t.fillText(`(${Q.unitLabel})`,0,0),t.restore());const N=h.size===0?u:u.filter(d=>h.has(U(d))),ft=N.length>5e3?Math.ceil(N.length/5e3):1;t.globalAlpha=Math.max(.15,Math.min(.7,200/(N.length/ft)));for(let d=0;d<N.length;d+=ft){const f=N[d],p=r+(f.x-y)/n*c,F=l+(1-(f.y-w)/o)*a;t.beginPath(),t.arc(p,F,2.5,0,Math.PI*2),t.fillStyle=st(U(f)),t.fill()}if(t.globalAlpha=1,m!==null&&x!==null){const d=H(m),f=H(x);t.strokeStyle=e.textMuted,t.fillStyle=e.textMuted,t.lineWidth=1,t.setLineDash([4,3]),t.font=`${R(-1)}px system-ui, sans-serif`,t.textBaseline="top";for(const[p,F]of[[d.limitLow,"Lo limit"],[d.limitHigh,"Hi limit"]]){if(p===void 0||p<y||p>A)continue;const k=r+(p-y)/n*c;t.beginPath(),t.moveTo(k,l),t.lineTo(k,l+a),t.stroke(),t.textAlign="left",t.fillText(F,k+2,l+2)}for(const[p,F]of[[f.limitLow,"Lo limit"],[f.limitHigh,"Hi limit"]]){if(p===void 0||p<w||p>$)continue;const k=l+(1-(p-w)/o)*a;t.beginPath(),t.moveTo(r,k),t.lineTo(r+c,k),t.stroke(),t.textAlign="left",t.fillText(F,r+3,k+2)}t.setLineDash([])}t.strokeStyle=e.border,t.lineWidth=1,t.beginPath(),t.moveTo(r,l),t.lineTo(r,l+a),t.moveTo(r,l+a),t.lineTo(r+c,l+a),t.stroke()}function E(){if(it(),j.length<2||m===null||x===null){u=[],rt([]),V(),X();return}u=S?At(G,m,x):Lt(nt(),m,x);let e;if(S){const n=new Set(u.map(U));e=et.filter(o=>n.has(o))}else e=binCategories(u);if(rt(e),V(),u.length>0){const n=u.map(W=>W.x),o=u.map(W=>W.y),s=pt(n),g=ut(n),c=pt(o),a=ut(o),I=(g-s)*.05||1,t=(a-c)*.05||1;y=s-I,A=g+I,w=c-t,$=a+t}X()}const kt=$t(i,()=>X());E();function vt(e,n){m=e,x=n,yt.value=String(e),St.value=String(n),E()}return{card:i,setXY:vt,destroy:()=>kt.disconnect()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildSweepData as ce}from"../../stats/sweep.js";import{fmt as C}from"../../renderer/fmt.js";import{SPACE as ne,fontPx as
|
|
1
|
+
import{buildSweepData as ce,sweepAppliesTo as fe}from"../../stats/sweep.js";import{fmt as C}from"../../renderer/fmt.js";import{SPACE as ne,fontPx as pe,FONT as oe,CLR as G,ALPHA as de}from"../toolbar.js";import{categorical as he}from"./palette.js";import{cardShell as me,observeResize as ue,makeTooltip as xe,positionChartTooltip as ge,renderEmptyState as se,chartFillHeight as be,populationPhrase as ye,PADDING as Y,prepareCanvas as $e,makeAxisFormat as Te,VERTICAL_TICK_SPACING_PX as ve}from"./chartShell.js";import{fitTicks as Se}from"../../renderer/axisTicks.js";import{escHtml as m}from"../../core/utils.js";import{unmarkedLabel as Me,derivedTestNote as Le,DERIVED_MARK as J,DERIVED_KEY as we}from"../../renderer/testLabel.js";function _(u,a){return a?C(u,a).replace(/(\.\d*?)0+(?=\D|$)/,"$1").replace(/\.(?=\D|$)/,""):Number.isInteger(u)?String(u):String(Number(u.toPrecision(3)))}function Pe(u){const a=Me(u.label),S=a.length>9?`${a.slice(0,8)}\u2026`:a;return u.derived?`${J} ${S}`:S}const ie=260,ke=62,Ae=56,Ee=3;function Ie(u,a){const S=u.series[0]?.points[0]?.testNumber;return S===void 0?void 0:a?.find(j=>j.testNumber===S)?.unit}export function renderSweepPanel(u){const{spec:a,testDefs:S,onSaveImage:j,population:re}=u;let z=u.dies;const{card:y,body:M}=me(a.title,j,u.ownerDocument);y.style.alignSelf="start";const V=y.ownerDocument,N=V.createElement("div");Object.assign(N.style,{color:G.label,fontSize:oe.body,marginBottom:ne.sm}),y.insertBefore(N,M);const W=V.createElement("div");Object.assign(W.style,{color:G.label,fontSize:oe.body,marginTop:ne.sm}),y.appendChild(W);const L=xe(y);let X=null,E=null,$;const H=n=>a.series[n]?.color??he(n);function Q(){if(M.innerHTML="",W.innerHTML="",X?.disconnect(),X=null,a.series.length===0){N.textContent="",se(M,"This sweep defines no series.");return}if(E=ce(z,S,a),$=Ie(E,S),E.series.filter(i=>i.points.some(T=>T.count>0)).length===0){N.textContent="",se(M,fe(a,S)?"No values recorded for the tests in this sweep.":"None of this sweep\u2019s tests are in this data \u2014 it may belong to another test program."),Z();return}N.textContent=`Line = median across ${ye(E.dieCount,z.length,re)}, band = p10\u2013p90`+(a.series.length>2?" \xB7 crossing and separation measured between the first two series":"");const c=V.createElement("canvas");c.style.display="block",M.appendChild(c);let o=null;const w=()=>{const i=E;if(i===null)return;const T=Math.max(1,M.clientWidth),B=Math.max(ie,be(y,M,c,ie)),F=$e(c,y,T,B);if(!F)return;const{ctx:e,theme:x}=F,g=t=>i.xScale==="log"?Math.log10(t):t;let f=1/0,h=-1/0,b=1/0,v=-1/0;for(const t of i.series)for(const s of t.points)s.count!==0&&(f=Math.min(f,s.p10),h=Math.max(h,s.p90),b=Math.min(b,g(s.x)),v=Math.max(v,g(s.x)));for(const t of i.separations)f=Math.min(f,t.y),h=Math.max(h,t.y);if(!Number.isFinite(f)||!Number.isFinite(h))return;f===h&&(f-=1,h+=1);const l=(h-f)*.08;if(f-=l,h+=l,b===v&&(b-=.5,v+=.5),i.xScale==="log"){const t=(v-b)*.03;b-=t,v+=t}e.font=`${pe(-1)}px system-ui, sans-serif`;const r=Y+ke,p=T-Y-8,R=Y,P=B-Ae,k=t=>P-(t-f)/(h-f)*(P-R),D=t=>r+(g(t)-b)/(v-b)*(p-r);o={left:r,right:p,xLo:b,xHi:v,yOf:k,ax:g},e.strokeStyle=x.border,e.lineWidth=1,e.beginPath(),e.moveTo(r,R),e.lineTo(r,P),e.lineTo(p,P),e.stroke(),e.fillStyle=x.text,e.textAlign="right",e.textBaseline="middle";const q=Se(f,h,P-R,()=>ve),le=Te(Math.max(Math.abs(f),Math.abs(h)),$,q.step||void 0);for(const t of q.ticks)e.fillText(le.tickWithUnit(t),r-6,k(t));for(const t of i.separations){const s=k(t.y);e.save(),e.strokeStyle=x.limitLine,e.globalAlpha=.55,e.setLineDash([4,3]),e.beginPath(),e.moveTo(r,s),e.lineTo(p,s),e.stroke(),e.restore()}if(i.series.forEach((t,s)=>{const A=t.points.filter(d=>d.count>0);if(!(A.length<2)){e.save(),e.fillStyle=H(s),e.globalAlpha=.14,e.beginPath(),A.forEach((d,I)=>{const O=D(d.x),te=k(d.p90);I===0?e.moveTo(O,te):e.lineTo(O,te)});for(let d=A.length-1;d>=0;d--){const I=A[d];e.lineTo(D(I.x),k(I.p10))}e.closePath(),e.fill(),e.restore()}}),i.series.forEach((t,s)=>{e.strokeStyle=H(s),e.lineWidth=1.75,e.beginPath();let A=!1;for(const d of t.points){if(d.count===0){A=!1;continue}const I=D(d.x),O=k(d.median);A?e.lineTo(I,O):e.moveTo(I,O),A=!0}e.stroke(),e.fillStyle=H(s);for(const d of t.points)d.count!==0&&(e.beginPath(),e.arc(D(d.x),k(d.median),Ee,0,Math.PI*2),e.fill())}),i.crossing){const t=D(i.crossing.x),s=k(i.crossing.y);e.save(),e.strokeStyle=x.text,e.globalAlpha=.45,e.setLineDash([3,3]),e.beginPath(),e.moveTo(t,s),e.lineTo(t,P),e.stroke(),e.restore(),e.save(),e.strokeStyle=x.text,e.lineWidth=2,e.beginPath(),e.arc(t,s,5.5,0,Math.PI*2),e.stroke(),e.restore()}e.fillStyle=x.text,e.textBaseline="top",e.textAlign="center";const K=i.xIsPhysical?[...new Set(i.series.flatMap(t=>t.points.map(s=>s.x)))].sort((t,s)=>t-s).map(t=>({x:t,label:_(t,i.xUnit)})):(i.series[0]?.points??[]).map(t=>({x:t.x,label:Pe(t)})),ae=Math.max(1,Math.ceil(K.length*46/Math.max(1,p-r)));K.forEach((t,s)=>{s%ae!==0&&s!==K.length-1||e.fillText(t.label,D(t.x),P+6)}),e.save(),e.fillStyle=x.text,e.globalAlpha=.75,e.textAlign="center",e.textBaseline="bottom",e.fillText(i.xLabel,(r+p)/2,B-4),e.restore(),e.save(),e.fillStyle=x.text,e.globalAlpha=.75,e.translate(12,(R+P)/2),e.rotate(-Math.PI/2),e.textBaseline="top",e.fillText(i.yLabel+($?` (${$})`:""),0,0),e.restore(),e.textAlign="left",e.textBaseline="middle";let U=r+4;const ee=R+8;i.series.forEach((t,s)=>{e.fillStyle=H(s),e.fillRect(U,ee-4,10,3),U+=14,e.fillStyle=x.text,e.fillText(t.label,U,ee),U+=e.measureText(t.label).width+16})};w(),X=ue(M,w),c.addEventListener("mousemove",i=>{const T=E;if(T===null||o===null){L.style.display="none";return}const B=c.getBoundingClientRect(),F=i.clientX-B.left,e=o.xLo+(F-o.left)/Math.max(1,o.right-o.left)*(o.xHi-o.xLo),x=o.ax;let g=null;for(const l of T.series)for(const r of l.points){if(r.count===0)continue;const p=Math.abs(x(r.x)-e);(g===null||p<g.dist)&&(g={x:r.x,dist:p})}if(g===null){L.style.display="none";return}const f=T.series.map(l=>l.points.find(r=>r.x===g.x&&r.count>0)),h=T.series.map((l,r)=>{const p=f[r];return p===void 0?"":`<span style="color:${H(r)}">\u25A0</span> ${p.derived?`${J} `:""}${m(l.label)}: <strong>${m(C(p.median,$))}</strong><br><span style="opacity:${de.muted}">p10 ${m(C(p.p10,$))} \u2013 p90 ${m(C(p.p90,$))} \xB7 n=${p.count.toLocaleString()}</span>`}).filter(Boolean);if(h.length===0){L.style.display="none";return}const b=T.series.flatMap(l=>l.points).find(l=>l.x===g.x&&l.count>0),v=[...new Set(f.map(l=>Le(l)).filter(l=>l!==void 0))];L.innerHTML=`<strong>${m(b?.label??"")}</strong><br>`+h.join("<br>")+v.map(l=>`<br><em>${m(l)}</em>`).join(""),L.style.display="block",ge(L,y,i.clientX,i.clientY)}),c.addEventListener("mouseleave",()=>{L.style.display="none"}),Z()}function Z(){const n=E;if(n===null)return;const c=[];n.notMeasured!==void 0?c.push(`<strong>Crossing and widths:</strong> not measured \u2014 ${m(n.notMeasured)}`):n.crossing?c.push(`<strong>Crossing:</strong> ${m(`${n.xLabel} = ${_(n.crossing.x,n.xUnit)}`)} \xB7 ${m(`${n.yLabel} ${C(n.crossing.y,$)}`)}`+(n.crossing.multiple?" <em>(first of several \u2014 the curves cross more than once)</em>":"")):(a.crossing??!0)&&a.series.length>=2&&c.push("<strong>Crossing:</strong> the first two series do not cross over the swept range");for(const o of n.separations)if(o.distance!==null){const w=o.ratio!==void 0&&o.from!==void 0&&o.to!==void 0?`\xD7${Number(o.ratio.toPrecision(3))} on the ${n.xLabel} axis (${_(o.from,n.xUnit)} \u2192 ${_(o.to,n.xUnit)})`:`${_(o.distance,n.xUnit)} on the ${n.xLabel} axis`;c.push(`<strong>Width at ${m(C(o.y,$))}:</strong> ${m(w)}`)}else{const w=o.reason==="second-series-never-reaches"?n.series[1]?.label:n.series[0]?.label;c.push(`<strong>Width at ${m(C(o.y,$))}:</strong> not measurable \u2014 ${m(w??"a series")} never reaches this level`)}n.series.some(o=>o.points.some(w=>w.derived))&&c.push(`${J} ${m(we)} \u2014 hover a point for the expression`);for(const o of n.warnings)c.push(`<span style="color:${G.errText}">${m(o)}</span>`);W.innerHTML=c.join("<br>")}return Q(),{card:y,destroy:()=>{X?.disconnect(),L.remove(),y.remove()},setDies:n=>{z=n,Q()}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestPassRateData as F,hasJudgeableTests as B}from"../../stats/testPassRate.js";import{categorical as I,QUANTITY as N}from"./palette.js";import{SPACE as H,FONT as _,CLR as
|
|
1
|
+
import{buildTestPassRateData as F,hasJudgeableTests as B}from"../../stats/testPassRate.js";import{categorical as I,QUANTITY as N}from"./palette.js";import{SPACE as H,FONT as _,CLR as S}from"../toolbar.js";import{cardShell as A,makeTooltip as O,makeSegmented as j,renderEmptyState as y}from"./chartShell.js";import{renderGroupedBarPlot as M}from"./groupedBarPlot.js";import{escHtml as g}from"../../core/utils.js";const k=100,G=8,K={spec:"Test limits",testFlag:"Tester flag",functional:"Functional"},U={spec:"Parametric pass rate \xB7 test limits",testFlag:"Parametric pass rate \xB7 tester flag",functional:"Functional test pass rate"};export function renderTestPassRatePanel(f){const{groups:u,testDefs:b,onSaveImage:$}=f,p=f.ownerDocument??document,P=u.flatMap(t=>t.items.flatMap(a=>a.dies??[])),d=["spec","testFlag","functional"].filter(t=>B(b,t,P));let s=d[0]??"spec";const h=t=>U[t],{card:o,heading:v,body:i,controlsRow:x}=A(h(s),$,p);o.style.alignSelf="start",d.length>1&&x.appendChild(j(d.map(t=>[t,K[t]]),s,t=>{s=t,v.textContent=h(s),T()},p));const n=p.createElement("div");Object.assign(n.style,{color:S.label,fontSize:_.body,marginBottom:H.sm}),o.insertBefore(n,i);const D=O(o);let m=null;function T(){if(m?.destroy(),m=null,i.innerHTML="",d.length===0){n.textContent="",y(i,"No tests with a pass/fail verdict \u2014 parametric tests need test limits, functional tests need recorded results.");return}const t=F(u,b,s);if(t.rows.length===0){n.textContent="",y(i,s==="spec"?"No parametric test has test limits to judge against.":s==="testFlag"?"No parametric test has a recorded tester verdict.":"No functional test has recorded results.");return}const a=t.groups.length>1,R=s==="spec"?"Dies within test limits, per test":s==="testFlag"?"Dies the tester marked pass, per test":"Dies passing the recorded verdict, per test";if(n.textContent=R+(a?" \xB7 one bar per group":"")+" \xB7 worst first",t.disagreementDies){const e=p.createElement("span");e.textContent=` \xB7 ${t.disagreementDies.toLocaleString()} die${t.disagreementDies===1?"":"s"} judged differently by limits and tester flag`,Object.assign(e.style,{color:S.warnText}),n.appendChild(e)}const L=t.rows,C=e=>a?I(e):N;m=M(o,i,D,{rows:L.map(e=>({label:e.label,bars:e.byGroup.map(l=>({fraction:l.passRatePercent===null?null:l.passRatePercent/100})),trailing:e.overall.passRatePercent===null?"\u2014":`${e.overall.passRatePercent.toFixed(1)}%`})),groups:t.groups,labelWidth:k,maxVisibleRows:G,showLegend:a,maxLabelChars:14,emptyBarText:"no data",colorOf:C,tooltipHtml:(e,l)=>{const r=L[e],c=r.byGroup[l],w=a?` \xB7 ${g(String(t.groups[l]))}`:"";if(c.passRatePercent===null)return`<strong>${g(r.label)}</strong>${w}<br>no dies with a verdict`;const E=s==="spec"&&!a&&(r.failLowDies||r.failHighDies)?`<br>${r.failLowDies??0} below the low limit \xB7 ${r.failHighDies??0} above the high limit`:"";return`<strong>${g(r.label)}</strong>${w}<br>${c.passRatePercent.toFixed(1)}% pass<br>${c.passDies.toLocaleString()} pass \xB7 ${c.failDies.toLocaleString()} fail \xB7 n = ${c.totalDies.toLocaleString()}`+E}})}return T(),{card:o,destroy:()=>{m?.destroy(),D.remove(),o.remove()}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestTrendData as he,trendCentre as pe}from"../../stats/trend.js";import{isParametricTest as xe}from"../../renderer/buildWaferMap.js";import{fmt as D}from"../../renderer/fmt.js";import{SPACE as le,fontPx as ge,FONT as ye,CLR as be}from"../toolbar.js";import{QUANTITY as
|
|
1
|
+
import{buildTestTrendData as he,trendCentre as pe}from"../../stats/trend.js";import{isParametricTest as xe}from"../../renderer/buildWaferMap.js";import{fmt as D}from"../../renderer/fmt.js";import{SPACE as le,fontPx as ge,FONT as ye,CLR as be}from"../toolbar.js";import{QUANTITY as X}from"./palette.js";import{cardShell as Te,observeResize as ve,makeTooltip as Le,positionChartTooltip as ke,makeLinkedTestSelect as we,makeLinkedAxisPrefs as Ce,renderEmptyState as z,chartFillHeight as Ae,PADDING as G,resolveAxisRange as Me,shouldIncludeLimitsByDefault as Pe,drawOffAxisLimits as Se,makeAxisFormat as Ne,VERTICAL_TICK_SPACING_PX as He,WAFER_MENU_HINT as Ee,prepareCanvas as Ie}from"./chartShell.js";import{fitTicks as De}from"../../renderer/axisTicks.js";import{escHtml as U}from"../../core/utils.js";const se=220,Oe=56,We=34,$e=3.5;export function renderTrendPanel(f){const{items:j,testDefs:re,onSaveImage:ae,onOpen:C,centreLabel:Y="mean of all wafers"}=f,ce=f.title??"Wafer-to-wafer trend",{card:s,body:u,controlsRow:K}=Te(ce,ae,f.ownerDocument);s.style.alignSelf="start";const N=re.filter(o=>o.testNumber!==void 0&&xe(o));let h=f.selectedTestNumber??N[0]?.testNumber??null,O=0;const Q=we(N,h,o=>{h=o,A(),f.onTestChange?.(o)},{maxWidth:"240px",emptyText:"No parametric tests",ownerDocument:s.ownerDocument});K.appendChild(Q.el);const W=s.ownerDocument.createElement("span");Object.assign(W.style,{display:"inline-flex",gap:le.lg,alignItems:"center"}),K.appendChild(W);const $=Ce(W,f.axisPrefs,o=>{f.onAxisPrefsChange?.(o),A()},s.ownerDocument),w=s.ownerDocument.createElement("div");Object.assign(w.style,{color:be.label,fontSize:ye.body,marginBottom:le.sm}),s.insertBefore(w,u);const g=Le(s);let H=null,i=[];function me(o){return N.find(R=>R.testNumber===o)}function A(){const{includeLimits:o,clipOutliers:R}=$.get();if(u.innerHTML="",H?.disconnect(),H=null,N.length===0||h===null){w.textContent="",z(u,"No parametric test data available for a trend.");return}if(j.length<2){w.textContent="",z(u,"A wafer-to-wafer trend needs at least two wafers.");return}if(i=he(j,h),!i.some(l=>l.count>0)){w.textContent="",z(u,"No values recorded for this test.");return}const r=me(h),y=pe(i),V=()=>{w.textContent=`Point = wafer mean, whisker = \xB11\u03C3, dashed = ${Y} \xB7 wafers in slot order`+(C?" \xB7 click a point to open that wafer":"")+(O?` \xB7 axis clipped, ${O} outside`:"")};V();const d=s.ownerDocument.createElement("canvas");d.style.display="block",u.appendChild(d);let M=null;const q=()=>{const l=Math.max(1,u.clientWidth),a=Math.max(se,Ae(s,u,d,se)),n=Ie(d,s,l,a);if(!n)return;const{ctx:e,theme:b}=n;let P=1/0,S=-1/0;const J=[];for(const t of i)!t.count||!Number.isFinite(t.mean)||(P=Math.min(P,t.mean-t.stddev),S=Math.max(S,t.mean+t.stddev),J.push(t.mean-t.stddev,t.mean+t.stddev));if(!Number.isFinite(P)||!Number.isFinite(S))return;const Z=o??Pe(P,S,r?.limitLow,r?.limitHigh);$.sync(Z,r?.limitLow!==void 0||r?.limitHigh!==void 0);const E=Me({dataMin:P,dataMax:S,limitLow:r?.limitLow,limitHigh:r?.limitHigh,includeLimits:Z,clipOutliers:R,values:J});O=E.clippedCount,V();let x=E.lo,T=E.hi;const ee=(T-x)*.08;x-=ee,T+=ee;const c=G+Oe;e.font=`${ge(-1)}px system-ui, sans-serif`;const te=t=>t.length>8?`${t.slice(0,7)}\u2026`:t,de=i.length?Math.ceil(Math.max(e.measureText(te(i[0].label)).width,e.measureText(te(i[i.length-1].label)).width)/2):0,v=l-G-de,I=G,L=a-We,k=t=>L-(t-x)/(T-x)*(L-I),ne=i.length>1?(v-c)/(i.length-1):0;M={left:c,step:ne};const _=t=>i.length>1?c+t*ne:(c+v)/2;e.strokeStyle=b.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,I),e.lineTo(c,L),e.lineTo(v,L),e.stroke(),e.fillStyle=b.text,e.textAlign="right",e.textBaseline="middle";const ie=De(x,T,L-I,()=>He),fe=Ne(Math.max(Math.abs(x),Math.abs(T)),r?.unit,ie.step||void 0);for(const t of ie.ticks)e.fillText(fe.tickWithUnit(t),c-6,k(t));Se(e,E.offAxis,{left:c,right:v,top:I,bottom:L},"vertical",b.limitLine,t=>D(t,r?.unit));for(const[t,p]of[[r?.limitLow,"Lo limit"],[r?.limitHigh,"Hi limit"]]){if(t===void 0||t<x||t>T)continue;const m=k(t);e.save(),e.strokeStyle=b.limitLine,e.globalAlpha=.7,e.setLineDash([4,3]),e.beginPath(),e.moveTo(c,m),e.lineTo(v,m),e.stroke(),e.restore(),e.save(),e.fillStyle=b.limitLine,e.textAlign="left",e.fillText(p,c+3,m-6),e.restore()}if(y!==null&&y>=x&&y<=T){const t=k(y);e.save(),e.strokeStyle=b.text,e.globalAlpha=.5,e.setLineDash([3,3]),e.beginPath(),e.moveTo(c,t),e.lineTo(v,t),e.stroke(),e.restore()}e.strokeStyle=X,e.lineWidth=1.5,e.beginPath();let B=!1;i.forEach((t,p)=>{if(!t.count||!Number.isFinite(t.mean)){B=!1;return}const m=_(p),oe=k(t.mean);B?e.lineTo(m,oe):e.moveTo(m,oe),B=!0}),e.stroke(),i.forEach((t,p)=>{if(!t.count||!Number.isFinite(t.mean))return;const m=_(p);t.stddev>0&&(e.save(),e.strokeStyle=X,e.globalAlpha=.55,e.lineWidth=1,e.beginPath(),e.moveTo(m,k(t.mean-t.stddev)),e.lineTo(m,k(t.mean+t.stddev)),e.stroke(),e.restore()),e.fillStyle=X,e.beginPath(),e.arc(m,k(t.mean),$e,0,Math.PI*2),e.fill()}),e.fillStyle=b.text,e.textBaseline="top",e.textAlign="center";const ue=Math.max(1,Math.ceil(i.length*42/Math.max(1,v-c)));i.forEach((t,p)=>{if(p%ue!==0&&p!==i.length-1)return;const m=t.label.length>8?`${t.label.slice(0,7)}\u2026`:t.label;e.fillText(m,_(p),L+6)})},F=l=>{if(!M||M.step<=0)return-1;const a=d.getBoundingClientRect(),n=Math.round((l.clientX-a.left-M.left)/M.step);return n>=0&&n<i.length&&i[n].count>0?n:-1};d.addEventListener("mousemove",l=>{const a=F(l);if(a<0){g.style.display="none",d.style.cursor="default";return}const n=i[a];d.style.cursor=C&&n.key!==void 0?"pointer":"default",g.innerHTML=`<strong>${U(n.label)}</strong><br>`+U(`mean ${D(n.mean,r?.unit)} \xB7 \u03C3 ${D(n.stddev,r?.unit)}`)+`<br>n = ${n.count.toLocaleString()}`+(y!==null?"<br>"+U(`\u0394 vs ${Y} ${n.mean-y>=0?"+":""}${D(n.mean-y,r?.unit)}`):"")+(C&&n.key!==void 0?"<br><em>click to open this wafer</em>":"")+(f.onWaferContextMenu&&n.key!==void 0?Ee:""),g.style.display="block",ke(g,s,l.clientX,l.clientY)}),d.addEventListener("mouseleave",()=>{g.style.display="none"}),d.addEventListener("click",l=>{const a=F(l);if(a<0||!C)return;const n=i[a];n.key!==void 0&&h!==null&&C(n.key,h)}),d.addEventListener("contextmenu",l=>{const a=F(l),n=a<0?void 0:i[a].key;n===void 0||!f.onWaferContextMenu||(g.style.display="none",f.onWaferContextMenu(n,h??void 0,l))}),q(),H=ve(u,q)}return A(),{card:s,destroy:()=>{H?.disconnect(),g.remove(),s.remove()},setTest:o=>{Q.set(o)&&(h=o,A())},setAxisPrefs:o=>{$.set(o)&&A()}}}
|