@wafertools/wafermap 0.30.2 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +16 -5
- package/CHANGELOG.md +384 -0
- package/README.md +6 -3
- package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
- package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -1
- 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 +81 -7
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
- package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/dieList.js +2 -2
- package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
- package/dist/packages/canvas-adapter/drilldown.js +1 -0
- package/dist/packages/canvas-adapter/icons.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +55 -3
- package/dist/packages/canvas-adapter/insightsTab.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.d.ts +30 -2
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +126 -3
- 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/renderer/axisTicks.d.ts +32 -0
- package/dist/packages/renderer/axisTicks.js +1 -0
- package/dist/packages/renderer/buildView.d.ts +11 -37
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +58 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +13 -5
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
- package/dist/packages/renderer/derivedTests/apply.js +1 -0
- package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
- package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
- package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
- package/dist/packages/renderer/derivedTests/parser.js +2 -0
- package/dist/packages/renderer/fmt.d.ts +24 -2
- package/dist/packages/renderer/fmt.js +1 -1
- package/dist/packages/renderer/index.d.ts +1 -0
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +49 -0
- package/dist/packages/renderer/spec.js +1 -0
- package/dist/packages/renderer/testLabel.d.ts +154 -0
- package/dist/packages/renderer/testLabel.js +1 -0
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +4 -0
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/correlation.d.ts +3 -0
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/deprecated.d.ts +3 -0
- package/dist/packages/stats/deprecated.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -2
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +8 -28
- package/dist/packages/stats/renderSummaryReport.js +21 -30
- package/dist/packages/stats/reportHtml.d.ts +27 -0
- package/dist/packages/stats/reportHtml.js +35 -14
- package/dist/packages/stats/sweep.d.ts +178 -0
- package/dist/packages/stats/sweep.js +1 -0
- package/dist/packages/stats/sweepXFromName.d.ts +27 -0
- package/dist/packages/stats/sweepXFromName.js +1 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/types.d.ts +28 -1
- package/llms.txt +3 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestBoxplotData 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 L,VALUE_WIDTH as vt,WAFER_MENU_HINT as kt,prepareCanvas as Lt}from"./chartShell.js";import{escHtml as M,maxOf as Mt,minOf as Ct}from"../../core/utils.js";const C=24,S=5,We=110,St=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:U}=u,_e=u.openActionLabel??"open that wafer",Ge=u.openActionLabel??"open this wafer",{card:a,heading:Ne,body:x,controlsRow:z}=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,Q=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),z.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(),A(),!0)}const we=ut(ne,y,i=>{y=i,A(),u.onTestChange?.(i)},{maxWidth:"240px",emptyText:"No parametric tests",ownerDocument:a.ownerDocument}),qe=we.el;z.appendChild(qe),z.appendChild(mt("Log scale",ie,i=>{ie=i,A()},a.ownerDocument));const le=a.ownerDocument.createElement("span");Object.assign(le.style,{display:"inline-flex",gap:Be.lg,alignItems:"center"}),z.appendChild(le);const se=dt(le,u.axisPrefs,i=>{u.onAxisPrefsChange?.(i),A()},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`):U&&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`+(Q?` \xB7 axis clipped, ${Q} value${Q===1?"":"s"} outside`:"")}F(),Te();const D=rt(a);let re=null;function A(){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=L*2+Math.min(f.length,St)*(C+S)+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)),Le=Mt(fe.map(t=>t.max)),Me=i??Tt(ke,Le,V,j);se.sync(Me,V!==void 0||j!==void 0);const _=bt({dataMin:ke,dataMax:Le,limitLow:V,limitHigh:j,includeLimits:Me,clipOutliers:p,values:fe.flatMap(t=>[t.min,t.q1,t.median,t.q3,t.max])});Q=_.clippedCount,F();const O=_.lo,K=_.hi,ze=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 Qe(){const t=L+We,s=m.clientWidth-t-vt-L;return{plotX:t,plotMaxWidth:Math.max(10,s)}}function $(t,s,n){return J?s+(Math.log10(t)-ue)/Ce*n:s+(t-O)/ze*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=L*2+f.length*(C+S)+Oe;x.style.maxHeight=`${xt(a,x,ve,s)}px`;const n=x.clientWidth,d=Lt(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}=Qe();f.forEach((o,T)=>{const v=L+T*(C+S),c=v+C/2;T===P&&(e.fillStyle=t.bgHover,e.fillRect(0,v-S/2,n,C+S)),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,L+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)),Ae=$(o.min,l,r),$e=$(o.max,l,r),I=N(Ae),X=N($(o.q1,l,r)),Ie=N($(o.median,l,r)),pe=N($(o.q3,l,r)),B=N($e),Ze=Ae<I,et=$e>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=L+f.length*(C+S);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,"LSL"],[j,"USL"]]){if(o===void 0||Ve.has(o))continue;const v=$(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==="LSL");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=$(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-L+S/2)/(C+S));return s>=0&&s<f.length?s:-1}function G(t){return he(t,W,"engineering")}const E=!!g&&g.length>0&&b===null,Se=t=>!E&&!!U&&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||Se(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 ${M(te)} by wafer</em>`:Se(n)?`<br><em>click to ${M(Ge)}</em>`:"",r=!E&&u.onWaferContextMenu&&h[n]?.waferIndex!==void 0?kt:"";D.innerHTML=`<strong>${M(e.label)}</strong> (${e.count} dies)<br>max ${M(G(e.max))}<br>q3 ${M(G(e.q3))}<br>median ${M(G(e.median))}<br>q1 ${M(G(e.q1))}<br>min ${M(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;U&&d!==void 0&&y!==null&&U(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()}A();function Ye(i){we.set(i)&&(y=i,A())}function Ue(i){se.set(i)&&A()}return{card:a,setTest:Ye,setAxisPrefs:Ue,destroy:()=>re?.disconnect()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildCapabilityData as
|
|
1
|
+
import{buildCapabilityData as xe}from"../../stats/capability.js";import{capabilityColor as j}from"./palette.js";import{LEADING as ge,SPACE as S,fontPx as K,FONT as Q,CLR as R}from"../toolbar.js";import{cardShell as ve,isExpandedCard as Se,chartFillHeight as Me,applyCanvasFlow as Te,observeResize as Ce,makeTooltip as Le,positionChartTooltip as ke,renderEmptyState as $e,resolveChartCanvasColors as Ee,chartSwatchCss as Pe,prepareCanvas as De,chartDpr as Ae}from"./chartShell.js";import{fmt as We}from"../../renderer/fmt.js";import{escHtml as z}from"../../core/utils.js";import{DERIVED_MARK as Y,DERIVED_KEY as oe}from"../../renderer/testLabel.js";const we=30,le=160,se=90,ie=28,T=42;function O(E){return E===null?"\u2014":E.toFixed(2)}export function renderCapabilityPanel(E){const{title:ae="Process capability",items:re,testDefs:X,onSaveImage:ce,onSelectTest:_}=E;let F=E.selectedTestNumber??null;const{card:r,body:C}=ve(ae,ce,E.ownerDocument),me=X.some(n=>n.testNumber!==void 0);C.style.overflowX="auto";function de(){return re}const P=r.ownerDocument.createElement("div");Object.assign(P.style,{display:"flex",flexDirection:"column",gap:S.xs,marginBottom:S.sm}),r.insertBefore(P,C);const A=Le(r);let B=()=>{};function pe(n,i,x,h){P.innerHTML="";const b=r.ownerDocument.createElement("span");Object.assign(b.style,{display:"inline-flex",alignItems:"center",gap:S.sm,color:R.value,fontSize:Q.body,fontWeight:"500"});const L=x-n,m=i>0?` \xB7 ${i} without spec limits`:"",u=r.ownerDocument.createElement("span");u.textContent=L>0?`${n} of ${x} tests shown${m} \xB7 ${L} excluded (no recorded values)`:`${n} test${n!==1?"s":""} shown${m}`,b.appendChild(u),P.appendChild(b);const g=i>=n,M=r.ownerDocument.createElement("div");M.textContent=g?"No test has both spec 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.":"Normalised to spec limits (LSL = 0, USL = 1), worst Ppk first. Tests without both limits are normalised to their own observed range and drawn muted/dashed.",Object.assign(M.style,{color:R.label,fontSize:Q.body,lineHeight:ge.base,marginTop:S.xs,maxWidth:"78ch"}),P.appendChild(M),P.appendChild(fe(r.ownerDocument,i>0,g,h>0))}function fe(n,i,x=!1,h=!1){const b=n.createElement("div");Object.assign(b.style,{display:"flex",flexWrap:"wrap",alignItems:"center",gap:`${S.xs} ${S.xl}`,marginTop:S.xl,marginBottom:S.xs,fontSize:Q.body,color:R.label});const L=x?[]:[{color:j(2),label:"Capable \xB7 Ppk \u2265 1.33"},{color:j(1.1),label:"Marginal \xB7 Ppk \u2265 1.0"},{color:j(.5),label:"Poor \xB7 Ppk < 1.0"}];i&&L.push({color:R.label,label:"No spec limits \xB7 no Ppk",dashed:!0});for(const m of L){const u=n.createElement("span");Object.assign(u.style,{display:"inline-flex",alignItems:"center",gap:S.sm});const g=n.createElement("span");g.style.cssText=Pe(m.color,m.dashed?"outline":"solid");const M=n.createElement("span");M.textContent=m.label,u.append(g,M),b.appendChild(u)}if(h){const m=n.createElement("span");Object.assign(m.style,{display:"inline-flex",alignItems:"center",gap:S.sm});const u=n.createElement("span");u.textContent=Y,Object.assign(u.style,{color:R.value,fontWeight:"600"});const g=n.createElement("span");g.textContent=oe,m.append(u,g),b.appendChild(m)}return b}function he(n){if(C.innerHTML="",n.length===0)return $e(C,"No parametric tests have any recorded values.",{maxWidth:"480px"}),()=>{};const i=r.ownerDocument.createElement("canvas");i.style.display="block",i.style.cursor="default",C.appendChild(i);const x=n.length,h=Math.min(0,...n.map(t=>t.min)),b=Math.max(1,...n.map(t=>t.max)),L=(b-h)*.05||.1,m=h-L,g=b+L-m||1;function M(t){const l=Math.floor(t/x);return Math.max(we,Se(r)?l:Math.min(le,l))}function k(t,l,o){return l+(1-(t-m)/g)*o}function ye(t,l,o,s,$){const y=n.filter(a=>a.hasSpec).length,D=y===n.length,d=y===0,H=d?"max":"USL",e=d?"min":"LSL",p=D?"normalised to spec":d?"normalised to range":"normalised (per test)";t.font=`${K(-1)}px system-ui, sans-serif`,t.textAlign="right",t.textBaseline="middle";for(const[a,w]of[[1,H],[0,e]]){const f=k(a,o,s);f<o-1||f>o+s+1||(t.strokeStyle=l.limitLine,t.lineWidth=1,t.beginPath(),t.moveTo(T-5,f),t.lineTo(T,f),t.stroke(),t.fillStyle=l.textMuted,t.fillText(w,T-8,f))}t.save(),t.translate(9,o+s/2),t.rotate(-Math.PI/2),t.textAlign="center",t.fillStyle=l.textMuted,t.fillText(p,0,0),t.restore()}let W=-1;function U(){Te(i);const t=C.clientWidth,l=M(t-T),o=l*x,s=ie,$=T+o,y=Me(r,C,i,ie+200+se),D=Math.max(s+60,y-se),d=D-s,H=De(i,r,$,y);if(!H)return;const{ctx:e}=H,p=Ee(r);n.forEach((a,w)=>{const f=T+w*l,c=f+l/2,v=Math.max(4,Math.min(l,le)*.55);if(w===W&&(e.fillStyle=p.bgHover,e.fillRect(f,s,l,D-s)),n[w].testNumber===F&&(e.fillStyle=p.accent,e.fillRect(f,D-2,l,2)),a.hasSpec){const te=k(0,s,d),ne=k(1,s,d);e.strokeStyle=p.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(f,te),e.lineTo(f+l,te),e.moveTo(f,ne),e.lineTo(f+l,ne),e.stroke(),e.setLineDash([])}const V=a.hasSpec?j(a.ppk):p.textMuted,q=k(a.min,s,d),N=k(a.q1,s,d),Z=k(a.median,s,d),I=k(a.q3,s,d),G=k(a.max,s,d);e.strokeStyle=p.textMuted,e.lineWidth=1,e.beginPath(),e.moveTo(c,q),e.lineTo(c,N),e.moveTo(c,I),e.lineTo(c,G),e.moveTo(c-v/4,q),e.lineTo(c+v/4,q),e.moveTo(c-v/4,G),e.lineTo(c+v/4,G),e.stroke(),e.fillStyle=V,e.globalAlpha=.5,e.fillRect(c-v/2,Math.min(N,I),v,Math.max(1,Math.abs(I-N))),e.globalAlpha=1,e.strokeStyle=V,a.hasSpec||e.setLineDash([4,2]),e.strokeRect(c-v/2,Math.min(N,I),v,Math.max(1,Math.abs(I-N))),e.setLineDash([]),e.strokeStyle=p.text,e.lineWidth=2,e.beginPath(),e.moveTo(c-v/2,Z),e.lineTo(c+v/2,Z),e.stroke(),e.lineWidth=1,e.font=`${K(-1)}px system-ui, sans-serif`,e.textAlign="center",e.textBaseline="alphabetic",e.fillStyle=a.hasSpec?V:p.textMuted,e.fillText(a.hasSpec?O(a.ppk):"\u2014",c,s-8);const ee=a.label.length>12?`${a.label.slice(0,11)}\u2026`:a.label;e.save(),e.translate(c,D+6),e.rotate(-Math.PI/4),e.textAlign="right",e.textBaseline="middle",e.font=`${K(-1)}px system-ui, sans-serif`,e.fillStyle=p.textMuted,e.fillText(ee,0,0),a.derived&&(e.fillStyle=p.text,e.fillText(`${Y} `,-e.measureText(ee).width,0)),e.restore()}),e.strokeStyle=p.border,e.lineWidth=1,e.strokeRect(T,s,o,d),ye(e,p,s,d,o)}function J(t){const l=i.getBoundingClientRect(),o=C.clientWidth,s=M(o-T),$=(t.clientX-l.left)*(i.width/Ae(i)/l.width)-T,y=Math.floor($/s);return y>=0&&y<x?y:-1}return i.addEventListener("mousemove",t=>{const l=J(t);if(l!==W&&(W=l,U()),l===-1){A.style.display="none";return}const o=n[l],s=y=>z(We(y,o.unit)),$=o.derived?`<em>${Y} ${z(oe)}</em>`+(o.expression?`<br><code>${z(o.expression)}</code>`:"")+"<br>":"";A.innerHTML=`<strong>${z(o.label)}</strong> (n=${o.n})<br>${$}`+(o.hasSpec?`LSL ${s(o.lsl)} \xB7 USL ${s(o.usl)}<br>mean ${s(o.mean)}<br>Cp ${O(o.cp)} \xB7 Cpk ${O(o.cpk)}<br>Pp ${O(o.pp)} \xB7 Ppk ${O(o.ppk)}`:`<em>No spec limits \u2014 sorted by variability</em><br>mean ${s(o.mean)} \xB7 stddev ${s(o.stdOverall)}`)+(_?"<br><em>click to view in boxplot</em>":""),A.style.display="block",ke(A,r,t.clientX,t.clientY),i.style.cursor=_?"pointer":"default"}),i.addEventListener("mouseleave",()=>{W!==-1&&(W=-1,U()),A.style.display="none"}),i.addEventListener("click",t=>{if(!_)return;const l=J(t);l!==-1&&_(n[l].testNumber)}),U}function be(){const n=xe(de(),X),i=X.filter(h=>h.testNumber!==void 0).length,x=n.filter(h=>!h.hasSpec).length;pe(n.length,x,i,n.filter(h=>h.derived).length),B=he(n),B()}const ue=Ce(r,()=>B());return be(),{card:r,hasData:me,setTest:n=>{F!==n&&(F=n,B())},destroy:()=>ue.disconnect()}}
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { type SaveImageHandler } from '../toolbar.js';
|
|
2
2
|
export type { SaveImageHandler };
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* The card's own expand button is hidden for the modal's duration and shown
|
|
5
5
|
* again on close — a card already sitting inside its own expand modal must
|
|
6
6
|
* not offer a second "expand" of itself: clicking it would reparent the same
|
|
7
7
|
* card into a *second*, nested modal, leaving the first one open but empty
|
|
8
8
|
* (found via exactly that — every card kept its expand icon once expanded).
|
|
9
|
+
* The button is found on the card (`data-wmap-chart-expand`) rather than
|
|
10
|
+
* passed in, so a card opened straight into a modal by something other than
|
|
11
|
+
* its own button — a drilldown — gets the same treatment.
|
|
12
|
+
*
|
|
13
|
+
* `opts.anchor` is for a card that is not on the page yet (see
|
|
14
|
+
* `ReparentModalOptions.anchor`); `opts.onClosed` runs once the modal has
|
|
15
|
+
* closed and the card is back where it came from — the place for a card that
|
|
16
|
+
* existed only for the modal to be destroyed.
|
|
9
17
|
*/
|
|
10
|
-
export declare function openChartExpandModal(card: HTMLElement, title: string,
|
|
18
|
+
export declare function openChartExpandModal(card: HTMLElement, title: string, opts?: {
|
|
19
|
+
anchor?: Element;
|
|
20
|
+
onClosed?: () => void;
|
|
21
|
+
}): void;
|
|
11
22
|
export interface ChartCanvasColors {
|
|
12
23
|
text: string;
|
|
13
24
|
textMuted: string;
|
|
@@ -58,10 +69,10 @@ export declare function observeResize(el: HTMLElement, onResize: () => void): {
|
|
|
58
69
|
* (drag-resize, maximize/restore); a "only grow, never shrink" policy would
|
|
59
70
|
* leave a stale, too-large min-height stuck from a bigger prior size after
|
|
60
71
|
* the modal shrinks, forcing the exact overflow this function exists to
|
|
61
|
-
* prevent.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
72
|
+
* prevent. The overhead is measured from `card`'s other children
|
|
73
|
+
* (`cardOverhead`), so it is the same whether or not the card is currently
|
|
74
|
+
* stretched, and one call is exact. Ask for the height the content NEEDS,
|
|
75
|
+
* never the height the card was given — the latter can only ever grow.
|
|
65
76
|
*
|
|
66
77
|
* The Analysis tab composes several of these cards into one CSS Grid row
|
|
67
78
|
* per section (`makeChartGridWrap`); nothing about that grid's own row
|
|
@@ -77,6 +88,46 @@ export declare function observeResize(el: HTMLElement, onResize: () => void): {
|
|
|
77
88
|
* directly with their visible-rows height.
|
|
78
89
|
*/
|
|
79
90
|
export declare function growCardToFitContent(card: HTMLElement, body: HTMLElement, contentHeight: number): void;
|
|
91
|
+
/**
|
|
92
|
+
* Right-click on one wafer's bar, box or point. The chart hands over the wafer
|
|
93
|
+
* and the test it is showing; the handler owns the event (`preventDefault` to
|
|
94
|
+
* replace the browser's menu) and decides what to offer — Insights opens the
|
|
95
|
+
* drilldown menu on that wafer. Group rows (several wafers pooled) never call it.
|
|
96
|
+
*/
|
|
97
|
+
export type WaferContextMenuHandler = (waferIndex: number, testNumber: number | undefined, e: MouseEvent) => void;
|
|
98
|
+
/** The tooltip line saying a mark has one — one wording on every chart. */
|
|
99
|
+
export declare const WAFER_MENU_HINT = "<br><em>right-click to chart this wafer</em>";
|
|
100
|
+
/**
|
|
101
|
+
* How many dies a chart is drawn from, and who they are: "12 dies", "12 dies
|
|
102
|
+
* selected on W03", or "10 of 12 dies selected on W03 (partial and
|
|
103
|
+
* edge-excluded dies left out)" when eligibility dropped some — which a
|
|
104
|
+
* hand-picked selection makes visible in a way a whole wafer does not.
|
|
105
|
+
*
|
|
106
|
+
* `counted` is what the chart actually used; `total` what it was given.
|
|
107
|
+
* `population` omitted = the view's own population, which says so elsewhere.
|
|
108
|
+
*/
|
|
109
|
+
export declare function populationPhrase(counted: number, total: number, population?: string): string;
|
|
110
|
+
export type ChartGrow = 'plot' | 'rows' | 'square';
|
|
111
|
+
/** Declare how a card grows when expanded — see the block comment above. */
|
|
112
|
+
export declare function setChartGrow(card: HTMLElement, grow: ChartGrow): void;
|
|
113
|
+
/** True while `card` is in its expand modal. */
|
|
114
|
+
export declare function isExpandedCard(card: HTMLElement): boolean;
|
|
115
|
+
/** Height `body` has for content when it takes all of `card`'s free space,
|
|
116
|
+
* less `body`'s children other than `except`. */
|
|
117
|
+
export declare function bodyRoom(card: HTMLElement, body: HTMLElement, except?: Element): number;
|
|
118
|
+
/**
|
|
119
|
+
* Height to show a list of rows at. The card asks for `cappedHeight` (the grid
|
|
120
|
+
* size, rows beyond it scroll); expanded, it shows as many more rows as the
|
|
121
|
+
* modal has room for, up to `fullHeight`. `besideHeight` is whatever else is in
|
|
122
|
+
* `body` above the rows (a legend).
|
|
123
|
+
*
|
|
124
|
+
* The request never includes the extra: a card that asked for the height it had
|
|
125
|
+
* been given would never shrink when the modal is made smaller.
|
|
126
|
+
*
|
|
127
|
+
* Also records the card's full height, for `openChartExpandModal` to open the
|
|
128
|
+
* box at a size that shows every row.
|
|
129
|
+
*/
|
|
130
|
+
export declare function fitRowsHeight(card: HTMLElement, body: HTMLElement, cappedHeight: number, fullHeight: number, besideHeight?: number): number;
|
|
80
131
|
/**
|
|
81
132
|
* Canvas height for a "fill the body" chart: siblings already in `body`
|
|
82
133
|
* above the canvas (a stats label, a legend, ...) get their own natural
|
|
@@ -149,11 +200,25 @@ export declare function formatValue(v: number): string;
|
|
|
149
200
|
* colorbar uses, so axes never show raw exponent soup like "861E-6" next
|
|
150
201
|
* to a tooltip that says "861 µA". Falls back to `formatValue` ticks and
|
|
151
202
|
* no label when the test has no unit.
|
|
203
|
+
*
|
|
204
|
+
* Pass `step` — the tick spacing from `fitTicks` (`renderer/axisTicks.ts`) —
|
|
205
|
+
* and every label carries exactly the decimals that step needs. `tickWithUnit`
|
|
206
|
+
* is the same label with the scaled unit appended, for an axis with no
|
|
207
|
+
* separate unit label.
|
|
152
208
|
*/
|
|
153
|
-
export declare function makeAxisFormat(vRef: number, unit: string | undefined): {
|
|
209
|
+
export declare function makeAxisFormat(vRef: number, unit: string | undefined, step?: number): {
|
|
154
210
|
tick: (v: number) => string;
|
|
211
|
+
tickWithUnit: (v: number) => string;
|
|
155
212
|
unitLabel: string;
|
|
156
213
|
};
|
|
214
|
+
/** Minimum distance between tick centres on a vertical axis, px — a label line and room around it. */
|
|
215
|
+
export declare const VERTICAL_TICK_SPACING_PX = 28;
|
|
216
|
+
/**
|
|
217
|
+
* The spacing a horizontal axis's ticks need at a given step: the widest label
|
|
218
|
+
* that step would draw — measured, in the context's current font — plus a gap.
|
|
219
|
+
* Passed to `fitTicks`, which then picks the finest round step whose labels fit.
|
|
220
|
+
*/
|
|
221
|
+
export declare function horizontalTickSpacing(ctx: CanvasRenderingContext2D, vRef: number, unit: string | undefined): (step: number, ticks: number[]) => number;
|
|
157
222
|
/** Draw a small "(unit)" label at (x, y), restoring the context's text state afterward. */
|
|
158
223
|
export declare function drawAxisUnit(ctx: CanvasRenderingContext2D, unit: string, x: number, y: number, color: string): void;
|
|
159
224
|
export declare function saveCanvasPng(canvas: HTMLCanvasElement, filenameStem: string, onSaveImage?: SaveImageHandler): void;
|
|
@@ -250,7 +315,16 @@ export declare function makeListSelect(options: readonly ListSelectOption[], sel
|
|
|
250
315
|
export interface TestSelectItem {
|
|
251
316
|
testNumber: number;
|
|
252
317
|
name?: string;
|
|
318
|
+
/** A derived test — listed as `"† name"`, like everywhere else it is named. */
|
|
319
|
+
derived?: true;
|
|
253
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Picker labels for a list of tests: the derived mark in front of a derived
|
|
323
|
+
* test's name, and — when the list holds any — measured names padded by the
|
|
324
|
+
* mark's width so every name starts at about the same x. A list with no derived
|
|
325
|
+
* test is plain names, unchanged.
|
|
326
|
+
*/
|
|
327
|
+
export declare function testOptionLabels(items: readonly TestSelectItem[]): string[];
|
|
254
328
|
/**
|
|
255
329
|
* Below `MENU_SEARCH_THRESHOLD` tests this is a plain native `<select>`. Past
|
|
256
330
|
* it, a long native dropdown becomes hard to scan, so it's replaced by a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SHADOW as I,LEADING as F,wireControlHover as L,controlStyle as T,SPACE as m,RADIUS as k,fontPx as j,FONT as v,CLR as f,Z_BASE as q,menuLayerFor as G,wireListNavigation as U,MENU_SEARCH_THRESHOLD as D,makeMenuSearchBox as Y,markMenuTrigger as R,saveImageBlob as X,openReparentedModal as Z}from"../toolbar.js";import{ICONS as N}from"../icons.js";import{fmt as K,fmtColorbarAxis as J}from"../../renderer/fmt.js";export function openChartExpandModal(e,r,n){for(const i of e.querySelectorAll('div[style*="position: absolute"]'))i.style.display==="block"&&i.style.pointerEvents==="none"&&(i.style.display="none");const t=e.getAttribute("style")??"";if(Object.assign(e.style,{flex:"1",minHeight:"0",border:"none",borderRadius:"0"}),!Z([e],{title:r,onClosed:()=>{e.setAttribute("style",t),n&&(n.style.display="flex")}})){e.setAttribute("style",t);return}n&&(n.style.display="none")}const Q={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},P={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 r=getComputedStyle(e),n={};for(const t of Object.keys(P)){const o=r.getPropertyValue(`--wmap-${P[t]}`).trim();n[t]=o||Q[t]}return n}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,r){const n=e.ownerDocument.defaultView??window;let t=0;const o=new n.ResizeObserver(()=>{t||(t=n.requestAnimationFrame(()=>{t=0,r()}))});return o.observe(e),{disconnect:()=>{t&&n.cancelAnimationFrame(t),t=0,o.disconnect()}}}export function growCardToFitContent(e,r,n){const o=`${e.offsetHeight-r.clientHeight+n}px`;e.style.minHeight!==o&&(e.style.minHeight=o)}export function chartFillHeight(e,r,n,t){const o=e.ownerDocument.defaultView??window;let i=0;for(const a of Array.from(r.children)){if(a===n)continue;const s=a,l=o.getComputedStyle(s);i+=s.offsetHeight+(parseFloat(l.marginTop)||0)+(parseFloat(l.marginBottom)||0)}return growCardToFitContent(e,r,i+t),Math.max(t,r.clientHeight-i)}export function chartDpr(e){return(e.ownerDocument.defaultView??window).devicePixelRatio||1}export function prepareCanvas(e,r,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 i=e.getContext("2d");return i?(i.setTransform(o,0,0,o,0,0),i.clearRect(0,0,n,t),i.font=`${j(-1)}px system-ui, sans-serif`,i.textBaseline="middle",{ctx:i,theme:resolveChartCanvasColors(r)}):null}export function applyCanvasFlow(e,r=0){let n;if(typeof r=="number")n=r;else{const t=(r.ownerDocument.defaultView??window).getComputedStyle(r);n=r.offsetTop+r.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}`:K(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,r){if(!r)return{tick:formatValue,unitLabel:""};const{tickFmt:n,axisLabel:t}=J(e,null,r);return{tick:n,unitLabel:t}}export function drawAxisUnit(e,r,n,t,o){const i={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font=`${j(-1)}px system-ui, sans-serif`,e.textAlign="left",e.textBaseline="middle",e.fillStyle=o,e.fillText(`(${r})`,n,t),Object.assign(e,i)}export function saveCanvasPng(e,r,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(i=>{i&&X(i,r,n)},"image/png")}const z="10px";export function chartSwatchCss(e,r="solid"){return["display:inline-block",`width:${z}`,`height:${z}`,`border-radius:${k.pill}`,`background:${r==="outline"?"transparent":e}`,`border:1px ${r==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const ee="0.35";export function makeSeriesLegendItem(e,r,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 ${f.menuBorder}`,background:"none",cursor:"pointer",fontSize:v.body,color:f.text,whiteSpace:"nowrap"}),L(t,"bare");const o=n.createElement("span");return o.style.cssText=chartSwatchCss(r),t.append(o,n.createTextNode(e)),{el:t,setState({selected:i,dimmed:a}){t.style.borderColor=i?f.iconActive:f.menuBorder,t.style.fontWeight=i?"600":"400",t.style.opacity=a?ee:"1",t.setAttribute("aria-pressed",i?"true":"false")}}}export function cardFrameStyle(){return{background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:k.container,padding:m.xl,fontVariantNumeric:"tabular-nums"}}export function cardShell(e,r,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 ${f.menuBorder}`});const i=t.ownerDocument.createElement("div");i.textContent=e,Object.assign(i.style,{color:f.value,fontSize:v.heading,fontWeight:"600",flex:"1"}),o.appendChild(i);const a=makeTooltip(t),s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Save as PNG"),s.innerHTML=N.download,Object.assign(s.style,{...T("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:F.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(s),attachChartTip(s,t,a,"Save as PNG"),s.addEventListener("click",()=>{const p=t.querySelector("canvas");p&&saveCanvasPng(p,e,r)}),o.appendChild(s);const l=t.ownerDocument.createElement("button");l.type="button",l.setAttribute("aria-label","Expand"),l.innerHTML=N.expand,Object.assign(l.style,{...T("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:F.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(l),attachChartTip(l,t,a,"Expand"),l.addEventListener("click",()=>openChartExpandModal(t,i.textContent??e,l)),o.appendChild(l),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:i,controlsRow:c,body:d}}export function makeSegmented(e,r,n,t=document,o=!1){const i=t.createElement("div");i.setAttribute("role","radiogroup"),Object.assign(i.style,{display:"inline-flex",border:`1px solid ${f.menuBorder}`,borderRadius:k.control,overflow:"hidden"});const a=`seg-${Math.random().toString(36).slice(2,9)}`,s=[];return e.forEach(([l,c],d)=>{const p=t.createElement("label");Object.assign(p.style,{display:"inline-flex",alignItems:"center",fontSize:v.body,padding:o?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:d>0?`1px solid ${f.menuBorder}`:"none"}),L(p,"bare");const u=t.createElement("input");u.type="radio",u.name=a,u.value=l,u.checked=l===r,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{p.style.background=u.checked?f.bgActive:f.menuBg,p.style.color=u.checked?f.iconActive:f.text,p.dataset.on=u.checked?"true":"false"};s.push(w),w(),u.addEventListener("change",()=>{if(u.checked){for(const C of s)C();n(l)}}),p.append(u,t.createTextNode(c)),i.appendChild(p)}),i}export function makeBackButton(e,r=document){const n=r.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...T("outlined"),background:f.menuBg,color:f.label}),L(n),n.addEventListener("click",e),n}export function renderEmptyState(e,r,n){const t=e.ownerDocument.createElement("div");t.textContent=r,Object.assign(t.style,{color:f.label,fontSize:v.body,padding:"8px 0"}),n&&Object.assign(t.style,n),e.appendChild(t)}export function makeListSelect(e,r,n,t={}){const{maxWidth:o="200px",ownerDocument:i=document,ariaLabel:a,emptyText:s,searchPlaceholder:l="Filter\u2026",hook:c}=t;let d=r;const p=x=>e.find(O=>O.value===x)?.label??"",u=i.createElement("button");u.type="button",c&&(u.dataset.wmapSelect=c),a&&u.setAttribute("aria-label",a),Object.assign(u.style,{fontSize:v.body,padding:`${m.xxs} ${m.sm}`,background:f.menuBg,color:f.text,border:`1px solid ${f.menuBorder}`,borderRadius:k.control,maxWidth:o,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:m.sm,justifyContent:"space-between"}),L(u),R(u,!1);const w=i.createElement("span");Object.assign(w.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const C=i.createElement("span");C.textContent="\u25BE",C.style.flex="0 0 auto",u.append(w,C);const S=()=>{w.textContent=e.length===0?s??"(none)":p(d)||s||"(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:x=>{d=x,S()}});let h=null;const E=()=>{h&&(h.remove(),h=null,R(u,!1))},A=()=>{E(),u.focus()};function W(){const x=u.getBoundingClientRect(),O=i.defaultView??window,H=Math.max(x.width,220),_=Math.min(x.left,Math.max(4,(O.innerWidth??1/0)-H-4));h=i.createElement("div"),h.setAttribute("role","listbox"),h.setAttribute("aria-label",a??"Options"),Object.assign(h.style,{position:"fixed",top:`${x.bottom+4}px`,left:`${_}px`,background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:k.control,boxShadow:I.menu,zIndex:q,minWidth:`${H}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const $=[],B=()=>$.filter(b=>b.row.style.display!=="none").map(b=>b.row);if(e.length>D){const b=Y(y=>{for(const g of $)g.row.style.display=g.label.includes(y)?"":"none"},l,i);Object.assign(b.style,{position:"sticky",top:"0",zIndex:"1",background:f.menuBg}),b.addEventListener("keydown",y=>{if(y.key==="Escape"){A();return}y.key==="ArrowDown"&&(y.preventDefault(),B()[0]?.focus())}),h.appendChild(b)}for(const b of e){const y=b.value===d,g=i.createElement("div");g.textContent=b.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?f.iconActive:f.text,fontWeight:y?"700":"400",background:y?f.menuActive:"transparent",whiteSpace:"nowrap"}),g.addEventListener("mouseenter",()=>{y||(g.style.background=f.menuHover)}),g.addEventListener("mouseleave",()=>{y||(g.style.background="transparent")}),g.addEventListener("click",V=>{V.stopPropagation(),d=b.value,S(),A(),n(b.value)}),h.appendChild(g),$.push({row:g,label:b.label.toLowerCase()})}if(U(h,B,A),G(u).appendChild(h),R(u,!0),e.length<=D){const b=e.findIndex(y=>y.value===d);B()[b>=0?b:0]?.focus()}}u.addEventListener("click",x=>{if(x.stopPropagation(),h){E();return}W()});const M=x=>{if(!u.isConnected){i.removeEventListener("click",M);return}h&&!h.contains(x.target)&&!u.contains(x.target)&&E()};return i.addEventListener("click",M),Object.defineProperty(u,"value",{get:()=>d,set:x=>{d=x,S()}})}export function makeTestSelect(e,r,n,t={}){const{maxWidth:o="200px",emptyText:i="No parametric tests",ownerDocument:a=document}=t;return makeListSelect(e.map(s=>({value:String(s.testNumber),label:s.name||`Test ${s.testNumber}`})),r!==null?String(r):"",s=>n(Number(s)),{maxWidth:o,ownerDocument:a,ariaLabel:"Test",emptyText:i,searchPlaceholder:"Filter tests\u2026"})}export function makeLinkedAxisPrefs(e,r,n,t=document){let o=r?.includeLimits,i=r?.clipOutliers??!1;const a=()=>n({includeLimits:o,clipOutliers:i});return{get:()=>({includeLimits:o,clipOutliers:i}),sync(s,l){e.innerHTML="",l&&e.appendChild(makeToggle("Axis includes limits",s,c=>{o=c,a()},t)),e.appendChild(makeToggle("Clip outliers",i,c=>{i=c,a()},t))},set(s){return s.includeLimits===o&&s.clipOutliers===i?!1:(o=s.includeLimits,i=s.clipOutliers,!0)}}}export function makeLinkedTestSelect(e,r,n,t={}){let o=r;const i=makeTestSelect(e,o,a=>{a!==o&&(o=a,n(a))},t);return{el:i,get:()=>o,set(a){return o===a||!e.some(s=>s.testNumber===a)?!1:(o=a,i.value=String(a),!0)}}}export const ALL_GROUPS="\0all";export function makeLinkedGroupSelect(e,r,n,t,o={}){let i=n;const a=d=>d??ALL_GROUPS,s=d=>d===ALL_GROUPS?null:d,l=makeLabeledSelect(r,[{value:ALL_GROUPS,label:"All groups"},...e.map(d=>({value:d,label:d}))],a(i),d=>{const p=s(d);p!==i&&(i=p,t(p))},{...o,hook:"group-scope"}),c=l.querySelector('[data-wmap-select="group-scope"]');return{el:l,get:()=>i,set(d){return i===d||d!==null&&!e.includes(d)?!1:(i=d,c&&(c.value=a(d)),!0)}}}export function makeWaferSelect(e,r,n,t={}){const{maxWidth:o="160px",allLabel:i="All wafers",ownerDocument:a=document}=t,s="\0all";return makeListSelect([{value:s,label:i},...e.map((l,c)=>({value:String(c),label:l.label??`#${c}`}))],r===null?s:String(r),l=>n(l===s?null:Number(l)),{maxWidth:o,ownerDocument:a,ariaLabel:i,searchPlaceholder:"Filter wafers\u2026"})}export function makeToggle(e,r,n,t=document){const o=t.createElement("label");Object.assign(o.style,{display:"inline-flex",alignItems:"center",gap:m.xs,fontSize:v.body,color:f.label,cursor:"pointer",userSelect:"none"}),L(o,"bare");const i=t.createElement("input");return i.type="checkbox",i.checked=r,i.style.cssText="margin:0;cursor:pointer;",i.addEventListener("change",()=>n(i.checked)),o.append(i,t.createTextNode(e)),o}export function makeLabeledSelect(e,r,n,t,o={}){const{maxWidth:i="160px",hook:a,ownerDocument:s=document}=o,l=s.createElement("label");l.textContent=e,Object.assign(l.style,{color:f.label,fontSize:v.body,display:"flex",alignItems:"center",gap:m.xs});const c=makeListSelect(r,n,t,{maxWidth:i,ownerDocument:s,hook:a,ariaLabel:e.replace(/:\s*$/,"")});return l.appendChild(c),l}export function makeChartGridWrap(e=document){const r=e.createElement("div");return r.dataset.wmapChartGrid="1",Object.assign(r.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:m.lg,flex:"0 0 auto"}),r}export function makeTooltip(e){const r=e.ownerDocument.createElement("div");return Object.assign(r.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:"280px",whiteSpace:"nowrap",boxShadow:I.menu}),e.appendChild(r),r}export function attachChartTip(e,r,n,t){e.removeAttribute("title"),e.textContent?.trim()||(e.ariaLabel=t),e.addEventListener("mouseenter",o=>{n.textContent=t,n.style.display="block",positionChartTooltip(n,r,o.clientX,o.clientY)}),e.addEventListener("mousemove",o=>{n.style.display==="block"&&positionChartTooltip(n,r,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,r,o.left+o.width/2,o.bottom)}),e.addEventListener("blur",()=>{n.style.display="none"})}export function positionChartTooltip(e,r,n,t){const o=r.getBoundingClientRect(),i=14,a=4;e.style.left="0",e.style.top="0";const s=e.offsetWidth,l=e.offsetHeight;let c=n-o.left+i,d=t-o.top+i;c+s+a>o.width&&(c=n-o.left-i-s),d+l+a>o.height&&(d=t-o.top-i-l),c=Math.max(a,c),d=Math.max(a,d),e.style.left=`${c}px`,e.style.top=`${d}px`}export function robustFence(e,r=1.5){const n=Float64Array.from(e.filter(s=>Number.isFinite(s)));if(n.sort(),n.length<8)return null;const t=s=>{const l=(n.length-1)*s,c=Math.floor(l),d=Math.ceil(l);return c===d?n[c]:n[c]+(n[d]-n[c])*(l-c)},o=t(.25),i=t(.75),a=i-o;return a===0?null:{lo:o-r*a,hi:i+r*a}}export function shouldIncludeLimitsByDefault(e,r,n,t,o=1/3){if(n===void 0&&t===void 0||!Number.isFinite(e)||!Number.isFinite(r))return!1;const i=Math.min(e,n??e),s=Math.max(r,t??r)-i;if(s<=0)return!0;const l=r-e;return l===0?!0:l/s>=o}export function resolveAxisRange(e){const{dataMin:r,dataMax:n,limitLow:t,limitHigh:o,includeLimits:i,clipOutliers:a,values:s}=e;let l=r,c=n,d=0;if(a&&s?.length){const u=robustFence(s);if(u){const w=Math.max(l,u.lo),C=Math.min(c,u.hi);C>w&&(d=s.filter(S=>Number.isFinite(S)&&(S<w||S>C)).length,l=w,c=C)}}i&&(t!==void 0&&(l=Math.min(l,t)),o!==void 0&&(c=Math.max(c,o))),l===c&&(l-=1,c+=1);const p=[];return t!==void 0&&t<l&&p.push({value:t,label:"LSL",side:"lo"}),t!==void 0&&t>c&&p.push({value:t,label:"LSL",side:"hi"}),o!==void 0&&o>c&&p.push({value:o,label:"USL",side:"hi"}),o!==void 0&&o<l&&p.push({value:o,label:"USL",side:"lo"}),{lo:l,hi:c,offAxis:p,clippedCount:d}}export function limitLabelSide(e,r,n,t,o,i=3){const a=o?-1:1;return(a<0?e-i-r>=n:e+i+r<=t)?a:-a}export function drawOffAxisLimits(e,r,n,t,o,i){if(r.length){e.save(),e.fillStyle=o,e.font=`${j(-1)}px system-ui, sans-serif`;for(const{value:a,label:s,side:l}of r){const c=t==="horizontal"?l==="lo"?"\u2190":"\u2192":l==="lo"?"\u2193":"\u2191",d=l==="lo"?`${c} ${s} ${i(a)}`:`${s} ${i(a)} ${c}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=l==="lo"?"left":"right",e.fillText(d,l==="lo"?n.left+2:n.right-2,n.top+2)):(e.textBaseline=l==="lo"?"bottom":"top",e.textAlign="left",e.fillText(d,n.left+4,l==="lo"?n.bottom-2:n.top+2))}e.restore()}}
|
|
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,2 +1,2 @@
|
|
|
1
|
-
import{buildCorrelationMatrix as
|
|
2
|
-
`),"test-correlation.csv","text/csv",
|
|
1
|
+
import{buildCorrelationMatrix as vt,filterCorrelationMatrix as Lt,correlationSampleNote as It}from"../../stats/correlation.js";import{csvField as K,maxOf as $t}from"../../core/utils.js";import{CORRELATION_POSITIVE as ct,CORRELATION_NEGATIVE as dt}from"./palette.js";import{buildFacetTable as St}from"../../stats/facets.js";import{wireControlHover as Tt,controlStyle as Dt,SPACE as v,RADIUS as st,fontPx as ut,FONT as j,CLR as b,saveTextFile as Rt}from"../toolbar.js";import{attachChartTip as At,cardShell as kt,setChartGrow as _t,isExpandedCard as Bt,bodyRoom as Nt,observeResize as Ot,makeTooltip as Wt,positionChartTooltip as Ht,makeWaferSelect as jt,renderEmptyState as Xt,resolveChartCanvasColors as Ft,prepareCanvas as zt,chartDpr as ft}from"./chartShell.js";import{escHtml as q}from"../../core/utils.js";import{unmarkedLabel as mt,derivedCsvCell as pt,DERIVED_CSV_HEADER as ht,DERIVED_MARK as Vt,DERIVED_KEY as Yt}from"../../renderer/testLabel.js";const xt=5,bt=100,gt=20;function lt(g){const d=g.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)/);if(d)return[+d[1],+d[2],+d[3]];const p=g.trim().replace("#","");return p.length===6?[parseInt(p.slice(0,2),16),parseInt(p.slice(2,4),16),parseInt(p.slice(4,6),16)]:null}function Pt(g){const d=lt(g);return d?(d[0]*299+d[1]*587+d[2]*114)/1e3>140?"#111":"#fff":"#000"}function Gt(g,d,p){const N=lt(g);if(!N)return g;const J=Math.round(d[0]+(N[0]-d[0])*p),O=Math.round(d[1]+(N[1]-d[1])*p),l=Math.round(d[2]+(N[2]-d[2])*p);return`rgb(${J},${O},${l})`}export function renderCorrelationPanel(g){const{title:d="Test correlation matrix",items:p,testDefs:N,onSaveImage:J,onSelectPair:O}=g,{card:l,body:L,controlsRow:Q}=kt(d,J,g.ownerDocument);_t(l,"square"),L.style.overflowX="auto",l.style.alignSelf="start";let V=gt,X=null;const F=l.ownerDocument.createElement("label");F.textContent="Max tests:",Object.assign(F.style,{color:b.label,fontSize:j.body,display:"flex",alignItems:"center",gap:v.xs});const w=l.ownerDocument.createElement("input");w.type="number",w.min=String(xt),w.max=String(bt),w.value=String(V),Object.assign(w.style,{width:"52px",fontSize:j.body,padding:`${v.xxs} ${v.xs}`,background:b.menuBg,color:b.value,border:`1px solid ${b.menuBorder}`,borderRadius:st.control}),w.addEventListener("change",()=>{const e=Math.max(xt,Math.min(bt,parseInt(w.value,10)||gt));w.value=String(e),e!==V&&(V=e,tt())}),F.appendChild(w),Q.appendChild(F),p.length>1&&Q.appendChild(jt(p,X,e=>{X=e,tt()},{ownerDocument:l.ownerDocument}));function yt(){return X!==null?p[X]?[p[X]]:[]:p}if(g.onSaveText){const e=l.ownerDocument.createElement("button");e.type="button",e.textContent="Correlation CSV",Object.assign(e.style,{...Dt("outlined"),color:b.text}),Tt(e),e.addEventListener("click",()=>{const o=rt;if(!o)return;const a=o.tests.some(f=>f.derived),u=["Test X,Test X number,Test Y,Test Y number,r,n"+(a?`,Test X ${ht.toLowerCase()},Test Y ${ht.toLowerCase()}`:"")];for(let f=0;f<o.tests.length;f++)for(let i=f+1;i<o.tests.length;i++){const y=o.cells.find(h=>h.xIndex===i&&h.yIndex===f);y&&u.push([K(mt(o.tests[i].label)),String(o.tests[i].testNumber),K(mt(o.tests[f].label)),String(o.tests[f].testNumber),y.r===null?"":y.r.toFixed(6),String(y.n),...a?[K(pt(o.tests[i])),K(pt(o.tests[f]))]:[]].join(","))}Rt(u.join(`
|
|
2
|
+
`),"test-correlation.csv","text/csv",g.onSaveText)}),Q.appendChild(e)}const k=l.ownerDocument.createElement("div");Object.assign(k.style,{display:"flex",flexDirection:"column",gap:v.xs,marginBottom:v.sm}),l.insertBefore(k,L);const I=Wt(l);At(F,l,I,"Cap on how many tests the matrix includes (strongest correlations kept first)");let Z=()=>{};function Et(e,o,a,u,f,i,y,h){k.innerHTML="";const _=It(y);if(_){const x=l.ownerDocument.createElement("div");x.textContent=`${_} \u2014 r is an estimate, not the whole population.`,Object.assign(x.style,{color:b.label,fontSize:j.body}),k.appendChild(x)}if(f.length>0){const x=l.ownerDocument.createElement("div");x.textContent=`\u26A0 Mixed ${f.join(", ")} within this set \u2014 correlations may be misleading (Simpson's paradox). Use Group by, or the Wafer picker, to narrow to a like-for-like set.`,Object.assign(x.style,{color:b.warnText,background:b.warnBg,border:`1px solid ${b.warnBorder}`,borderRadius:st.control,padding:`${v.xs} ${v.md}`,fontSize:j.body}),k.appendChild(x)}const M=l.ownerDocument.createElement("span");Object.assign(M.style,{display:"inline-flex",alignItems:"center",gap:v.sm,color:b.label,fontSize:j.body,flexWrap:"wrap"});const W=l.ownerDocument.createElement("span");W.textContent=(i!==null?`Pearson r \xB7 n \u2248 ${i.toLocaleString()} dies per pair \xB7 click a cell to view that pair in scatter \xB7`:"Pearson r \xB7 click a cell to view that pair in scatter \xB7")+(h?` ${Vt} ${Yt} \xB7`:""),M.appendChild(W);const r=l.ownerDocument.createElement("span");Object.assign(r.style,{display:"inline-flex",alignItems:"center",gap:v.xs});const $=l.ownerDocument.createElement("span");$.textContent="\u22121";const Y=l.ownerDocument.createElement("span");Object.assign(Y.style,{display:"inline-block",width:"64px",height:"8px",borderRadius:st.control,border:`1px solid ${b.menuBorder}`,background:`linear-gradient(to right, ${dt}, ${b.menuBg}, ${ct})`});const z=l.ownerDocument.createElement("span");z.textContent="+1",r.append($,Y,z),M.appendChild(r),k.appendChild(M);const S=l.ownerDocument.createElement("span");if(Object.assign(S.style,{color:b.value,fontSize:j.body,fontWeight:"500"}),e===0&&o===0)S.textContent=u?`No significant correlations found \u2014 strongest pair: ${u.xLabel} \u2194 ${u.yLabel} (r = ${u.r.toFixed(2)})`:"No significant correlations found";else{const x=[];e>0&&x.push(`${e} strong (|r| \u2265 0.7)`),o>0&&x.push(`${o} moderate (0.4\u20130.7)`);const P=e+o;let s=x.join(", ")+` pair${P!==1?"s":""} found`;a>0&&(s+=` \xB7 ${a} weak pair${a!==1?"s":""} not shown`),S.textContent=s}k.appendChild(S)}function Mt(e){if(L.innerHTML="",e.tests.length<2)return Xt(L,"Need at least two parametric tests for a correlation matrix."),()=>{};const o=l.ownerDocument.createElement("canvas");o.style.display="block",o.style.cursor="default",L.appendChild(o);const a=e.tests.length,u=s=>s.label.split(" (#")[0],f=Math.min(14,$t(e.tests.map(s=>u(s).length))),i=f*6.5+8,y=10,h=Math.round(y*6.5*Math.sin(Math.PI/4))+14,_=14,M=28,W=44;let r=-1,$=-1;const Y=96;function z(s){const c=Math.max(0,s-i),m=Math.floor(c/a);if(!Bt(l))return Math.max(_,Math.min(W,m));const n=Math.floor((Nt(l,L,o)-h-4)/a);return Math.max(_,Math.min(Y,m,n))}const S=new Map;for(const s of e.cells){let c=S.get(s.yIndex);c||(c=[],S.set(s.yIndex,c)),c.push(s)}function x(){const s=Ft(l),c=lt(s.bg)??[255,255,255],m=L.clientWidth,n=z(m),H=n*a,T=h+n*a+4,D=i+H,R=zt(o,l,D,T);if(!R)return;const{ctx:t}=R;t.font=`${ut(-1)}px system-ui, sans-serif`,t.fillStyle=s.text,e.tests.forEach((A,E)=>{const C=u(A),B=C.length>y?`${C.slice(0,y-1)}\u2026`:C,et=i+E*n+n/2;t.save(),t.translate(et,h-4),t.rotate(-Math.PI/4),t.textAlign="left",t.textBaseline="bottom",t.fillStyle=E===r?s.text:s.textMuted,t.fillText(B,0,0),t.restore()}),e.tests.forEach((A,E)=>{const C=u(A),B=h+E*n,et=B+n/2;t.font=`${ut(-1)}px system-ui, sans-serif`,t.textAlign="right",t.textBaseline="middle",t.fillStyle=E===$?s.text:s.textMuted,t.fillText(C.length>f?`${C.slice(0,f-1)}\u2026`:C,i-4,et),(S.get(E)??[]).forEach(it=>{const nt=it.xIndex,G=i+nt*n,wt=nt===r&&E===$,at=nt===E,U=it.r;if(U===null){t.fillStyle=s.border,t.globalAlpha=.4,t.fillRect(G+1,B+1,n-2,n-2),t.globalAlpha=1;return}let ot=s.bgHover;at?t.fillStyle=s.bgHover:(ot=Gt(U>=0?ct:dt,c,Math.abs(U)),t.fillStyle=ot),t.fillRect(G+1,B+1,n-2,n-2),n>=M&&!at&&(t.font=`${Math.min(10,n*.35)}px system-ui, sans-serif`,t.fillStyle=Pt(ot),t.textAlign="center",t.textBaseline="middle",t.fillText(U.toFixed(2),G+n/2,B+n/2)),wt&&(t.strokeStyle=s.text,t.lineWidth=2,t.strokeRect(G+1,B+1,n-2,n-2),t.lineWidth=1)})}),t.strokeStyle=s.bg,t.lineWidth=1;for(let A=0;A<=a;A++){const E=i+A*n;t.beginPath(),t.moveTo(E,h),t.lineTo(E,h+a*n),t.stroke();const C=h+A*n;t.beginPath(),t.moveTo(i,C),t.lineTo(i+a*n,C),t.stroke()}}function P(s){const c=o.getBoundingClientRect(),m=L.clientWidth,n=z(m),H=(s.clientX-c.left)*(o.width/ft(o)/c.width),T=(s.clientY-c.top)*(o.height/ft(o)/c.height),D=Math.floor((H-i)/n),R=Math.floor((T-h)/n);return D>=0&&D<a&&R>=0&&R<a?{xi:D,yi:R}:null}return o.addEventListener("mousemove",s=>{const c=P(s);if(!c){I.style.display="none",o.style.cursor="default";return}const{xi:m,yi:n}=c,H=m===n;o.style.cursor=H||!O?"default":"pointer";const T=e.cells.find(t=>t.xIndex===m&&t.yIndex===n),D=e.tests[m].label,R=e.tests[n].label;H?I.innerHTML=`<strong>${q(D)}</strong>`:T?.r!==null&&T?.r!==void 0?I.innerHTML=`<strong>${q(u(e.tests[n]))}</strong> (#${e.tests[n].testNumber}) vs <strong>${q(u(e.tests[m]))}</strong> (#${e.tests[m].testNumber})<br>r = ${T.r.toFixed(4)} \xB7 n = ${T.n.toLocaleString()}${O?"<br><em>click to view in scatter</em>":""}`:I.innerHTML=`${q(`${R} vs ${D}`)}<br><em>insufficient data</em>`,I.style.display="block",Ht(I,l,s.clientX,s.clientY)}),o.addEventListener("mouseleave",()=>{I.style.display="none"}),o.addEventListener("click",s=>{const c=P(s);if(!c||!O)return;const{xi:m,yi:n}=c;m!==n&&(r=m,$=n,x(),O(e.tests[m].testNumber,e.tests[n].testNumber))}),x}let rt=null;function tt(){const e=yt(),o=e.flatMap(r=>r.dies??[]),a=vt(o,N),{matrix:u,strongPairs:f,moderatePairs:i,hiddenWeakPairs:y,strongestPair:h}=Lt(a,{minTests:6,maxTests:V}),_=St(e,{facetableOnly:!0}).filter(r=>r.splittable).map(r=>r.label),M=u.cells.filter(r=>r.xIndex!==r.yIndex).map(r=>r.n).sort((r,$)=>r-$),W=M.length?M[Math.floor(M.length/2)]:null;rt=u,Et(f,i,y,h,_,W,a.sample,u.tests.some(r=>r.derived)),Z=Mt(u),Z()}const Ct=Ot(l,()=>Z());return tt(),{card:l,destroy:()=>Ct.disconnect()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{categorical as Y}from"./palette.js";import{SPACE as E,FONT as q,CLR as J}from"../toolbar.js";import{observeResize as K,positionChartTooltip as Q,
|
|
1
|
+
import{categorical as Y}from"./palette.js";import{SPACE as E,FONT as q,CLR as J}from"../toolbar.js";import{observeResize as K,positionChartTooltip as Q,fitRowsHeight as Z,setChartGrow as tt,prepareCanvas as et,chartSwatchCss as nt,PADDING as c,VALUE_WIDTH as D}from"./chartShell.js";const lt=8,w=1,a=14,ot=8;export function renderGroupedBarPlot(h,C,p,U){const{rows:v,groups:L,labelWidth:T,tooltipHtml:$,maxVisibleRows:j=ot,showLegend:k=!0,maxLabelChars:H,emptyBarText:R,colorOf:B=Y}=U,f=h.ownerDocument;tt(h,"rows");const A=Math.max(1,L.length),M=A*a+(A-1)*w,u=M+lt;let W=0;if(k){const e=f.createElement("div");Object.assign(e.style,{display:"flex",flexWrap:"wrap",gap:`${E.xs} ${E.xl}`,marginBottom:E.xs}),L.forEach((l,n)=>{const t=f.createElement("span");Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:E.sm,fontSize:q.body,color:J.text});const o=f.createElement("span");o.style.cssText=nt(B(n));const i=f.createElement("span");i.textContent=l,t.append(o,i),e.appendChild(t)}),C.appendChild(e),W=e.offsetHeight}const d=f.createElement("div"),G=c*2+Math.min(v.length,j)*u;Object.assign(d.style,{overflowX:"hidden",overflowY:"auto",minHeight:"0",flex:"1",maxHeight:`${G}px`,scrollbarGutter:"stable"}),C.appendChild(d);const s=f.createElement("canvas");s.style.display="block",s.style.cursor="default",d.appendChild(s);let r=null;function _(){const e=c+T,l=Math.max(10,s.clientWidth-e-D-c);return{barX:e,barMaxWidth:l}}function z(e,l){const n=Math.floor((l-c)/u);if(n<0||n>=v.length)return null;const t=l-c-n*u,o=Math.floor(t/(a+w));if(o<0||o>=A)return null;const{barX:i,barMaxWidth:m}=_();return e<i||e>i+m?null:{row:n,group:o}}function g(){const e=c*2+v.length*u;d.style.maxHeight=`${Z(h,C,G,e,W)}px`;const l=d.clientWidth,n=et(s,h,l,e);if(!n)return;const{ctx:t,theme:o}=n,{barX:i,barMaxWidth:m}=_();v.forEach((x,P)=>{const b=c+P*u;if(r&&r.row===P){const y=b+r.group*(a+w);t.fillStyle=o.bgHover,t.fillRect(0,y-1,l,a+2)}t.fillStyle=o.text,t.textAlign="right";const F=H!==void 0&&x.label.length>H?`${x.label.slice(0,H-1)}\u2026`:x.label;t.fillText(F,c+T-8,b+M/2),x.bars.forEach((y,X)=>{const S=b+X*(a+w);if(t.fillStyle=o.track,t.fillRect(i,S,m,a),y.fraction===null){R&&(t.fillStyle=o.textMuted,t.textAlign="left",t.fillText(R,i+4,S+a/2));return}const N=Math.max(y.fraction>0?1:0,y.fraction*m);t.fillStyle=B(X),t.fillRect(i,S,N,a)}),t.fillStyle=o.text,t.textAlign="right",t.fillText(x.trailing,i+m+D,b+M/2)})}const I=e=>{const l=s.getBoundingClientRect(),n=z(e.clientX-l.left,e.clientY-l.top),t=n?.row!==r?.row||n?.group!==r?.group;if(r=n,t&&g(),!n){p.style.display="none";return}p.innerHTML=$(n.row,n.group),p.style.display="block",Q(p,h,e.clientX,e.clientY)},O=()=>{r&&(r=null,g()),p.style.display="none"};s.addEventListener("mousemove",I),s.addEventListener("mouseleave",O);const V=K(h,()=>g());return g(),{draw:g,destroy:()=>{V?.disconnect(),s.removeEventListener("mousemove",I),s.removeEventListener("mouseleave",O)}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestHistogramData as _t,collectTestValues as At,buildTestHistogramSeries as Ut,testValueExtent as Ct}from"../../stats/histogram.js";import{SPACE as at,fontPx as Q,FONT as $t,CLR as Pt}from"../toolbar.js";import{fmt as bt}from"../../renderer/fmt.js";import{QUANTITY as Xt,categorical as Yt}from"./palette.js";import{cardShell as Qt,observeResize as Wt,makeTooltip as qt,attachChartTip as Jt,positionChartTooltip as Dt,makeLinkedTestSelect as Zt,makeWaferSelect as te,makeLinkedAxisPrefs as ee,renderEmptyState as Tt,chartFillHeight as Et,applyCanvasFlow as It,makeAxisFormat as Bt,PADDING as ft,robustFence as Ot,shouldIncludeLimitsByDefault as Ft,drawOffAxisLimits as ne,resolveAxisRange as ie,chartSwatchCss as oe,makeSeriesLegendItem as le,prepareCanvas as Kt}from"./chartShell.js";import{escHtml as St,maxOf as se}from"../../core/utils.js";const wt=230,Rt=36,yt=18;function Nt(l,G,q,J,Mt,B,r,y=4){const Z=Math.max(1,B/y),rt=Math.pow(10,Math.floor(Math.log10(Z))),d=Z/rt,tt=(d<=1?1:d<=2?2:d<=5?5:10)*rt,F=J-q;l.save(),l.font=`${Q(-1)}px system-ui, sans-serif`,l.textAlign="right",l.textBaseline="middle";for(let V=0;V<=B+1e-9;V+=tt){const ct=J-V/(B||1)*F;l.strokeStyle=r.grid,l.lineWidth=1,l.beginPath(),l.moveTo(G,ct),l.lineTo(G+Mt,ct),l.stroke(),l.fillStyle=r.text,l.fillText(String(Math.round(V)),G-4,ct)}l.strokeStyle=r.axis,l.lineWidth=1,l.beginPath(),l.moveTo(G,q),l.lineTo(G,J),l.stroke(),l.restore()}export function renderHistogramPanel(l){const{title:G="Test value distribution",items:q,testDefs:J,onSaveImage:Mt,groups:B}=l,{card:r,body:y,controlsRow:Z}=Qt(G,Mt,l.ownerDocument),rt=J.filter(s=>s.testNumber!==void 0);let d=l.selectedTestNumber??rt[0]?.testNumber??null,tt=null,F=0;const V=Zt(rt,d,s=>{d=s,j(),l.onTestChange?.(s)},{maxWidth:"200px",ownerDocument:r.ownerDocument}),ct=V.el;Z.appendChild(ct);const vt=te(q,tt,s=>{tt=s,j()},{ownerDocument:r.ownerDocument});Z.appendChild(vt);function zt(s){return S===s||s!==null&&!(B??[]).some(v=>v.key===s)?!1:(S=s,j(),!0)}const Lt=r.ownerDocument.createElement("span");Object.assign(Lt.style,{display:"inline-flex",gap:at.lg,alignItems:"center"}),Z.appendChild(Lt);const gt=ee(Lt,l.axisPrefs,s=>{l.onAxisPrefsChange?.(s),j()},r.ownerDocument),C=qt(r);let dt=null,S=null;function et(s){const v=J.find(nt=>nt.testNumber===s);return{unit:v?.unit,limitLow:v?.limitLow,limitHigh:v?.limitHigh}}function j(){const{includeLimits:s,clipOutliers:v}=gt.get();if(dt?.disconnect(),dt=null,y.innerHTML="",d===null){Tt(y,"No parametric test data available for a histogram.");return}const nt=!!B&&B.length>0;let it=!1,u;if(nt){const{limitLow:a,limitHigh:m}=et(d),o=B.flatMap(x=>x.items),{min:e,max:p}=Ct(o,d);if(it=s??Ft(e,p,a,m),v){const x=At(o,d),H=Ot(x);H&&(u={lo:Math.max(e,H.lo),hi:Math.min(p,H.hi)},F=x.reduce((lt,E)=>lt+(E<u.lo||E>u.hi?1:0),0))}u||(F=0)}const $=nt?Ut(B,d,16,it?et(d).limitLow:void 0,it?et(d).limitHigh:void 0,u):null;if(vt.style.display=$?"none":"",$){const{limitLow:a,limitHigh:m}=et(d);if(gt.sync(it,a!==void 0||m!==void 0),$.series.length===0){Tt(y,"No parametric test data available for a histogram.");return}Gt($);return}const _=tt!==null?[q[tt]]:q,{unit:K,limitLow:P,limitHigh:W}=et(d),k=At(_,d),{min:ot,max:mt}=Ct(_,d),ut=s??Ft(ot,mt,P,W);gt.sync(ut,P!==void 0||W!==void 0);const U=v?Ot(k):null,X=U?{lo:Math.max(ot,U.lo),hi:Math.min(mt,U.hi)}:void 0;F=X?k.filter(a=>a<X.lo||a>X.hi).length:0;const h=_t(_,d,16,ut?P:void 0,ut?W:void 0,X);if(h.length===0){Tt(y,"No parametric test data available for a histogram.");return}const D=Math.max(se(h.map(a=>a.count)),1),Y=r.ownerDocument.createElement("div");Object.assign(Y.style,{fontSize:$t.body,color:Pt.label,marginBottom:at.xxs}),Y.textContent=`max ${D} dies/bucket`+(F?` \xB7 ${F} value${F===1?"":"s"} outside clipped range`:""),y.appendChild(Y);const M=r.ownerDocument.createElement("canvas");M.style.display="block",M.style.cursor="default",y.appendChild(M);let O=-1;const n=h[0].rangeLow,g=h[h.length-1].rangeHigh,L=g-n||1,t=Bt(Math.max(Math.abs(n),Math.abs(g)),K);function c(a){const m=ft+36,o=M.clientWidth-m-ft,e=a-Rt-yt;return{plotX:m,plotMaxWidth:Math.max(10,o),plotMaxHeight:e,plotTop:yt}}function f(a){const{plotX:m,plotMaxWidth:o}=c(wt);if(a<m||a>m+o)return-1;const e=Math.floor((a-m)/o*h.length);return e>=0&&e<h.length?e:-1}function T(){It(M,Y);const a=y.clientWidth,m=Et(r,y,M,wt),o=Kt(M,r,a,m);if(!o)return;const{ctx:e,theme:p}=o,{plotX:x,plotMaxWidth:H,plotMaxHeight:lt,plotTop:E}=c(m),i=E+lt,w=H/h.length;Nt(e,x,E,i,H,D,{text:p.textMuted,axis:p.border,grid:p.border}),h.forEach((b,z)=>{const I=b.count/D*lt,kt=x+z*w,Ht=i-I;e.fillStyle=z===O?p.text:Xt,e.globalAlpha=z===O?1:.7,e.fillRect(kt+1,Ht,Math.max(1,w-2),I),e.globalAlpha=1,z===O&&(e.fillStyle=p.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${b.count}`,kt+w/2,Ht-2))});const A=b=>x+(b-n)/L*H;e.font=`${Q(-1)}px system-ui, sans-serif`;const{offAxis:ht}=ie({dataMin:n,dataMax:g,limitLow:P,limitHigh:W,includeLimits:!1});ne(e,ht,{left:x,right:x+H,top:E,bottom:i},"horizontal",p.limitLine,t.tick);const pt=new Set(ht.map(b=>b.value));for(const[b,z]of[[P,"LSL"],[W,"USL"]]){if(b===void 0||pt.has(b))continue;const I=A(b);e.strokeStyle=p.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(I,E),e.lineTo(I,i),e.stroke(),e.setLineDash([]),e.fillStyle=p.limitLine,e.textAlign="center",e.textBaseline="bottom",e.fillText(z,I,E-2)}e.font=`${Q(-1)}px system-ui, sans-serif`,e.strokeStyle=p.border,e.lineWidth=1,e.beginPath(),e.moveTo(x,i),e.lineTo(x+H,i),e.stroke(),e.fillStyle=p.textMuted,e.textAlign="center",e.textBaseline="top";const st=Math.max(2,Math.floor(H/44)),R=Math.ceil(h.length/st),N=Math.max(1,R);for(let b=0;b<=h.length;b+=N){const z=b<h.length?h[b].rangeLow:h[h.length-1].rangeHigh,I=x+b*w;e.beginPath(),e.moveTo(I,i),e.lineTo(I,i+4),e.stroke(),e.fillText(t.tick(z),I,i+6)}t.unitLabel&&(e.save(),e.font=`${Q(-1)}px system-ui, sans-serif`,e.fillStyle=p.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${t.unitLabel})`,x+H,i+20),e.restore())}M.addEventListener("mousemove",a=>{const m=M.getBoundingClientRect(),o=f(a.clientX-m.left);if(o!==O&&(O=o,T()),o>=0){const e=h[o];C.innerHTML=`<strong>${St(`${bt(e.rangeLow,K,"engineering")} \u2013 ${bt(e.rangeHigh,K,"engineering")}`)}</strong><br>${e.count} dies`,C.style.display="block",Dt(C,r,a.clientX,a.clientY)}else C.style.display="none"}),M.addEventListener("mouseleave",()=>{O!==-1&&(O=-1,T()),C.style.display="none"}),dt=Wt(r,()=>T()),T()}function Gt(s){const{unit:v,limitLow:nt,limitHigh:it}=et(d),u=s.ranges,$=s.series,_=Yt;S&&!$.some(n=>n.groupKey===S)&&(S=null);const K=Math.max(1,...$.flatMap(n=>n.counts)),P=r.ownerDocument.createElement("div");Object.assign(P.style,{fontSize:$t.body,color:Pt.label,marginBottom:at.xxs}),P.textContent=`${$.length} groups \xB7 max ${K} dies/bucket`,y.appendChild(P);const W=r.ownerDocument.createElement("div");Object.assign(W.style,{display:"flex",flexWrap:"wrap",gap:at.sm,marginTop:at.md,marginBottom:at.xl}),$.forEach((n,g)=>{const L=le(n.groupKey,_(g),r.ownerDocument),t=L.el;Jt(t,r,C,`${n.groupKey} \u2014 click to emphasize (dim the rest)`),L.setState({selected:S===n.groupKey,dimmed:S!==null&&S!==n.groupKey}),t.addEventListener("click",()=>{const c=S===n.groupKey?null:n.groupKey;zt(c),l.onGroupChange?.(c)}),W.appendChild(t)}),y.appendChild(W);const k=r.ownerDocument.createElement("canvas");k.style.display="block",k.style.cursor="default",y.appendChild(k);const ot=u[0].rangeLow,mt=u[u.length-1].rangeHigh,ut=mt-ot||1,U=Bt(Math.max(Math.abs(ot),Math.abs(mt)),v),X=n=>{const g=(n.ownerDocument.defaultView??window).getComputedStyle(n);return n.offsetHeight+(parseFloat(g.marginTop)||0)+(parseFloat(g.marginBottom)||0)},h=()=>X(P)+X(W);let D=-1,Y={plotX:ft+36,plotMaxWidth:10,barWidth:10};function M(){It(k,h());const n=y.clientWidth,g=Et(r,y,k,wt),L=Kt(k,r,n,g);if(!L)return;const{ctx:t,theme:c}=L,f=ft+36,T=Math.max(10,n-f-ft),a=yt,m=g-Rt-yt,o=a+m,e=T/u.length;Y={plotX:f,plotMaxWidth:T,barWidth:e},Nt(t,f,a,o,T,K,{text:c.textMuted,axis:c.border,grid:c.border}),D>=0&&(t.fillStyle=c.bgHover,t.globalAlpha=.5,t.fillRect(f+D*e,a,e,m),t.globalAlpha=1);const p=$.map((i,w)=>({s:i,i:w})).sort((i,w)=>(i.s.groupKey===S?1:0)-(w.s.groupKey===S?1:0));for(const{s:i,i:w}of p){const A=S===i.groupKey,ht=S!==null&&!A,pt=_(w);t.strokeStyle=pt,t.fillStyle=pt,t.lineWidth=A?2:1,t.globalAlpha=ht?.12:S?.32:.22,t.beginPath(),t.moveTo(f,o),i.counts.forEach((xt,st)=>{const R=f+st*e,N=o-xt/K*m;t.lineTo(R,N),t.lineTo(R+e,N)}),t.lineTo(f+T,o),t.closePath(),t.fill(),t.globalAlpha=ht?.3:1,t.beginPath(),i.counts.forEach((xt,st)=>{const R=f+st*e,N=o-xt/K*m;st===0?t.moveTo(R,N):t.lineTo(R,N),t.lineTo(R+e,N)}),t.stroke()}t.globalAlpha=1;const x=i=>f+(i-ot)/ut*T;t.font=`${Q(-1)}px system-ui, sans-serif`;for(const[i,w]of[[nt,"LSL"],[it,"USL"]]){if(i===void 0)continue;const A=x(i);t.strokeStyle=c.limitLine,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(A,a),t.lineTo(A,o),t.stroke(),t.setLineDash([]),t.fillStyle=c.limitLine,t.textAlign="center",t.textBaseline="bottom",t.fillText(w,A,a-2)}t.font=`${Q(-1)}px system-ui, sans-serif`,t.strokeStyle=c.border,t.lineWidth=1,t.beginPath(),t.moveTo(f,o),t.lineTo(f+T,o),t.stroke(),t.fillStyle=c.textMuted,t.textAlign="center",t.textBaseline="top";const lt=Math.max(2,Math.floor(T/44)),E=Math.max(1,Math.ceil(u.length/lt));for(let i=0;i<=u.length;i+=E){const w=i<u.length?u[i].rangeLow:u[u.length-1].rangeHigh,A=f+i*e;t.beginPath(),t.moveTo(A,o),t.lineTo(A,o+4),t.stroke(),t.fillText(U.tick(w),A,o+6)}U.unitLabel&&(t.save(),t.font=`${Q(-1)}px system-ui, sans-serif`,t.fillStyle=c.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${U.unitLabel})`,f+T,o+20),t.restore())}function O(n){const{plotX:g,plotMaxWidth:L,barWidth:t}=Y;if(n<g||n>g+L)return-1;const c=Math.floor((n-g)/t);return c>=0&&c<u.length?c:-1}k.addEventListener("mousemove",n=>{const g=k.getBoundingClientRect(),L=O(n.clientX-g.left);if(L!==D&&(D=L,M()),L>=0){const t=u[L],c=$.map((f,T)=>`<span style="${oe(_(T))};margin-right:4px"></span>${St(`${f.groupKey}: ${f.counts[L]}`)}`).join("<br>");C.innerHTML=`<strong>${St(`${bt(t.rangeLow,v,"engineering")} \u2013 ${bt(t.rangeHigh,v,"engineering")}`)}</strong><br>${c}`,C.style.display="block",Dt(C,r,n.clientX,n.clientY)}else C.style.display="none"}),k.addEventListener("mouseleave",()=>{D!==-1&&(D=-1,M()),C.style.display="none"}),dt=Wt(r,()=>M()),M()}j();function Vt(s){V.set(s)&&(d=s,j())}function jt(s){gt.set(s)&&j()}return{card:r,setTest:Vt,setAxisPrefs:jt,destroy:()=>dt?.disconnect()}}
|
|
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 +1 @@
|
|
|
1
|
-
import{parseRegionKey as
|
|
1
|
+
import{parseRegionKey as A}from"../../stats/regions.js";import{yieldFill as D}from"./palette.js";import{fontPx as C}from"../toolbar.js";import{cardShell as N,resolveChartCanvasColors as F,observeResize as $,growCardToFitContent as j,setChartGrow as q,isExpandedCard as z,bodyRoom as G,renderEmptyState as O,prepareCanvas as _}from"./chartShell.js";const v=320,L=220,Q={SE:[0,Math.PI/2],SW:[Math.PI/2,Math.PI],NW:[Math.PI,1.5*Math.PI],NE:[1.5*Math.PI,2*Math.PI]};function Y(e,a,o,r,l,n){e.beginPath(),e.moveTo(a+n,o),e.arcTo(a+r,o,a+r,o+l,n),e.arcTo(a+r,o+l,a,o+l,n),e.arcTo(a,o+l,a,o,n),e.arcTo(a,o,a+r,o,n),e.closePath()}function w(e,a,o,r,l,n){e.font=`${C(-1)}px system-ui, sans-serif`;const c=e.measureText(r).width;e.font="700 11px system-ui, sans-serif";const y=e.measureText(l).width,m=Math.max(c,y)+12,g=30;e.fillStyle=n.bg,Y(e,a-m/2,o-g/2,m,g,4),e.fill(),e.strokeStyle=n.border,e.lineWidth=1,e.stroke(),e.textAlign="center",e.textBaseline="middle",e.fillStyle=n.textMuted,e.font=`${C(-1)}px system-ui, sans-serif`,e.fillText(r,a,o-7),e.fillStyle=n.text,e.font="700 11px system-ui, sans-serif",e.fillText(l,a,o+7)}export function renderRegionYieldDiagram(e){const{mode:a,rows:o,onSaveImage:r}=e,l=e.title??(a==="ring"?"Ring yield":"Quadrant yield"),{card:n,body:c}=N(l,r,e.ownerDocument);q(n,"square");const y=o.length>0;if(!y)return O(c,"No bin data available for regional yield."),{card:n,hasData:y,destroy:()=>{}};Object.assign(c.style,{display:"flex",justifyContent:"center",alignItems:"center"});const m=n.ownerDocument.createElement("canvas");Object.assign(m.style,{display:"block",flex:"none"}),c.appendChild(m);function g(){const S=Math.max(L,Math.min(c.clientWidth||v,v));j(n,c,S);const b=z(n)?Math.max(S,Math.floor(Math.min(c.clientWidth,G(n,c,m)))):S,k=_(m,n,b,b);if(!k)return;const{ctx:t}=k,u=F(n),T=D,d=b/2,h=b/2,f=b/2-34;if(a==="ring"){const i=o.length;for(let s=0;s<i;s++){const M=s/i*f,P=(s+1)/i*f;t.beginPath(),t.arc(d,h,P,0,2*Math.PI),t.arc(d,h,M,0,2*Math.PI,!0),t.closePath(),t.fillStyle=T(o[s].yieldPercent),t.fill(),t.strokeStyle=u.border,t.lineWidth=1,t.stroke()}t.beginPath(),t.arc(d,h,f,0,2*Math.PI),t.strokeStyle=u.text,t.lineWidth=1.5,t.stroke();for(let s=0;s<i;s++){const M=(s+.5)/i*f,P=-Math.PI/2+s/i*2*Math.PI,I=d+Math.cos(P)*M,p=h+Math.sin(P)*M;w(t,I,p,o[s].label,`${o[s].yieldPercent.toFixed(1)}%`,u)}}else{for(const i of o){const s=A(i.key).quadrant,M=s?Q[s]:void 0;if(!M)continue;const[P,I]=M;t.beginPath(),t.moveTo(d,h),t.arc(d,h,f,P,I),t.closePath(),t.fillStyle=T(i.yieldPercent),t.fill(),t.strokeStyle=u.border,t.lineWidth=1,t.stroke();const p=(P+I)/2,W=d+Math.cos(p)*f*.6,E=h+Math.sin(p)*f*.6;w(t,W,E,i.label,`${i.yieldPercent.toFixed(1)}%`,u)}t.beginPath(),t.arc(d,h,f,0,2*Math.PI),t.strokeStyle=u.text,t.lineWidth=1.5,t.stroke()}}const R=$(n,()=>g());return g(),{card:n,hasData:y,destroy:()=>R.disconnect()}}
|