@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.
Files changed (82) hide show
  1. package/AGENTS.md +16 -5
  2. package/CHANGELOG.md +384 -0
  3. package/README.md +6 -3
  4. package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
  5. package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
  6. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
  7. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -1
  9. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +81 -7
  12. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  13. package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
  14. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
  15. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  16. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  17. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  18. package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
  19. package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
  21. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  22. package/dist/packages/canvas-adapter/dieList.js +2 -2
  23. package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
  24. package/dist/packages/canvas-adapter/drilldown.js +1 -0
  25. package/dist/packages/canvas-adapter/icons.js +1 -1
  26. package/dist/packages/canvas-adapter/insightsTab.d.ts +55 -3
  27. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  28. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  29. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  30. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  31. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  32. package/dist/packages/canvas-adapter/toolbar.d.ts +30 -2
  33. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  34. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  35. package/dist/packages/canvas-adapter/userGuideHtml.js +126 -3
  36. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  37. package/dist/packages/canvas-adapter/version.js +1 -1
  38. package/dist/packages/canvas-adapter/warnings.js +1 -1
  39. package/dist/packages/renderer/axisTicks.d.ts +32 -0
  40. package/dist/packages/renderer/axisTicks.js +1 -0
  41. package/dist/packages/renderer/buildView.d.ts +11 -37
  42. package/dist/packages/renderer/buildView.js +1 -1
  43. package/dist/packages/renderer/buildWaferMap.d.ts +58 -1
  44. package/dist/packages/renderer/buildWaferMap.js +1 -1
  45. package/dist/packages/renderer/deprecate.d.ts +13 -5
  46. package/dist/packages/renderer/deprecate.js +1 -1
  47. package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
  48. package/dist/packages/renderer/derivedTests/apply.js +1 -0
  49. package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
  50. package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
  51. package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
  52. package/dist/packages/renderer/derivedTests/parser.js +2 -0
  53. package/dist/packages/renderer/fmt.d.ts +24 -2
  54. package/dist/packages/renderer/fmt.js +1 -1
  55. package/dist/packages/renderer/index.d.ts +1 -0
  56. package/dist/packages/renderer/index.js +1 -1
  57. package/dist/packages/renderer/spec.d.ts +49 -0
  58. package/dist/packages/renderer/spec.js +1 -0
  59. package/dist/packages/renderer/testLabel.d.ts +154 -0
  60. package/dist/packages/renderer/testLabel.js +1 -0
  61. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  62. package/dist/packages/stats/capability.d.ts +4 -0
  63. package/dist/packages/stats/capability.js +1 -1
  64. package/dist/packages/stats/correlation.d.ts +3 -0
  65. package/dist/packages/stats/correlation.js +1 -1
  66. package/dist/packages/stats/deprecated.d.ts +3 -0
  67. package/dist/packages/stats/deprecated.js +1 -1
  68. package/dist/packages/stats/index.d.ts +2 -2
  69. package/dist/packages/stats/index.js +1 -1
  70. package/dist/packages/stats/mergeTestDefs.js +1 -1
  71. package/dist/packages/stats/renderFindingsReport.js +8 -28
  72. package/dist/packages/stats/renderSummaryReport.js +21 -30
  73. package/dist/packages/stats/reportHtml.d.ts +27 -0
  74. package/dist/packages/stats/reportHtml.js +35 -14
  75. package/dist/packages/stats/sweep.d.ts +178 -0
  76. package/dist/packages/stats/sweep.js +1 -0
  77. package/dist/packages/stats/sweepXFromName.d.ts +27 -0
  78. package/dist/packages/stats/sweepXFromName.js +1 -0
  79. package/dist/packages/stats/testPassRate.js +1 -1
  80. package/dist/packages/stats/types.d.ts +28 -1
  81. package/llms.txt +3 -2
  82. package/package.json +1 -1
