@wafertools/wafermap 0.28.0 → 0.29.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.
@@ -128,6 +128,13 @@ export interface View {
128
128
  binColorScheme: string;
129
129
  /** Value gradient used for value and stacked modes, as registered with `registerValueColorScheme`. */
130
130
  valueColorScheme: string;
131
+ /**
132
+ * Whether that gradient was flipped (high value takes the gradient's low-end
133
+ * colour). Any surface colouring by value must pass this to
134
+ * `resolveValueColorFn` alongside {@link valueColorScheme}, or it will
135
+ * disagree with the die fills.
136
+ */
137
+ reverseValueScheme: boolean;
131
138
  /**
132
139
  * Every bin's resolved colour, both bin types — the single source for bin
133
140
  * colour. Die fills are built from it, and legends, panels and charts must
@@ -282,6 +289,14 @@ export interface ViewOptions {
282
289
  binColorScheme?: string;
283
290
  /** Value gradient for `value` and the stacked modes — any name registered via `registerValueColorScheme()`. Default `'default'`. */
284
291
  valueColorScheme?: string;
292
+ /**
293
+ * Flip the value gradient so the high end takes its low-end colour. Default
294
+ * false: every built-in reads low = dark, high = light. Set this for a
295
+ * parameter where low is the notable end, or for monochrome print output
296
+ * where more ink should mean more. Applies to the dies, the colorbar and the
297
+ * mapless summary together.
298
+ */
299
+ reverseValueScheme?: boolean;
285
300
  /** Honour `BinDef.color` where a bin definition supplies one. Default true. See `resolveBinColors`. */
286
301
  useDefinedBinColors?: boolean;
287
302
  /**
@@ -1 +1 @@
1
- import{hasPosition as ie}from"../core/dies.js";import{getReticleCell as se}from"../core/reticle.js";import{metadataCategoricalValue as oe,metadataDisplayValue as re}from"../core/metadata.js";import{affineRotation as mt,affineMirror as Lt,affineCompose as it,affinePoint as Y,affineVector as pt,affineSwapsAxes as le}from"../core/transforms.js";import{contrastTextColor as G,SPEC_PASS_FILL as yt,SPEC_FAIL_LOW as ce,SPEC_FAIL_HIGH as gt}from"./colorMap.js";import{getValueColorScheme as ue}from"./colorSchemes.js";import{resolveBinColors as fe,binColorsCover as de}from"./binColors.js";import{NO_DATA_FILL as j}from"./colorMap.js";import{diePassStatus as he}from"../core/dies.js";import{getDieTestValue as L,getTestPassStatus as Q,isParametricTest as bt}from"./buildWaferMap.js";import{fmt as xt,fmtColorbarAxis as Mt,fmtAggregationMethod as Pt}from"./fmt.js";import{metadataValueColor as me}from"./colorMap.js";import{compareNatural as pe,clamp01 as Rt}from"../core/utils.js";import{prettyKey as Nt}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const a=new Set;for(const n of t){if(n.partial||n.edgeExcluded)continue;const s=st(n,e);s!==void 0&&a.add(s)}return[...a].sort(pe)}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,a)=>e-a)}const ye="#d3d6db",Ot="#e8e9ea",ge="#aab0ba";function st(t,e){return e?oe(t.metadata?.[e]):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function be(t,e,a){const{x:n,y:s}=t.center,i=mt(t.orientation,n,s),o=it(Lt(e?.x??!1,e?.y??!1,n,s),mt(t.orientation,n,s)),l=it(Lt(a?.flipX??!1,a?.flipY??!1,n,s),mt(a?.rotation??0,n,s));return{bakedToScreen:l,physicalToScreen:it(l,i),gridToScreen:it(l,o)}}function z(t,e=!1){return{points:[t],closed:e}}function xe(t,e){const{center:a,radius:n,notch:s}=t;let i=a.x+n*Math.cos(e),o=a.y+n*Math.sin(e);if(!s)return{x:i,y:o};const l=Math.sqrt(n**2-(s.length/2)**2),u=s.length/2,g=i-a.x,p=o-a.y;return s.type==="bottom"&&p<-l?(o=a.y-l,i=a.x+Math.max(-u,Math.min(u,g))):s.type==="top"&&p>l?(o=a.y+l,i=a.x+Math.max(-u,Math.min(u,g))):s.type==="left"&&g<-l?(i=a.x-l,o=a.y+Math.max(-u,Math.min(u,p))):s.type==="right"&&g>l&&(i=a.x+l,o=a.y+Math.max(-u,Math.min(u,p))),{x:i,y:o}}function Ht(t,e){return t.map(e).join(" / ")}function Me(t,e=256){const a=Array.from({length:e},(n,s)=>t(s/(e-1)));return n=>a[Math.min(e-1,Math.max(0,Math.round(n*(e-1))))]}function Se(t,e){const a=Math.max(1,Math.min(t.width,t.height)),n=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,n*1.8))))}function ke(t,e,a){if(e?.length){const n=e.flatMap(s=>{const i=s.testNumber;if(!bt(s)){const u=Q(t,i,s);return u===void 0?[]:[{key:i,label:s.name,value:u?"Pass":"Fail"}]}const o=L(t,i);if(o===void 0)return[];const l=t.testPass?.[i]===!1?" <i>(recorded fail)</i>":"";return[{key:i,label:s.name,value:`${xt(o,s.unit,a)}${l}`}]});if(n.length)return n}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:xt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:n,hbinDefs:s,sbinDefs:i,fallbackFormat:o,aggrMethod:l,lotSize:u,activeTest:g,reticleConfig:p,metadataFields:B}=a,D=s?new Map(s.map(f=>[f.bin,f])):null,T=i?new Map(i.map(f=>[f.bin,f])):null,b=[`Die (${t.x}, ${t.y})`];if(p&&ie(t)){const f=se(t,p);b.push(`Reticle (${f.column}, ${f.row})`)}if(e==="stackedValues"){const f=L(t,0);if(f!==void 0){const d=n?.[0],h=d?.testNumber,k=d?.name??(h!=null?`Test ${h}`:"Value"),m=l?` (${l})`:"";b.push(`${k}${m}: ${xt(f,d?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=L(t,0),d=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:D,k=d!==void 0?h?.get(d)?.name?`${d} \xB7 ${h.get(d).name}`:`Bin ${d}`:"Bin";let m;if(l==="percent")m=`${f.toFixed(0)}%`;else{const C=u?` (${(f/u*100).toFixed(0)}% of stacked wafers)`:"";m=`${f}${C}`}const x=l?` [${Pt(l)}]`:"";b.push(`${k}: ${m}${x}`)}}else{const f=ke(t,n,o);if(e==="value"&&f.length){const{testNumber:d}=resolveTestNumber(g??0,n),h=findTestDef(n,d),k=L(t,d);let m=f.findIndex(y=>y.key===d);m<0&&(m=0);const x=f[m];let C=`<b>${x.label}: ${x.value}</b>`;if(x.key===d&&h){const y=classifySpec(k,h);(y==="failLow"||y==="failHigh")&&(C+=" <i>(out of spec)</i>")}b.push(C);const R=f.length-1;R>0&&b.push(`<i>+${R} more test${R===1?"":"s"}</i>`)}else if(f.length){const d=f.length;b.push(`<i>${d} test result${d===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const d=[];if(t.hbin!==void 0){const h=D?.get(t.hbin)?.name,k=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);d.push(`HBin: ${k}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,k=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);d.push(`SBin: ${k}`)}b.push(d.join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&b.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&b.push(`Site: ${t.siteNum}`),t.partId!==void 0&&b.push(`Part ID: ${t.partId}`),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(`Probe: #${t.probeIndex}`);const $={...t.metadata??{}};for(const[f,d]of Object.entries($)){const h=re(d);if(h===void 0)continue;const k=B?.find(m=>m.key===f)?.label??Nt(f);b.push(`${k}: ${h}`)}return b.join("<br>")}export function buildMapTitle(t,e="engineering",a){const n=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=i=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${i} Bin`:o.name?`${i} Bin ${o.bin} \xB7 ${o.name}`:`${i} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:n};case"stackedSoftBins":return{primary:s("Soft"),secondary:n};case"metadata":{const i=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Nt(t.activeMetadataKey):"Metadata";return{primary:i?.label??o,secondary:""}}case"stackedValues":{const i=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=Mt(o,i?.name,i?.unit,e),u=l||"Value",g=t.aggrMethod?` \xB7 ${Pt(t.aggrMethod)}`:"";return{primary:`${u}${g}`,secondary:n}}default:{const i=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=Mt(o,i?.name,i?.unit,e),u=l||i?.name;if(t.passFailDisplay!=="off"){const g=i?.testNumber??t.activeTest,p=u?`${u} \xB7 #${g}`:`Test ${g}`,B=t.passFailDisplay==="spec"?"Spec pass/fail":i!==void 0&&!bt(i)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B}}return{primary:u??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:n,colorFns:s,normalize:i,activeTest:o,valueRange:l,testDefs:u,fallbackFormat:g,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:D,allIntegerValues:T}=a,b=findTestDef(u,o),{tickFmt:$}=Mt(l[1],b?.name,b?.unit,g),f=T?h=>String(Math.round(h)):$,{testNumber:d}=resolveTestNumber(o,u);return t.flatMap((h,k)=>{let m="",x="#111111";if(n==="value"&&p==="test"){const y=Q(h,d,b);if(y===void 0)return[];m=y?"P":"F",x=G(y?yt:gt)}else if(n==="value"){const y=L(h,d);if(y===void 0)return[];m=Ht([y],f),x=G(s.forValue(i(y)))}else if(n==="hardBin"||n==="softBin"){const y=n==="softBin"?h.sbin:h.hbin;if(y===void 0)return[];m=String(y),x=G(s.forBin(y))}else if(n==="metadata"){const y=st(h,B);if(y===void 0)return[];m=y,x=G(D?.get(m)??j)}else{const y=L(h,0);if(y===void 0)return[];m=Ht([y],f),x=G(s.forValue(i(y)))}const C=e?e[k*2]:h.physX,R=e?e[k*2+1]:h.physY;return[{x:C,y:R,text:m,fontSize:Se(h,m),color:x,align:"center"}]})}function At(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function ve(t,e,a=720){const{center:n,radius:s,notch:i}=t,o=$=>Y(e,$.x,$.y);if(!(i!==void 0&&t.diameter>150)){const $=[];for(let f=0;f<=a;f++){const d=2*Math.PI*f/a;$.push(o(xe(t,d)))}return[{kind:"wafer-boundary",...z($,!0),lineColor:"#888888",lineWidth:1}]}const u=1.25,g=i.length,p=At(i.type),B=Math.atan2(g,s),D=s-u,T=[];let b=!1;for(let $=0;$<=a;$++){const f=2*Math.PI*$/a;let d=f-p;for(;d>Math.PI;)d-=2*Math.PI;for(;d<-Math.PI;)d+=2*Math.PI;if(Math.abs(d)<=B){if(!b){b=!0;const h={x:n.x+s*Math.cos(p-B),y:n.y+s*Math.sin(p-B)},k={x:n.x+D*Math.cos(p),y:n.y+D*Math.sin(p)},m={x:n.x+s*Math.cos(p+B),y:n.y+s*Math.sin(p+B)};T.push(o(h),o(k),o(m))}continue}T.push(o({x:n.x+s*Math.cos(f),y:n.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...z(T,!0),lineColor:"#888888",lineWidth:1}]}function $e(t,e,a,n=360){const s=[],i=Math.max(1,Math.floor(a));for(let o=1;o<i;o++){const l=t.radius*o/i,u=[];for(let g=0;g<=n;g++){const p=2*Math.PI*g/n,B={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};u.push(Y(e,B.x,B.y))}s.push({kind:"ring-boundary",...z(u,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Be(t,e,a,n){const{x:s,y:i}=t.center,o=t.radius,l=(f,d)=>Y(e,f,d),u=a-s,g=Math.sqrt(Math.max(0,o*o-u*u)),p=l(a,i-g),B=l(a,i+g),D=n-i,T=Math.sqrt(Math.max(0,o*o-D*D)),b=l(s-T,n),$=l(s+T,n);return[{kind:"quadrant-boundary",...z([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...z([b,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Te(t,e){return t.map(a=>{const n=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-n,a.y-s],[a.x+n,a.y-s],[a.x+n,a.y+s],[a.x-n,a.y+s]].map(([o,l])=>Y(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Ie(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,n)=>(a.probeIndex??0)-(n.probeIndex??0));return e.length?[{kind:"probe-path",...z(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function Ve(t,e,a,n,s,i,o,l,u,g,p,B,D,T,b="off",$,f,d){const h=e.width-o,k=e.height-o,m=i?k:h,x=i?h:k,C=S=>s==="softBin"?S.sbin:S.hbin,R=S=>st(S,$);if(e.partial){t.push({x:a,y:n,width:m,height:x,fill:ye,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:n,width:m,height:x,fill:ge,type:"stacked",metadata:e.metadata});return}if(u!==void 0&&(s==="hardBin"||s==="softBin")&&C(e)!==u){t.push({x:a,y:n,width:m,height:x,fill:Ot,type:"hardBin",metadata:e.metadata});return}if(d!==void 0&&s==="metadata"&&R(e)!==d){t.push({x:a,y:n,width:m,height:x,fill:Ot,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const w=Q(e,p,T),ot=w===void 0?j:w?yt:gt;t.push({x:a,y:n,width:m,height:x,fill:ot,type:"value",metadata:e.metadata});return}const S=L(e,p),N=classifySpec(S,T);let _,U;S===void 0?_=j:b==="spec"?_=N==="failLow"?ce:N==="failHigh"?gt:yt:(_=l.forValue(g(S)),(N==="failLow"||N==="failHigh")&&(U=N)),t.push({x:a,y:n,width:m,height:x,fill:_,type:"value",specMark:U,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const S=C(e),N=S!=null?l.forBin(S):j,_=S!=null&&he(e,D)===!1?!0:void 0;t.push({x:a,y:n,width:m,height:x,fill:N,type:s,binFail:_,metadata:e.metadata});return}if(s==="metadata"){const S=R(e),N=S!==void 0?f?.get(S)??j:j;t.push({x:a,y:n,width:m,height:x,fill:N,type:"metadata",metadata:e.metadata});return}const y=L(e,0),X=y!==void 0?l.forValue(g(y)):j;t.push({x:a,y:n,width:m,height:x,fill:X,type:"value",metadata:e.metadata})}function Ce(t,e,a){const n=t.radius*.15,s=pt(e,n,0),i=pt(e,0,n),o=s.x+i.x>=0?-1:1,l=s.y+i.y>=0?-1:1,u={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},g={x:u.x+s.x,y:u.y+s.y},p={x:u.x+i.x,y:u.y+i.y};return a.push({x:g.x+s.x*.35,y:g.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+i.x*.35,y:p.y+i.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...z([u,g]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...z([u,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},n){const s=a.reticles??[],{plotMode:i="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:u=!1,ringCount:g=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,dieGap:T=1,binColorScheme:b="default",valueColorScheme:$="default",useDefinedBinColors:f=!0,binColors:d,passBins:h=[1],highlightBin:k,highlightMetadataValue:m,interactiveTransform:x,valueRange:C,metadataValueOrder:R,testDefs:y,activeTest:X=0,activeMetadataKey:S,fallbackFormat:N="engineering",aggregationMethod:_,lotSize:U,dataAxisFlip:w,isLotStack:ot=!1,logScale:Et,colorbarRangeMode:_t="spec",passFailDisplay:wt,showPartialDies:Wt=!0}=a,St=wt??"off",rt=n?.hbinDefs,lt=n?.sbinDefs,kt=n?.metadataFields,jt=kt?.find(c=>c.key===S),zt={x:(w?.x??!1)!==(x?.flipX??!1),y:(w?.y??!1)!==(x?.flipY??!1)},Kt=rt?new Map(rt.map(c=>[c.bin,c])):null,qt=lt?new Map(lt.map(c=>[c.bin,c])):null,Xt=i==="softBin"?qt:Kt,Yt=new Set(h),ct=d&&de(d,e)?d:fe(e,{passBins:h,binColorScheme:b,hbinDefs:rt,sbinDefs:lt,useDefinedBinColors:f}),Gt=i==="softBin"?ct.soft:ct.hard,vt={forValue:Me(ue($).forValue),forBin:c=>Gt.get(c)??j};let{testNumber:H}=resolveTestNumber(X,y);if(!y?.length&&i==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((M,V)=>{if(!V.testValues)return M;const v=Object.keys(V.testValues).map(Number),P=v.length?Math.min(...v):void 0;return P!==void 0&&(M===void 0||P<M)?P:M},void 0);r!==void 0&&(H=r)}let J;if(Array.isArray(C))J=C;else if(C){const{testNumber:c}=resolveTestNumber(C.test,y);J=c===H?C.range:void 0}const I=findTestDef(y,H),$t=i==="value"&&I!==void 0&&!bt(I),Qt=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let A="off";i==="value"&&($t?A="test":St==="spec"&&Qt?A="spec":St==="test"&&e.some(c=>Q(c,H,I)!==void 0)&&(A="test"));const Bt=A==="spec"?"spec":_t,ut=i==="stackedValues"||i==="stackedBins"||i==="stackedSoftBins";let O,E,Z=!1;if(J){[O,E]=J;let c=!0,r=!1;for(const M of e){const V=ut?L(M,0):L(M,H);if(V!==void 0&&(r=!0,!Number.isInteger(V))){c=!1;break}}Z=r&&c}else{const c=!ut&&i==="value"&&Bt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let r=1/0,M=-1/0,V=!0;for(const v of e){const P=ut?L(v,0):L(v,H);P!==void 0&&(P<r&&(r=P),P>M&&(M=P),Number.isInteger(P)||(V=!1))}isFinite(r)||(V=!1),Z=V,c?(O=I.limitLow!==void 0?I.limitLow:isFinite(r)?r:0,E=I.limitHigh!==void 0?I.limitHigh:isFinite(M)?M:1):(O=isFinite(r)?r:0,E=isFinite(M)?M:1)}O===E&&(E=O+1);const Tt=$t?!1:Et??I?.logScale??!1,It=Tt&&O>0&&E>0,Ut=It;let tt;if(It){const c=Math.log10(O),r=Math.log10(E)-c;tt=M=>M<=0?0:Rt((Math.log10(M)-c)/r)}else tt=c=>Rt((c-O)/(E-O));const F=be(t,w,x);let Vt=null;if(t.notch){const c=At(t.notch.type),r=pt(F.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),M=Math.hypot(r.x,r.y);M>0&&(Vt={x:r.x/M,y:r.y/M})}let K=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<K&&(K=r.width),r.height<K&&(K=r.height)}const Jt=K!==1/0?Math.min(T,K*.12):T,Ct=[],ft=[],Zt=F.bakedToScreen.a===1&&F.bakedToScreen.b===0&&F.bakedToScreen.c===0&&F.bakedToScreen.d===1&&F.bakedToScreen.e===0&&F.bakedToScreen.f===0;let W=null;if(!Zt){W=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=Y(F.bakedToScreen,e[c].physX,e[c].physY);W[c*2]=r.x,W[c*2+1]=r.y}}const te=le(F.gridToScreen),ee=i==="hardBin"||i==="softBin"||i==="stackedBins"||i==="stackedSoftBins",ne=i==="softBin"||i==="stackedSoftBins";let et=ee?new Map:void 0;const dt=i==="value"&&A==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ht=i==="value"&&A==="test"?{pass:0,fail:0}:void 0,Ft=i==="metadata";let nt=Ft?new Map:void 0,at=null;if(Ft&&S){const c=collectMetadataValues(e,S),r=R?.key===S?R.values:void 0,M=r?[...r,...c.filter(V=>!r.includes(V))]:c;at=new Map(M.map((V,v)=>[V,jt?.values?.find(P=>P.value===V)?.color??me(v)]))}for(let c=0;c<e.length;c++){const r=e[c],M=W?W[c*2]:r.physX,V=W?W[c*2+1]:r.physY;if(ft.push({x:M,y:V}),et&&!r.partial&&!r.edgeExcluded){const v=ne?r.sbin:r.hbin;v!=null&&et.set(v,(et.get(v)??0)+1)}if(dt&&!r.partial&&!r.edgeExcluded){const v=classifySpec(L(r,H),I);v&&dt[v]++}if(ht&&!r.partial&&!r.edgeExcluded){const v=Q(r,H,I);v!==void 0&&ht[v?"pass":"fail"]++}if(nt&&!r.partial&&!r.edgeExcluded){const v=st(r,S);v!==void 0&&nt.set(v,(nt.get(v)??0)+1)}r.partial&&!Wt||Ve(Ct,r,M,V,i,te,Jt,vt,k,tt,H,Xt,Yt,I,A,S,at,m)}const Dt=o?generateTextOverlay(e,W,{plotMode:i,colorFns:vt,normalize:tt,activeTest:X,valueRange:[O,E],testDefs:y,fallbackFormat:N,passFailDisplay:A,activeMetadataKey:S,metadataColorMap:at,allIntegerValues:Z}):[],q=ve(t,F.physicalToScreen);p&&q.push(...$e(t,F.physicalToScreen,g)),B&&q.push(...Be(t,F.physicalToScreen,t.center.x,t.center.y)),l&&q.push(...Te(s,F.gridToScreen)),u&&q.push(...Ie(e)),D&&q.push(...Ce(t,F.gridToScreen,Dt));const ae=ft.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Ct,hoverPoints:ft,texts:Dt,overlays:q,plotMode:i,binColorScheme:b,valueColorScheme:$,binColors:ct,metadata:t.metadata??null,dies:e,valueRange:[O,E],allIntegerValues:Z,testDefs:y,activeTest:X,logScale:Ut,logScaleRequested:Tt,aggrMethod:_,lotSize:U,axisFlip:zt,dataAxisFlip:{x:w?.x??!1,y:w?.y??!1},gridToScreen:F.gridToScreen,rotation:((t.orientation+(x?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:ot,passFailDisplay:A,colorBySpec:A==="spec",colorbarRangeMode:Bt,waferCenter:t.center,waferRadius:t.radius,notchDir:Vt,binCounts:et,metadataCounts:nt,metadataColorMap:at,activeMetadataKey:S,metadataFields:kt,specCounts:dt,passFailCounts:ht,dieBounds:ae}}export{getDieKey}from"../core/dies.js";
1
+ import{hasPosition as se}from"../core/dies.js";import{getReticleCell as oe}from"../core/reticle.js";import{metadataCategoricalValue as re,metadataDisplayValue as le}from"../core/metadata.js";import{affineRotation as mt,affineMirror as Lt,affineCompose as st,affinePoint as Y,affineVector as pt,affineSwapsAxes as ce}from"../core/transforms.js";import{contrastTextColor as G,SPEC_PASS_FILL as yt,SPEC_FAIL_LOW as ue,SPEC_FAIL_HIGH as gt}from"./colorMap.js";import{resolveValueColorFn as fe}from"./colorSchemes.js";import{resolveBinColors as de,binColorsCover as he}from"./binColors.js";import{NO_DATA_FILL as j}from"./colorMap.js";import{diePassStatus as me}from"../core/dies.js";import{getDieTestValue as R,getTestPassStatus as Q,isParametricTest as bt}from"./buildWaferMap.js";import{fmt as xt,fmtColorbarAxis as Mt,fmtAggregationMethod as Pt}from"./fmt.js";import{metadataValueColor as pe}from"./colorMap.js";import{compareNatural as ye,clamp01 as Rt}from"../core/utils.js";import{prettyKey as Nt}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function collectMetadataValues(t,e){const a=new Set;for(const n of t){if(n.partial||n.edgeExcluded)continue;const s=ot(n,e);s!==void 0&&a.add(s)}return[...a].sort(ye)}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,a)=>e-a)}const ge="#d3d6db",Ot="#e8e9ea",be="#aab0ba";function ot(t,e){return e?re(t.metadata?.[e]):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function xe(t,e,a){const{x:n,y:s}=t.center,i=mt(t.orientation,n,s),o=st(Lt(e?.x??!1,e?.y??!1,n,s),mt(t.orientation,n,s)),l=st(Lt(a?.flipX??!1,a?.flipY??!1,n,s),mt(a?.rotation??0,n,s));return{bakedToScreen:l,physicalToScreen:st(l,i),gridToScreen:st(l,o)}}function z(t,e=!1){return{points:[t],closed:e}}function Me(t,e){const{center:a,radius:n,notch:s}=t;let i=a.x+n*Math.cos(e),o=a.y+n*Math.sin(e);if(!s)return{x:i,y:o};const l=Math.sqrt(n**2-(s.length/2)**2),f=s.length/2,y=i-a.x,p=o-a.y;return s.type==="bottom"&&p<-l?(o=a.y-l,i=a.x+Math.max(-f,Math.min(f,y))):s.type==="top"&&p>l?(o=a.y+l,i=a.x+Math.max(-f,Math.min(f,y))):s.type==="left"&&y<-l?(i=a.x-l,o=a.y+Math.max(-f,Math.min(f,p))):s.type==="right"&&y>l&&(i=a.x+l,o=a.y+Math.max(-f,Math.min(f,p))),{x:i,y:o}}function Ht(t,e){return t.map(e).join(" / ")}function Se(t,e=256){const a=Array.from({length:e},(n,s)=>t(s/(e-1)));return n=>a[Math.min(e-1,Math.max(0,Math.round(n*(e-1))))]}function ke(t,e){const a=Math.max(1,Math.min(t.width,t.height)),n=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,n*1.8))))}function ve(t,e,a){if(e?.length){const n=e.flatMap(s=>{const i=s.testNumber;if(!bt(s)){const f=Q(t,i,s);return f===void 0?[]:[{key:i,label:s.name,value:f?"Pass":"Fail"}]}const o=R(t,i);if(o===void 0)return[];const l=t.testPass?.[i]===!1?" <i>(recorded fail)</i>":"";return[{key:i,label:s.name,value:`${xt(o,s.unit,a)}${l}`}]});if(n.length)return n}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:xt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([n,s])=>({key:Number(n),label:`Test ${n}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:n,hbinDefs:s,sbinDefs:i,fallbackFormat:o,aggrMethod:l,lotSize:f,activeTest:y,reticleConfig:p,metadataFields:$}=a,D=s?new Map(s.map(u=>[u.bin,u])):null,I=i?new Map(i.map(u=>[u.bin,u])):null,g=[`Die (${t.x}, ${t.y})`];if(p&&se(t)){const u=oe(t,p);g.push(`Reticle (${u.column}, ${u.row})`)}if(e==="stackedValues"){const u=R(t,0);if(u!==void 0){const d=n?.[0],h=d?.testNumber,S=d?.name??(h!=null?`Test ${h}`:"Value"),m=l?` (${l})`:"";g.push(`${S}${m}: ${xt(u,d?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const u=R(t,0),d=e==="stackedSoftBins"?t.sbin:t.hbin;if(u!==void 0){const h=e==="stackedSoftBins"?I:D,S=d!==void 0?h?.get(d)?.name?`${d} \xB7 ${h.get(d).name}`:`Bin ${d}`:"Bin";let m;if(l==="percent")m=`${u.toFixed(0)}%`;else{const L=f?` (${(u/f*100).toFixed(0)}% of stacked wafers)`:"";m=`${u}${L}`}const M=l?` [${Pt(l)}]`:"";g.push(`${S}: ${m}${M}`)}}else{const u=ve(t,n,o);if(e==="value"&&u.length){const{testNumber:d}=resolveTestNumber(y??0,n),h=findTestDef(n,d),S=R(t,d);let m=u.findIndex(b=>b.key===d);m<0&&(m=0);const M=u[m];let L=`<b>${M.label}: ${M.value}</b>`;if(M.key===d&&h){const b=classifySpec(S,h);(b==="failLow"||b==="failHigh")&&(L+=" <i>(out of spec)</i>")}g.push(L);const P=u.length-1;P>0&&g.push(`<i>+${P} more test${P===1?"":"s"}</i>`)}else if(u.length){const d=u.length;g.push(`<i>${d} test result${d===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const d=[];if(t.hbin!==void 0){const h=D?.get(t.hbin)?.name,S=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);d.push(`HBin: ${S}`)}if(t.sbin!==void 0){const h=I?.get(t.sbin)?.name,S=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);d.push(`SBin: ${S}`)}g.push(d.join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&g.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&g.push(`Site: ${t.siteNum}`),t.partId!==void 0&&g.push(`Part ID: ${t.partId}`),t.partial&&g.push("<i>partial die</i>"),t.probeIndex!==void 0&&g.push(`Probe: #${t.probeIndex}`);const v={...t.metadata??{}};for(const[u,d]of Object.entries(v)){const h=le(d);if(h===void 0)continue;const S=$?.find(m=>m.key===u)?.label??Nt(u);g.push(`${S}: ${h}`)}return g.join("<br>")}export function buildMapTitle(t,e="engineering",a){const n=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=i=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${i} Bin`:o.name?`${i} Bin ${o.bin} \xB7 ${o.name}`:`${i} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:n};case"stackedSoftBins":return{primary:s("Soft"),secondary:n};case"metadata":{const i=t.metadataFields?.find(l=>l.key===t.activeMetadataKey),o=t.activeMetadataKey?Nt(t.activeMetadataKey):"Metadata";return{primary:i?.label??o,secondary:""}}case"stackedValues":{const i=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=Mt(o,i?.name,i?.unit,e),f=l||"Value",y=t.aggrMethod?` \xB7 ${Pt(t.aggrMethod)}`:"";return{primary:`${f}${y}`,secondary:n}}default:{const i=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:l}=Mt(o,i?.name,i?.unit,e),f=l||i?.name;if(t.passFailDisplay!=="off"){const y=i?.testNumber??t.activeTest,p=f?`${f} \xB7 #${y}`:`Test ${y}`,$=t.passFailDisplay==="spec"?"Spec pass/fail":i!==void 0&&!bt(i)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:$}}return{primary:f??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:n,colorFns:s,normalize:i,activeTest:o,valueRange:l,testDefs:f,fallbackFormat:y,passFailDisplay:p="off",activeMetadataKey:$,metadataColorMap:D,allIntegerValues:I}=a,g=findTestDef(f,o),{tickFmt:v}=Mt(l[1],g?.name,g?.unit,y),u=I?h=>String(Math.round(h)):v,{testNumber:d}=resolveTestNumber(o,f);return t.flatMap((h,S)=>{let m="",M="#111111";if(n==="value"&&p==="test"){const b=Q(h,d,g);if(b===void 0)return[];m=b?"P":"F",M=G(b?yt:gt)}else if(n==="value"){const b=R(h,d);if(b===void 0)return[];m=Ht([b],u),M=G(s.forValue(i(b)))}else if(n==="hardBin"||n==="softBin"){const b=n==="softBin"?h.sbin:h.hbin;if(b===void 0)return[];m=String(b),M=G(s.forBin(b))}else if(n==="metadata"){const b=ot(h,$);if(b===void 0)return[];m=b,M=G(D?.get(m)??j)}else{const b=R(h,0);if(b===void 0)return[];m=Ht([b],u),M=G(s.forValue(i(b)))}const L=e?e[S*2]:h.physX,P=e?e[S*2+1]:h.physY;return[{x:L,y:P,text:m,fontSize:ke(h,m),color:M,align:"center"}]})}function At(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function $e(t,e,a=720){const{center:n,radius:s,notch:i}=t,o=v=>Y(e,v.x,v.y);if(!(i!==void 0&&t.diameter>150)){const v=[];for(let u=0;u<=a;u++){const d=2*Math.PI*u/a;v.push(o(Me(t,d)))}return[{kind:"wafer-boundary",...z(v,!0),lineColor:"#888888",lineWidth:1}]}const f=1.25,y=i.length,p=At(i.type),$=Math.atan2(y,s),D=s-f,I=[];let g=!1;for(let v=0;v<=a;v++){const u=2*Math.PI*v/a;let d=u-p;for(;d>Math.PI;)d-=2*Math.PI;for(;d<-Math.PI;)d+=2*Math.PI;if(Math.abs(d)<=$){if(!g){g=!0;const h={x:n.x+s*Math.cos(p-$),y:n.y+s*Math.sin(p-$)},S={x:n.x+D*Math.cos(p),y:n.y+D*Math.sin(p)},m={x:n.x+s*Math.cos(p+$),y:n.y+s*Math.sin(p+$)};I.push(o(h),o(S),o(m))}continue}I.push(o({x:n.x+s*Math.cos(u),y:n.y+s*Math.sin(u)}))}return[{kind:"wafer-boundary",...z(I,!0),lineColor:"#888888",lineWidth:1}]}function Be(t,e,a,n=360){const s=[],i=Math.max(1,Math.floor(a));for(let o=1;o<i;o++){const l=t.radius*o/i,f=[];for(let y=0;y<=n;y++){const p=2*Math.PI*y/n,$={x:t.center.x+l*Math.cos(p),y:t.center.y+l*Math.sin(p)};f.push(Y(e,$.x,$.y))}s.push({kind:"ring-boundary",...z(f,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function Te(t,e,a,n){const{x:s,y:i}=t.center,o=t.radius,l=(u,d)=>Y(e,u,d),f=a-s,y=Math.sqrt(Math.max(0,o*o-f*f)),p=l(a,i-y),$=l(a,i+y),D=n-i,I=Math.sqrt(Math.max(0,o*o-D*D)),g=l(s-I,n),v=l(s+I,n);return[{kind:"quadrant-boundary",...z([p,$]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...z([g,v]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function Ie(t,e){return t.map(a=>{const n=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-n,a.y-s],[a.x+n,a.y-s],[a.x+n,a.y+s],[a.x-n,a.y+s]].map(([o,l])=>Y(e,o,l))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function Ve(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,n)=>(a.probeIndex??0)-(n.probeIndex??0));return e.length?[{kind:"probe-path",...z(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function Ce(t,e,a,n,s,i,o,l,f,y,p,$,D,I,g="off",v,u,d){const h=e.width-o,S=e.height-o,m=i?S:h,M=i?h:S,L=B=>s==="softBin"?B.sbin:B.hbin,P=B=>ot(B,v);if(e.partial){t.push({x:a,y:n,width:m,height:M,fill:ge,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:n,width:m,height:M,fill:be,type:"stacked",metadata:e.metadata});return}if(f!==void 0&&(s==="hardBin"||s==="softBin")&&L(e)!==f){t.push({x:a,y:n,width:m,height:M,fill:Ot,type:"hardBin",metadata:e.metadata});return}if(d!==void 0&&s==="metadata"&&P(e)!==d){t.push({x:a,y:n,width:m,height:M,fill:Ot,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(g==="test"){const J=Q(e,p,I),K=J===void 0?j:J?yt:gt;t.push({x:a,y:n,width:m,height:M,fill:K,type:"value",metadata:e.metadata});return}const B=R(e,p),T=classifySpec(B,I);let w,U;B===void 0?w=j:g==="spec"?w=T==="failLow"?ue:T==="failHigh"?gt:yt:(w=l.forValue(y(B)),(T==="failLow"||T==="failHigh")&&(U=T)),t.push({x:a,y:n,width:m,height:M,fill:w,type:"value",specMark:U,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const B=L(e),T=B!=null?l.forBin(B):j,w=B!=null&&me(e,D)===!1?!0:void 0;t.push({x:a,y:n,width:m,height:M,fill:T,type:s,binFail:w,metadata:e.metadata});return}if(s==="metadata"){const B=P(e),T=B!==void 0?u?.get(B)??j:j;t.push({x:a,y:n,width:m,height:M,fill:T,type:"metadata",metadata:e.metadata});return}const b=R(e,0),_=b!==void 0?l.forValue(y(b)):j;t.push({x:a,y:n,width:m,height:M,fill:_,type:"value",metadata:e.metadata})}function Fe(t,e,a){const n=t.radius*.15,s=pt(e,n,0),i=pt(e,0,n),o=s.x+i.x>=0?-1:1,l=s.y+i.y>=0?-1:1,f={x:t.center.x+o*t.radius*.9,y:t.center.y+l*t.radius*.9},y={x:f.x+s.x,y:f.y+s.y},p={x:f.x+i.x,y:f.y+i.y};return a.push({x:y.x+s.x*.35,y:y.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+i.x*.35,y:p.y+i.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...z([f,y]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...z([f,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},n){const s=a.reticles??[],{plotMode:i="value",showDieLabels:o=!1,showReticle:l=!1,showProbePath:f=!1,ringCount:y=4,showRingBoundaries:p=!1,showQuadrantBoundaries:$=!1,showXYIndicator:D=!1,dieGap:I=1,binColorScheme:g="default",valueColorScheme:v="default",reverseValueScheme:u=!1,useDefinedBinColors:d=!0,binColors:h,passBins:S=[1],highlightBin:m,highlightMetadataValue:M,interactiveTransform:L,valueRange:P,metadataValueOrder:b,testDefs:_,activeTest:B=0,activeMetadataKey:T,fallbackFormat:w="engineering",aggregationMethod:U,lotSize:J,dataAxisFlip:K,isLotStack:Et=!1,logScale:_t,colorbarRangeMode:wt="spec",passFailDisplay:Wt,showPartialDies:jt=!0}=a,St=Wt??"off",rt=n?.hbinDefs,lt=n?.sbinDefs,kt=n?.metadataFields,zt=kt?.find(c=>c.key===T),Kt={x:(K?.x??!1)!==(L?.flipX??!1),y:(K?.y??!1)!==(L?.flipY??!1)},qt=rt?new Map(rt.map(c=>[c.bin,c])):null,Xt=lt?new Map(lt.map(c=>[c.bin,c])):null,Yt=i==="softBin"?Xt:qt,Gt=new Set(S),ct=h&&he(h,e)?h:de(e,{passBins:S,binColorScheme:g,hbinDefs:rt,sbinDefs:lt,useDefinedBinColors:d}),Qt=i==="softBin"?ct.soft:ct.hard,vt={forValue:Se(fe(v,u)),forBin:c=>Qt.get(c)??j};let{testNumber:H}=resolveTestNumber(B,_);if(!_?.length&&i==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((x,C)=>{if(!C.testValues)return x;const k=Object.keys(C.testValues).map(Number),N=k.length?Math.min(...k):void 0;return N!==void 0&&(x===void 0||N<x)?N:x},void 0);r!==void 0&&(H=r)}let Z;if(Array.isArray(P))Z=P;else if(P){const{testNumber:c}=resolveTestNumber(P.test,_);Z=c===H?P.range:void 0}const V=findTestDef(_,H),$t=i==="value"&&V!==void 0&&!bt(V),Ut=V!==void 0&&(V.limitLow!==void 0||V.limitHigh!==void 0);let A="off";i==="value"&&($t?A="test":St==="spec"&&Ut?A="spec":St==="test"&&e.some(c=>Q(c,H,V)!==void 0)&&(A="test"));const Bt=A==="spec"?"spec":wt,ut=i==="stackedValues"||i==="stackedBins"||i==="stackedSoftBins";let O,E,tt=!1;if(Z){[O,E]=Z;let c=!0,r=!1;for(const x of e){const C=ut?R(x,0):R(x,H);if(C!==void 0&&(r=!0,!Number.isInteger(C))){c=!1;break}}tt=r&&c}else{const c=!ut&&i==="value"&&Bt==="spec"&&V&&(V.limitLow!==void 0||V.limitHigh!==void 0);let r=1/0,x=-1/0,C=!0;for(const k of e){const N=ut?R(k,0):R(k,H);N!==void 0&&(N<r&&(r=N),N>x&&(x=N),Number.isInteger(N)||(C=!1))}isFinite(r)||(C=!1),tt=C,c?(O=V.limitLow!==void 0?V.limitLow:isFinite(r)?r:0,E=V.limitHigh!==void 0?V.limitHigh:isFinite(x)?x:1):(O=isFinite(r)?r:0,E=isFinite(x)?x:1)}O===E&&(E=O+1);const Tt=$t?!1:_t??V?.logScale??!1,It=Tt&&O>0&&E>0,Jt=It;let et;if(It){const c=Math.log10(O),r=Math.log10(E)-c;et=x=>x<=0?0:Rt((Math.log10(x)-c)/r)}else et=c=>Rt((c-O)/(E-O));const F=xe(t,K,L);let Vt=null;if(t.notch){const c=At(t.notch.type),r=pt(F.physicalToScreen,Math.cos(c)*t.radius,Math.sin(c)*t.radius),x=Math.hypot(r.x,r.y);x>0&&(Vt={x:r.x/x,y:r.y/x})}let q=1/0;for(let c=0;c<e.length;c++){const r=e[c];r.width<q&&(q=r.width),r.height<q&&(q=r.height)}const Zt=q!==1/0?Math.min(I,q*.12):I,Ct=[],ft=[],te=F.bakedToScreen.a===1&&F.bakedToScreen.b===0&&F.bakedToScreen.c===0&&F.bakedToScreen.d===1&&F.bakedToScreen.e===0&&F.bakedToScreen.f===0;let W=null;if(!te){W=new Float64Array(e.length*2);for(let c=0;c<e.length;c++){const r=Y(F.bakedToScreen,e[c].physX,e[c].physY);W[c*2]=r.x,W[c*2+1]=r.y}}const ee=ce(F.gridToScreen),ne=i==="hardBin"||i==="softBin"||i==="stackedBins"||i==="stackedSoftBins",ae=i==="softBin"||i==="stackedSoftBins";let nt=ne?new Map:void 0;const dt=i==="value"&&A==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ht=i==="value"&&A==="test"?{pass:0,fail:0}:void 0,Ft=i==="metadata";let at=Ft?new Map:void 0,it=null;if(Ft&&T){const c=collectMetadataValues(e,T),r=b?.key===T?b.values:void 0,x=r?[...r,...c.filter(C=>!r.includes(C))]:c;it=new Map(x.map((C,k)=>[C,zt?.values?.find(N=>N.value===C)?.color??pe(k)]))}for(let c=0;c<e.length;c++){const r=e[c],x=W?W[c*2]:r.physX,C=W?W[c*2+1]:r.physY;if(ft.push({x,y:C}),nt&&!r.partial&&!r.edgeExcluded){const k=ae?r.sbin:r.hbin;k!=null&&nt.set(k,(nt.get(k)??0)+1)}if(dt&&!r.partial&&!r.edgeExcluded){const k=classifySpec(R(r,H),V);k&&dt[k]++}if(ht&&!r.partial&&!r.edgeExcluded){const k=Q(r,H,V);k!==void 0&&ht[k?"pass":"fail"]++}if(at&&!r.partial&&!r.edgeExcluded){const k=ot(r,T);k!==void 0&&at.set(k,(at.get(k)??0)+1)}r.partial&&!jt||Ce(Ct,r,x,C,i,ee,Zt,vt,m,et,H,Yt,Gt,V,A,T,it,M)}const Dt=o?generateTextOverlay(e,W,{plotMode:i,colorFns:vt,normalize:et,activeTest:B,valueRange:[O,E],testDefs:_,fallbackFormat:w,passFailDisplay:A,activeMetadataKey:T,metadataColorMap:it,allIntegerValues:tt}):[],X=$e(t,F.physicalToScreen);p&&X.push(...Be(t,F.physicalToScreen,y)),$&&X.push(...Te(t,F.physicalToScreen,t.center.x,t.center.y)),l&&X.push(...Ie(s,F.gridToScreen)),f&&X.push(...Ve(e)),D&&X.push(...Fe(t,F.gridToScreen,Dt));const ie=ft.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Ct,hoverPoints:ft,texts:Dt,overlays:X,plotMode:i,binColorScheme:g,valueColorScheme:v,reverseValueScheme:u,binColors:ct,metadata:t.metadata??null,dies:e,valueRange:[O,E],allIntegerValues:tt,testDefs:_,activeTest:B,logScale:Jt,logScaleRequested:Tt,aggrMethod:U,lotSize:J,axisFlip:Kt,dataAxisFlip:{x:K?.x??!1,y:K?.y??!1},gridToScreen:F.gridToScreen,rotation:((t.orientation+(L?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:Et,passFailDisplay:A,colorBySpec:A==="spec",colorbarRangeMode:Bt,waferCenter:t.center,waferRadius:t.radius,notchDir:Vt,binCounts:nt,metadataCounts:at,metadataColorMap:it,activeMetadataKey:T,metadataFields:kt,specCounts:dt,passFailCounts:ht,dieBounds:ie}}export{getDieKey}from"../core/dies.js";
@@ -23,6 +23,19 @@ export declare function listValueColorSchemes(): Array<{
23
23
  name: string;
24
24
  label: string;
25
25
  }>;
26
+ /**
27
+ * The colour function a value surface must actually use: the named gradient,
28
+ * flipped when `reversed`.
29
+ *
30
+ * **Every surface that colours by value resolves through this** — die fills
31
+ * (`buildView`), the colorbar (`toCanvas`) and the mapless summary's bars, which
32
+ * each look the gradient up independently. Applying the flip at only some of
33
+ * them would show one reading as two different colours on the same screen, with
34
+ * the colorbar — the thing an engineer reads the map against — the most likely
35
+ * to be missed. Reversing is a flag rather than a second registered entry per
36
+ * ramp so the menu stays one list and a host's own gradient gets it for free.
37
+ */
38
+ export declare function resolveValueColorFn(name?: string, reversed?: boolean): (t: number) => string;
26
39
  export interface BinColorScheme {
27
40
  /** Human-readable display name. */
28
41
  label: string;
@@ -1 +1 @@
1
- import{VIRIDIS as o,lerpKp as a,valueToGreyscale as i}from"./colorMap.js";const r=new Map;export function registerValueColorScheme(e,l){r.set(e,l)}export function getValueColorScheme(e){return r.get(e??"default")??r.get("default")}export function listValueColorSchemes(){return[...r.entries()].map(([e,l])=>({name:e,label:l.label}))}const t=new Map;export function registerBinColorScheme(e,l){if(!l.pass.length||!l.fail.length)throw new Error(`registerBinColorScheme('${e}'): 'pass' and 'fail' must each list at least one colour`);t.set(e,l)}export function getBinColorScheme(e){return t.get(e??"default")??t.get("default")}export function listBinColorSchemes(){return[...t.entries()].map(([e,l])=>({name:e,label:l.label}))}const n=[[0,0,255],[0,255,255],[255,255,0],[255,0,0]];registerValueColorScheme("default",{label:"Default (Blue\u2013Cyan\u2013Yellow\u2013Red)",forValue:e=>a(n,e)}),registerValueColorScheme("viridis",{label:"Viridis",forValue:e=>a(o,1-e)});const f=[[0,32,77],[54,68,130],[107,107,145],[180,154,108],[253,228,32]];registerValueColorScheme("cividis",{label:"Cividis (colour-blind safe)",forValue:e=>a(f,1-e)}),registerValueColorScheme("greyscale",{label:"Greyscale",forValue:e=>i(1-e)});const s=[[13,8,135],[126,3,168],[204,71,120],[248,149,64],[240,249,33]];registerValueColorScheme("plasma",{label:"Plasma",forValue:e=>a(s,1-e)});const c=[[0,0,4],[87,16,110],[188,55,84],[249,142,9],[252,255,164]];registerValueColorScheme("inferno",{label:"Inferno",forValue:e=>a(c,1-e)});const u=[[46,204,113],[241,196,15],[231,76,60]];registerValueColorScheme("traffic",{label:"Traffic (Green\u2013Yellow\u2013Red)",forValue:e=>a(u,e)});const b=[[0,0,128],[0,0,255],[0,255,255],[255,255,0],[255,0,0],[128,0,0]];registerValueColorScheme("jet",{label:"Jet (MATLAB rainbow)",forValue:e=>a(b,e)}),registerBinColorScheme("default",{label:"Default",pass:["#2ca02c","#98df8a","#1b5e20"],fail:["#d62728","#332288","#e69f00","#000000","#0072b2","#654522","#ff9d9a","#aa3377","#499894","#e377c2","#f0e442","#225555","#79706e","#33bbee","#ee7733","#999933","#9467bd","#663333","#cc6677"]}),registerBinColorScheme("accessible",{label:"Colour-blind safe",pass:["#009e73","#44bb99"],fail:["#d55e00","#000000","#604e97","#882d17","#f3c300","#222255","#225555","#a1caf1","#9467bd","#8c564b","#b07aa1","#eedd88","#f38400","#663333"]});
1
+ import{VIRIDIS as f,lerpKp as a,valueToGreyscale as i}from"./colorMap.js";const o=new Map;export function registerValueColorScheme(e,l){o.set(e,l)}export function getValueColorScheme(e){return o.get(e??"default")??o.get("default")}export function listValueColorSchemes(){return[...o.entries()].map(([e,l])=>({name:e,label:l.label}))}export function resolveValueColorFn(e,l){const{forValue:t}=getValueColorScheme(e);return l?n=>t(1-n):t}const r=new Map;export function registerBinColorScheme(e,l){if(!l.pass.length||!l.fail.length)throw new Error(`registerBinColorScheme('${e}'): 'pass' and 'fail' must each list at least one colour`);r.set(e,l)}export function getBinColorScheme(e){return r.get(e??"default")??r.get("default")}export function listBinColorSchemes(){return[...r.entries()].map(([e,l])=>({name:e,label:l.label}))}registerValueColorScheme("default",{label:"Default (Viridis)",forValue:e=>a(f,e)});const s=[[0,32,77],[54,68,130],[107,107,145],[180,154,108],[253,228,32]];registerValueColorScheme("cividis",{label:"Cividis (colour-blind safe)",forValue:e=>a(s,e)}),registerValueColorScheme("greyscale",{label:"Greyscale",forValue:e=>i(e)});const c=[[13,8,135],[126,3,168],[204,71,120],[248,149,64],[240,249,33]];registerValueColorScheme("plasma",{label:"Plasma",forValue:e=>a(c,e)});const u=[[0,0,4],[87,16,110],[188,55,84],[249,142,9],[252,255,164]];registerValueColorScheme("inferno",{label:"Inferno",forValue:e=>a(u,e)});const b=[[11,4,5],[43,28,53],[62,53,107],[59,86,152],[53,123,163],[53,159,171],[75,194,173],[153,221,182],[222,245,229]];registerValueColorScheme("mako",{label:"Mako",forValue:e=>a(b,e)});const d=[[46,204,113],[241,196,15],[231,76,60]];registerValueColorScheme("traffic",{label:"Traffic (Green\u2013Yellow\u2013Red)",forValue:e=>a(d,e)});const p=[[0,0,128],[0,0,255],[0,255,255],[255,255,0],[255,0,0],[128,0,0]];registerValueColorScheme("jet",{label:"Jet (MATLAB rainbow)",forValue:e=>a(p,e)}),registerBinColorScheme("default",{label:"Default",pass:["#2ca02c","#98df8a","#1b5e20"],fail:["#d62728","#332288","#e69f00","#000000","#0072b2","#654522","#ff9d9a","#aa3377","#499894","#e377c2","#f0e442","#225555","#79706e","#33bbee","#ee7733","#999933","#9467bd","#663333","#cc6677"]}),registerBinColorScheme("accessible",{label:"Colour-blind safe",pass:["#009e73","#44bb99"],fail:["#d55e00","#000000","#604e97","#882d17","#f3c300","#222255","#225555","#a1caf1","#9467bd","#8c564b","#b07aa1","#eedd88","#f38400","#663333"]});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wafertools/wafermap",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Interactive wafer map visualization and yield analysis for semiconductor test data. Hard bins, soft bins, test values, retests, edge exclusion, reticle overlays, spatial statistics, failure clustering, and lot-level trend analysis — pure ES modules, no runtime dependencies, no server required.",
5
5
  "type": "module",
6
6
  "license": "MIT",