@wafertools/wafermap 0.26.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +360 -0
- package/README.md +46 -3
- package/dist/packages/canvas-adapter/canvasTheme.d.ts +4 -0
- package/dist/packages/canvas-adapter/canvasTheme.js +1 -1
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +13 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.d.ts +10 -10
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +337 -3
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.d.ts +8 -9
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -1
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +50 -0
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -0
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +12 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +20 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -0
- package/dist/packages/canvas-adapter/charts/trend.d.ts +30 -0
- package/dist/packages/canvas-adapter/charts/trend.js +1 -0
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/identityHeader.d.ts +46 -0
- package/dist/packages/canvas-adapter/identityHeader.js +1 -0
- package/dist/packages/canvas-adapter/index.d.ts +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +27 -3
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +15 -2
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -20
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +72 -17
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +219 -33
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +6 -0
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +310 -6
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +97 -34
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.d.ts +0 -4
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/utils.d.ts +11 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +34 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +104 -5
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorSchemes.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +15 -0
- package/dist/packages/stats/binPareto.js +1 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.d.ts +14 -0
- package/dist/packages/stats/connectedComponents.js +1 -0
- package/dist/packages/stats/correlation.d.ts +26 -0
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/facets.js +1 -1
- package/dist/packages/stats/filterFindings.d.ts +18 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/findingsNarrative.js +1 -1
- package/dist/packages/stats/histogram.d.ts +19 -1
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +6 -0
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +44 -0
- package/dist/packages/stats/mergeTestDefs.js +1 -0
- package/dist/packages/stats/metadataColumns.d.ts +20 -4
- package/dist/packages/stats/metadataColumns.js +1 -1
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +14 -14
- package/dist/packages/stats/renderSummaryReport.js +32 -29
- package/dist/packages/stats/reportHtml.js +38 -13
- package/dist/packages/stats/testPassRate.d.ts +85 -0
- package/dist/packages/stats/testPassRate.js +1 -0
- package/dist/packages/stats/trend.d.ts +40 -0
- package/dist/packages/stats/trend.js +1 -0
- package/dist/packages/stats/types.d.ts +117 -6
- package/llms.txt +6 -0
- package/package.json +7 -5
- package/dist/packages/canvas-adapter/metadataBadge.d.ts +0 -30
- package/dist/packages/canvas-adapter/metadataBadge.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{findTestDef as xe,buildMapTitle as ye}from"../renderer/buildView.js";import{affineInvert as Oe,affineVector as Me}from"../core/transforms.js";import{compareNatural as Ee}from"../core/utils.js";import{getColorScheme as be}from"../renderer/colorSchemes.js";import{SPEC_PASS_FILL as Te,SPEC_FAIL_LOW as Ye,SPEC_FAIL_HIGH as Se,contrastTextColor as ke}from"../renderer/colorMap.js";import{fmt as Le,fmtColorbarAxis as we}from"../renderer/fmt.js";import{resolveCanvasTheme as Xe}from"./canvasTheme.js";function Ae(g,e,N,M,h){g.moveTo(e,N+h*M),g.lineTo(e-M*.9,N-h*M*.6),g.lineTo(e+M*.9,N-h*M*.6),g.closePath()}const He=new Set(["value","stackedValues","stackedBins","stackedSoftBins"]),$e=new Set(["hardBin","softBin","metadata"]),v="10px system-ui, sans-serif",Ge="600 12px system-ui, sans-serif",Gt="11px system-ui, sans-serif",Ve="600 11px system-ui, sans-serif",Vt=128,Ue="10px system-ui, sans-serif",Re=4,Y=17,X=11;export const BIN_LEGEND_W=110,BIN_LEGEND_W_COMPACT=64,BIN_LEGEND_ADAPT_COMPACT=280,BIN_LEGEND_ADAPT_FLOATING=180,BIN_LEGEND_MIN_CANVAS_W=150,BIN_LEGEND_MIN_CANVAS_H=120;const Ut=28,gt=5,mt=8,qe=15;export function toCanvas(g,e,N={}){const M=Xe(g.parentElement??g),{padding:h=16,showColorbar:$=!0,markFailingDies:G=!1,colorbarWidth:_=16,background:ut=M.background,legendPosition:Z="default",legendOffset:pt,showAxes:xt=!1,diePitchMm:V,viewport:K,activeBin:E,fallbackFormat:z,topClearance:j=0,minRightReserve:kt,hbinDefs:at,sbinDefs:yt,metadataFields:Mt,showTitle:Q=!0}=N,I=Math.floor(g.clientWidth||g.width),H=Math.floor(g.clientHeight||g.height),bt=I>=BIN_LEGEND_MIN_CANVAS_W&&H>=BIN_LEGEND_MIN_CANVAS_H,Lt=$&&bt&&He.has(e.plotMode)&&e.passFailDisplay==="off",Tt=$&&bt&&$e.has(e.plotMode),tt=$&&bt&&e.plotMode==="value"&&(e.passFailDisplay==="spec"&&e.specCounts!==void 0||e.passFailDisplay==="test"&&e.passFailCounts!==void 0),et=Tt||tt,At=Tt&&e.plotMode!=="metadata"&&e.binCounts?[...e.binCounts.entries()].sort(([o],[r])=>o-r):[],Rt=Tt&&e.plotMode==="metadata"&&e.metadataCounts?[...e.metadataColorMap?.keys()??[...e.metadataCounts.keys()].sort(Ee)].map(o=>[o,e.metadataCounts.get(o)??0]):[],ot=[];if(tt&&e.passFailDisplay==="test"){const o=e.passFailCounts;ot.push({key:-1,color:Te,label:"Pass",compactLabel:"Pass",count:o.pass,tooltip:`Pass \xB7 ${o.pass} dies`}),ot.push({key:-2,color:Se,label:"Fail",compactLabel:"Fail",count:o.fail,tooltip:`Fail \xB7 ${o.fail} dies`})}else if(tt){const o=e.specCounts,r=xe(e.testDefs,e.activeTest);ot.push({key:-1,color:Te,label:"Pass",compactLabel:"Pass",count:o.pass,tooltip:`Pass \xB7 ${o.pass} dies`}),r?.limitHigh!==void 0&&ot.push({key:-2,color:Se,label:"Fail high",compactLabel:"Fail hi",count:o.failHigh,tooltip:`Fail high \xB7 ${o.failHigh} dies`}),r?.limitLow!==void 0&&ot.push({key:-3,color:Ye,label:"Fail low",compactLabel:"Fail lo",count:o.failLow,tooltip:`Fail low \xB7 ${o.failLow} dies`})}const Ct=tt?ot.length:e.plotMode==="metadata"?Rt.length:At.length,m=(g.ownerDocument.defaultView??window).devicePixelRatio??1;if(I<=0||H<=0)return{hitTarget:{getDieAtPoint:()=>null},viewport:{originX:0,originY:0,ppm:1,snapDist:1},binLegendRows:[]};const C=Z!=="default"?Z:I<BIN_LEGEND_ADAPT_FLOATING?"floating":I<BIN_LEGEND_ADAPT_COMPACT?"compact":"default",dt=C==="default"||C==="compact",ne=C==="left",zt=C==="bottom",_t=C==="top",Pt=C==="floating",Wt=C==="compact"||C==="floating"?BIN_LEGEND_W_COMPACT:BIN_LEGEND_W,se=Math.floor((H-2*h)/Y),le=Ct>se?se-1:Ct,ie=et&&zt?le*Y:0,Kt=et&&_t?le*Y:0,Jt=Lt?_+28:et&&dt?Wt:0,Zt=et&&ne?Wt:0,jt=xt?32:0,Bt=xt?36:0,Ce=kt!=null?Math.max(Jt,kt):Jt,_e=I-2*h-Ce-Bt-Zt,ae=H-2*h-jt-ie-Kt;g.width=Math.round(I*m),g.height=Math.round(H*m);const t=g.getContext("2d");t.scale(m,m),t.fillStyle=ut,t.fillRect(0,0,I,H);const U=e.hoverPoints;if(!U.length)return{hitTarget:{getDieAtPoint:()=>null},viewport:{originX:0,originY:0,ppm:1,snapDist:1},binLegendRows:[]};const Dt=e.rectangles[0],Qt=Dt?Dt.width/2:0,vt=Dt?Dt.height/2:0,Ft=e.dieBounds,ce=(Ft?.minX??0)-Qt,Pe=(Ft?.maxX??0)+Qt,We=(Ft?.minY??0)-vt,re=(Ft?.maxY??0)+vt,fe=Pe-ce,he=re-We;let nt,st,b;if(K)({originX:nt,originY:st,ppm:b}=K);else{const r=e.notchDir,l=r&&r.y>.1?13:0,a=r&&r.y<-.1?13:0,c=r&&r.x<-.1?13:0,f=r&&r.x>.1?13:0,d=_e-c-f,i=ae-j-l-a;b=Math.min(d/fe,i/he),nt=h+Bt+Zt+c+(d-fe*b)/2-ce*b,st=h+j+Kt+l+(i-he*b)/2+re*b}const It=K?.snapDist??Math.max(Qt,vt,1)*1.5,Nt=nt+e.waferCenter.x*b,Be=st-e.waferCenter.y*b,te=e.waferRadius*b,ct=o=>{const r=Math.abs(o-Be);return r>=te?0:Math.sqrt(te*te-r*r)},rt=(o,r,l,a,c,f,d=Ge,i=M.text)=>{if(!o)return;t.save(),t.fillStyle=i,t.font=d,t.textAlign=a,t.textBaseline=c;const p=a==="right"?r-f:f-r;let s=o;if(p>8&&t.measureText(s).width>p){for(;s.length>1&&t.measureText(s+"\u2026").width>p;)s=s.slice(0,-1);s=s+"\u2026"}p>8&&t.fillText(s,r,l),t.restore()};if(t.save(),t.setTransform(b*m,0,0,-b*m,nt*m,st*m),e.rectangles.length>0){const o=new Map;for(const l of e.rectangles){const a=String(l.fill);let c=o.get(a);c||(c=[],o.set(a,c)),c.push(l)}for(const[l,a]of o){t.beginPath();for(const c of a)t.rect(c.x-c.width/2,c.y-c.height/2,c.width,c.height);t.fillStyle=l,t.fill()}t.strokeStyle="rgba(0,0,0,0.18)",t.lineWidth=.5/(b*m),t.beginPath();for(const l of e.rectangles)t.rect(l.x-l.width/2,l.y-l.height/2,l.width,l.height);if(t.stroke(),G){const l=e.rectangles.filter(a=>a.binFail);if(l.length>0){const a=new Map;for(const c of l){const f=ke(String(c.fill));let d=a.get(f);d||(d=[],a.set(f,d)),d.push(c)}t.save(),t.lineCap="butt";for(const[c,f]of a){t.strokeStyle=c,t.globalAlpha=.55,t.lineWidth=Math.max(.6/(b*m),Math.min(f[0].width,f[0].height)*.1);for(const d of f){const i=d.width/2,p=d.height/2,s=Math.max(1.6/(b*m),Math.min(d.width,d.height)/3);t.save(),t.beginPath(),t.rect(d.x-i,d.y-p,d.width,d.height),t.clip(),t.beginPath();for(let x=-(i+p);x<=i+p;x+=s)t.moveTo(d.x-i+x,d.y-p),t.lineTo(d.x-i+x+d.height,d.y+p);t.stroke(),t.restore()}}t.restore()}}const r=e.rectangles.filter(l=>l.specMark);if(r.length>0){const l=new Map;for(const c of r){const f=ke(String(c.fill)),d=`${c.specMark}|${f}`;let i=l.get(d);i||(i=[],l.set(d,i)),i.push(c)}const a=2.5/(b*m);t.lineJoin="round";for(const[c,f]of l){const[d,i]=c.split("|"),p=i==="#000000"?"#ffffff":"#000000",s=d==="failHigh"?1:-1,x=Math.min(f[0].width,f[0].height),w=Math.max(2.5/(b*m),x*.3);t.beginPath();for(const k of f)Ae(t,k.x,k.y,w,s);t.strokeStyle=p,t.lineWidth=a,t.stroke(),t.fillStyle=i,t.fill()}}}for(const o of e.overlays){t.beginPath();for(const r of o.points){for(let l=0;l<r.length;l++)l===0?t.moveTo(r[l].x,r[l].y):t.lineTo(r[l].x,r[l].y);o.closed&&t.closePath()}o.fill&&!o.fill.startsWith("rgba(0,0,0,0)")&&(t.fillStyle=o.fill,t.fill()),o.kind==="ring-boundary"||o.kind==="quadrant-boundary"||o.kind==="reticle"?(t.strokeStyle="rgba(0,0,0,0.45)",t.lineWidth=3/(b*m),t.stroke(),t.strokeStyle="rgba(255,255,255,0.8)",t.lineWidth=1/(b*m),t.stroke()):(t.strokeStyle=o.lineColor,t.lineWidth=o.lineWidth/(b*m),t.stroke())}if(t.restore(),e.notchDir){const o=nt+e.waferCenter.x*b,r=st-e.waferCenter.y*b,l=e.notchDir.x,a=-e.notchDir.y,c=e.waferRadius*b,f=4,d=9,i=5,p=o+l*(c+f),s=r+a*(c+f),x=p+l*d,w=s+a*d,k={x:-a,y:l},B=h+Bt+Zt,lt=I-h-Jt,q=h+j+Kt,ht=H-h-ie-jt;t.save(),t.beginPath(),t.rect(B,q,lt-B,ht-q),t.clip(),t.beginPath(),t.moveTo(p,s),t.lineTo(x+k.x*i,w+k.y*i),t.lineTo(x-k.x*i,w-k.y*i),t.closePath(),t.fillStyle=M.text,t.fill(),t.restore()}t.save(),t.textAlign="center",t.textBaseline="middle";let de=0;if(e.rectangles.length>0){let o=1;for(const f of e.texts)f.role!=="indicator"&&f.text.length>o&&(o=f.text.length);const r=e.rectangles[0].width*b,l=e.rectangles[0].height*b,a=Math.min(r,l),c=r/o;de=Math.max(8,Math.min(64,Math.round(Math.min(a*.55,c*1.8))))}for(const o of e.texts){const r=nt+o.x*b,l=st-o.y*b;t.font=`${o.role==="indicator"?o.fontSize:de}px system-ui, sans-serif`,t.fillStyle=o.color,t.fillText(o.text,r,l)}if(t.restore(),xt&&ze(t,I,H,nt,st,b,h,jt,Bt,V,e.gridToScreen,e.rotation,M),Lt){const o=be(e.colorScheme),r=20,l=ae-j,a=Math.round(l*.75),c=h+j+Math.round((l-a)/2),f=I-h-_-r,[d,i]=e.valueRange,p=i-d,s=e.plotMode==="stackedSoftBins"?yt:at,{primary:x,secondary:w}=Q?ye(e,z,s):{primary:"",secondary:""};if(Q){const n=Math.max(c-6,h+j+11),u=Nt+Math.max(ct(n),ct(n-12))+8;rt(x,I-h,n,"right","bottom",u)}let k=c+a+4;const B=n=>Nt+Math.max(ct(n),ct(n+12))+8,lt=e.logScale&&d>0?Math.log10(d):0,q=e.logScale&&i>0?Math.log10(i)-lt:1,ht=n=>e.logScale?(1-(Math.log10(n)-lt)/q)*a:(1-(n-d)/p)*a;for(let n=0;n<Vt;n++){const u=1-n/(Vt-1),D=c+n/Vt*a,W=a/Vt+1;t.fillStyle=o.forValue(u),t.fillRect(f,D,_,W)}t.strokeStyle="rgba(0,0,0,0.25)",t.lineWidth=.5,t.strokeRect(f,c,_,a),t.fillStyle=M.text,t.font=v,t.textAlign="left",t.strokeStyle="rgba(0,0,0,0.35)",t.lineWidth=.5;const L=3,O=36,it=14,Xt=e.logScale&&q>0?Ke(d,i,a,O,it,lt,q):e.allIntegerValues?(()=>{const n=Math.max(1,qt(p*O/a)),u=[],D=Math.ceil(d/n)*n;for(let W=D;W<=i+n*1e-6;W+=n){const F=(1-(W-d)/p)*a;F>it&&F<a-it&&u.push(Math.round(W))}return u})():(()=>{const n=p>0?qt(p*O/a):0,u=[];if(n>0){const D=Math.ceil(d/n)*n;for(let W=D;W<=i+n*1e-6;W+=n){const F=(1-(W-d)/p)*a;F>it&&F<a-it&&u.push(W)}}return u})(),A=xe(e.testDefs,e.activeTest),R=e.plotMode==="stackedBins"||e.plotMode==="stackedSoftBins",y=R?"Count":A?.name??(e.activeTest!=null?`Test ${e.activeTest}`:void 0),T=R?void 0:A?.unit,{tickFmt:S}=we(i,y,T,z),P=e.allIntegerValues?n=>String(Math.round(n)):S;t.textBaseline="middle";for(const n of Xt){const u=c+ht(n);t.beginPath(),t.moveTo(f+_,u),t.lineTo(f+_+L,u),t.stroke(),t.fillText(P(n),f+_+L+2,u)}if(t.beginPath(),t.moveTo(f+_,c),t.lineTo(f+_+L,c),t.stroke(),t.textBaseline="top",t.fillText(P(i),f+_+L+2,c),t.beginPath(),t.moveTo(f+_,c+a),t.lineTo(f+_+L,c+a),t.stroke(),t.textBaseline="bottom",t.fillText(P(d),f+_+L+2,c+a),!R&&A&&(A.limitLow!==void 0||A.limitHigh!==void 0)){const n=[],u=e.colorbarRangeMode==="spec";for(const[D,W]of[[A.limitLow,"LSL"],[A.limitHigh,"USL"]]){if(D===void 0||e.logScale&&D<=0)continue;const F=ht(D);!u&&(F<-it||F>a+it)||n.push({value:D,label:W,py:Math.max(0,Math.min(a,F))})}if(n.length>0){t.save(),t.font=v,t.textAlign="right",t.textBaseline="middle";for(const{py:D,label:W}of n){const F=c+D;u||(t.strokeStyle="rgba(255,255,255,0.85)",t.lineWidth=3,t.beginPath(),t.moveTo(f,F),t.lineTo(f+_,F),t.stroke(),t.strokeStyle="rgba(0,0,0,0.7)",t.lineWidth=1,t.beginPath(),t.moveTo(f,F),t.lineTo(f+_,F),t.stroke()),t.fillStyle=M.text,t.fillText(W,f-3,F);const Ht=W==="USL"?-1:1,$t=4,Ne=f-3-t.measureText(W).width-$t-4;t.beginPath(),Ae(t,Ne,F,$t,Ht),t.strokeStyle="#fff",t.lineWidth=2,t.lineJoin="round",t.stroke(),t.fillStyle=M.text,t.fill()}t.restore()}}if(!R){const n=e.logScale?"log\u2081\u2080":e.logScaleRequested?"linear \u2014 log n/a":null;n&&(t.save(),t.fillStyle=M.text,t.font=Ve,t.textAlign="right",t.textBaseline="top",t.fillText(n,I-h,k),t.restore(),k+=15)}Q&&w&&rt(w,I-h,k,"right","top",B(k),Gt,M.text)}const ee=[];let J;if(et){const o=be(e.colorScheme),r=C==="compact",l=e.plotMode==="softBin"?yt:at,a=l?new Map(l.map(s=>[s.bin,s])):null,c=s=>{const x=a?.get(s);return(e.colorScheme==="custom"?x?.color:void 0)??o.forBin(s)},f=(s,x)=>{const w=t.measureText(s).width,k=r?0:t.measureText(String(x)).width,B=X+gt+w+(k?Ut:0);return{labelWidth:w,countWidth:k,totalWidth:B}},d=Mt?.find(s=>s.key===e.activeMetadataKey),i=tt?ot.map(s=>{const x=r?s.compactLabel:s.label;return{key:s.key,color:s.color,count:s.count,label:x,tooltipLabel:s.tooltip,...f(x,s.count)}}):e.plotMode==="metadata"?Rt.map(([s,x])=>{const k=d?.values?.find(q=>q.value===s)?.label??s,B=r?s:k,lt=e.metadataColorMap?.get(s)??"#d6d9dd";return{key:s,color:lt,count:x,label:B,tooltipLabel:`${k} \xB7 ${x} dies`,...f(B,x)}}):At.map(([s,x])=>{const w=a?.get(s),k=w?.name?`${s} \xB7 ${w.name}`:`Bin ${s}`,B=r?String(s):k;return{key:s,color:c(s),count:x,label:B,tooltipLabel:`${k} \xB7 ${x} dies`,...f(B,x)}}),p=I-h*2;if(i.length>0){const s=Math.max(1,Math.floor((H-2*h)/Y));let x=i.length,w=1,k=x*Y,B=[],lt=x,q=0;const ht=zt||_t;if(ht){const A=Math.max(...i.map(y=>y.totalWidth)),R=Math.max(1,Math.min(i.length,Math.floor((p+8)/(A+8))));for(let y=R;y>=1;y--){const T=Math.ceil(i.length/y);if(T>s)continue;const S=new Array(y).fill(0);for(let n=0;n<i.length;n++){const u=n%y;S[u]=Math.max(S[u],i[n].totalWidth)}if(S.reduce((n,u)=>n+u,0)+(y-1)*8<=p||y===1){w=y,B=S,lt=T,x=T,k=T*Y;break}}}else x=Math.min(i.length,s),k=x*Y,i.length>s&&(q=i.length-(s-1)),B=[Wt];let L,O;if(Pt){const A=Math.max(...i.map(u=>X+gt+t.measureText(u.label).width+Ut)),R=Math.ceil(A);B=[R];const y=Q?qe:0,T=R+mt*2,S=k+y+mt*2,P=pt?.x??0,n=pt?.y??0;L=Math.min(Math.max(I-h-T+P,h),I-h-T),O=Math.min(Math.max(H-h-S+n,h),H-h-S),J={x:L,y:O,w:T,h:S},t.save(),t.globalAlpha=.92,t.fillStyle=M.surface,t.fillRect(L,O,T,S),t.globalAlpha=1,t.strokeStyle=M.axisLine,t.lineWidth=1,t.strokeRect(L,O,T,S),t.restore(),L+=mt,O+=mt+y}else zt?(L=h,O=H-h-k):_t?(L=h,O=h+j):ne?(L=h+4,O=h+Math.round((H-2*h-k)/2)):(L=I-h-Wt+4,O=h+Math.round((H-2*h-k)/2));if(Q){const A=e.plotMode==="softBin"?yt:at,{primary:R,secondary:y}=ye(e,z,A),T=6,S=u=>Nt-Math.max(ct(u),ct(u-12))-8,P=u=>dt?I-h:S(u),n=Pt?J.y+J.h:O+k;if(_t){const u=n+T;rt(R,L,u,"left","top",Nt-Math.max(ct(u),ct(u+12))-8),y&&rt(y,L,u+16,"left","top",S(u+16),Gt,M.text)}else if(Pt)rt(R,J.x+mt,J.y+mt,"left","top",J.x+J.w-mt),y&&rt(y,J.x,n+T,"left","top",J.x+J.w,Gt,M.text);else{const u=Math.max(O-T,h+j+12);if(rt(R,L,u,"left","bottom",P(u)),y){const D=n+T;rt(y,L,D,"left","top",P(D),Gt,M.text)}}}t.save(),t.font=v;const it=ht?8:0,Xt=(A,R)=>{if(t.measureText(A).width<=R)return A;let y=0,T=A.length;const S="\u2026",P=t.measureText(S).width;for(;y<T;){const n=y+T+1>>1;t.measureText(A.slice(0,n)).width+P<=R?y=n:T=n-1}return y===0?S:A.slice(0,y)+S};if(ht){let A=0;for(let R=0;R<x;R++){let y=L;for(let T=0;T<w;T++){const S=i[A++];if(!S)break;const P=S.key===E,n=y,u=y+X+gt,D=O+R*Y+Y/2,W=O+R*Y+Math.round((Y-X)/2),F=B[T]-X-gt-Ut;t.fillStyle=S.color,t.fillRect(n,W,X,X),t.strokeStyle=P?M.accent:"rgba(0,0,0,0.25)",t.lineWidth=P?2:.75,t.strokeRect(n,W,X,X),t.fillStyle=P?M.accent:M.text,t.font=P?`bold ${v}`:v,t.textAlign="left",t.textBaseline="middle",t.fillText(Xt(S.label,F),u,D),t.fillStyle=M.textMuted,t.font=v,t.textAlign="right",t.fillText(String(S.count),y+B[T]-2,D),ee.push({bin:S.key,x:y,y:O+R*Y,w:B[T],h:Y,label:S.tooltipLabel}),y+=B[T]+it}}}else{const A=B[0],R=C!=="compact",y=A-X-gt-(R?Ut:0),T=L+A-2;let S=i;q>0&&(S=i.slice(0,s-1));let P=O;for(const n of S){const u=n.key===E,D=L,W=L+X+gt,F=P+Y/2,Ht=P+Math.round((Y-X)/2),$t=n.label;t.fillStyle=n.color,t.fillRect(D,Ht,X,X),t.strokeStyle=u?M.accent:"rgba(0,0,0,0.25)",t.lineWidth=u?2:.75,t.strokeRect(D,Ht,X,X),t.fillStyle=u?M.accent:M.text,t.font=u?`bold ${v}`:v,t.textAlign="left",t.textBaseline="middle",t.fillText(Xt($t,y),W,F),(R||Pt)&&(t.fillStyle=M.textMuted,t.font=v,t.textAlign="right",t.fillText(String(n.count),T,F)),ee.push({bin:n.key,x:L,y:P,w:A,h:Y,label:n.tooltipLabel}),P+=Y}q>0&&(t.fillStyle=M.textMuted,t.font=v,t.textAlign="left",t.textBaseline="middle",t.fillText(`+ ${q} more`,L+X+gt,P+Y/2))}}i.length>0&&t.restore()}const De={originX:nt,originY:st,ppm:b,snapDist:It},ge=e.rectangles[0]?.width??1,me=e.rectangles[0]?.height??1,Ot=ge*1.5,Et=me*1.5,ft=e.dieBounds,Yt=ft?ft.minX:U.length?Math.min(...U.map(o=>o.x)):0,wt=ft?ft.minY:U.length?Math.min(...U.map(o=>o.y)):0,Fe=ft?ft.maxX:U.length?Math.max(...U.map(o=>o.x)):1,Ie=ft?ft.maxY:U.length?Math.max(...U.map(o=>o.y)):1,St=Math.max(1,Math.ceil((Fe-Yt)/Ot)+1),oe=Math.max(1,Math.ceil((Ie-wt)/Et)+1),ue=Array.from({length:St*oe},()=>[]);for(let o=0;o<U.length;o++){const r=Math.floor((U[o].x-Yt)/Ot),l=Math.floor((U[o].y-wt)/Et),a=Math.max(0,Math.min(St-1,r)),c=Math.max(0,Math.min(oe-1,l));ue[c*St+a].push(o)}function pe(o,r,l){const a=Math.max(0,Math.floor((o-l-Yt)/Ot)),c=Math.min(St-1,Math.floor((o+l-Yt)/Ot)),f=Math.max(0,Math.floor((r-l-wt)/Et)),d=Math.min(oe-1,Math.floor((r+l-wt)/Et)),i=[];for(let p=f;p<=d;p++)for(let s=a;s<=c;s++)for(const x of ue[p*St+s])i.push(x);return i}return{hitTarget:{getDieAtPoint(o,r){const l=(o-nt)/b,a=(st-r)/b,c=Math.max(ge,me);for(const i of pe(l,a,c)){const p=e.rectangles[i];if(p&&Math.abs(l-p.x)<=p.width/2&&Math.abs(a-p.y)<=p.height/2)return e.dies[i]??null}let f=null,d=It*It;for(const i of pe(l,a,It)){const p=U[i].x-l,s=U[i].y-a,x=p*p+s*s;x<d&&(d=x,f=e.dies[i]??null)}return f}},viewport:De,binLegendRows:ee,legendBox:J}}function ze(g,e,N,M,h,$,G,_,ut,Z,pt,xt,V){g.save(),g.font=Ue,g.fillStyle=V.text,g.strokeStyle=V.axisLine,g.lineWidth=.5;const K=N-_+4,E=G+ut-4,z=Oe(pt),j=Me(z,1,0),kt=Me(z,0,1),at=1e-9;function yt(m){return Z?Math.abs(m.y)<at&&Math.abs(m.x)>at?{pitch:Z.x,sign:Math.sign(m.x)}:Math.abs(m.x)<at&&Math.abs(m.y)>at?{pitch:Z.y,sign:Math.sign(m.y)}:null:null}const Mt=yt(j),Q=yt(kt);function I(m){return Mt?Math.round(m*Mt.sign/Mt.pitch):m}function H(m){return Q?Math.round(m*Q.sign/Q.pitch):m}function bt(m){if(!m)return qt(50/$);const C=50/(m*$);return Math.max(1,Math.round(C))*m}const Lt=Mt?.pitch,Tt=Q?.pitch,tt=bt(Lt),et=bt(Tt);g.textAlign="center",g.textBaseline="top";const At=Math.ceil((G-M)/$/tt)*tt,Rt=(e-G-M)/$;for(let m=At;m<=Rt;m+=tt){const C=M+m*$;if(C<G||C>e-G)continue;g.beginPath(),g.moveTo(C,K-Re),g.lineTo(C,K),g.stroke();const dt=Z?String(I(m)):Le(m);g.fillText(dt,C,K+2)}g.textAlign="right",g.textBaseline="middle";const ot=Math.ceil((h-(N-G))/$/et)*et,Ct=(h-G)/$;for(let m=ot;m<=Ct;m+=et){const C=h-m*$;if(C<G||C>N-G)continue;g.beginPath(),g.moveTo(E,C),g.lineTo(E+Re,C),g.stroke();const dt=Z?String(H(m)):Le(m);g.fillText(dt,E-2,C)}g.restore()}function qt(g){const e=Math.pow(10,Math.floor(Math.log10(g))),N=g/e;return(N<1.5?1:N<3.5?2:N<7.5?5:10)*e}function Ke(g,e,N,M,h,$,G){const _=[],ut=G;if(ut<1){const V=qt((e-g)*M/N);if(V>0){const K=Math.ceil(g/V)*V;for(let E=K;E<=e+V*1e-6;E+=V){if(E<=g||E>=e)continue;const z=(1-(Math.log10(E)-$)/G)*N;z>h&&z<N-h&&_.push(E)}}return _}const Z=ut<3?[1,2,5]:[1],pt=Math.floor(Math.log10(g)),xt=Math.ceil(Math.log10(e));for(let V=pt;V<=xt;V++)for(const K of Z){const E=K*Math.pow(10,V);if(E<=g||E>=e)continue;const z=(1-(Math.log10(E)-$)/G)*N;z>h&&z<N-h&&_.push(E)}return _}export{fmt,fmtColorbarAxis}from"../renderer/fmt.js";
|
|
1
|
+
import{fontPx as _t}from"./toolbar.js";import{findTestDef as Re,buildMapTitle as _e}from"../renderer/buildView.js";import{affineInvert as Je,affineVector as Ce}from"../core/transforms.js";import{compareNatural as Ze}from"../core/utils.js";import{getColorScheme as Be}from"../renderer/colorSchemes.js";import{SPEC_PASS_FILL as Pe,SPEC_FAIL_LOW as je,SPEC_FAIL_HIGH as We,contrastTextColor as De}from"../renderer/colorMap.js";import{fmt as Oe,fmtColorbarAxis as Qe}from"../renderer/fmt.js";import{resolveCanvasTheme as ve}from"./canvasTheme.js";function Ie(m,e,F,M,h){m.moveTo(e,F+h*M),m.lineTo(e-M*.9,F-h*M*.6),m.lineTo(e+M*.9,F-h*M*.6),m.closePath()}const to=new Set(["value","stackedValues","stackedBins","stackedSoftBins"]),eo=new Set(["hardBin","softBin","metadata"]),Z=()=>`${_t(-2)}px system-ui, sans-serif`,Fe=3,Ut=2,oo=6,no=20,so=()=>`600 ${_t()}px system-ui, sans-serif`,Kt=()=>`${_t(-1)}px system-ui, sans-serif`,lo=()=>`600 ${_t(-1)}px system-ui, sans-serif`,qt=128,io=()=>`${_t(-2)}px system-ui, sans-serif`,Ne=4,D=17,Y=11;export const BIN_LEGEND_W=110,BIN_LEGEND_W_COMPACT=64,BIN_LEGEND_ADAPT_COMPACT=280,BIN_LEGEND_ADAPT_FLOATING=180,BIN_LEGEND_MIN_CANVAS_W=150,BIN_LEGEND_MIN_CANVAS_H=120;const zt=28,mt=5,ut=8,ao=15;export function toCanvas(m,e,F={}){const M=ve(m.parentElement??m),{padding:h=16,showColorbar:H=!0,markFailingDies:X=!1,colorbarWidth:P=16,background:pt=M.background,legendPosition:j="default",legendOffset:xt,showAxes:Mt=!1,diePitchMm:G,viewport:q,activeBin:N,hoverBin:z,fallbackFormat:St,topClearance:Q=0,minRightReserve:ct,hbinDefs:yt,sbinDefs:st,metadataFields:bt,showTitle:rt=!0}=F,O=Math.floor(m.clientWidth||m.width),E=Math.floor(m.clientHeight||m.height),Lt=O>=BIN_LEGEND_MIN_CANVAS_W&&E>=BIN_LEGEND_MIN_CANVAS_H,kt=H&&Lt&&to.has(e.plotMode)&&e.passFailDisplay==="off",ft=H&&Lt&&eo.has(e.plotMode),tt=H&&Lt&&e.plotMode==="value"&&(e.passFailDisplay==="spec"&&e.specCounts!==void 0||e.passFailDisplay==="test"&&e.passFailCounts!==void 0),ht=ft||tt,Ct=ft&&e.plotMode!=="metadata"&&e.binCounts?[...e.binCounts.entries()].sort(([o],[c])=>o-c):[],Bt=ft&&e.plotMode==="metadata"&&e.metadataCounts?[...e.metadataColorMap?.keys()??[...e.metadataCounts.keys()].sort(Ze)].map(o=>[o,e.metadataCounts.get(o)??0]):[],et=[];if(tt&&e.passFailDisplay==="test"){const o=e.passFailCounts;et.push({key:-1,color:Pe,label:"Pass",compactLabel:"Pass",count:o.pass,tooltip:`Pass \xB7 ${o.pass} dies`}),et.push({key:-2,color:We,label:"Fail",compactLabel:"Fail",count:o.fail,tooltip:`Fail \xB7 ${o.fail} dies`})}else if(tt){const o=e.specCounts,c=Re(e.testDefs,e.activeTest);et.push({key:-1,color:Pe,label:"Pass",compactLabel:"Pass",count:o.pass,tooltip:`Pass \xB7 ${o.pass} dies`}),c?.limitHigh!==void 0&&et.push({key:-2,color:We,label:"Fail high",compactLabel:"Fail hi",count:o.failHigh,tooltip:`Fail high \xB7 ${o.failHigh} dies`}),c?.limitLow!==void 0&&et.push({key:-3,color:je,label:"Fail low",compactLabel:"Fail lo",count:o.failLow,tooltip:`Fail low \xB7 ${o.failLow} dies`})}const S=tt?et.length:e.plotMode==="metadata"?Bt.length:Ct.length,L=(m.ownerDocument.defaultView??window).devicePixelRatio??1;if(O<=0||E<=0)return{hitTarget:{getDieAtPoint:()=>null},viewport:{originX:0,originY:0,ppm:1,snapDist:1},binLegendRows:[]};const $=j!=="default"?j:O<BIN_LEGEND_ADAPT_FLOATING?"floating":O<BIN_LEGEND_ADAPT_COMPACT?"compact":"default",re=$==="default"||$==="compact",fe=$==="left",Zt=$==="bottom",Pt=$==="top",Wt=$==="floating",Dt=$==="compact"||$==="floating"?BIN_LEGEND_W_COMPACT:BIN_LEGEND_W,he=Math.floor((E-2*h)/D),de=S>he?he-1:S,ge=ht&&Zt?de*D:0,jt=ht&&Pt?de*D:0,Qt=e.plotMode==="stackedBins"||e.plotMode==="stackedSoftBins",vt=Re(e.testDefs,e.activeTest),Ee=Qt?"Count":vt?.name??(e.activeTest!=null?`Test ${e.activeTest}`:void 0),we=Qt?void 0:vt?.unit,[Ot,It]=e.valueRange,{tickFmt:Ye}=Qe(It,Ee,we,St),Ft=e.allIntegerValues?o=>String(Math.round(o)):Ye;let te=0;if(kt){const o=m.getContext("2d");o.font=Z();const c=[Ft(Ot),Ft(It)];Ot<0&&c.push(Ft(-Math.max(Math.abs(Ot),Math.abs(It))));for(const s of c)te=Math.max(te,o.measureText(s).width)}const me=Math.max(no,Math.ceil(te)+Fe+Ut+oo-h),ee=kt?P+me+8:ht&&re?Dt:0,oe=ht&&fe?Dt:0,ne=Mt?32:0,Nt=Mt?36:0,He=ct!=null?Math.max(ee,ct):ee,Xe=O-2*h-He-Nt-oe,ue=E-2*h-ne-ge-jt;m.width=Math.round(O*L),m.height=Math.round(E*L);const t=m.getContext("2d");t.scale(L,L),t.fillStyle=pt,t.fillRect(0,0,O,E);const V=e.hoverPoints;if(!V.length)return{hitTarget:{getDieAtPoint:()=>null},viewport:{originX:0,originY:0,ppm:1,snapDist:1},binLegendRows:[]};const Et=e.rectangles[0],se=Et?Et.width/2:0,le=Et?Et.height/2:0,wt=e.dieBounds,pe=(wt?.minX??0)-se,$e=(wt?.maxX??0)+se,Ge=(wt?.minY??0)-le,xe=(wt?.maxY??0)+le,Me=$e-pe,ye=xe-Ge;let ot,nt,k;if(q)({originX:ot,originY:nt,ppm:k}=q);else{const c=e.notchDir,s=c&&c.y>.1?13:0,a=c&&c.y<-.1?13:0,r=c&&c.x<-.1?13:0,f=c&&c.x>.1?13:0,d=Xe-r-f,i=ue-Q-s-a;k=Math.min(d/Me,i/ye),ot=h+Nt+oe+r+(d-Me*k)/2-pe*k,nt=h+Q+jt+s+(i-ye*k)/2+xe*k}const Yt=q?.snapDist??Math.max(se,le,1)*1.5,Ht=ot+e.waferCenter.x*k,Ve=nt-e.waferCenter.y*k,ie=e.waferRadius*k,lt=o=>{const c=Math.abs(o-Ve);return c>=ie?0:Math.sqrt(ie*ie-c*c)},it=(o,c,s,a,r,f,d=so(),i=M.text)=>{if(!o)return;t.save(),t.fillStyle=i,t.font=d,t.textAlign=a,t.textBaseline=r;const p=a==="right"?c-f:f-c;let l=o;if(p>8&&t.measureText(l).width>p){for(;l.length>1&&t.measureText(l+"\u2026").width>p;)l=l.slice(0,-1);l=l+"\u2026"}p>8&&t.fillText(l,c,s),t.restore()};if(t.save(),t.setTransform(k*L,0,0,-k*L,ot*L,nt*L),e.rectangles.length>0){const o=new Map;for(const s of e.rectangles){const a=String(s.fill);let r=o.get(a);r||(r=[],o.set(a,r)),r.push(s)}for(const[s,a]of o){t.beginPath();for(const r of a)t.rect(r.x-r.width/2,r.y-r.height/2,r.width,r.height);t.fillStyle=s,t.fill()}t.strokeStyle="rgba(0,0,0,0.18)",t.lineWidth=.5/(k*L),t.beginPath();for(const s of e.rectangles)t.rect(s.x-s.width/2,s.y-s.height/2,s.width,s.height);if(t.stroke(),X){const s=e.rectangles.filter(a=>a.binFail);if(s.length>0){const a=new Map;for(const r of s){const f=De(String(r.fill));let d=a.get(f);d||(d=[],a.set(f,d)),d.push(r)}t.save(),t.lineCap="butt";for(const[r,f]of a){t.strokeStyle=r,t.globalAlpha=.55,t.lineWidth=Math.max(.6/(k*L),Math.min(f[0].width,f[0].height)*.1);for(const d of f){const i=d.width/2,p=d.height/2,l=Math.max(1.6/(k*L),Math.min(d.width,d.height)/3);t.save(),t.beginPath(),t.rect(d.x-i,d.y-p,d.width,d.height),t.clip(),t.beginPath();for(let y=-(i+p);y<=i+p;y+=l)t.moveTo(d.x-i+y,d.y-p),t.lineTo(d.x-i+y+d.height,d.y+p);t.stroke(),t.restore()}}t.restore()}}const c=e.rectangles.filter(s=>s.specMark);if(c.length>0){const s=new Map;for(const r of c){const f=De(String(r.fill)),d=`${r.specMark}|${f}`;let i=s.get(d);i||(i=[],s.set(d,i)),i.push(r)}const a=2.5/(k*L);t.lineJoin="round";for(const[r,f]of s){const[d,i]=r.split("|"),p=i==="#000000"?"#ffffff":"#000000",l=d==="failHigh"?1:-1,y=Math.min(f[0].width,f[0].height),w=Math.max(2.5/(k*L),y*.3);t.beginPath();for(const A of f)Ie(t,A.x,A.y,w,l);t.strokeStyle=p,t.lineWidth=a,t.stroke(),t.fillStyle=i,t.fill()}}}for(const o of e.overlays){t.beginPath();for(const c of o.points){for(let s=0;s<c.length;s++)s===0?t.moveTo(c[s].x,c[s].y):t.lineTo(c[s].x,c[s].y);o.closed&&t.closePath()}o.fill&&!o.fill.startsWith("rgba(0,0,0,0)")&&(t.fillStyle=o.fill,t.fill()),o.kind==="ring-boundary"||o.kind==="quadrant-boundary"||o.kind==="reticle"?(t.strokeStyle="rgba(0,0,0,0.45)",t.lineWidth=3/(k*L),t.stroke(),t.strokeStyle="rgba(255,255,255,0.8)",t.lineWidth=1/(k*L),t.stroke()):(t.strokeStyle=o.lineColor,t.lineWidth=o.lineWidth/(k*L),t.stroke())}if(t.restore(),e.notchDir){const o=ot+e.waferCenter.x*k,c=nt-e.waferCenter.y*k,s=e.notchDir.x,a=-e.notchDir.y,r=e.waferRadius*k,f=4,d=9,i=5,p=o+s*(r+f),l=c+a*(r+f),y=p+s*d,w=l+a*d,A={x:-a,y:s},W=h+Nt+oe,U=O-h-ee,K=h+Q+jt,_=E-h-ge-ne;t.save(),t.beginPath(),t.rect(W,K,U-W,_-K),t.clip(),t.beginPath(),t.moveTo(p,l),t.lineTo(y+A.x*i,w+A.y*i),t.lineTo(y-A.x*i,w-A.y*i),t.closePath(),t.fillStyle=M.text,t.fill(),t.restore()}t.save(),t.textAlign="center",t.textBaseline="middle";let be=0;if(e.rectangles.length>0){let o=1;for(const f of e.texts)f.role!=="indicator"&&f.text.length>o&&(o=f.text.length);const c=e.rectangles[0].width*k,s=e.rectangles[0].height*k,a=Math.min(c,s),r=c/o;be=Math.max(8,Math.min(64,Math.round(Math.min(a*.55,r*1.8))))}for(const o of e.texts){const c=ot+o.x*k,s=nt-o.y*k;t.font=`${o.role==="indicator"?o.fontSize:be}px system-ui, sans-serif`,t.fillStyle=o.color,t.fillText(o.text,c,s)}if(t.restore(),Mt&&co(t,O,E,ot,nt,k,h,ne,Nt,G,e.gridToScreen,e.rotation,M),kt){const o=Be(e.colorScheme),c=me,s=ue-Q,a=Math.round(s*.75),r=h+Q+Math.round((s-a)/2),f=O-h-P-c,d=Ot,i=It,p=i-d,l=e.plotMode==="stackedSoftBins"?st:yt,{primary:y,secondary:w}=rt?_e(e,St,l):{primary:"",secondary:""};if(rt){const n=Math.max(r-6,h+Q+11),b=Ht+Math.max(lt(n),lt(n-12))+8;it(y,O-h,n,"right","bottom",b)}let A=r+a+4;const W=n=>Ht+Math.max(lt(n),lt(n+12))+8,U=e.logScale&&d>0?Math.log10(d):0,K=e.logScale&&i>0?Math.log10(i)-U:1,_=n=>e.logScale?(1-(Math.log10(n)-U)/K)*a:(1-(n-d)/p)*a;for(let n=0;n<qt;n++){const b=1-n/(qt-1),u=r+n/qt*a,g=a/qt+1;t.fillStyle=o.forValue(b),t.fillRect(f,u,P,g)}t.strokeStyle="rgba(0,0,0,0.25)",t.lineWidth=.5,t.strokeRect(f,r,P,a),t.fillStyle=M.text,t.font=Z(),t.textAlign="left",t.strokeStyle="rgba(0,0,0,0.35)",t.lineWidth=.5;const C=Fe,Rt=36,v=14,I=e.logScale&&K>0?ro(d,i,a,Rt,v,U,K):e.allIntegerValues?(()=>{const n=Math.max(1,Jt(p*Rt/a)),b=[],u=Math.ceil(d/n)*n;for(let g=u;g<=i+n*1e-6;g+=n){const B=(1-(g-d)/p)*a;B>v&&B<a-v&&b.push(Math.round(g))}return b})():(()=>{const n=p>0?Jt(p*Rt/a):0,b=[];if(n>0){const u=Math.ceil(d/n)*n;for(let g=u;g<=i+n*1e-6;g+=n){const B=(1-(g-d)/p)*a;B>v&&B<a-v&&b.push(g)}}return b})(),R=vt,x=Qt,T=Ft;t.textBaseline="middle";for(const n of I){const b=r+_(n);t.beginPath(),t.moveTo(f+P,b),t.lineTo(f+P+C,b),t.stroke(),t.fillText(T(n),f+P+C+Ut,b)}if(t.beginPath(),t.moveTo(f+P,r),t.lineTo(f+P+C,r),t.stroke(),t.textBaseline="top",t.fillText(T(i),f+P+C+Ut,r),t.beginPath(),t.moveTo(f+P,r+a),t.lineTo(f+P+C,r+a),t.stroke(),t.textBaseline="bottom",t.fillText(T(d),f+P+C+Ut,r+a),!x&&R&&(R.limitLow!==void 0||R.limitHigh!==void 0)){const n=[],b=e.colorbarRangeMode==="spec";for(const[u,g]of[[R.limitLow,"LSL"],[R.limitHigh,"USL"]]){if(u===void 0||e.logScale&&u<=0)continue;const B=_(u);!b&&(B<-v||B>a+v)||n.push({value:u,label:g,py:Math.max(0,Math.min(a,B))})}if(n.length>0){t.save(),t.font=Z(),t.textAlign="right",t.textBaseline="middle";for(const{py:u,label:g}of n){const B=r+u;b||(t.strokeStyle="rgba(255,255,255,0.85)",t.lineWidth=3,t.beginPath(),t.moveTo(f,B),t.lineTo(f+P,B),t.stroke(),t.strokeStyle="rgba(0,0,0,0.7)",t.lineWidth=1,t.beginPath(),t.moveTo(f,B),t.lineTo(f+P,B),t.stroke()),t.fillStyle=M.text,t.fillText(g,f-3,B);const dt=g==="USL"?-1:1,gt=4,Tt=f-3-t.measureText(g).width-gt-4;t.beginPath(),Ie(t,Tt,B,gt,dt),t.strokeStyle="#fff",t.lineWidth=2,t.lineJoin="round",t.stroke(),t.fillStyle=M.text,t.fill()}t.restore()}}if(!x){const n=e.logScale?"log\u2081\u2080":e.logScaleRequested?"linear \u2014 log n/a":null;n&&(t.save(),t.fillStyle=M.text,t.font=lo(),t.textAlign="right",t.textBaseline="top",t.fillText(n,O-h,A),t.restore(),A+=15)}rt&&w&&it(w,O-h,A,"right","top",W(A),Kt(),M.text)}const ae=[];let J;if(ht){const o=Be(e.colorScheme),c=$==="compact",s=e.plotMode==="softBin"?st:yt,a=s?new Map(s.map(l=>[l.bin,l])):null,r=l=>{const y=a?.get(l);return(e.colorScheme==="custom"?y?.color:void 0)??o.forBin(l)},f=(l,y)=>{const w=t.measureText(l).width,A=c?0:t.measureText(String(y)).width,W=Y+mt+w+(A?zt:0);return{labelWidth:w,countWidth:A,totalWidth:W}},d=bt?.find(l=>l.key===e.activeMetadataKey),i=tt?et.map(l=>{const y=c?l.compactLabel:l.label;return{key:l.key,color:l.color,count:l.count,label:y,tooltipLabel:l.tooltip,...f(y,l.count)}}):e.plotMode==="metadata"?Bt.map(([l,y])=>{const A=d?.values?.find(K=>K.value===l)?.label??l,W=c?l:A,U=e.metadataColorMap?.get(l)??"#d6d9dd";return{key:l,color:U,count:y,label:W,tooltipLabel:`${A} \xB7 ${y} dies`,...f(W,y)}}):Ct.map(([l,y])=>{const w=a?.get(l),A=w?.name?`${l} \xB7 ${w.name}`:`Bin ${l}`,W=c?String(l):A;return{key:l,color:r(l),count:y,label:W,tooltipLabel:`${A} \xB7 ${y} dies`,...f(W,y)}}),p=O-h*2;if(i.length>0){const l=Math.max(1,Math.floor((E-2*h)/D));let y=i.length,w=1,A=y*D,W=[],U=0;const K=Zt||Pt;if(K){const I=Math.max(...i.map(x=>x.totalWidth)),R=Math.max(1,Math.min(i.length,Math.floor((p+8)/(I+8))));for(let x=R;x>=1;x--){const T=Math.ceil(i.length/x);if(T>l)continue;const n=new Array(x).fill(0);for(let u=0;u<i.length;u++){const g=u%x;n[g]=Math.max(n[g],i[u].totalWidth)}if(n.reduce((u,g)=>u+g,0)+(x-1)*8<=p||x===1){w=x,W=n,y=T,A=T*D;break}}}else y=Math.min(i.length,l),A=y*D,i.length>l&&(U=i.length-(l-1)),W=[Dt];let _,C;if(Wt){const I=Math.max(...i.map(g=>Y+mt+t.measureText(g.label).width+zt)),R=Math.ceil(I);W=[R];const x=rt?ao:0,T=R+ut*2,n=A+x+ut*2,b=xt?.x??0,u=xt?.y??0;_=Math.min(Math.max(O-h-T+b,h),O-h-T),C=Math.min(Math.max(E-h-n+u,h),E-h-n),J={x:_,y:C,w:T,h:n},t.save(),t.globalAlpha=.92,t.fillStyle=M.surface,t.fillRect(_,C,T,n),t.globalAlpha=1,t.strokeStyle=M.axisLine,t.lineWidth=1,t.strokeRect(_,C,T,n),t.restore(),_+=ut,C+=ut+x}else Zt?(_=h,C=E-h-A):Pt?(_=h,C=h+Q):fe?(_=h+4,C=h+Math.round((E-2*h-A)/2)):(_=O-h-Dt+4,C=h+Math.round((E-2*h-A)/2));if(rt){const I=e.plotMode==="softBin"?st:yt,{primary:R,secondary:x}=_e(e,St,I),T=6,n=g=>Ht-Math.max(lt(g),lt(g-12))-8,b=g=>re?O-h:n(g),u=Wt?J.y+J.h:C+A;if(Pt){const g=u+T;it(R,_,g,"left","top",Ht-Math.max(lt(g),lt(g+12))-8),x&&it(x,_,g+16,"left","top",n(g+16),Kt(),M.text)}else if(Wt)it(R,J.x+ut,J.y+ut,"left","top",J.x+J.w-ut),x&&it(x,J.x,u+T,"left","top",J.x+J.w,Kt(),M.text);else{const g=Math.max(C-T,h+Q+12);if(it(R,_,g,"left","bottom",b(g)),x){const B=u+T;it(x,_,B,"left","top",b(B),Kt(),M.text)}}}t.save(),t.font=Z();const Rt=K?8:0,v=(I,R)=>{if(t.measureText(I).width<=R)return I;let x=0,T=I.length;const n="\u2026",b=t.measureText(n).width;for(;x<T;){const u=x+T+1>>1;t.measureText(I.slice(0,u)).width+b<=R?x=u:T=u-1}return x===0?n:I.slice(0,x)+n};if(K){let I=0;for(let R=0;R<y;R++){let x=_;for(let T=0;T<w;T++){const n=i[I++];if(!n)break;const b=n.key===N,u=!b&&n.key===z,g=x,B=x+Y+mt,dt=C+R*D+D/2,gt=C+R*D+Math.round((D-Y)/2),Tt=W[T]-Y-mt-zt;u&&(t.fillStyle=M.hoverRow,t.fillRect(x,C+R*D,W[T],D)),t.fillStyle=n.color,t.fillRect(g,gt,Y,Y),t.strokeStyle=b?M.accent:"rgba(0,0,0,0.25)",t.lineWidth=b?2:.75,t.strokeRect(g,gt,Y,Y),t.fillStyle=b?M.accent:M.text,t.font=b?`bold ${Z()}`:Z(),t.textAlign="left",t.textBaseline="middle",t.fillText(v(n.label,Tt),B,dt),t.fillStyle=M.textMuted,t.font=Z(),t.textAlign="right",t.fillText(String(n.count),x+W[T]-2,dt),ae.push({bin:n.key,x,y:C+R*D,w:W[T],h:D,label:n.tooltipLabel}),x+=W[T]+Rt}}}else{const I=W[0],R=$!=="compact",x=I-Y-mt-(R?zt:0),T=_+I-2;let n=i;U>0&&(n=i.slice(0,l-1));let b=C;for(const u of n){const g=u.key===N,B=!g&&u.key===z,dt=_,gt=_+Y+mt,Tt=b+D/2,Ae=b+Math.round((D-Y)/2),ze=u.label;B&&(t.fillStyle=M.hoverRow,t.fillRect(_-3,b,I+3,D)),t.fillStyle=u.color,t.fillRect(dt,Ae,Y,Y),t.strokeStyle=g?M.accent:"rgba(0,0,0,0.25)",t.lineWidth=g?2:.75,t.strokeRect(dt,Ae,Y,Y),t.fillStyle=g?M.accent:M.text,t.font=g?`bold ${Z()}`:Z(),t.textAlign="left",t.textBaseline="middle",t.fillText(v(ze,x),gt,Tt),(R||Wt)&&(t.fillStyle=M.textMuted,t.font=Z(),t.textAlign="right",t.fillText(String(u.count),T,Tt)),ae.push({bin:u.key,x:_,y:b,w:I,h:D,label:u.tooltipLabel}),b+=D}U>0&&(t.fillStyle=M.textMuted,t.font=Z(),t.textAlign="left",t.textBaseline="middle",t.fillText(`+ ${U} more`,_+Y+mt,b+D/2))}}i.length>0&&t.restore()}const Ue={originX:ot,originY:nt,ppm:k,snapDist:Yt},Te=e.rectangles[0]?.width??1,Se=e.rectangles[0]?.height??1,Xt=Te*1.5,$t=Se*1.5,at=e.dieBounds,Gt=at?at.minX:V.length?Math.min(...V.map(o=>o.x)):0,Vt=at?at.minY:V.length?Math.min(...V.map(o=>o.y)):0,Ke=at?at.maxX:V.length?Math.max(...V.map(o=>o.x)):1,qe=at?at.maxY:V.length?Math.max(...V.map(o=>o.y)):1,At=Math.max(1,Math.ceil((Ke-Gt)/Xt)+1),ce=Math.max(1,Math.ceil((qe-Vt)/$t)+1),Le=Array.from({length:At*ce},()=>[]);for(let o=0;o<V.length;o++){const c=Math.floor((V[o].x-Gt)/Xt),s=Math.floor((V[o].y-Vt)/$t),a=Math.max(0,Math.min(At-1,c)),r=Math.max(0,Math.min(ce-1,s));Le[r*At+a].push(o)}function ke(o,c,s){const a=Math.max(0,Math.floor((o-s-Gt)/Xt)),r=Math.min(At-1,Math.floor((o+s-Gt)/Xt)),f=Math.max(0,Math.floor((c-s-Vt)/$t)),d=Math.min(ce-1,Math.floor((c+s-Vt)/$t)),i=[];for(let p=f;p<=d;p++)for(let l=a;l<=r;l++)for(const y of Le[p*At+l])i.push(y);return i}return{hitTarget:{getDieAtPoint(o,c){const s=(o-ot)/k,a=(nt-c)/k,r=Math.max(Te,Se);for(const i of ke(s,a,r)){const p=e.rectangles[i];if(p&&Math.abs(s-p.x)<=p.width/2&&Math.abs(a-p.y)<=p.height/2)return e.dies[i]??null}let f=null,d=Yt*Yt;for(const i of ke(s,a,Yt)){const p=V[i].x-s,l=V[i].y-a,y=p*p+l*l;y<d&&(d=y,f=e.dies[i]??null)}return f}},viewport:Ue,binLegendRows:ae,legendBox:J}}function co(m,e,F,M,h,H,X,P,pt,j,xt,Mt,G){m.save(),m.font=io(),m.fillStyle=G.text,m.strokeStyle=G.axisLine,m.lineWidth=.5;const q=F-P+4,N=X+pt-4,z=Je(xt),St=Ce(z,1,0),Q=Ce(z,0,1),ct=1e-9;function yt(S){return j?Math.abs(S.y)<ct&&Math.abs(S.x)>ct?{pitch:j.x,sign:Math.sign(S.x)}:Math.abs(S.x)<ct&&Math.abs(S.y)>ct?{pitch:j.y,sign:Math.sign(S.y)}:null:null}const st=yt(St),bt=yt(Q);function rt(S){return st?Math.round(S*st.sign/st.pitch):S}function O(S){return bt?Math.round(S*bt.sign/bt.pitch):S}function E(S){if(!S)return Jt(50/H);const L=50/(S*H);return Math.max(1,Math.round(L))*S}const Lt=st?.pitch,kt=bt?.pitch,ft=E(Lt),tt=E(kt);m.textAlign="center",m.textBaseline="top";const ht=Math.ceil((X-M)/H/ft)*ft,Ct=(e-X-M)/H;for(let S=ht;S<=Ct;S+=ft){const L=M+S*H;if(L<X||L>e-X)continue;m.beginPath(),m.moveTo(L,q-Ne),m.lineTo(L,q),m.stroke();const $=j?String(rt(S)):Oe(S);m.fillText($,L,q+2)}m.textAlign="right",m.textBaseline="middle";const Bt=Math.ceil((h-(F-X))/H/tt)*tt,et=(h-X)/H;for(let S=Bt;S<=et;S+=tt){const L=h-S*H;if(L<X||L>F-X)continue;m.beginPath(),m.moveTo(N,L),m.lineTo(N+Ne,L),m.stroke();const $=j?String(O(S)):Oe(S);m.fillText($,N-2,L)}m.restore()}function Jt(m){const e=Math.pow(10,Math.floor(Math.log10(m))),F=m/e;return(F<1.5?1:F<3.5?2:F<7.5?5:10)*e}function ro(m,e,F,M,h,H,X){const P=[],pt=X;if(pt<1){const G=Jt((e-m)*M/F);if(G>0){const q=Math.ceil(m/G)*G;for(let N=q;N<=e+G*1e-6;N+=G){if(N<=m||N>=e)continue;const z=(1-(Math.log10(N)-H)/X)*F;z>h&&z<F-h&&P.push(N)}}return P}const j=pt<3?[1,2,5]:[1],xt=Math.floor(Math.log10(m)),Mt=Math.ceil(Math.log10(e));for(let G=xt;G<=Mt;G++)for(const q of j){const N=q*Math.pow(10,G);if(N<=m||N>=e)continue;const z=(1-(Math.log10(N)-H)/X)*F;z>h&&z<F-h&&P.push(N)}return P}export{fmt,fmtColorbarAxis}from"../renderer/fmt.js";
|
|
@@ -3,8 +3,172 @@ import type { Die } from '../core/dies.js';
|
|
|
3
3
|
import type { TestDef, MetadataFieldDef } from '../renderer/buildWaferMap.js';
|
|
4
4
|
import type { StatsSeverity } from '../stats/types.js';
|
|
5
5
|
export declare function logWmapVersionOnce(): void;
|
|
6
|
-
export declare const WMAP_TOKEN_NAMES: readonly ["z", "icon", "icon-hover", "icon-active", "bg-hover", "bg-active", "separator", "surface", "border", "menu-hover", "menu-active", "panel-bg", "text-muted", "text", "text-strong", "warn-bg", "warn-border", "warn-text", "err-bg", "err-border", "err-text", "info-bg", "info-text", "selected", "finding-indicator", "canvas-bg", "bar-fill"
|
|
6
|
+
export declare const WMAP_TOKEN_NAMES: readonly ["z", "icon", "icon-hover", "icon-active", "bg-hover", "bg-active", "separator", "surface", "border", "control-border", "menu-hover", "menu-active", "font-family", "font-size", "density", "panel-bg", "text-muted", "text", "text-strong", "warn-bg", "warn-border", "warn-text", "err-bg", "err-border", "err-text", "info-bg", "info-text", "selected", "finding-indicator", "canvas-bg", "bar-fill"];
|
|
7
7
|
export type WmapTokenName = (typeof WMAP_TOKEN_NAMES)[number];
|
|
8
|
+
/**
|
|
9
|
+
* The shapes an interactive element comes in. Nineteen sites set
|
|
10
|
+
* `cursor: pointer` with their own padding, border, radius and hover handling;
|
|
11
|
+
* they were not nineteen different controls, but four shapes restated by hand.
|
|
12
|
+
* That restatement is the mechanism behind most of the drift this file's other
|
|
13
|
+
* scales exist to stop.
|
|
14
|
+
*
|
|
15
|
+
* These return STYLE, not elements — call sites keep their own markup and event
|
|
16
|
+
* wiring, and merely stop deciding what a button looks like. Spread first, then
|
|
17
|
+
* add layout:
|
|
18
|
+
*
|
|
19
|
+
* Object.assign(btn.style, { ...controlStyle('outlined'), marginLeft: 'auto' });
|
|
20
|
+
*/
|
|
21
|
+
export type ControlKind =
|
|
22
|
+
/** Bordered, transparent ground — the default button (Summary report, Export CSV). */
|
|
23
|
+
'outlined'
|
|
24
|
+
/** No border or ground — icon buttons and inline text actions. */
|
|
25
|
+
| 'bare'
|
|
26
|
+
/** Bordered, tinted when on — segmented options and filter toggles. */
|
|
27
|
+
| 'toggle';
|
|
28
|
+
/**
|
|
29
|
+
* Give a control the library's hover feedback. `controlStyle` returns static
|
|
30
|
+
* style and cannot attach listeners, so this is the second half of the pair —
|
|
31
|
+
* call it wherever you spread `controlStyle`.
|
|
32
|
+
*
|
|
33
|
+
* Without it a bordered control is indistinguishable from a bordered label: it
|
|
34
|
+
* has the shape of a button and does nothing when the pointer is over it.
|
|
35
|
+
* wmap's toolbar icon buttons wired this from the start; its outlined TEXT
|
|
36
|
+
* buttons (Summary report, View die list, the CSV exports, Back) wired nothing
|
|
37
|
+
* at all, so the two halves of the same library disagreed about whether a
|
|
38
|
+
* button reacts. tsmap's `.tb-btn` has always had it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function wireControlHover(el: HTMLElement, kind?: ControlKind): void;
|
|
41
|
+
export declare function controlStyle(kind: ControlKind, on?: boolean): Partial<CSSStyleDeclaration>;
|
|
42
|
+
export declare const SPACE: {
|
|
43
|
+
xxs: string;
|
|
44
|
+
xs: string;
|
|
45
|
+
sm: string;
|
|
46
|
+
md: string;
|
|
47
|
+
lg: string;
|
|
48
|
+
xl: string;
|
|
49
|
+
xxl: string;
|
|
50
|
+
xxxl: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Elevation. Fourteen distinct box-shadow literals existed across the two repos
|
|
54
|
+
* with no rule for which belonged where, so every new component invented one.
|
|
55
|
+
* Three tiers is what the values actually clustered into.
|
|
56
|
+
*/
|
|
57
|
+
export declare const SHADOW: {
|
|
58
|
+
/** Resting surface — a panel or card sitting on the page. */
|
|
59
|
+
panel: string;
|
|
60
|
+
/** Transient overlay — menu, dropdown, popover, tooltip. */
|
|
61
|
+
menu: string;
|
|
62
|
+
/** Modal — the only thing above everything else. */
|
|
63
|
+
modal: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Motion. One duration and one easing: a UI this dense has no reason to vary
|
|
67
|
+
* them, and ten different `transition` literals is not expressiveness, it is
|
|
68
|
+
* ten authors guessing. Respect `prefers-reduced-motion` at the call site.
|
|
69
|
+
*/
|
|
70
|
+
export declare const MOTION: {
|
|
71
|
+
/** State changes — hover, active, disclosure. */
|
|
72
|
+
fast: string;
|
|
73
|
+
/** Movement the eye should follow — a chevron rotating, a panel sliding. */
|
|
74
|
+
base: string;
|
|
75
|
+
};
|
|
76
|
+
/** Line height. Tight for single-line chrome, base for prose. */
|
|
77
|
+
export declare const LEADING: {
|
|
78
|
+
none: string;
|
|
79
|
+
tight: string;
|
|
80
|
+
base: string;
|
|
81
|
+
};
|
|
82
|
+
/** Opacity. `muted` de-emphasises; `disabled` is the one WCAG-relevant value. */
|
|
83
|
+
export declare const ALPHA: {
|
|
84
|
+
muted: string;
|
|
85
|
+
disabled: string;
|
|
86
|
+
};
|
|
87
|
+
/** Uppercase micro-label tracking — one value, not four. */
|
|
88
|
+
export declare const TRACKING = "0.05em";
|
|
89
|
+
/**
|
|
90
|
+
* Horizontal gutter between a scrolling surface's cards and the edge of the
|
|
91
|
+
* window (or host container) they scroll inside.
|
|
92
|
+
*
|
|
93
|
+
* A card is a bounded surface: flush to the viewport you see three borders and
|
|
94
|
+
* the screen edge standing in for the fourth, which reads as clipped rather
|
|
95
|
+
* than as a design choice. This is the gap that stops that.
|
|
96
|
+
*
|
|
97
|
+
* Why 12 (`SPACE.xl`): three different insets were in use when this was added —
|
|
98
|
+
* the Insights bands at 10, the gallery bar at 4, and tsmap's own `#toolbar` at
|
|
99
|
+
* 12 — so there was no incumbent value to inherit. 12 is on the `SPACE` scale,
|
|
100
|
+
* sits at the tight end of the 12-24px page-margin range the major design
|
|
101
|
+
* systems use (Material/HIG 16, Bootstrap 12), which is the right end for a
|
|
102
|
+
* dense analysis tool, and is the only candidate with an anchor outside this
|
|
103
|
+
* library: it matches the host toolbar directly above the map, so cards line up
|
|
104
|
+
* with the toolbar's own content rather than missing it by a couple of pixels.
|
|
105
|
+
*
|
|
106
|
+
* Deliberately NOT applied to the wafer map canvas, which stays full-bleed —
|
|
107
|
+
* map area is the priority and a margin would only shrink it. Same exception
|
|
108
|
+
* UI_STANDARDS.md already names for content that wants to fill its box.
|
|
109
|
+
*/
|
|
110
|
+
export declare const EDGE_GUTTER: string;
|
|
111
|
+
/**
|
|
112
|
+
* Inset for a wafer map's OWN chrome — its chrome row (identity + toolbar) and
|
|
113
|
+
* a Summary panel docked inside the map area — from the edge of that map area.
|
|
114
|
+
*
|
|
115
|
+
* Deliberately small, and deliberately NOT `EDGE_GUTTER`. That one is the gap
|
|
116
|
+
* between a bounded surface and the edge of the REGION it lives in. A map
|
|
117
|
+
* embedded in a gallery card is already inside such a region: the card supplies
|
|
118
|
+
* the outer inset, and a second 12px within it stacks two gutters — the thing
|
|
119
|
+
* UI_STANDARDS.md's gutter rule forbids. It reads exactly like what it is, a
|
|
120
|
+
* toolbar standing 12px off the side of a card.
|
|
121
|
+
*
|
|
122
|
+
* A STANDALONE map is the other case: there the map area IS the region, so its
|
|
123
|
+
* chrome takes `EDGE_GUTTER` like any other bounded surface against an edge.
|
|
124
|
+
* `RenderOptions.chromeInset` is how the caller says which it is —
|
|
125
|
+
* `renderWaferGallery` passes this value for its cards, everything else takes
|
|
126
|
+
* the `EDGE_GUTTER` default.
|
|
127
|
+
*
|
|
128
|
+
* This constant was deleted once, when the toolbar moved out of the map box and
|
|
129
|
+
* the Summary panel was briefly its only user. That was wrong: the chrome row
|
|
130
|
+
* inherited the same context problem the moment it started carrying the
|
|
131
|
+
* toolbar, and giving every map `EDGE_GUTTER` put the two-gutter bug straight
|
|
132
|
+
* back into gallery cards.
|
|
133
|
+
*/
|
|
134
|
+
export declare const MAP_CHROME_INSET: string;
|
|
135
|
+
export declare const RADIUS: {
|
|
136
|
+
/** Buttons, inputs, menu rows, segmented toggles, swatches. */
|
|
137
|
+
control: string;
|
|
138
|
+
/** Cards, panels, menus, modals — anything that contains other components. */
|
|
139
|
+
container: string;
|
|
140
|
+
/** Badges, count pills, status chips — fully rounded, size-independent. */
|
|
141
|
+
pill: string;
|
|
142
|
+
};
|
|
143
|
+
export declare const FONT: {
|
|
144
|
+
/**
|
|
145
|
+
* The typeface. wmap is embedded in a host and should read as part of it, so
|
|
146
|
+
* this INHERITS by default rather than imposing a stack — a host that sets
|
|
147
|
+
* nothing still gets the fallback below.
|
|
148
|
+
*
|
|
149
|
+
* There were three different hardcoded stacks across five sites
|
|
150
|
+
* (`system-ui, sans-serif` in the summary panel and toolbar,
|
|
151
|
+
* `system-ui, -apple-system, "Segoe UI", sans-serif` in the gallery), none
|
|
152
|
+
* matching tsmap's own `-apple-system`-first body stack. On Linux those
|
|
153
|
+
* resolve to different faces, so the summary panel's value table and the
|
|
154
|
+
* Insights value table — the same data, side by side — rendered in different
|
|
155
|
+
* typefaces.
|
|
156
|
+
*/
|
|
157
|
+
family: string;
|
|
158
|
+
meta: string;
|
|
159
|
+
body: string;
|
|
160
|
+
sub: string;
|
|
161
|
+
heading: string;
|
|
162
|
+
stat: string;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* The resolved numeric size for canvas `ctx.font`, in px. `delta` matches FONT's
|
|
166
|
+
* tiers for 0 and above (`body`, `sub`, `heading`, `stat`) and `-1` for `meta`.
|
|
167
|
+
* `-2` has no DOM tier by design: it is the map canvas's plot-coupled floor
|
|
168
|
+
* (colorbar tick labels, axis ticks — see toCanvas.ts), which annotates dense
|
|
169
|
+
* data rather than chrome and so sits below the 11px DOM minimum.
|
|
170
|
+
*/
|
|
171
|
+
export declare function fontPx(delta?: number, el?: Element | null): number;
|
|
8
172
|
export declare const CLR: {
|
|
9
173
|
icon: string;
|
|
10
174
|
iconHover: string;
|
|
@@ -14,8 +178,10 @@ export declare const CLR: {
|
|
|
14
178
|
separator: string;
|
|
15
179
|
menuBg: string;
|
|
16
180
|
menuBorder: string;
|
|
181
|
+
controlBorder: string;
|
|
17
182
|
menuHover: string;
|
|
18
183
|
menuActive: string;
|
|
184
|
+
canvasBg: string;
|
|
19
185
|
panelBg: string;
|
|
20
186
|
label: string;
|
|
21
187
|
value: string;
|
|
@@ -31,7 +197,6 @@ export declare const CLR: {
|
|
|
31
197
|
findingHighlight: string;
|
|
32
198
|
findingIndicator: string;
|
|
33
199
|
barFill: string;
|
|
34
|
-
barFillMuted: string;
|
|
35
200
|
};
|
|
36
201
|
/**
|
|
37
202
|
* Findings-severity → colour, shared by summaryPanel.ts (severity dots/borders
|
|
@@ -110,7 +275,29 @@ export declare function setDetachWindowOpener(opener: DetachWindowOpener | null)
|
|
|
110
275
|
* window is treated as titleless regardless of `document.title`). Navigating
|
|
111
276
|
* to a real URL would fix it but means either re-running the host page's own
|
|
112
277
|
* script inside the popup (real risk of double side effects) or requiring a
|
|
113
|
-
* dedicated blank same-origin asset — not worth it for a cosmetic issue.
|
|
278
|
+
* dedicated blank same-origin asset — not worth it for a cosmetic issue.
|
|
279
|
+
*
|
|
280
|
+
* Why this looks different from renderWaferMap's own "Expand" (a modal with
|
|
281
|
+
* wmap's own JS-drawn title bar, see `openOverlay` below): a real
|
|
282
|
+
* `window.open()` popup always carries genuine OS/browser chrome (title bar,
|
|
283
|
+
* and — deliberately, for anti-phishing reasons — an address-bar-ish
|
|
284
|
+
* element), and no window-features flag suppresses that in modern browsers;
|
|
285
|
+
* the feature string above (`width=560,height=600`) is sized-only for
|
|
286
|
+
* exactly this reason. This is a real inconsistency (raised 2026-08-29):
|
|
287
|
+
* `openWindowForCard`'s own fallback path (`window.open` returning null,
|
|
288
|
+
* e.g. Tauri's WebView) already routes through `openFloatingWindow` —
|
|
289
|
+
* wmap's own chrome, pixel-identical to the modal — so the mismatch is
|
|
290
|
+
* scoped to the real-popup case specifically.
|
|
291
|
+
*
|
|
292
|
+
* Possible future change: make `openWindowForCard` always take the
|
|
293
|
+
* `openFloatingWindow` branch (never call this function), for visual
|
|
294
|
+
* consistency with the standalone map's modal everywhere. Deliberately not
|
|
295
|
+
* done: the whole reason gallery cards detach into real windows rather than
|
|
296
|
+
* modals is so several can be open at once for side-by-side comparison,
|
|
297
|
+
* including dragged out to a second monitor — `openFloatingWindow`'s content
|
|
298
|
+
* is confined inside the host page/tab, so forcing it would trade away that
|
|
299
|
+
* capability for cosmetic consistency. Revisit only if that comparison
|
|
300
|
+
* workflow turns out not to matter in practice. */
|
|
114
301
|
export declare function openDetachWindow(label: string): Window | null;
|
|
115
302
|
export declare const MODE_LABELS: Record<PlotMode, string>;
|
|
116
303
|
export declare const BIN_LEGEND_MODES: Set<PlotMode>;
|
|
@@ -170,6 +357,29 @@ export declare function saveTextFile(text: string, filename: string, mimeType: s
|
|
|
170
357
|
* window." Passing the popup's own `window` fixes this.
|
|
171
358
|
*/
|
|
172
359
|
export declare function nextFrame(fn: () => void, ownerWindow?: Window): void;
|
|
360
|
+
/**
|
|
361
|
+
* Wire roving-focus keyboard navigation onto an already-roled menu:
|
|
362
|
+
* ArrowUp/Down move between items, Home/End jump to ends, Enter/Space activate
|
|
363
|
+
* the focused item, Escape (or Tab) closes the menu and returns focus to the
|
|
364
|
+
* trigger. `close` should remove the menu and clear any open-menu bookkeeping.
|
|
365
|
+
* Focus moves to the first item on open. Returns nothing; the listener lives on
|
|
366
|
+
* the menu element and dies with it.
|
|
367
|
+
*/
|
|
368
|
+
/**
|
|
369
|
+
* Roving keyboard focus over a live list of options — Arrow/Home/End to move,
|
|
370
|
+
* Enter/Space to activate, Escape/Tab to dismiss.
|
|
371
|
+
*
|
|
372
|
+
* Deliberately does NOT move focus itself: that is the one thing the two
|
|
373
|
+
* callers genuinely disagree about. A toolbar menu wants its first item focused
|
|
374
|
+
* on open; the searchable list in `chartShell.ts` must leave focus on its
|
|
375
|
+
* search box, and had its own copy of this whole switch for that single
|
|
376
|
+
* difference — thirty duplicated lines guarding one line of divergence.
|
|
377
|
+
*
|
|
378
|
+
* `items()` is called per keystroke, not captured: the searchable list filters
|
|
379
|
+
* its rows as you type, and a snapshot would navigate rows that are no longer
|
|
380
|
+
* shown.
|
|
381
|
+
*/
|
|
382
|
+
export declare function wireListNavigation(container: HTMLElement, items: () => HTMLElement[], dismiss: () => void): void;
|
|
173
383
|
export declare function overlayRootFor(anchor: Element): Element;
|
|
174
384
|
export declare function menuLayerFor(anchor: Element): HTMLElement;
|
|
175
385
|
/** The shared tooltip element for `doc` (default: the host page's own
|
|
@@ -202,7 +412,48 @@ export declare function reparentTooltip(parent?: HTMLElement): void;
|
|
|
202
412
|
* map), and the `parentElement !==` guard means the DOM is only actually
|
|
203
413
|
* touched when the root has changed, not on every mousemove.
|
|
204
414
|
*/
|
|
415
|
+
/**
|
|
416
|
+
* Place the tooltip near the cursor without covering the thing it describes.
|
|
417
|
+
*
|
|
418
|
+
* `anchor` used to be consulted only for `overlayRootFor` — placement was
|
|
419
|
+
* computed from the cursor point alone, and since `y` starts ABOVE the cursor
|
|
420
|
+
* (`clientY - 8`) the box then extended down over whatever was being pointed at.
|
|
421
|
+
* On the map canvas that is harmless and in fact necessary: the anchor is the
|
|
422
|
+
* whole canvas, so "don't overlap the anchor" would push the tooltip off the map
|
|
423
|
+
* entirely. On a small anchor — a bar, a badge, a row, an icon button — there IS
|
|
424
|
+
* somewhere else to go, and covering it while describing it is a real defect.
|
|
425
|
+
*
|
|
426
|
+
* So the anchor's box is avoided only when the anchor is small enough for that to
|
|
427
|
+
* be feasible: flip to just below it, else just above it. A large anchor keeps
|
|
428
|
+
* exactly the previous cursor-following behaviour.
|
|
429
|
+
*/
|
|
205
430
|
export declare function positionTooltip(tooltip: HTMLDivElement, anchor: Element, clientX: number, clientY: number): void;
|
|
431
|
+
/**
|
|
432
|
+
* Wire the shared themed tooltip onto a plain DOM element.
|
|
433
|
+
*
|
|
434
|
+
* Hoisted out of `maplessSummary.ts`, which had the only copy — and whose comment
|
|
435
|
+
* already recorded why this exists: a native `title` is a different, browser-native
|
|
436
|
+
* tooltip, slow to appear (OS hover delay) and in an unthemed system font/colour,
|
|
437
|
+
* while every hover surface built through `createToolbarHelpers` uses this instant
|
|
438
|
+
* dark one. `summaryPanel.ts` and the hand-built expand buttons in
|
|
439
|
+
* `renderWaferMap`/`renderWaferGallery` were still on `title`.
|
|
440
|
+
*
|
|
441
|
+
* `text` may be a string, a getter (for a hint that changes with the control's own
|
|
442
|
+
* state, e.g. the findings severity chips' show/hide), or omitted to live-read the
|
|
443
|
+
* element's `aria-label` at hover time — the same thing `makeBtn` does, so a
|
|
444
|
+
* control whose label changes with its state (expand ⇄ reattach) stays correct
|
|
445
|
+
* without re-wiring.
|
|
446
|
+
*
|
|
447
|
+
* `asDataPoint` is for an element that is NOT already a control: it makes the
|
|
448
|
+
* element a tab stop, announces it as a single datum (`role="img"`), and sets
|
|
449
|
+
* `aria-label`, so the tooltip's content reaches the keyboard and a screen reader
|
|
450
|
+
* (WCAG 1.4.13 — content shown on hover must be reachable and dismissable without
|
|
451
|
+
* a pointer). Never pass it for a `<button>`: that would add a nested focus stop
|
|
452
|
+
* and overwrite the control's own accessible name.
|
|
453
|
+
*/
|
|
454
|
+
export declare function wireTooltip(target: HTMLElement, text?: string | (() => string), opts?: {
|
|
455
|
+
asDataPoint?: boolean;
|
|
456
|
+
}): void;
|
|
206
457
|
export interface ToolbarHelpers {
|
|
207
458
|
makeBtn(iconKey: string, label: string, onClick: () => void): HTMLButtonElement;
|
|
208
459
|
setActive(btn: HTMLButtonElement, active: boolean): void;
|
|
@@ -302,8 +553,9 @@ export interface ExpandToggleHandle {
|
|
|
302
553
|
/**
|
|
303
554
|
* Shared "click/Enter/Space toggles an expand/collapse state, Escape or an
|
|
304
555
|
* outside click closes it" wiring — the small interaction pattern behind both
|
|
305
|
-
* the
|
|
306
|
-
* header's
|
|
556
|
+
* the shared identity header's metadata reveal (identityHeader.ts, used by
|
|
557
|
+
* renderWaferMap.ts) and the gallery card header's own reveal
|
|
558
|
+
* (renderWaferGallery.ts). `trigger` gets
|
|
307
559
|
* `role="button"`/`tabindex` and the event listeners; `onChange(open)` is
|
|
308
560
|
* called whenever the state actually changes (never redundantly) — the
|
|
309
561
|
* caller owns all the resulting DOM (aria-expanded text, panel visibility,
|
|
@@ -334,13 +586,27 @@ export type CheckMenuRow = {
|
|
|
334
586
|
* Each row with an `onClick` is a toggleable item; rows with `section` are headers.
|
|
335
587
|
* Stays open after each click so the user can toggle multiple items.
|
|
336
588
|
*/
|
|
337
|
-
export declare function buildCheckMenuEl(anchorRect: DOMRect, rows: CheckMenuRow[], helpers: Pick<ToolbarHelpers, '
|
|
589
|
+
export declare function buildCheckMenuEl(anchorRect: DOMRect, rows: CheckMenuRow[], helpers: Pick<ToolbarHelpers, 'makeMenuSection'>, ownerWindow?: Window): HTMLDivElement;
|
|
338
590
|
export declare function createToolbarHelpers(tooltip: HTMLDivElement): ToolbarHelpers;
|
|
339
591
|
export type OverlayMode = 'modal' | 'window';
|
|
340
592
|
export interface OverlayOptions {
|
|
341
593
|
/** Optional title shown in the header left. */
|
|
342
594
|
title?: string;
|
|
343
595
|
mode: OverlayMode;
|
|
596
|
+
/**
|
|
597
|
+
* Override the box's natural (non-maximized) size. Defaults to a 700px
|
|
598
|
+
* square, which suits a circular wafer map — the content this overlay was
|
|
599
|
+
* built for — but is actively wrong for wide content: the Insights suite
|
|
600
|
+
* lays out ~1330px wide in a normal page, so a 700px square modal makes
|
|
601
|
+
* "expand" produce something SMALLER than the view it expanded from.
|
|
602
|
+
*
|
|
603
|
+
* Also the restore target for maximize and minimize, so an overridden size
|
|
604
|
+
* survives both round trips.
|
|
605
|
+
*/
|
|
606
|
+
boxSize?: {
|
|
607
|
+
width: string;
|
|
608
|
+
height: string;
|
|
609
|
+
};
|
|
344
610
|
/**
|
|
345
611
|
* Called when the maximized state changes — use to reparent tooltips etc. Uses
|
|
346
612
|
* a CSS maximize (the box grows to fill its backdrop/the viewport), not the
|
|
@@ -412,6 +678,11 @@ export declare function openFloatingWindow(opts: Omit<OverlayOptions, 'mode'>):
|
|
|
412
678
|
export interface ReparentModalOptions {
|
|
413
679
|
ownerDocument?: Document;
|
|
414
680
|
title?: string;
|
|
681
|
+
/** Natural box size — see `OverlayOptions.boxSize`. */
|
|
682
|
+
boxSize?: {
|
|
683
|
+
width: string;
|
|
684
|
+
height: string;
|
|
685
|
+
};
|
|
415
686
|
/** Called after the modal has closed and every element has been restored to its original position (or appended back, if its original spot is no longer available — see the stale-reference note above). Runs once, even if nothing was actually moved (the re-entrancy guard tripped). */
|
|
416
687
|
onClosed?: () => void;
|
|
417
688
|
}
|
|
@@ -460,6 +731,39 @@ export declare function requestedPassFailDisplay(opts: {
|
|
|
460
731
|
* test returns no entries at all — its value mode IS test pass/fail, there is
|
|
461
732
|
* no alternative display to choose.
|
|
462
733
|
*/
|
|
734
|
+
/** The overlay flags an overlays menu offers, however the host stores them. */
|
|
735
|
+
export interface OverlayFlags {
|
|
736
|
+
showRingBoundaries?: boolean;
|
|
737
|
+
showQuadrantBoundaries?: boolean;
|
|
738
|
+
showDieLabels?: boolean;
|
|
739
|
+
showReticle?: boolean;
|
|
740
|
+
showXYIndicator?: boolean;
|
|
741
|
+
passFailDisplay?: 'off' | 'spec' | 'test';
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* The Overlays menu's rows — the five geometry toggles plus whatever
|
|
745
|
+
* `passFailMenuRows` offers for the active test.
|
|
746
|
+
*
|
|
747
|
+
* Built identically by `renderWaferMap` and `renderWaferGallery`, which is the
|
|
748
|
+
* problem it solves: the two lists were the same five labels in the same order
|
|
749
|
+
* with the same option keys, differing only in where the flags live
|
|
750
|
+
* (`viewOpts` vs `sharedOpts`) and how a change is applied (`applyOpts` vs
|
|
751
|
+
* `updateShared`). Adding a sixth overlay meant remembering both, and the map
|
|
752
|
+
* and the gallery quietly offering different menus is the failure that follows
|
|
753
|
+
* from forgetting.
|
|
754
|
+
*
|
|
755
|
+
* `reticleEnabled` stays a caller input: a single map knows whether ITS wafer
|
|
756
|
+
* has a reticle, and the gallery whether ANY item does — genuinely different
|
|
757
|
+
* questions with the same answer type.
|
|
758
|
+
*/
|
|
759
|
+
export declare function overlayMenuRows(flags: OverlayFlags, reticleEnabled: boolean, passFail: {
|
|
760
|
+
functionalActive: boolean;
|
|
761
|
+
hasLimits: boolean;
|
|
762
|
+
hasRecorded: boolean;
|
|
763
|
+
}, apply: (patch: OverlayFlags) => void): CheckMenuRow[];
|
|
764
|
+
/** Whether any overlay is on — the Overlays button's "active" state. Shared for
|
|
765
|
+
* the same reason as the rows: it enumerates the same five flags. */
|
|
766
|
+
export declare function anyOverlayActive(flags: OverlayFlags): boolean;
|
|
463
767
|
export declare function passFailMenuRows(state: {
|
|
464
768
|
functionalActive: boolean;
|
|
465
769
|
hasLimits: boolean;
|