@@ -1 +1 @@
1
- import{buildTestBoxplotData as Ze}from"../../stats/boxplot.js";import{SPACE as De,fontPx as N,FONT as et,CLR as tt}from"../toolbar.js";import{fmt as xe}from"../../renderer/fmt.js";import{QUANTITY as Ie}from"./palette.js";import{cardShell as nt,observeResize as ot,makeTooltip as it,positionChartTooltip as lt,makeBackButton as st,makeLinkedTestSelect as at,makeToggle as rt,makeLinkedAxisPrefs as ct,renderEmptyState as He,growCardToFitContent as ft,resolveChartCanvasColors as mt,makeAxisFormat as ut,resolveAxisRange as dt,shouldIncludeLimitsByDefault as xt,drawOffAxisLimits as pt,limitLabelSide as ht,PADDING as L,VALUE_WIDTH as gt,prepareCanvas as bt}from"./chartShell.js";import{escHtml as k,maxOf as Tt,minOf as yt}from"../../core/utils.js";const M=24,C=5,Oe=110,vt=12,Re=20;export function renderBoxplotPanel(h){const{title:Z="Test value distribution",items:We,testDefs:pe,onSaveImage:Ee,groups:u,groupLabelText:ee="group",onOpen:q}=h,Pe=h.openActionLabel??"open that wafer",_e=h.openActionLabel??"open this wafer",{card:a,heading:Ge,body:d,controlsRow:Y}=nt(Z,Ee,h.ownerDocument);a.style.flex="0 0 auto",d.style.flex="0 0 auto",a.style.alignSelf="start";const te=pe.filter(n=>n.testNumber!==void 0);let v=h.selectedTestNumber??te[0]?.testNumber??null,ne=!1,Q=0,x=null,H=null,oe=null,he=null;function Xe(){const n=!u||u.length===0?"":x??"\0overview";if(oe&&he===n)return oe;const m=!u||u.length===0?We:x!==null?u.find(g=>g.key===x)?.items??[]:u.map(g=>({label:g.key,dies:g.items.flatMap(c=>c.dies??[])}));return oe=m,he=n,m}function ge(){x!==null&&!H?(H=st(()=>{be(null),h.onGroupChange?.(null)},a.ownerDocument),Y.appendChild(H)):x===null&&H&&(H.remove(),H=null),Ge.textContent=x!==null?`${Z} \u2014 ${ee}: ${x}`:Z}function be(n){return x===n||n!==null&&!(u??[]).some(m=>m.key===n)?!1:(x=n,ge(),U(),S(),!0)}const Te=at(te,v,n=>{v=n,S(),h.onTestChange?.(n)},{maxWidth:"240px",emptyText:"No parametric tests",ownerDocument:a.ownerDocument}),Ne=Te.el;Y.appendChild(Ne),Y.appendChild(rt("Log scale",ne,n=>{ne=n,S()},a.ownerDocument));const ie=a.ownerDocument.createElement("span");Object.assign(ie.style,{display:"inline-flex",gap:De.lg,alignItems:"center"}),Y.appendChild(ie);const le=ct(ie,h.axisPrefs,n=>{h.onAxisPrefsChange?.(n),S()},a.ownerDocument),se=a.ownerDocument.createElement("div");Object.assign(se.style,{color:tt.label,fontSize:et.body,marginBottom:De.sm}),a.insertBefore(se,d);function U(){const n=!!u&&u.length>0&&x===null,m=[];n?m.push(`click a ${ee}'s box to see it by wafer`):q&&m.push(`click a box to ${Pe}`);const g=m.length?`${m[0][0].toUpperCase()}${m[0].slice(1)} \xB7 `:"";se.textContent=`${g}box = Q1\u2013Q3, line = median, whiskers = min/max \xB7 value shown is the median`+(Q?` \xB7 axis clipped, ${Q} value${Q===1?"":"s"} outside`:"")}U(),ge();const R=it(a);let ae=null;function S(){const{includeLimits:n,clipOutliers:m}=le.get();if(U(),d.innerHTML="",te.length===0||v===null){He(d,"No parametric test data available for box plots.");return}const g=Xe(),c=Ze(g,v),re=pe.find(t=>t.testNumber===v),z=re?.unit,F=re?.limitLow,V=re?.limitHigh;if(c.every(t=>t.count===0)){He(d,"No parametric test data available for box plots.");return}const p=a.ownerDocument.createElement("canvas");p.style.display="block",p.style.cursor="default",d.appendChild(p);const ye=L*2+Math.min(c.length,vt)*(M+C)+Re;d.style.maxHeight=`${ye}px`,d.style.overflowY="auto",d.style.scrollbarGutter="stable";let W=-1;const ce=c.filter(t=>t.count>0),ve=yt(ce.map(t=>t.min)),we=Tt(ce.map(t=>t.max)),Le=n??xt(ve,we,F,V);le.sync(Le,F!==void 0||V!==void 0);const E=dt({dataMin:ve,dataMax:we,limitLow:F,limitHigh:V,includeLimits:Le,clipOutliers:m,values:ce.flatMap(t=>[t.min,t.q1,t.median,t.q3,t.max])});Q=E.clippedCount,U();const O=E.lo,fe=E.hi,ke=fe-O||1,j=ne&&O>0,me=j?Math.log10(O):0,Me=(j?Math.log10(fe):0)-me||1,ue=ut(Math.max(Math.abs(O),Math.abs(fe)),z);function Qe(){const t=L+Oe,i=p.clientWidth-t-gt-L;return{plotX:t,plotMaxWidth:Math.max(10,i)}}function A(t,i,s){return j?i+(Math.log10(t)-me)/Me*s:i+(t-O)/ke*s}function Ue(){const t=[];for(let i=0;i<=4;i++)t.push(j?Math.pow(10,me+Me*i/4):O+ke*i/4);return t}function K(){const t=mt(a),i=d.clientWidth,s=L*2+c.length*(M+C)+Re,$=bt(p,a,i,s);if(!$)return;const{ctx:e}=$;e.font=`${N(-1)}px system-ui, sans-serif`,e.textBaseline="middle";const{plotX:l,plotMaxWidth:f}=Qe();c.forEach((o,b)=>{const y=L+b*(M+C),r=y+M/2;b===W&&(e.fillStyle=t.bgHover,e.fillRect(0,y-C/2,i,M+C)),e.fillStyle=t.text,e.textAlign="right";const Fe=o.label.length>16?`${o.label.slice(0,15)}\u2026`:o.label;if(e.fillText(Fe,L+Oe-8,r),o.count===0){e.fillStyle=t.textMuted,e.textAlign="left",e.fillText("no data",l,r);return}const Ve=l+f,_=Je=>Math.min(Ve,Math.max(l,Je)),Ae=A(o.min,l,f),$e=A(o.max,l,f),B=_(Ae),G=_(A(o.q1,l,f)),Be=_(A(o.median,l,f)),de=_(A(o.q3,l,f)),D=_($e),je=Ae<B,Ke=$e>D,I=y+3,X=y+M-3;e.strokeStyle=t.textMuted,e.lineWidth=1,e.beginPath(),e.moveTo(B,r),e.lineTo(G,r),e.moveTo(de,r),e.lineTo(D,r);const w=3;je?(e.moveTo(B+w,r-w),e.lineTo(B,r),e.lineTo(B+w,r+w)):(e.moveTo(B,I),e.lineTo(B,X)),Ke?(e.moveTo(D-w,r-w),e.lineTo(D,r),e.lineTo(D-w,r+w)):(e.moveTo(D,I),e.lineTo(D,X)),e.stroke(),e.fillStyle=Ie,e.globalAlpha=.35,e.fillRect(G,I,Math.max(1,de-G),X-I),e.globalAlpha=1,e.strokeStyle=Ie,e.strokeRect(G,I,Math.max(1,de-G),X-I),e.strokeStyle=t.text,e.lineWidth=2,e.beginPath(),e.moveTo(Be,I),e.lineTo(Be,X),e.stroke(),e.lineWidth=1,e.fillStyle=t.textMuted,e.textAlign="left",e.fillText(xe(o.median,z,"engineering"),l+f+10,r)});const T=L+c.length*(M+C);e.font=`${N(-1)}px system-ui, sans-serif`,pt(e,E.offAxis,{left:l,right:l+f,top:0,bottom:T},"horizontal",t.limitLine,o=>xe(o,z,"engineering"));const ze=new Set(E.offAxis.map(o=>o.value));for(const[o,b]of[[F,"LSL"],[V,"USL"]]){if(o===void 0||ze.has(o))continue;const y=A(o,l,f);e.strokeStyle=t.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(y,0),e.lineTo(y,T),e.stroke(),e.setLineDash([]),e.fillStyle=t.limitLine;const r=ht(y,e.measureText(b).width,l,l+f,b==="LSL");e.textAlign=r<0?"right":"left",e.textBaseline="bottom",e.fillText(b,y+r*3,T-1)}e.font=`${N(-1)}px system-ui, sans-serif`,e.strokeStyle=t.border,e.lineWidth=1,e.beginPath(),e.moveTo(l,T),e.lineTo(l+f,T),e.stroke(),e.fillStyle=t.textMuted,e.textAlign="center",e.textBaseline="top";for(const o of Ue()){const b=A(o,l,f);e.beginPath(),e.moveTo(b,T),e.lineTo(b,T+4),e.stroke(),e.fillText(ue.tick(o),b,T+6)}ue.unitLabel&&(e.textAlign="left",e.font=`${N(-1)}px system-ui, sans-serif`,e.fillText(`(${ue.unitLabel})`,l+f+22,T+6),e.font=`${N(-1)}px system-ui, sans-serif`),e.textBaseline="middle"}function Ce(t){const i=Math.floor((t-L+C/2)/(M+C));return i>=0&&i<c.length?i:-1}function P(t){return xe(t,z,"engineering")}const J=!!u&&u.length>0&&x===null,Se=t=>!J&&!!q&&g[t]?.waferIndex!==void 0;p.addEventListener("mousemove",t=>{const i=p.getBoundingClientRect(),s=Ce(t.clientY-i.top),$=s>=0&&c[s].count>0&&(J||Se(s));if(s!==W&&(W=s,p.style.cursor=$?"pointer":"default",K()),s>=0&&c[s].count>0){const e=c[s],l=J?`<br><em>click to see this ${k(ee)} by wafer</em>`:Se(s)?`<br><em>click to ${k(_e)}</em>`:"";R.innerHTML=`<strong>${k(e.label)}</strong> (${e.count} dies)<br>max ${k(P(e.max))}<br>q3 ${k(P(e.q3))}<br>median ${k(P(e.median))}<br>q1 ${k(P(e.q1))}<br>min ${k(P(e.min))}${l}`,R.style.display="block",lt(R,a,t.clientX,t.clientY)}else R.style.display="none"}),p.addEventListener("mouseleave",()=>{W!==-1&&(W=-1,K()),R.style.display="none"}),p.addEventListener("click",t=>{const i=p.getBoundingClientRect(),s=Ce(t.clientY-i.top);if(s===-1||c[s].count===0)return;if(J){const e=c[s].label;be(e),h.onGroupChange?.(e);return}const $=g[s]?.waferIndex;q&&$!==void 0&&v!==null&&q($,v)}),ae?.disconnect(),ae=ot(a,()=>K()),K(),ft(a,d,ye)}S();function qe(n){Te.set(n)&&(v=n,S())}function Ye(n){le.set(n)&&S()}return{card:a,setTest:qe,setAxisPrefs:Ye,destroy:()=>ae?.disconnect()}}
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 ue}from"../../stats/capability.js";import{capabilityColor as B}from"./palette.js";import{LEADING as be,SPACE as g,fontPx as G,FONT as Q,CLR as H}from"../toolbar.js";import{cardShell as ye,chartFillHeight as xe,applyCanvasFlow as ge,observeResize as ve,makeTooltip as Se,positionChartTooltip as Me,renderEmptyState as Te,resolveChartCanvasColors as Le,chartSwatchCss as ke,prepareCanvas as Ce,chartDpr as Pe}from"./chartShell.js";import{fmt as $e}from"../../renderer/fmt.js";import{escHtml as ee}from"../../core/utils.js";const Ae=30,We=160,te=90,ne=28,v=42;function I(C){return C===null?"\u2014":C.toFixed(2)}export function renderCapabilityPanel(C){const{title:oe="Process capability",items:le,testDefs:z,onSaveImage:se,onSelectTest:O}=C;let X=C.selectedTestNumber??null;const{card:r,body:S}=ye(oe,se,C.ownerDocument),ie=z.some(n=>n.testNumber!==void 0);S.style.overflowX="auto";function ae(){return le}const P=r.ownerDocument.createElement("div");Object.assign(P.style,{display:"flex",flexDirection:"column",gap:g.xs,marginBottom:g.sm}),r.insertBefore(P,S);const W=Se(r);let R=()=>{};function re(n,i,f){P.innerHTML="";const c=r.ownerDocument.createElement("span");Object.assign(c.style,{display:"inline-flex",alignItems:"center",gap:g.sm,color:H.value,fontSize:Q.body,fontWeight:"500"});const M=f-n,b=i>0?` \xB7 ${i} without spec limits`:"",y=r.ownerDocument.createElement("span");y.textContent=M>0?`${n} of ${f} tests shown${b} \xB7 ${M} excluded (no recorded values)`:`${n} test${n!==1?"s":""} shown${b}`,c.appendChild(y),P.appendChild(c);const $=i>=n,T=r.ownerDocument.createElement("div");T.textContent=$?"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(T.style,{color:H.label,fontSize:Q.body,lineHeight:be.base,marginTop:g.xs,maxWidth:"78ch"}),P.appendChild(T),P.appendChild(ce(r.ownerDocument,i>0,$))}function ce(n,i,f=!1){const c=n.createElement("div");Object.assign(c.style,{display:"flex",flexWrap:"wrap",alignItems:"center",gap:`${g.xs} ${g.xl}`,marginTop:g.xl,marginBottom:g.xs,fontSize:Q.body,color:H.label});const M=f?[]:[{color:B(2),label:"Capable \xB7 Ppk \u2265 1.33"},{color:B(1.1),label:"Marginal \xB7 Ppk \u2265 1.0"},{color:B(.5),label:"Poor \xB7 Ppk < 1.0"}];i&&M.push({color:H.label,label:"No spec limits \xB7 no Ppk",dashed:!0});for(const b of M){const y=n.createElement("span");Object.assign(y.style,{display:"inline-flex",alignItems:"center",gap:g.sm});const $=n.createElement("span");$.style.cssText=ke(b.color,b.dashed?"outline":"solid");const T=n.createElement("span");T.textContent=b.label,y.append($,T),c.appendChild(y)}return c}function me(n){if(S.innerHTML="",n.length===0)return Te(S,"No parametric tests have any recorded values.",{maxWidth:"480px"}),()=>{};const i=r.ownerDocument.createElement("canvas");i.style.display="block",i.style.cursor="default",S.appendChild(i);const f=n.length,c=Math.min(0,...n.map(t=>t.min)),M=Math.max(1,...n.map(t=>t.max)),b=(M-c)*.05||.1,y=c-b,T=M+b-y||1;function V(t){return Math.max(Ae,Math.min(We,Math.floor(t/f)))}function L(t,s,o){return s+(1-(t-y)/T)*o}function he(t,s,o,l,k){const x=n.filter(a=>a.hasSpec).length,A=x===n.length,d=x===0,_=d?"max":"USL",e=d?"min":"LSL",h=A?"normalised to spec":d?"normalised to range":"normalised (per test)";t.font=`${G(-1)}px system-ui, sans-serif`,t.textAlign="right",t.textBaseline="middle";for(const[a,D]of[[1,_],[0,e]]){const p=L(a,o,l);p<o-1||p>o+l+1||(t.strokeStyle=s.limitLine,t.lineWidth=1,t.beginPath(),t.moveTo(v-5,p),t.lineTo(v,p),t.stroke(),t.fillStyle=s.textMuted,t.fillText(D,v-8,p))}t.save(),t.translate(9,o+l/2),t.rotate(-Math.PI/2),t.textAlign="center",t.fillStyle=s.textMuted,t.fillText(h,0,0),t.restore()}let w=-1;function j(){ge(i);const t=S.clientWidth,s=V(t-v),o=s*f,l=ne,k=v+o,x=xe(r,S,i,ne+200+te),A=Math.max(l+60,x-te),d=A-l,_=Ce(i,r,k,x);if(!_)return;const{ctx:e}=_,h=Le(r);n.forEach((a,D)=>{const p=v+D*s,m=p+s/2,u=Math.max(4,s*.55);if(D===w&&(e.fillStyle=h.bgHover,e.fillRect(p,l,s,A-l)),n[D].testNumber===X&&(e.fillStyle=h.accent,e.fillRect(p,A-2,s,2)),a.hasSpec){const K=L(0,l,d),Z=L(1,l,d);e.strokeStyle=h.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(p,K),e.lineTo(p+s,K),e.moveTo(p,Z),e.lineTo(p+s,Z),e.stroke(),e.setLineDash([])}const F=a.hasSpec?B(a.ppk):h.textMuted,U=L(a.min,l,d),E=L(a.q1,l,d),J=L(a.median,l,d),N=L(a.q3,l,d),q=L(a.max,l,d);e.strokeStyle=h.textMuted,e.lineWidth=1,e.beginPath(),e.moveTo(m,U),e.lineTo(m,E),e.moveTo(m,N),e.lineTo(m,q),e.moveTo(m-u/4,U),e.lineTo(m+u/4,U),e.moveTo(m-u/4,q),e.lineTo(m+u/4,q),e.stroke(),e.fillStyle=F,e.globalAlpha=.5,e.fillRect(m-u/2,Math.min(E,N),u,Math.max(1,Math.abs(N-E))),e.globalAlpha=1,e.strokeStyle=F,a.hasSpec||e.setLineDash([4,2]),e.strokeRect(m-u/2,Math.min(E,N),u,Math.max(1,Math.abs(N-E))),e.setLineDash([]),e.strokeStyle=h.text,e.lineWidth=2,e.beginPath(),e.moveTo(m-u/2,J),e.lineTo(m+u/2,J),e.stroke(),e.lineWidth=1,e.font=`${G(-1)}px system-ui, sans-serif`,e.textAlign="center",e.textBaseline="alphabetic",e.fillStyle=a.hasSpec?F:h.textMuted,e.fillText(a.hasSpec?I(a.ppk):"\u2014",m,l-8);const fe=a.label.length>12?`${a.label.slice(0,11)}\u2026`:a.label;e.save(),e.translate(m,A+6),e.rotate(-Math.PI/4),e.textAlign="right",e.textBaseline="middle",e.font=`${G(-1)}px system-ui, sans-serif`,e.fillStyle=h.textMuted,e.fillText(fe,0,0),e.restore()}),e.strokeStyle=h.border,e.lineWidth=1,e.strokeRect(v,l,o,d),he(e,h,l,d,o)}function Y(t){const s=i.getBoundingClientRect(),o=S.clientWidth,l=V(o-v),k=(t.clientX-s.left)*(i.width/Pe(i)/s.width)-v,x=Math.floor(k/l);return x>=0&&x<f?x:-1}return i.addEventListener("mousemove",t=>{const s=Y(t);if(s!==w&&(w=s,j()),s===-1){W.style.display="none";return}const o=n[s],l=k=>ee($e(k,o.unit));W.innerHTML=`<strong>${ee(o.label)}</strong> (n=${o.n})<br>`+(o.hasSpec?`LSL ${l(o.lsl)} \xB7 USL ${l(o.usl)}<br>mean ${l(o.mean)}<br>Cp ${I(o.cp)} \xB7 Cpk ${I(o.cpk)}<br>Pp ${I(o.pp)} \xB7 Ppk ${I(o.ppk)}`:`<em>No spec limits \u2014 sorted by variability</em><br>mean ${l(o.mean)} \xB7 stddev ${l(o.stdOverall)}`)+(O?"<br><em>click to view in boxplot</em>":""),W.style.display="block",Me(W,r,t.clientX,t.clientY),i.style.cursor=O?"pointer":"default"}),i.addEventListener("mouseleave",()=>{w!==-1&&(w=-1,j()),W.style.display="none"}),i.addEventListener("click",t=>{if(!O)return;const s=Y(t);s!==-1&&O(n[s].testNumber)}),j}function de(){const n=ue(ae(),z),i=z.filter(c=>c.testNumber!==void 0).length,f=n.filter(c=>!c.hasSpec).length;re(n.length,f,i),R=me(n),R()}const pe=ve(r,()=>R());return de(),{card:r,hasData:ie,setTest:n=>{X!==n&&(X=n,R())},destroy:()=>pe.disconnect()}}
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
- * `triggerBtn`, when given, is hidden for the modal's duration and shown
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, triggerBtn?: HTMLButtonElement): void;
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. Recomputing from `card`'s current live overhead every call is
62
- * self-correcting in both directions and converges in one extra resize-
63
- * observer pass (chrome overhead doesn't change with `card`'s own height,
64
- * so the fixed point is reached immediately, not oscillating).
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 wt,filterCorrelationMatrix as Mt,correlationSampleNote as vt}from"../../stats/correlation.js";import{csvField as it,maxOf as Ct}from"../../core/utils.js";import{CORRELATION_POSITIVE as at,CORRELATION_NEGATIVE as ct}from"./palette.js";import{buildFacetTable as Lt}from"../../stats/facets.js";import{wireControlHover as St,controlStyle as It,SPACE as C,RADIUS as nt,fontPx as dt,FONT as H,CLR as x,saveTextFile as Tt}from"../toolbar.js";import{attachChartTip as $t,cardShell as Et,observeResize as Dt,makeTooltip as Rt,positionChartTooltip as kt,makeWaferSelect as At,renderEmptyState as Bt,resolveChartCanvasColors as Ot,prepareCanvas as Nt,chartDpr as ut}from"./chartShell.js";import{escHtml as U}from"../../core/utils.js";const ft=5,pt=100,mt=20;function ot(b){const d=b.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)/);if(d)return[+d[1],+d[2],+d[3]];const f=b.trim().replace("#","");return f.length===6?[parseInt(f.slice(0,2),16),parseInt(f.slice(2,4),16),parseInt(f.slice(4,6),16)]:null}function _t(b){const d=ot(b);return d?(d[0]*299+d[1]*587+d[2]*114)/1e3>140?"#111":"#fff":"#000"}function Wt(b,d,f){const _=ot(b);if(!_)return b;const q=Math.round(d[0]+(_[0]-d[0])*f),W=Math.round(d[1]+(_[1]-d[1])*f),l=Math.round(d[2]+(_[2]-d[2])*f);return`rgb(${q},${W},${l})`}export function renderCorrelationPanel(b){const{title:d="Test correlation matrix",items:f,testDefs:_,onSaveImage:q,onSelectPair:W}=b,{card:l,body:A,controlsRow:J}=Et(d,q,b.ownerDocument);A.style.overflowX="auto",l.style.alignSelf="start";let Y=mt,F=null;const X=l.ownerDocument.createElement("label");X.textContent="Max tests:",Object.assign(X.style,{color:x.label,fontSize:H.body,display:"flex",alignItems:"center",gap:C.xs});const v=l.ownerDocument.createElement("input");v.type="number",v.min=String(ft),v.max=String(pt),v.value=String(Y),Object.assign(v.style,{width:"52px",fontSize:H.body,padding:`${C.xxs} ${C.xs}`,background:x.menuBg,color:x.value,border:`1px solid ${x.menuBorder}`,borderRadius:nt.control}),v.addEventListener("change",()=>{const e=Math.max(ft,Math.min(pt,parseInt(v.value,10)||mt));v.value=String(e),e!==Y&&(Y=e,Q())}),X.appendChild(v),J.appendChild(X),f.length>1&&J.appendChild(At(f,F,e=>{F=e,Q()},{ownerDocument:l.ownerDocument}));function ht(){return F!==null?f[F]?[f[F]]:[]:f}if(b.onSaveText){const e=l.ownerDocument.createElement("button");e.type="button",e.textContent="Correlation CSV",Object.assign(e.style,{...It("outlined"),color:x.text}),St(e),e.addEventListener("click",()=>{const s=st;if(!s)return;const u=["Test X,Test X number,Test Y,Test Y number,r,n"];for(let r=0;r<s.tests.length;r++)for(let p=r+1;p<s.tests.length;p++){const i=s.cells.find(w=>w.xIndex===p&&w.yIndex===r);i&&u.push([it(s.tests[p].label),String(s.tests[p].testNumber),it(s.tests[r].label),String(s.tests[r].testNumber),i.r===null?"":i.r.toFixed(6),String(i.n)].join(","))}Tt(u.join(`
2
- `),"test-correlation.csv","text/csv",b.onSaveText)}),J.appendChild(e)}const B=l.ownerDocument.createElement("div");Object.assign(B.style,{display:"flex",flexDirection:"column",gap:C.xs,marginBottom:C.sm}),l.insertBefore(B,A);const L=Rt(l);$t(X,l,L,"Cap on how many tests the matrix includes (strongest correlations kept first)");let K=()=>{};function xt(e,s,u,r,p,i,w){B.innerHTML="";const g=vt(w);if(g){const m=l.ownerDocument.createElement("div");m.textContent=`${g} \u2014 r is an estimate, not the whole population.`,Object.assign(m.style,{color:x.label,fontSize:H.body}),B.appendChild(m)}if(p.length>0){const m=l.ownerDocument.createElement("div");m.textContent=`\u26A0 Mixed ${p.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(m.style,{color:x.warnText,background:x.warnBg,border:`1px solid ${x.warnBorder}`,borderRadius:nt.control,padding:`${C.xs} ${C.md}`,fontSize:H.body}),B.appendChild(m)}const S=l.ownerDocument.createElement("span");Object.assign(S.style,{display:"inline-flex",alignItems:"center",gap:C.sm,color:x.label,fontSize:H.body,flexWrap:"wrap"});const I=l.ownerDocument.createElement("span");I.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",S.appendChild(I);const O=l.ownerDocument.createElement("span");Object.assign(O.style,{display:"inline-flex",alignItems:"center",gap:C.xs});const a=l.ownerDocument.createElement("span");a.textContent="\u22121";const T=l.ownerDocument.createElement("span");Object.assign(T.style,{display:"inline-block",width:"64px",height:"8px",borderRadius:nt.control,border:`1px solid ${x.menuBorder}`,background:`linear-gradient(to right, ${ct}, ${x.menuBg}, ${at})`});const z=l.ownerDocument.createElement("span");z.textContent="+1",O.append(a,T,z),S.appendChild(O),B.appendChild(S);const $=l.ownerDocument.createElement("span");if(Object.assign($.style,{color:x.value,fontSize:H.body,fontWeight:"500"}),e===0&&s===0)$.textContent=r?`No significant correlations found \u2014 strongest pair: ${r.xLabel} \u2194 ${r.yLabel} (r = ${r.r.toFixed(2)})`:"No significant correlations found";else{const m=[];e>0&&m.push(`${e} strong (|r| \u2265 0.7)`),s>0&&m.push(`${s} moderate (0.4\u20130.7)`);const P=e+s;let o=m.join(", ")+` pair${P!==1?"s":""} found`;u>0&&(o+=` \xB7 ${u} weak pair${u!==1?"s":""} not shown`),$.textContent=o}B.appendChild($)}function bt(e){if(A.innerHTML="",e.tests.length<2)return Bt(A,"Need at least two parametric tests for a correlation matrix."),()=>{};const s=l.ownerDocument.createElement("canvas");s.style.display="block",s.style.cursor="default",A.appendChild(s);const u=e.tests.length,r=o=>o.label.split(" (#")[0],p=Math.min(14,Ct(e.tests.map(o=>r(o).length))),i=p*6.5+8,w=10,g=Math.round(w*6.5*Math.sin(Math.PI/4))+14,S=14,I=28,O=44;let a=-1,T=-1;function z(o){const c=Math.max(0,o-i);return Math.max(S,Math.min(O,Math.floor(c/u)))}const $=new Map;for(const o of e.cells){let c=$.get(o.yIndex);c||(c=[],$.set(o.yIndex,c)),c.push(o)}function m(){const o=Ot(l),c=ot(o.bg)??[255,255,255],h=A.clientWidth,n=z(h),j=n*u,E=g+n*u+4,D=i+j,R=Nt(s,l,D,E);if(!R)return;const{ctx:t}=R;t.font=`${dt(-1)}px system-ui, sans-serif`,t.fillStyle=o.text,e.tests.forEach((k,y)=>{const M=r(k),N=M.length>w?`${M.slice(0,w-1)}\u2026`:M,Z=i+y*n+n/2;t.save(),t.translate(Z,g-4),t.rotate(-Math.PI/4),t.textAlign="left",t.textBaseline="bottom",t.fillStyle=y===a?o.text:o.textMuted,t.fillText(N,0,0),t.restore()}),e.tests.forEach((k,y)=>{const M=r(k),N=g+y*n,Z=N+n/2;t.font=`${dt(-1)}px system-ui, sans-serif`,t.textAlign="right",t.textBaseline="middle",t.fillStyle=y===T?o.text:o.textMuted,t.fillText(M.length>p?`${M.slice(0,p-1)}\u2026`:M,i-4,Z),($.get(y)??[]).forEach(lt=>{const tt=lt.xIndex,V=i+tt*n,yt=tt===a&&y===T,rt=tt===y,G=lt.r;if(G===null){t.fillStyle=o.border,t.globalAlpha=.4,t.fillRect(V+1,N+1,n-2,n-2),t.globalAlpha=1;return}let et=o.bgHover;rt?t.fillStyle=o.bgHover:(et=Wt(G>=0?at:ct,c,Math.abs(G)),t.fillStyle=et),t.fillRect(V+1,N+1,n-2,n-2),n>=I&&!rt&&(t.font=`${Math.min(10,n*.35)}px system-ui, sans-serif`,t.fillStyle=_t(et),t.textAlign="center",t.textBaseline="middle",t.fillText(G.toFixed(2),V+n/2,N+n/2)),yt&&(t.strokeStyle=o.text,t.lineWidth=2,t.strokeRect(V+1,N+1,n-2,n-2),t.lineWidth=1)})}),t.strokeStyle=o.bg,t.lineWidth=1;for(let k=0;k<=u;k++){const y=i+k*n;t.beginPath(),t.moveTo(y,g),t.lineTo(y,g+u*n),t.stroke();const M=g+k*n;t.beginPath(),t.moveTo(i,M),t.lineTo(i+u*n,M),t.stroke()}}function P(o){const c=s.getBoundingClientRect(),h=A.clientWidth,n=z(h),j=(o.clientX-c.left)*(s.width/ut(s)/c.width),E=(o.clientY-c.top)*(s.height/ut(s)/c.height),D=Math.floor((j-i)/n),R=Math.floor((E-g)/n);return D>=0&&D<u&&R>=0&&R<u?{xi:D,yi:R}:null}return s.addEventListener("mousemove",o=>{const c=P(o);if(!c){L.style.display="none",s.style.cursor="default";return}const{xi:h,yi:n}=c,j=h===n;s.style.cursor=j||!W?"default":"pointer";const E=e.cells.find(t=>t.xIndex===h&&t.yIndex===n),D=e.tests[h].label,R=e.tests[n].label;j?L.innerHTML=`<strong>${U(D)}</strong>`:E?.r!==null&&E?.r!==void 0?L.innerHTML=`<strong>${U(r(e.tests[n]))}</strong> (#${e.tests[n].testNumber}) vs <strong>${U(r(e.tests[h]))}</strong> (#${e.tests[h].testNumber})<br>r = ${E.r.toFixed(4)} \xB7 n = ${E.n.toLocaleString()}${W?"<br><em>click to view in scatter</em>":""}`:L.innerHTML=`${U(`${R} vs ${D}`)}<br><em>insufficient data</em>`,L.style.display="block",kt(L,l,o.clientX,o.clientY)}),s.addEventListener("mouseleave",()=>{L.style.display="none"}),s.addEventListener("click",o=>{const c=P(o);if(!c||!W)return;const{xi:h,yi:n}=c;h!==n&&(a=h,T=n,m(),W(e.tests[h].testNumber,e.tests[n].testNumber))}),m}let st=null;function Q(){const e=ht(),s=e.flatMap(a=>a.dies??[]),u=wt(s,_),{matrix:r,strongPairs:p,moderatePairs:i,hiddenWeakPairs:w,strongestPair:g}=Mt(u,{minTests:6,maxTests:Y}),S=Lt(e,{facetableOnly:!0}).filter(a=>a.splittable).map(a=>a.label),I=r.cells.filter(a=>a.xIndex!==a.yIndex).map(a=>a.n).sort((a,T)=>a-T),O=I.length?I[Math.floor(I.length/2)]:null;st=r,xt(p,i,w,g,S,O,u.sample),K=bt(r),K()}const gt=Dt(l,()=>K());return Q(),{card:l,destroy:()=>gt.disconnect()}}
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,growCardToFitContent as Z,prepareCanvas as tt,chartSwatchCss as et,PADDING as c,VALUE_WIDTH as D}from"./chartShell.js";const nt=8,w=1,a=14,lt=8;export function renderGroupedBarPlot(h,C,d,U){const{rows:y,groups:H,labelWidth:L,tooltipHtml:$,maxVisibleRows:j=lt,showLegend:k=!0,maxLabelChars:A,emptyBarText:R,colorOf:B=Y}=U,f=h.ownerDocument,M=Math.max(1,H.length),S=M*a+(M-1)*w,u=S+nt;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}),H.forEach((o,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 l=f.createElement("span");l.style.cssText=et(B(n));const s=f.createElement("span");s.textContent=o,t.append(l,s),e.appendChild(t)}),C.appendChild(e),W=e.offsetHeight}const v=f.createElement("div"),_=c*2+Math.min(y.length,j)*u;Object.assign(v.style,{overflowX:"hidden",overflowY:"auto",minHeight:"0",flex:"1",maxHeight:`${_}px`,scrollbarGutter:"stable"}),Z(h,C,W+_),C.appendChild(v);const i=f.createElement("canvas");i.style.display="block",i.style.cursor="default",v.appendChild(i);let r=null;function G(){const e=c+L,o=Math.max(10,i.clientWidth-e-D-c);return{barX:e,barMaxWidth:o}}function z(e,o){const n=Math.floor((o-c)/u);if(n<0||n>=y.length)return null;const t=o-c-n*u,l=Math.floor(t/(a+w));if(l<0||l>=M)return null;const{barX:s,barMaxWidth:g}=G();return e<s||e>s+g?null:{row:n,group:l}}function p(){const e=v.clientWidth,o=c*2+y.length*u,n=tt(i,h,e,o);if(!n)return;const{ctx:t,theme:l}=n,{barX:s,barMaxWidth:g}=G();y.forEach((m,P)=>{const b=c+P*u;if(r&&r.row===P){const x=b+r.group*(a+w);t.fillStyle=l.bgHover,t.fillRect(0,x-1,e,a+2)}t.fillStyle=l.text,t.textAlign="right";const V=A!==void 0&&m.label.length>A?`${m.label.slice(0,A-1)}\u2026`:m.label;t.fillText(V,c+L-8,b+S/2),m.bars.forEach((x,X)=>{const T=b+X*(a+w);if(t.fillStyle=l.track,t.fillRect(s,T,g,a),x.fraction===null){R&&(t.fillStyle=l.textMuted,t.textAlign="left",t.fillText(R,s+4,T+a/2));return}const N=Math.max(x.fraction>0?1:0,x.fraction*g);t.fillStyle=B(X),t.fillRect(s,T,N,a)}),t.fillStyle=l.text,t.textAlign="right",t.fillText(m.trailing,s+g+D,b+S/2)})}const I=e=>{const o=i.getBoundingClientRect(),n=z(e.clientX-o.left,e.clientY-o.top),t=n?.row!==r?.row||n?.group!==r?.group;if(r=n,t&&p(),!n){d.style.display="none";return}d.innerHTML=$(n.row,n.group),d.style.display="block",Q(d,h,e.clientX,e.clientY)},O=()=>{r&&(r=null,p()),d.style.display="none"};i.addEventListener("mousemove",I),i.addEventListener("mouseleave",O);const F=K(h,()=>p());return p(),{draw:p,destroy:()=>{F?.disconnect(),i.removeEventListener("mousemove",I),i.removeEventListener("mouseleave",O)}}}
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 E}from"../../stats/regions.js";import{yieldFill as A}from"./palette.js";import{fontPx as T}from"../toolbar.js";import{cardShell as D,resolveChartCanvasColors as N,observeResize as F,growCardToFitContent as $,renderEmptyState as j,prepareCanvas as z}from"./chartShell.js";const v=320,O=220,_={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 q(e,a,n,r,l,o){e.beginPath(),e.moveTo(a+o,n),e.arcTo(a+r,n,a+r,n+l,o),e.arcTo(a+r,n+l,a,n+l,o),e.arcTo(a,n+l,a,n,o),e.arcTo(a,n,a+r,n,o),e.closePath()}function w(e,a,n,r,l,o){e.font=`${T(-1)}px system-ui, sans-serif`;const h=e.measureText(r).width;e.font="700 11px system-ui, sans-serif";const M=e.measureText(l).width,g=Math.max(h,M)+12,b=30;e.fillStyle=o.bg,q(e,a-g/2,n-b/2,g,b,4),e.fill(),e.strokeStyle=o.border,e.lineWidth=1,e.stroke(),e.textAlign="center",e.textBaseline="middle",e.fillStyle=o.textMuted,e.font=`${T(-1)}px system-ui, sans-serif`,e.fillText(r,a,n-7),e.fillStyle=o.text,e.font="700 11px system-ui, sans-serif",e.fillText(l,a,n+7)}export function renderRegionYieldDiagram(e){const{mode:a,rows:n,onSaveImage:r}=e,l=e.title??(a==="ring"?"Ring yield":"Quadrant yield"),{card:o,body:h}=D(l,r,e.ownerDocument),M=n.length>0;if(!M)return j(h,"No bin data available for regional yield."),{card:o,hasData:M,destroy:()=>{}};Object.assign(h.style,{display:"flex",justifyContent:"center",alignItems:"center"});const g=o.ownerDocument.createElement("canvas");Object.assign(g.style,{display:"block",flex:"none"}),h.appendChild(g);function b(){const u=Math.max(O,Math.min(h.clientWidth||v,v));$(o,h,u);const S=z(g,o,u,u);if(!S)return;const{ctx:t}=S,y=N(o),k=A,c=u/2,d=u/2,f=u/2-34;if(a==="ring"){const i=n.length;for(let s=0;s<i;s++){const m=s/i*f,P=(s+1)/i*f;t.beginPath(),t.arc(c,d,P,0,2*Math.PI),t.arc(c,d,m,0,2*Math.PI,!0),t.closePath(),t.fillStyle=k(n[s].yieldPercent),t.fill(),t.strokeStyle=y.border,t.lineWidth=1,t.stroke()}t.beginPath(),t.arc(c,d,f,0,2*Math.PI),t.strokeStyle=y.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=c+Math.cos(P)*m,p=d+Math.sin(P)*m;w(t,I,p,n[s].label,`${n[s].yieldPercent.toFixed(1)}%`,y)}}else{for(const i of n){const s=E(i.key).quadrant,m=s?_[s]:void 0;if(!m)continue;const[P,I]=m;t.beginPath(),t.moveTo(c,d),t.arc(c,d,f,P,I),t.closePath(),t.fillStyle=k(i.yieldPercent),t.fill(),t.strokeStyle=y.border,t.lineWidth=1,t.stroke();const p=(P+I)/2,R=c+Math.cos(p)*f*.6,W=d+Math.sin(p)*f*.6;w(t,R,W,i.label,`${i.yieldPercent.toFixed(1)}%`,y)}t.beginPath(),t.arc(c,d,f,0,2*Math.PI),t.strokeStyle=y.text,t.lineWidth=1.5,t.stroke()}}const C=F(o,()=>b());return b(),{card:o,hasData:M,destroy:()=>C.disconnect()}}
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()}}