@wafertools/wafermap 0.30.4 → 0.32.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/AGENTS.md +21 -31
- package/CHANGELOG.md +265 -1
- package/README.md +3 -3
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +89 -6
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +11 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/dieList.d.ts +1 -1
- package/dist/packages/canvas-adapter/dieList.js +7 -7
- package/dist/packages/canvas-adapter/drilldown.js +1 -1
- package/dist/packages/canvas-adapter/exportName.d.ts +1 -12
- package/dist/packages/canvas-adapter/exportName.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +2 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +32 -34
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.d.ts +23 -2
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- 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 +38 -24
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/core/aggregates.d.ts +0 -6
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/dieTable.d.ts +137 -0
- package/dist/packages/core/dieTable.js +1 -0
- package/dist/packages/core/dies.d.ts +22 -2
- package/dist/packages/core/dies.js +1 -1
- package/dist/packages/core/index.d.ts +8 -10
- package/dist/packages/core/index.js +1 -1
- package/dist/packages/core/stdf.d.ts +9 -0
- package/dist/packages/core/stdf.js +1 -0
- package/dist/packages/core/transforms.d.ts +0 -23
- package/dist/packages/core/transforms.js +1 -1
- package/dist/packages/core/utils.d.ts +14 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +4 -12
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +42 -32
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/colorMap.d.ts +0 -2
- package/dist/packages/renderer/colorMap.js +1 -1
- package/dist/packages/renderer/columnarInput.d.ts +87 -0
- package/dist/packages/renderer/columnarInput.js +1 -0
- package/dist/packages/renderer/deprecate.d.ts +1 -1
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.d.ts +8 -6
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/index.d.ts +7 -5
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +3 -7
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.d.ts +134 -2
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/boxplot.js +1 -1
- package/dist/packages/stats/capability.d.ts +7 -7
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +2 -1
- package/dist/packages/stats/clusterDetection.js +1 -1
- package/dist/packages/stats/connectedComponents.js +1 -1
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/facets.d.ts +1 -1
- package/dist/packages/stats/filterFindings.d.ts +5 -0
- package/dist/packages/stats/filterFindings.js +1 -1
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +6 -16
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/math.d.ts +22 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/normalizeInput.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +5 -4
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/regions.d.ts +9 -1
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
- package/dist/packages/stats/renderFindingsReport.js +1 -21
- package/dist/packages/stats/renderSummaryReport.js +18 -18
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +5 -0
- package/dist/packages/worker/index.js +1 -1
- package/dist/packages/worker/wafermap.worker.d.ts +3 -0
- package/dist/packages/worker/wafermap.worker.js +1 -1
- package/package.json +2 -2
- package/dist/packages/canvas-adapter/deprecated.d.ts +0 -7
- package/dist/packages/canvas-adapter/deprecated.js +0 -1
- package/dist/packages/core/deprecated.d.ts +0 -60
- package/dist/packages/core/deprecated.js +0 -1
- package/dist/packages/renderer/deprecated.d.ts +0 -45
- package/dist/packages/renderer/deprecated.js +0 -1
- package/dist/packages/stats/deprecated.d.ts +0 -85
- package/dist/packages/stats/deprecated.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SHADOW as D,LEADING as j,wireControlHover as E,controlStyle as T,SPACE as m,RADIUS as k,fontPx as F,FONT as v,CLR as p,Z_BASE as U,menuLayerFor as X,wireListNavigation as Y,MENU_SEARCH_THRESHOLD as N,makeMenuSearchBox as K,markMenuTrigger as M,saveImageBlob as Z,openReparentedModal as J}from"../toolbar.js";import{ICONS as P}from"../icons.js";import{fmt as Q,fmtColorbarAxis as ee}from"../../renderer/fmt.js";import{markedTestLabel as te,DERIVED_LANE_PAD as ne}from"../../renderer/testLabel.js";export function openChartExpandModal(e,i,n={}){for(const a of e.querySelectorAll('div[style*="position: absolute"]'))a.style.display==="block"&&a.style.pointerEvents==="none"&&(a.style.display="none");const t=e.querySelector("[data-wmap-chart-expand]"),o=e.getAttribute("style")??"",r=e.dataset.wmapChartGrow??"plot";Object.assign(e.style,{flex:"1",minHeight:"0",alignSelf:"stretch",border:"none",borderRadius:"0"}),e.dataset.wmapExpanded="1";const l=J([e],{title:i,anchor:n.anchor,ownerDocument:e.ownerDocument,boxSize:oe[r],onClosed:()=>{delete e.dataset.wmapExpanded,e.setAttribute("style",o),t&&(t.style.display="flex"),n.onClosed?.()}});if(!l){delete e.dataset.wmapExpanded,e.setAttribute("style",o);return}if(t&&(t.style.display="none"),r==="rows"){const a=()=>{const s=Number(e.dataset.wmapChartFullH);if(!s)return;const c=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(c+s)}px)`)};a(),(e.ownerDocument.defaultView??window).requestAnimationFrame(a)}}const oe={plot:{width:"min(94vw, 1100px)",height:"min(90vh, 760px)"},rows:{width:"min(94vw, 960px)",height:"min(90vh, 700px)"},square:{width:"min(94vw, 820px)",height:"min(90vh, 860px)"}},ie={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},W={text:"text",textMuted:"text-muted",border:"border",bg:"surface",bgHover:"bg-hover",track:"panel-bg",warnBorder:"warn-border",accent:"icon-active",limitLine:"warn-text"};export function resolveChartCanvasColors(e){const i=getComputedStyle(e),n={};for(const t of Object.keys(W)){const o=i.getPropertyValue(`--wmap-${W[t]}`).trim();n[t]=o||ie[t]}return n}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,i){const n=e.ownerDocument.defaultView??window;let t=0;const o=new n.ResizeObserver(()=>{t||(t=n.requestAnimationFrame(()=>{t=0,i()}))});return o.observe(e),{disconnect:()=>{t&&n.cancelAnimationFrame(t),t=0,o.disconnect()}}}export function growCardToFitContent(e,i,n){const t=`${R(e,i)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function R(e,i){const n=e.ownerDocument.defaultView??window,t=n.getComputedStyle(e);let o=0;for(const r of Array.from(e.children)){if(r===i)continue;const l=r,a=n.getComputedStyle(l);a.display==="none"||a.position==="absolute"||a.position==="fixed"||(o+=l.offsetHeight+(parseFloat(a.marginTop)||0)+(parseFloat(a.marginBottom)||0))}return t.boxSizing==="border-box"&&(o+=(parseFloat(t.paddingTop)||0)+(parseFloat(t.paddingBottom)||0)+(parseFloat(t.borderTopWidth)||0)+(parseFloat(t.borderBottomWidth)||0)),o}function _(e,i){const n=e.ownerDocument.defaultView??window;let t=0;for(const o of Array.from(e.children)){if(o===i)continue;const r=o,l=n.getComputedStyle(r);l.display==="none"||l.position==="absolute"||l.position==="fixed"||(t+=r.offsetHeight+(parseFloat(l.marginTop)||0)+(parseFloat(l.marginBottom)||0))}return t}export const WAFER_MENU_HINT="<br><em>right-click to chart this wafer</em>";export function populationPhrase(e,i,n){const t=e.toLocaleString(),o=e===1?"die":"dies";return n?e===i?`${t} ${o} ${n}`:`${t} of ${i.toLocaleString()} dies ${n} (partial and edge-excluded dies left out)`:`${t} ${o}`}export function setChartGrow(e,i){e.dataset.wmapChartGrow=i}export function isExpandedCard(e){return e.dataset.wmapExpanded==="1"}export function bodyRoom(e,i,n){const o=(e.ownerDocument.defaultView??window).getComputedStyle(e),r=e.clientHeight-(parseFloat(o.paddingTop)||0)-(parseFloat(o.paddingBottom)||0),l=o.boxSizing==="border-box"?(parseFloat(o.paddingTop)||0)+(parseFloat(o.paddingBottom)||0)+(parseFloat(o.borderTopWidth)||0)+(parseFloat(o.borderBottomWidth)||0):0;return r-(R(e,i)-l)-(n?_(i,n):0)}export function fitRowsHeight(e,i,n,t,o=0){if(growCardToFitContent(e,i,o+n),e.dataset.wmapChartFullH=String(Math.ceil(R(e,i)+o+t)),!isExpandedCard(e))return n;const r=bodyRoom(e,i)-o;return Math.max(n,Math.min(t,Math.floor(r)))}export function chartFillHeight(e,i,n,t){const o=_(i,n);return growCardToFitContent(e,i,o+t),Math.max(t,i.clientHeight-o)}export function chartDpr(e){return(e.ownerDocument.defaultView??window).devicePixelRatio||1}export function prepareCanvas(e,i,n,t){const o=chartDpr(e);e.width=Math.max(1,Math.floor(n*o)),e.height=Math.max(1,Math.floor(t*o)),e.style.width=`${n}px`,e.style.height=`${t}px`;const r=e.getContext("2d");return r?(r.setTransform(o,0,0,o,0,0),r.clearRect(0,0,n,t),r.font=`${F(-1)}px system-ui, sans-serif`,r.textBaseline="middle",{ctx:r,theme:resolveChartCanvasColors(i)}):null}export function applyCanvasFlow(e,i=0){let n;if(typeof i=="number")n=i;else{const t=(i.ownerDocument.defaultView??window).getComputedStyle(i);n=i.offsetTop+i.offsetHeight+(parseFloat(t.marginBottom)||0)}e.style.position="absolute",e.style.left="0",e.style.right="0",e.style.top=`${n}px`,e.style.bottom="0"}export function formatValue(e){return Number.isFinite(e)?Number.isInteger(e)?`${e}`:Q(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,i,n){if(!i&&n===void 0)return{tick:formatValue,tickWithUnit:formatValue,unitLabel:""};const{tickFmt:t,axisLabel:o,scaledUnit:r}=ee(e,null,i,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:o}}const re=8;export const VERTICAL_TICK_SPACING_PX=28;export function horizontalTickSpacing(e,i,n){return(t,o)=>{const r=makeAxisFormat(i,n,t).tick;return o.reduce((l,a)=>Math.max(l,e.measureText(r(a)).width),0)+re}}export function drawAxisUnit(e,i,n,t,o){const r={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font=`${F(-1)}px system-ui, sans-serif`,e.textAlign="left",e.textBaseline="middle",e.fillStyle=o,e.fillText(`(${i})`,n,t),Object.assign(e,r)}export function saveCanvasPng(e,i,n){const t=e.ownerDocument.createElement("canvas");t.width=e.width,t.height=e.height;const o=t.getContext("2d");o.fillStyle="#ffffff",o.fillRect(0,0,t.width,t.height),o.drawImage(e,0,0),t.toBlob(r=>{r&&Z(r,i,n)},"image/png")}const z="10px";export function chartSwatchCss(e,i="solid"){return["display:inline-block",`width:${z}`,`height:${z}`,`border-radius:${k.pill}`,`background:${i==="outline"?"transparent":e}`,`border:1px ${i==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const le="0.35";export function makeSeriesLegendItem(e,i,n=document){const t=n.createElement("button");t.type="button",Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:m.xs,padding:`${m.xs} ${m.md}`,borderRadius:k.pill,border:`1px solid ${p.menuBorder}`,background:"none",cursor:"pointer",fontSize:v.body,color:p.text,whiteSpace:"nowrap"}),E(t,"bare");const o=n.createElement("span");return o.style.cssText=chartSwatchCss(i),t.append(o,n.createTextNode(e)),{el:t,setState({selected:r,dimmed:l}){t.style.borderColor=r?p.iconActive:p.menuBorder,t.style.fontWeight=r?"600":"400",t.style.opacity=l?le:"1",t.setAttribute("aria-pressed",r?"true":"false")}}}export function cardFrameStyle(){return{background:p.menuBg,border:`1px solid ${p.menuBorder}`,borderRadius:k.container,padding:m.xl,fontVariantNumeric:"tabular-nums"}}export function cardShell(e,i,n=document){const t=n.createElement("div");t.dataset.wmapChartCard="1",t.dataset.wmapChartTitle=e,Object.assign(t.style,{display:"flex",flexDirection:"column",...cardFrameStyle(),minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const o=t.ownerDocument.createElement("div");Object.assign(o.style,{display:"flex",alignItems:"center",gap:m.sm,paddingBottom:"6px",marginBottom:m.md,borderBottom:`1px solid ${p.menuBorder}`});const r=t.ownerDocument.createElement("div");r.textContent=e,Object.assign(r.style,{color:p.value,fontSize:v.heading,fontWeight:"600",flex:"1"}),o.appendChild(r);const l=makeTooltip(t),a=t.ownerDocument.createElement("button");a.type="button",a.setAttribute("aria-label","Save as PNG"),a.innerHTML=P.download,Object.assign(a.style,{...T("outlined"),color:p.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),E(a),attachChartTip(a,t,l,"Save as PNG"),a.addEventListener("click",()=>{const f=t.querySelector("canvas");f&&saveCanvasPng(f,e,i)}),o.appendChild(a);const s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Expand"),s.innerHTML=P.expand,Object.assign(s.style,{...T("outlined"),color:p.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),E(s),attachChartTip(s,t,l,"Expand"),s.dataset.wmapChartExpand="1",s.addEventListener("click",()=>openChartExpandModal(t,r.textContent??e)),o.appendChild(s),t.appendChild(o);const c=t.ownerDocument.createElement("div");Object.assign(c.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(c);const d=t.ownerDocument.createElement("div");return Object.assign(d.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),t.appendChild(d),{card:t,heading:r,controlsRow:c,body:d}}export function makeSegmented(e,i,n,t=document,o=!1){const r=t.createElement("div");r.setAttribute("role","radiogroup"),Object.assign(r.style,{display:"inline-flex",border:`1px solid ${p.menuBorder}`,borderRadius:k.control,overflow:"hidden"});const l=`seg-${Math.random().toString(36).slice(2,9)}`,a=[];return e.forEach(([s,c],d)=>{const f=t.createElement("label");Object.assign(f.style,{display:"inline-flex",alignItems:"center",fontSize:v.body,padding:o?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:d>0?`1px solid ${p.menuBorder}`:"none"}),E(f,"bare");const u=t.createElement("input");u.type="radio",u.name=l,u.value=s,u.checked=s===i,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{f.style.background=u.checked?p.bgActive:p.menuBg,f.style.color=u.checked?p.iconActive:p.text,f.dataset.on=u.checked?"true":"false"};a.push(w),w(),u.addEventListener("change",()=>{if(u.checked){for(const C of a)C();n(s)}}),f.append(u,t.createTextNode(c)),r.appendChild(f)}),r}export function makeBackButton(e,i=document){const n=i.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...T("outlined"),background:p.menuBg,color:p.label}),E(n),n.addEventListener("click",e),n}export function renderEmptyState(e,i,n){const t=e.ownerDocument.createElement("div");t.textContent=i,Object.assign(t.style,{color:p.label,fontSize:v.body,padding:"8px 0"}),n&&Object.assign(t.style,n),e.appendChild(t)}export function makeListSelect(e,i,n,t={}){const{maxWidth:o="200px",ownerDocument:r=document,ariaLabel:l,emptyText:a,searchPlaceholder:s="Filter\u2026",hook:c}=t;let d=i;const f=b=>e.find($=>$.value===b)?.label??"",u=r.createElement("button");u.type="button",c&&(u.dataset.wmapSelect=c),l&&u.setAttribute("aria-label",l),Object.assign(u.style,{fontSize:v.body,padding:`${m.xxs} ${m.sm}`,background:p.menuBg,color:p.text,border:`1px solid ${p.menuBorder}`,borderRadius:k.control,maxWidth:o,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:m.sm,justifyContent:"space-between"}),E(u),M(u,!1);const w=r.createElement("span");Object.assign(w.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const C=r.createElement("span");C.textContent="\u25BE",C.style.flex="0 0 auto",u.append(w,C);const S=()=>{w.textContent=e.length===0?a??"(none)":f(d)||a||"(none)"};if(S(),e.length===0)return u.disabled=!0,u.style.opacity="0.6",u.style.cursor="default",Object.defineProperty(u,"value",{get:()=>d,set:b=>{d=b,S()}});let x=null;const L=()=>{x&&(x.remove(),x=null,M(u,!1))},A=()=>{L(),u.focus()};function V(){const b=u.getBoundingClientRect(),$=r.defaultView??window,I=Math.max(b.width,220),G=Math.min(b.left,Math.max(4,($.innerWidth??1/0)-I-4));x=r.createElement("div"),x.setAttribute("role","listbox"),x.setAttribute("aria-label",l??"Options"),Object.assign(x.style,{position:"fixed",top:`${b.bottom+4}px`,left:`${G}px`,background:p.menuBg,border:`1px solid ${p.menuBorder}`,borderRadius:k.control,boxShadow:D.menu,zIndex:U,minWidth:`${I}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const B=[],O=()=>B.filter(h=>h.row.style.display!=="none").map(h=>h.row);if(e.length>N){const h=K(y=>{for(const g of B)g.row.style.display=g.label.includes(y)?"":"none"},s,r);Object.assign(h.style,{position:"sticky",top:"0",zIndex:"1",background:p.menuBg}),h.addEventListener("keydown",y=>{if(y.key==="Escape"){A();return}y.key==="ArrowDown"&&(y.preventDefault(),O()[0]?.focus())}),x.appendChild(h)}for(const h of e){const y=h.value===d,g=r.createElement("div");g.textContent=h.label,g.setAttribute("role","option"),g.setAttribute("aria-selected",y?"true":"false"),g.tabIndex=-1,Object.assign(g.style,{padding:`${m.sm} ${m.xl}`,fontSize:v.body,cursor:"pointer",color:y?p.iconActive:p.text,fontWeight:y?"700":"400",background:y?p.menuActive:"transparent",whiteSpace:"nowrap"}),g.addEventListener("mouseenter",()=>{y||(g.style.background=p.menuHover)}),g.addEventListener("mouseleave",()=>{y||(g.style.background="transparent")}),g.addEventListener("click",q=>{q.stopPropagation(),d=h.value,S(),A(),n(h.value)}),x.appendChild(g),B.push({row:g,label:h.label.toLowerCase()})}if(Y(x,O,A),X(u).appendChild(x),M(u,!0),e.length<=N){const h=e.findIndex(y=>y.value===d);O()[h>=0?h:0]?.focus()}}u.addEventListener("click",b=>{if(b.stopPropagation(),x){L();return}V()});const H=b=>{if(!u.isConnected){r.removeEventListener("click",H);return}x&&!x.contains(b.target)&&!u.contains(b.target)&&L()};return r.addEventListener("click",H),Object.defineProperty(u,"value",{get:()=>d,set:b=>{d=b,S()}})}export function testOptionLabels(e){const i=e.some(n=>n.derived);return e.map(n=>(i&&!n.derived?ne:"")+te(n,n.testNumber))}export function makeTestSelect(e,i,n,t={}){const{maxWidth:o="200px",emptyText:r="No parametric tests",ownerDocument:l=document}=t;return makeListSelect(testOptionLabels(e).map((a,s)=>({value:String(e[s].testNumber),label:a})),i!==null?String(i):"",a=>n(Number(a)),{maxWidth:o,ownerDocument:l,ariaLabel:"Test",emptyText:r,searchPlaceholder:"Filter tests\u2026"})}export function makeLinkedAxisPrefs(e,i,n,t=document){let o=i?.includeLimits,r=i?.clipOutliers??!1;const l=()=>n({includeLimits:o,clipOutliers:r});return{get:()=>({includeLimits:o,clipOutliers:r}),sync(a,s){e.innerHTML="",s&&e.appendChild(makeToggle("Axis includes limits",a,c=>{o=c,l()},t)),e.appendChild(makeToggle("Clip outliers",r,c=>{r=c,l()},t))},set(a){return a.includeLimits===o&&a.clipOutliers===r?!1:(o=a.includeLimits,r=a.clipOutliers,!0)}}}export function makeLinkedTestSelect(e,i,n,t={}){let o=i;const r=makeTestSelect(e,o,l=>{l!==o&&(o=l,n(l))},t);return{el:r,get:()=>o,set(l){return o===l||!e.some(a=>a.testNumber===l)?!1:(o=l,r.value=String(l),!0)}}}export const ALL_GROUPS="\0all";export function makeLinkedGroupSelect(e,i,n,t,o={}){let r=n;const l=d=>d??ALL_GROUPS,a=d=>d===ALL_GROUPS?null:d,s=makeLabeledSelect(i,[{value:ALL_GROUPS,label:"All groups"},...e.map(d=>({value:d,label:d}))],l(r),d=>{const f=a(d);f!==r&&(r=f,t(f))},{...o,hook:"group-scope"}),c=s.querySelector('[data-wmap-select="group-scope"]');return{el:s,get:()=>r,set(d){return r===d||d!==null&&!e.includes(d)?!1:(r=d,c&&(c.value=l(d)),!0)}}}export function makeWaferSelect(e,i,n,t={}){const{maxWidth:o="160px",allLabel:r="All wafers",ownerDocument:l=document}=t,a="\0all";return makeListSelect([{value:a,label:r},...e.map((s,c)=>({value:String(c),label:s.label??`#${c}`}))],i===null?a:String(i),s=>n(s===a?null:Number(s)),{maxWidth:o,ownerDocument:l,ariaLabel:r,searchPlaceholder:"Filter wafers\u2026"})}export function makeToggle(e,i,n,t=document){const o=t.createElement("label");Object.assign(o.style,{display:"inline-flex",alignItems:"center",gap:m.xs,fontSize:v.body,color:p.label,cursor:"pointer",userSelect:"none"}),E(o,"bare");const r=t.createElement("input");return r.type="checkbox",r.checked=i,r.style.cssText="margin:0;cursor:pointer;",r.addEventListener("change",()=>n(r.checked)),o.append(r,t.createTextNode(e)),o}export function makeLabeledSelect(e,i,n,t,o={}){const{maxWidth:r="160px",hook:l,ownerDocument:a=document}=o,s=a.createElement("label");s.textContent=e,Object.assign(s.style,{color:p.label,fontSize:v.body,display:"flex",alignItems:"center",gap:m.xs});const c=makeListSelect(i,n,t,{maxWidth:r,ownerDocument:a,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return s.appendChild(c),s}export function makeChartGridWrap(e=document){const i=e.createElement("div");return i.dataset.wmapChartGrid="1",Object.assign(i.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:m.lg,flex:"0 0 auto"}),i}export function makeTooltip(e){const i=e.ownerDocument.createElement("div");return Object.assign(i.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:"rgba(30, 32, 40, 0.93)",color:"#f0f0f2",border:"1px solid rgba(255,255,255,0.10)",borderRadius:k.control,padding:`${m.sm} ${m.xl}`,fontSize:v.sub,fontFamily:v.family,maxWidth:"320px",whiteSpace:"normal",overflowWrap:"break-word",boxShadow:D.menu}),e.appendChild(i),i}export function attachChartTip(e,i,n,t){e.removeAttribute("title"),e.textContent?.trim()||(e.ariaLabel=t),e.addEventListener("mouseenter",o=>{n.textContent=t,n.style.display="block",positionChartTooltip(n,i,o.clientX,o.clientY)}),e.addEventListener("mousemove",o=>{n.style.display==="block"&&positionChartTooltip(n,i,o.clientX,o.clientY)}),e.addEventListener("mouseleave",()=>{n.style.display="none"}),e.addEventListener("click",()=>{n.style.display="none"}),e.addEventListener("focus",()=>{n.textContent=t,n.style.display="block";const o=e.getBoundingClientRect();positionChartTooltip(n,i,o.left+o.width/2,o.bottom)}),e.addEventListener("blur",()=>{n.style.display="none"})}export function positionChartTooltip(e,i,n,t){const o=i.getBoundingClientRect(),r=14,l=4;e.style.left="0",e.style.top="0";const a=e.offsetWidth,s=e.offsetHeight;let c=n-o.left+r,d=t-o.top+r;c+a+l>o.width&&(c=n-o.left-r-a),d+s+l>o.height&&(d=t-o.top-r-s),c=Math.max(l,c),d=Math.max(l,d),e.style.left=`${c}px`,e.style.top=`${d}px`}export function robustFence(e,i=1.5){const n=Float64Array.from(e.filter(a=>Number.isFinite(a)));if(n.sort(),n.length<8)return null;const t=a=>{const s=(n.length-1)*a,c=Math.floor(s),d=Math.ceil(s);return c===d?n[c]:n[c]+(n[d]-n[c])*(s-c)},o=t(.25),r=t(.75),l=r-o;return l===0?null:{lo:o-i*l,hi:r+i*l}}export function shouldIncludeLimitsByDefault(e,i,n,t,o=1/3){if(n===void 0&&t===void 0||!Number.isFinite(e)||!Number.isFinite(i))return!1;const r=Math.min(e,n??e),a=Math.max(i,t??i)-r;if(a<=0)return!0;const s=i-e;return s===0?!0:s/a>=o}export function resolveAxisRange(e){const{dataMin:i,dataMax:n,limitLow:t,limitHigh:o,includeLimits:r,clipOutliers:l,values:a}=e;let s=i,c=n,d=0;if(l&&a?.length){const u=robustFence(a);if(u){const w=Math.max(s,u.lo),C=Math.min(c,u.hi);C>w&&(d=a.filter(S=>Number.isFinite(S)&&(S<w||S>C)).length,s=w,c=C)}}r&&(t!==void 0&&(s=Math.min(s,t)),o!==void 0&&(c=Math.max(c,o))),s===c&&(s-=1,c+=1);const f=[];return t!==void 0&&t<s&&f.push({value:t,label:"Lo limit",side:"lo"}),t!==void 0&&t>c&&f.push({value:t,label:"Lo limit",side:"hi"}),o!==void 0&&o>c&&f.push({value:o,label:"Hi limit",side:"hi"}),o!==void 0&&o<s&&f.push({value:o,label:"Hi limit",side:"lo"}),{lo:s,hi:c,offAxis:f,clippedCount:d}}export function limitLabelSide(e,i,n,t,o,r=3){const l=o?-1:1;return(l<0?e-r-i>=n:e+r+i<=t)?l:-l}export function drawOffAxisLimits(e,i,n,t,o,r){if(i.length){e.save(),e.fillStyle=o,e.font=`${F(-1)}px system-ui, sans-serif`;for(const{value:l,label:a,side:s}of i){const c=t==="horizontal"?s==="lo"?"\u2190":"\u2192":s==="lo"?"\u2193":"\u2191",d=s==="lo"?`${c} ${a} ${r(l)}`:`${a} ${r(l)} ${c}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=s==="lo"?"left":"right",e.fillText(d,s==="lo"?n.left+2:n.right-2,n.top+2)):(e.textBaseline=s==="lo"?"bottom":"top",e.textAlign="left",e.fillText(d,n.left+4,s==="lo"?n.bottom-2:n.top+2))}e.restore()}}
|
|
1
|
+
import{SHADOW as D,LEADING as j,wireControlHover as L,controlStyle as I,SPACE as m,RADIUS as C,fontPx as k,FONT as S,CLR as f,Z_BASE as X,menuLayerFor as K,wireListNavigation as Y,MENU_SEARCH_THRESHOLD as W,makeMenuSearchBox as Z,markMenuTrigger as R,saveImageBlob as J,openReparentedModal as Q}from"../toolbar.js";import{ICONS as _}from"../icons.js";import{minOf as ee,maxOf as te}from"../../core/utils.js";import{fiveNumberSummary as ne}from"../../stats/math.js";import{fmt as oe,fmtColorbarAxis as ie}from"../../renderer/fmt.js";import{markedTestLabel as re,DERIVED_LANE_PAD as le}from"../../renderer/testLabel.js";export function openChartExpandModal(e,o,n={}){for(const s of e.querySelectorAll('div[style*="position: absolute"]'))s.style.display==="block"&&s.style.pointerEvents==="none"&&(s.style.display="none");const t=e.querySelector("[data-wmap-chart-expand]"),i=e.getAttribute("style")??"",r=e.dataset.wmapChartGrow??"plot";Object.assign(e.style,{flex:"1",minHeight:"0",alignSelf:"stretch",border:"none",borderRadius:"0"}),e.dataset.wmapExpanded="1";const l=Q([e],{title:o,anchor:n.anchor,ownerDocument:e.ownerDocument,boxSize:se[r],onClosed:()=>{delete e.dataset.wmapExpanded,e.setAttribute("style",i),t&&(t.style.display="flex"),n.onClosed?.()}});if(!l){delete e.dataset.wmapExpanded,e.setAttribute("style",i);return}if(t&&(t.style.display="none"),r==="rows"){const s=()=>{const a=Number(e.dataset.wmapChartFullH);if(!a)return;const p=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(p+a)}px)`)};s(),(e.ownerDocument.defaultView??window).requestAnimationFrame(s)}}const se={plot:{width:"min(94vw, 1100px)",height:"min(90vh, 760px)"},rows:{width:"min(94vw, 960px)",height:"min(90vh, 700px)"},square:{width:"min(94vw, 820px)",height:"min(90vh, 860px)"}};export const GRID_ALPHA=.4;export function withAlpha(e,o){const n=e.trim();let t,i,r,l=1;const s=n.match(/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i),a=n.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/i);if(s){const p=s[1].length===3?s[1].split("").map(c=>c+c).join(""):s[1];[t,i,r]=[0,2,4].map(c=>parseInt(p.slice(c,c+2),16)),p.length===8&&(l=parseInt(p.slice(6,8),16)/255)}else if(a)[t,i,r]=[a[1],a[2],a[3]].map(Number),a[4]!==void 0&&(l=a[4].endsWith("%")?parseFloat(a[4])/100:Number(a[4]));else return e;return`rgba(${t}, ${i}, ${r}, ${+(l*o).toFixed(3)})`}const ae={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},P={text:"text",textMuted:"text-muted",border:"border",bg:"surface",bgHover:"bg-hover",track:"panel-bg",warnBorder:"warn-border",accent:"icon-active",limitLine:"warn-text"};export function resolveChartCanvasColors(e){const o=getComputedStyle(e),n={};for(const t of Object.keys(P)){const i=o.getPropertyValue(`--wmap-${P[t]}`).trim();n[t]=i||ae[t]}return n.grid=withAlpha(n.border,GRID_ALPHA),n}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,o){const n=e.ownerDocument.defaultView??window;let t=0;const i=new n.ResizeObserver(()=>{t||(t=n.requestAnimationFrame(()=>{t=0,o()}))});return i.observe(e),{disconnect:()=>{t&&n.cancelAnimationFrame(t),t=0,i.disconnect()}}}export function growCardToFitContent(e,o,n){const t=`${H(e,o)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function H(e,o){const n=e.ownerDocument.defaultView??window,t=n.getComputedStyle(e);let i=0;for(const r of Array.from(e.children)){if(r===o)continue;const l=r,s=n.getComputedStyle(l);s.display==="none"||s.position==="absolute"||s.position==="fixed"||(i+=l.offsetHeight+(parseFloat(s.marginTop)||0)+(parseFloat(s.marginBottom)||0))}return t.boxSizing==="border-box"&&(i+=(parseFloat(t.paddingTop)||0)+(parseFloat(t.paddingBottom)||0)+(parseFloat(t.borderTopWidth)||0)+(parseFloat(t.borderBottomWidth)||0)),i}function z(e,o){const n=e.ownerDocument.defaultView??window;let t=0;for(const i of Array.from(e.children)){if(i===o)continue;const r=i,l=n.getComputedStyle(r);l.display==="none"||l.position==="absolute"||l.position==="fixed"||(t+=r.offsetHeight+(parseFloat(l.marginTop)||0)+(parseFloat(l.marginBottom)||0))}return t}export const WAFER_MENU_HINT="<br><em>right-click to chart this wafer</em>";export function populationPhrase(e,o,n){const t=e.toLocaleString(),i=e===1?"die":"dies";return n?e===o?`${t} ${i} ${n}`:`${t} of ${o.toLocaleString()} dies ${n} (partial and edge-excluded dies left out)`:`${t} ${i}`}export function setChartGrow(e,o){e.dataset.wmapChartGrow=o}export function isExpandedCard(e){return e.dataset.wmapExpanded==="1"}export function bodyRoom(e,o,n){const i=(e.ownerDocument.defaultView??window).getComputedStyle(e),r=e.clientHeight-(parseFloat(i.paddingTop)||0)-(parseFloat(i.paddingBottom)||0),l=i.boxSizing==="border-box"?(parseFloat(i.paddingTop)||0)+(parseFloat(i.paddingBottom)||0)+(parseFloat(i.borderTopWidth)||0)+(parseFloat(i.borderBottomWidth)||0):0;return r-(H(e,o)-l)-(n?z(o,n):0)}export function fitRowsHeight(e,o,n,t,i=0){if(growCardToFitContent(e,o,i+n),e.dataset.wmapChartFullH=String(Math.ceil(H(e,o)+i+t)),!isExpandedCard(e))return n;const r=bodyRoom(e,o)-i;return Math.max(n,Math.min(t,Math.floor(r)))}export function chartFillHeight(e,o,n,t){const i=z(o,n);return growCardToFitContent(e,o,i+t),Math.max(t,o.clientHeight-i)}export function chartDpr(e){return(e.ownerDocument.defaultView??window).devicePixelRatio||1}export function prepareCanvas(e,o,n,t){const i=chartDpr(e);e.width=Math.max(1,Math.floor(n*i)),e.height=Math.max(1,Math.floor(t*i)),e.style.width=`${n}px`,e.style.height=`${t}px`;const r=e.getContext("2d");return r?(r.setTransform(i,0,0,i,0,0),r.clearRect(0,0,n,t),r.font=`${k(-1)}px system-ui, sans-serif`,r.textBaseline="middle",{ctx:r,theme:resolveChartCanvasColors(o)}):null}export function applyCanvasFlow(e,o=0){let n;if(typeof o=="number")n=o;else{const t=(o.ownerDocument.defaultView??window).getComputedStyle(o);n=o.offsetTop+o.offsetHeight+(parseFloat(t.marginBottom)||0)}e.style.position="absolute",e.style.left="0",e.style.right="0",e.style.top=`${n}px`,e.style.bottom="0"}export function formatValue(e){return Number.isFinite(e)?Number.isInteger(e)?`${e}`:oe(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,o,n){if(!o&&n===void 0)return{tick:formatValue,tickWithUnit:formatValue,unitLabel:""};const{tickFmt:t,axisLabel:i,scaledUnit:r}=ie(e,null,o,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:i}}const ce=8;export const VERTICAL_TICK_SPACING_PX=28;export function horizontalTickSpacing(e,o,n){return(t,i)=>{const r=makeAxisFormat(o,n,t).tick;return i.reduce((l,s)=>Math.max(l,e.measureText(r(s)).width),0)+ce}}export function drawAxisUnit(e,o,n,t,i){const r={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font=`${k(-1)}px system-ui, sans-serif`,e.textAlign="left",e.textBaseline="middle",e.fillStyle=i,e.fillText(`(${o})`,n,t),Object.assign(e,r)}export function saveCanvasPng(e,o,n){const t=e.ownerDocument.createElement("canvas");t.width=e.width,t.height=e.height;const i=t.getContext("2d");i.fillStyle="#ffffff",i.fillRect(0,0,t.width,t.height),i.drawImage(e,0,0),t.toBlob(r=>{r&&J(r,o,n)},"image/png")}const V="10px";export function chartSwatchCss(e,o="solid"){return["display:inline-block",`width:${V}`,`height:${V}`,`border-radius:${C.pill}`,`background:${o==="outline"?"transparent":e}`,`border:1px ${o==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const de="0.35";export function makeSeriesLegendItem(e,o,n=document){const t=n.createElement("button");t.type="button",Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:m.xs,padding:`${m.xs} ${m.md}`,borderRadius:C.pill,border:`1px solid ${f.menuBorder}`,background:"none",cursor:"pointer",fontSize:S.body,color:f.text,whiteSpace:"nowrap"}),L(t,"bare");const i=n.createElement("span");return i.style.cssText=chartSwatchCss(o),t.append(i,n.createTextNode(e)),{el:t,setState({selected:r,dimmed:l}){t.style.borderColor=r?f.iconActive:f.menuBorder,t.style.fontWeight=r?"600":"400",t.style.opacity=l?de:"1",t.setAttribute("aria-pressed",r?"true":"false")}}}export function cardFrameStyle(){return{background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:C.container,padding:m.xl,fontVariantNumeric:"tabular-nums"}}export function cardShell(e,o,n=document){const t=n.createElement("div");t.dataset.wmapChartCard="1",t.dataset.wmapChartTitle=e,Object.assign(t.style,{display:"flex",flexDirection:"column",...cardFrameStyle(),minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const i=t.ownerDocument.createElement("div");Object.assign(i.style,{display:"flex",alignItems:"center",gap:m.sm,paddingBottom:"6px",marginBottom:m.md,borderBottom:`1px solid ${f.menuBorder}`});const r=t.ownerDocument.createElement("div");r.textContent=e,Object.assign(r.style,{color:f.value,fontSize:S.heading,fontWeight:"600",flex:"1"}),i.appendChild(r);const l=makeTooltip(t),s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Save as PNG"),s.innerHTML=_.download,Object.assign(s.style,{...I("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(s),attachChartTip(s,t,l,"Save as PNG"),s.addEventListener("click",()=>{const d=t.querySelector("canvas");d&&saveCanvasPng(d,e,o)}),i.appendChild(s);const a=t.ownerDocument.createElement("button");a.type="button",a.setAttribute("aria-label","Expand"),a.innerHTML=_.expand,Object.assign(a.style,{...I("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(a),attachChartTip(a,t,l,"Expand"),a.dataset.wmapChartExpand="1",a.addEventListener("click",()=>openChartExpandModal(t,r.textContent??e)),i.appendChild(a),t.appendChild(i);const p=t.ownerDocument.createElement("div");Object.assign(p.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(p);const c=t.ownerDocument.createElement("div");return Object.assign(c.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),t.appendChild(c),{card:t,heading:r,controlsRow:p,body:c}}export function makeSegmented(e,o,n,t=document,i=!1){const r=t.createElement("div");r.setAttribute("role","radiogroup"),Object.assign(r.style,{display:"inline-flex",border:`1px solid ${f.menuBorder}`,borderRadius:C.control,overflow:"hidden"});const l=`seg-${Math.random().toString(36).slice(2,9)}`,s=[];return e.forEach(([a,p],c)=>{const d=t.createElement("label");Object.assign(d.style,{display:"inline-flex",alignItems:"center",fontSize:S.body,padding:i?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:c>0?`1px solid ${f.menuBorder}`:"none"}),L(d,"bare");const u=t.createElement("input");u.type="radio",u.name=l,u.value=a,u.checked=a===o,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{d.style.background=u.checked?f.bgActive:f.menuBg,d.style.color=u.checked?f.iconActive:f.text,d.dataset.on=u.checked?"true":"false"};s.push(w),w(),u.addEventListener("change",()=>{if(u.checked){for(const v of s)v();n(a)}}),d.append(u,t.createTextNode(p)),r.appendChild(d)}),r}export function makeBackButton(e,o=document){const n=o.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...I("outlined"),background:f.menuBg,color:f.label}),L(n),n.addEventListener("click",e),n}export function renderEmptyState(e,o,n){const t=e.ownerDocument.createElement("div");t.textContent=o,Object.assign(t.style,{color:f.label,fontSize:S.body,padding:"8px 0"}),n&&Object.assign(t.style,n),e.appendChild(t)}export function makeListSelect(e,o,n,t={}){const{maxWidth:i="200px",ownerDocument:r=document,ariaLabel:l,emptyText:s,searchPlaceholder:a="Filter\u2026",hook:p}=t;let c=o;const d=x=>e.find(O=>O.value===x)?.label??"",u=r.createElement("button");u.type="button",p&&(u.dataset.wmapSelect=p),l&&u.setAttribute("aria-label",l),Object.assign(u.style,{fontSize:S.body,padding:`${m.xxs} ${m.sm}`,background:f.menuBg,color:f.text,border:`1px solid ${f.menuBorder}`,borderRadius:C.control,maxWidth:i,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:m.sm,justifyContent:"space-between"}),L(u),R(u,!1);const w=r.createElement("span");Object.assign(w.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const v=r.createElement("span");v.textContent="\u25BE",v.style.flex="0 0 auto",u.append(w,v);const E=()=>{w.textContent=e.length===0?s??"(none)":d(c)||s||"(none)"};if(E(),e.length===0)return u.disabled=!0,u.style.opacity="0.6",u.style.cursor="default",Object.defineProperty(u,"value",{get:()=>c,set:x=>{c=x,E()}});let b=null;const B=()=>{b&&(b.remove(),b=null,R(u,!1))},$=()=>{B(),u.focus()};function G(){const x=u.getBoundingClientRect(),O=r.defaultView??window,N=Math.max(x.width,220),q=Math.min(x.left,Math.max(4,(O.innerWidth??1/0)-N-4));b=r.createElement("div"),b.setAttribute("role","listbox"),b.setAttribute("aria-label",l??"Options"),Object.assign(b.style,{position:"fixed",top:`${x.bottom+4}px`,left:`${q}px`,background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:C.control,boxShadow:D.menu,zIndex:X,minWidth:`${N}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const T=[],F=()=>T.filter(h=>h.row.style.display!=="none").map(h=>h.row);if(e.length>W){const h=Z(y=>{for(const g of T)g.row.style.display=g.label.includes(y)?"":"none"},a,r);Object.assign(h.style,{position:"sticky",top:"0",zIndex:"1",background:f.menuBg}),h.addEventListener("keydown",y=>{if(y.key==="Escape"){$();return}y.key==="ArrowDown"&&(y.preventDefault(),F()[0]?.focus())}),b.appendChild(h)}for(const h of e){const y=h.value===c,g=r.createElement("div");g.textContent=h.label,g.setAttribute("role","option"),g.setAttribute("aria-selected",y?"true":"false"),g.tabIndex=-1,Object.assign(g.style,{padding:`${m.sm} ${m.xl}`,fontSize:S.body,cursor:"pointer",color:y?f.iconActive:f.text,fontWeight:y?"700":"400",background:y?f.menuActive:"transparent",whiteSpace:"nowrap"}),g.addEventListener("mouseenter",()=>{y||(g.style.background=f.menuHover)}),g.addEventListener("mouseleave",()=>{y||(g.style.background="transparent")}),g.addEventListener("click",U=>{U.stopPropagation(),c=h.value,E(),$(),n(h.value)}),b.appendChild(g),T.push({row:g,label:h.label.toLowerCase()})}if(Y(b,F,$),K(u).appendChild(b),R(u,!0),e.length<=W){const h=e.findIndex(y=>y.value===c);F()[h>=0?h:0]?.focus()}}u.addEventListener("click",x=>{if(x.stopPropagation(),b){B();return}G()});const M=x=>{if(!u.isConnected){r.removeEventListener("click",M);return}b&&!b.contains(x.target)&&!u.contains(x.target)&&B()};return r.addEventListener("click",M),Object.defineProperty(u,"value",{get:()=>c,set:x=>{c=x,E()}})}export function testOptionLabels(e){const o=e.some(n=>n.derived);return e.map(n=>(o&&!n.derived?le:"")+re(n,n.testNumber))}export function makeTestSelect(e,o,n,t={}){const{maxWidth:i="200px",emptyText:r="No parametric tests",ownerDocument:l=document}=t;return makeListSelect(testOptionLabels(e).map((s,a)=>({value:String(e[a].testNumber),label:s})),o!==null?String(o):"",s=>n(Number(s)),{maxWidth:i,ownerDocument:l,ariaLabel:"Test",emptyText:r,searchPlaceholder:"Filter tests\u2026"})}export function makeLinkedAxisPrefs(e,o,n,t=document){let i=o?.includeLimits,r=o?.clipOutliers??!1,l=o?.limits??"both";const s=()=>n({includeLimits:i,clipOutliers:r,limits:l});return{get:()=>({includeLimits:i,clipOutliers:r,limits:l}),sync(a,p,c=!1){e.innerHTML="",c&&e.appendChild(makeLimitsSelect(l,d=>{l=d,s()},t)),p&&e.appendChild(makeToggle("Axis includes limits",a,d=>{i=d,s()},t)),e.appendChild(makeToggle("Clip outliers",r,d=>{r=d,s()},t))},set(a){const p=a.limits??"both";return a.includeLimits===i&&a.clipOutliers===r&&p===l?!1:(i=a.includeLimits,r=a.clipOutliers,l=p,!0)}}}export function makeLinkedTestSelect(e,o,n,t={}){let i=o;const r=makeTestSelect(e,i,l=>{l!==i&&(i=l,n(l))},t);return{el:r,get:()=>i,set(l){return i===l||!e.some(s=>s.testNumber===l)?!1:(i=l,r.value=String(l),!0)}}}export const ALL_GROUPS="\0all";export function makeLinkedGroupSelect(e,o,n,t,i={}){let r=n;const l=c=>c??ALL_GROUPS,s=c=>c===ALL_GROUPS?null:c,a=makeLabeledSelect(o,[{value:ALL_GROUPS,label:"All groups"},...e.map(c=>({value:c,label:c}))],l(r),c=>{const d=s(c);d!==r&&(r=d,t(d))},{...i,hook:"group-scope"}),p=a.querySelector('[data-wmap-select="group-scope"]');return{el:a,get:()=>r,set(c){return r===c||c!==null&&!e.includes(c)?!1:(r=c,p&&(p.value=l(c)),!0)}}}export function makeWaferSelect(e,o,n,t={}){const{maxWidth:i="160px",allLabel:r="All wafers",ownerDocument:l=document}=t,s="\0all";return makeListSelect([{value:s,label:r},...e.map((a,p)=>({value:String(p),label:a.label??`#${p}`}))],o===null?s:String(o),a=>n(a===s?null:Number(a)),{maxWidth:i,ownerDocument:l,ariaLabel:r,searchPlaceholder:"Filter wafers\u2026"})}export function makeToggle(e,o,n,t=document){const i=t.createElement("label");Object.assign(i.style,{display:"inline-flex",alignItems:"center",gap:m.xs,fontSize:S.body,color:f.label,cursor:"pointer",userSelect:"none"}),L(i,"bare");const r=t.createElement("input");return r.type="checkbox",r.checked=o,r.style.cssText="margin:0;cursor:pointer;",r.addEventListener("change",()=>n(r.checked)),i.append(r,t.createTextNode(e)),i}export function makeLabeledSelect(e,o,n,t,i={}){const{maxWidth:r="160px",hook:l,ownerDocument:s=document}=i,a=s.createElement("label");a.textContent=e,Object.assign(a.style,{color:f.label,fontSize:S.body,display:"flex",alignItems:"center",gap:m.xs});const p=makeListSelect(o,n,t,{maxWidth:r,ownerDocument:s,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return a.appendChild(p),a}export function makeChartGridWrap(e=document){const o=e.createElement("div");return o.dataset.wmapChartGrid="1",Object.assign(o.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:m.lg,flex:"0 0 auto"}),o}export function makeTooltip(e){const o=e.ownerDocument.createElement("div");return Object.assign(o.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:"rgba(30, 32, 40, 0.93)",color:"#f0f0f2",border:"1px solid rgba(255,255,255,0.10)",borderRadius:C.control,padding:`${m.sm} ${m.xl}`,fontSize:S.sub,fontFamily:S.family,maxWidth:"320px",whiteSpace:"normal",overflowWrap:"break-word",boxShadow:D.menu}),e.appendChild(o),o}export function attachChartTip(e,o,n,t){e.removeAttribute("title"),e.textContent?.trim()||(e.ariaLabel=t),e.addEventListener("mouseenter",i=>{n.textContent=t,n.style.display="block",positionChartTooltip(n,o,i.clientX,i.clientY)}),e.addEventListener("mousemove",i=>{n.style.display==="block"&&positionChartTooltip(n,o,i.clientX,i.clientY)}),e.addEventListener("mouseleave",()=>{n.style.display="none"}),e.addEventListener("click",()=>{n.style.display="none"}),e.addEventListener("focus",()=>{n.textContent=t,n.style.display="block";const i=e.getBoundingClientRect();positionChartTooltip(n,o,i.left+i.width/2,i.bottom)}),e.addEventListener("blur",()=>{n.style.display="none"})}export function positionChartTooltip(e,o,n,t){const i=o.getBoundingClientRect(),r=14,l=4;e.style.left="0",e.style.top="0";const s=e.offsetWidth,a=e.offsetHeight;let p=n-i.left+r,c=t-i.top+r;p+s+l>i.width&&(p=n-i.left-r-s),c+a+l>i.height&&(c=t-i.top-r-a),p=Math.max(l,p),c=Math.max(l,c),e.style.left=`${p}px`,e.style.top=`${c}px`}export const LIMIT_LABEL={test:{lo:"Lo limit",hi:"Hi limit"},spec:{lo:"LSL",hi:"USL"}},LIMIT_DASH={test:[3,3],spec:[10,4]},LIMITS_SHOWN_OPTIONS=[{value:"both",label:"Test + spec"},{value:"test",label:"Test limits"},{value:"spec",label:"Spec limits (LSL/USL)"},{value:"none",label:"None"}];function A(e,o){const[n,t]=o==="test"?[e?.limitLow,e?.limitHigh]:[e?.specLow,e?.specHigh],i=[];return n!==void 0&&Number.isFinite(n)&&i.push({value:n,kind:o,end:"lo",label:LIMIT_LABEL[o].lo}),t!==void 0&&Number.isFinite(t)&&i.push({value:t,kind:o,end:"hi",label:LIMIT_LABEL[o].hi}),i}export function hasBothLimitKinds(e){return A(e,"test").length>0&&A(e,"spec").length>0}export function limitLines(e,o="both"){if(o==="none")return[];const n=A(e,"test"),t=A(e,"spec"),i=o==="test"?n:o==="spec"?t:[...n,...t];return i.length?i:[...n,...t]}export function limitExtent(e){if(!e.length)return{};const o=e.map(n=>n.value);return{lo:ee(o),hi:te(o)}}export function stackLabelRows(e,o=4){const n=e.map((r,l)=>({...r,i:l})).sort((r,l)=>r.start-l.start),t=[],i=new Array(e.length).fill(0);for(const r of n){let l=t.findIndex(s=>r.start>=s+o);l===-1?(l=t.length,t.push(r.end)):t[l]=r.end,i[r.i]=l}return i}export function fillTextOnHalo(e,o,n,t,i){if(i){const r=e.measureText(o),l=2,s=e.fillStyle;e.fillStyle=i,e.fillRect(n-r.actualBoundingBoxLeft-l,t-r.actualBoundingBoxAscent-l,r.actualBoundingBoxLeft+r.actualBoundingBoxRight+2*l,r.actualBoundingBoxAscent+r.actualBoundingBoxDescent+2*l),e.fillStyle=s}e.fillText(o,n,t)}export function strokeLimitLine(e,o,n,t,i,r,l){e.save(),e.strokeStyle=n,e.lineWidth=1,e.setLineDash(LIMIT_DASH[o.kind]),e.beginPath(),e.moveTo(t,i),e.lineTo(r,l),e.stroke(),e.restore()}export function makeLimitsSelect(e,o,n=document){return makeLabeledSelect("Limits:",LIMITS_SHOWN_OPTIONS,e,t=>o(t),{maxWidth:"170px",hook:"limits-shown",ownerDocument:n})}export function robustFence(e,o=1.5){const n=Float64Array.from(e.filter(l=>Number.isFinite(l)));if(n.length<8)return null;const{q1:t,q3:i}=ne(n),r=i-t;return r===0?null:{lo:t-o*r,hi:i+o*r}}export function shouldIncludeLimitsByDefault(e,o,n,t,i=1/3){if(n===void 0&&t===void 0||!Number.isFinite(e)||!Number.isFinite(o))return!1;const r=Math.min(e,n??e),s=Math.max(o,t??o)-r;if(s<=0)return!0;const a=o-e;return a===0?!0:a/s>=i}export function resolveAxisRange(e){const{dataMin:o,dataMax:n,limits:t=[],includeLimits:i,clipOutliers:r,values:l}=e;let s=o,a=n,p=0;if(r&&l?.length){const d=robustFence(l);if(d){const u=Math.max(s,d.lo),w=Math.min(a,d.hi);w>u&&(p=l.filter(v=>Number.isFinite(v)&&(v<u||v>w)).length,s=u,a=w)}}if(i)for(const d of t)s=Math.min(s,d.value),a=Math.max(a,d.value);s===a&&(s-=1,a+=1);const c=[];for(const d of t)d.value<s?c.push({value:d.value,label:d.label,side:"lo"}):d.value>a&&c.push({value:d.value,label:d.label,side:"hi"});return{lo:s,hi:a,offAxis:c,clippedCount:p}}export function limitLabelSide(e,o,n,t,i,r=3){const l=i?-1:1;return(l<0?e-r-o>=n:e+r+o<=t)?l:-l}export function drawOffAxisLimits(e,o,n,t,i,r,l){if(!o.length)return;e.save(),e.fillStyle=i,e.font=`${k(-1)}px system-ui, sans-serif`;const s={lo:0,hi:0},a=k(-1)+2;for(const{value:p,label:c,side:d}of o){const u=s[d]++*a,w=t==="horizontal"?d==="lo"?"\u2190":"\u2192":d==="lo"?"\u2193":"\u2191",v=d==="lo"?`${w} ${c} ${r(p)}`:`${c} ${r(p)} ${w}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=d==="lo"?"left":"right",fillTextOnHalo(e,v,d==="lo"?n.left+2:n.right-2,n.top+2+u,l)):(e.textBaseline=d==="lo"?"bottom":"top",e.textAlign="left",fillTextOnHalo(e,v,n.left+4,d==="lo"?n.bottom-2-u:n.top+2+u,l))}e.restore()}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestHistogramData as Xt,collectTestValues as Ht,buildTestHistogramSeries as Yt,testValueExtent as At}from"../../stats/histogram.js";import{SPACE as st,fontPx as Y,FONT as Ct,CLR as $t}from"../toolbar.js";import{fmt as pt}from"../../renderer/fmt.js";import{niceStep as Qt,fitTicks as Wt}from"../../renderer/axisTicks.js";import{QUANTITY as Ut,categorical as qt}from"./palette.js";import{cardShell as Jt,observeResize as Dt,makeTooltip as Zt,attachChartTip as te,positionChartTooltip as Et,makeLinkedTestSelect as ee,makeWaferSelect as ne,makeLinkedAxisPrefs as ie,renderEmptyState as Tt,chartFillHeight as Pt,applyCanvasFlow as It,makeAxisFormat as vt,horizontalTickSpacing as Bt,PADDING as ut,robustFence as Rt,shouldIncludeLimitsByDefault as Ot,drawOffAxisLimits as oe,resolveAxisRange as le,chartSwatchCss as se,makeSeriesLegendItem as ae,prepareCanvas as Ft}from"./chartShell.js";import{escHtml as Mt,maxOf as re}from"../../core/utils.js";const Lt=230,Kt=36,xt=18;function Nt(o,z,G,Q,bt,R,r,y=4){const at=Math.max(1,Qt(R/y)),mt=Q-G;o.save(),o.font=`${Y(-1)}px system-ui, sans-serif`,o.textAlign="right",o.textBaseline="middle";for(let c=0;c<=R+1e-9;c+=at){const F=Q-c/(R||1)*mt;o.strokeStyle=r.grid,o.lineWidth=1,o.beginPath(),o.moveTo(z,F),o.lineTo(z+bt,F),o.stroke(),o.fillStyle=r.text,o.fillText(String(Math.round(c)),z-4,F)}o.strokeStyle=r.axis,o.lineWidth=1,o.beginPath(),o.moveTo(z,G),o.lineTo(z,Q),o.stroke(),o.restore()}export function renderHistogramPanel(o){const{title:z="Test value distribution",items:G,testDefs:Q,onSaveImage:bt,groups:R}=o,{card:r,body:y,controlsRow:at}=Jt(z,bt,o.ownerDocument),mt=Q.filter(l=>l.testNumber!==void 0);let c=o.selectedTestNumber??mt[0]?.testNumber??null,F=null,U=0;const kt=ee(mt,c,l=>{c=l,V(),o.onTestChange?.(l)},{maxWidth:"200px",ownerDocument:r.ownerDocument}),zt=kt.el;at.appendChild(zt);const ft=G.length>1?ne(G,F,l=>{F=l,V()},{ownerDocument:r.ownerDocument}):null;ft&&at.appendChild(ft);function Gt(l){return M===l||l!==null&&!(R??[]).some(k=>k.key===l)?!1:(M=l,V(),!0)}const yt=r.ownerDocument.createElement("span");Object.assign(yt.style,{display:"inline-flex",gap:st.lg,alignItems:"center"}),at.appendChild(yt);const ht=ie(yt,o.axisPrefs,l=>{o.onAxisPrefsChange?.(l),V()},r.ownerDocument),C=Zt(r);let rt=null,M=null;function q(l){const k=Q.find(J=>J.testNumber===l);return{unit:k?.unit,limitLow:k?.limitLow,limitHigh:k?.limitHigh}}function V(){const{includeLimits:l,clipOutliers:k}=ht.get();if(rt?.disconnect(),rt=null,y.innerHTML="",c===null){Tt(y,"No parametric test data available for a histogram.");return}const J=!!R&&R.length>0;let Z=!1,S;if(J){const{limitLow:s,limitHigh:u}=q(c),i=R.flatMap(b=>b.items),{min:e,max:x}=At(i,c);if(Z=l??Ot(e,x,s,u),k){const b=Ht(i,c),L=Rt(b);L&&(S={lo:Math.max(e,L.lo),hi:Math.min(x,L.hi)},U=b.reduce((it,A)=>it+(A<S.lo||A>S.hi?1:0),0))}S||(U=0)}const $=J?Yt(R,c,16,Z?q(c).limitLow:void 0,Z?q(c).limitHigh:void 0,S):null;if(ft&&(ft.style.display=$?"none":""),$){const{limitLow:s,limitHigh:u}=q(c);if(ht.sync(Z,s!==void 0||u!==void 0),$.series.length===0){Tt(y,"No parametric test data available for a histogram.");return}Vt($);return}const j=F!==null?[G[F]]:G,{unit:W,limitLow:D,limitHigh:E}=q(c),w=Ht(j,c),{min:K,max:tt}=At(j,c),et=l??Ot(K,tt,D,E);ht.sync(et,D!==void 0||E!==void 0);const nt=k?Rt(w):null,_=nt?{lo:Math.max(K,nt.lo),hi:Math.min(tt,nt.hi)}:void 0;U=_?w.filter(s=>s<_.lo||s>_.hi).length:0;const H=Xt(j,c,16,et?D:void 0,et?E:void 0,_);if(H.length===0){Tt(y,"No parametric test data available for a histogram.");return}const P=Math.max(re(H.map(s=>s.count)),1),X=r.ownerDocument.createElement("div");Object.assign(X.style,{fontSize:Ct.body,color:$t.label,marginBottom:st.xxs}),X.textContent=`max ${P} dies/bucket`+(U?` \xB7 ${U} value${U===1?"":"s"} outside clipped range`:""),y.appendChild(X);const T=r.ownerDocument.createElement("canvas");T.style.display="block",T.style.cursor="default",y.appendChild(T);let O=-1;const n=H[0].rangeLow,f=H[H.length-1].rangeHigh,g=f-n||1,t=Math.max(Math.abs(n),Math.abs(f));function d(s){const u=ut+36,i=T.clientWidth-u-ut,e=s-Kt-xt;return{plotX:u,plotMaxWidth:Math.max(10,i),plotMaxHeight:e,plotTop:xt}}function h(s){const{plotX:u,plotMaxWidth:i}=d(Lt);if(s<u||s>u+i)return-1;const e=Math.floor((s-u)/i*H.length);return e>=0&&e<H.length?e:-1}function p(){It(T,X);const s=y.clientWidth,u=Pt(r,y,T,Lt),i=Ft(T,r,s,u);if(!i)return;const{ctx:e,theme:x}=i,{plotX:b,plotMaxWidth:L,plotMaxHeight:it,plotTop:A}=d(u),a=A+it,v=L/H.length;Nt(e,b,A,a,L,P,{text:x.textMuted,axis:x.border,grid:x.border}),H.forEach((m,B)=>{const lt=m.count/P*it,St=b+B*v,wt=a-lt;e.fillStyle=B===O?x.text:Ut,e.globalAlpha=B===O?1:.7,e.fillRect(St+1,wt,Math.max(1,v-2),lt),e.globalAlpha=1,B===O&&(e.fillStyle=x.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${m.count}`,St+v/2,wt-2))});const N=m=>b+(m-n)/g*L;e.font=`${Y(-1)}px system-ui, sans-serif`;const{offAxis:ct}=le({dataMin:n,dataMax:f,limitLow:D,limitHigh:E,includeLimits:!1});oe(e,ct,{left:b,right:b+L,top:A,bottom:a},"horizontal",x.limitLine,vt(t,W).tick);const gt=new Set(ct.map(m=>m.value));for(const[m,B]of[[D,"Lo limit"],[E,"Hi limit"]]){if(m===void 0||gt.has(m))continue;const lt=N(m);e.strokeStyle=x.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(lt,A),e.lineTo(lt,a),e.stroke(),e.setLineDash([]),e.fillStyle=x.limitLine,e.textAlign="center",e.textBaseline="bottom",e.fillText(B,lt,A-2)}e.font=`${Y(-1)}px system-ui, sans-serif`,e.strokeStyle=x.border,e.lineWidth=1,e.beginPath(),e.moveTo(b,a),e.lineTo(b+L,a),e.stroke(),e.fillStyle=x.textMuted,e.textAlign="center",e.textBaseline="top";const{ticks:dt,step:ot}=Wt(n,f,L,Bt(e,t,W)),I=vt(t,W,ot||void 0);for(const m of dt){const B=b+(m-n)/g*L;e.beginPath(),e.moveTo(B,a),e.lineTo(B,a+4),e.stroke(),e.fillText(I.tick(m),B,a+6)}I.unitLabel&&(e.save(),e.font=`${Y(-1)}px system-ui, sans-serif`,e.fillStyle=x.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${I.unitLabel})`,b+L,a+20),e.restore())}T.addEventListener("mousemove",s=>{const u=T.getBoundingClientRect(),i=h(s.clientX-u.left);if(i!==O&&(O=i,p()),i>=0){const e=H[i];C.innerHTML=`<strong>${Mt(`${pt(e.rangeLow,W,"engineering")} \u2013 ${pt(e.rangeHigh,W,"engineering")}`)}</strong><br>${e.count} dies`,C.style.display="block",Et(C,r,s.clientX,s.clientY)}else C.style.display="none"}),T.addEventListener("mouseleave",()=>{O!==-1&&(O=-1,p()),C.style.display="none"}),rt=Dt(r,()=>p()),p()}function Vt(l){const{unit:k,limitLow:J,limitHigh:Z}=q(c),S=l.ranges,$=l.series,j=qt;M&&!$.some(n=>n.groupKey===M)&&(M=null);const W=Math.max(1,...$.flatMap(n=>n.counts)),D=r.ownerDocument.createElement("div");Object.assign(D.style,{fontSize:Ct.body,color:$t.label,marginBottom:st.xxs}),D.textContent=`${$.length} groups \xB7 max ${W} dies/bucket`,y.appendChild(D);const E=r.ownerDocument.createElement("div");Object.assign(E.style,{display:"flex",flexWrap:"wrap",gap:st.sm,marginTop:st.md,marginBottom:st.xl}),$.forEach((n,f)=>{const g=ae(n.groupKey,j(f),r.ownerDocument),t=g.el;te(t,r,C,`${n.groupKey} \u2014 click to emphasize (dim the rest)`),g.setState({selected:M===n.groupKey,dimmed:M!==null&&M!==n.groupKey}),t.addEventListener("click",()=>{const d=M===n.groupKey?null:n.groupKey;Gt(d),o.onGroupChange?.(d)}),E.appendChild(t)}),y.appendChild(E);const w=r.ownerDocument.createElement("canvas");w.style.display="block",w.style.cursor="default",y.appendChild(w);const K=S[0].rangeLow,tt=S[S.length-1].rangeHigh,et=tt-K||1,nt=Math.max(Math.abs(K),Math.abs(tt)),_=n=>{const f=(n.ownerDocument.defaultView??window).getComputedStyle(n);return n.offsetHeight+(parseFloat(f.marginTop)||0)+(parseFloat(f.marginBottom)||0)},H=()=>_(D)+_(E);let P=-1,X={plotX:ut+36,plotMaxWidth:10,barWidth:10};function T(){It(w,H());const n=y.clientWidth,f=Pt(r,y,w,Lt),g=Ft(w,r,n,f);if(!g)return;const{ctx:t,theme:d}=g,h=ut+36,p=Math.max(10,n-h-ut),s=xt,u=f-Kt-xt,i=s+u,e=p/S.length;X={plotX:h,plotMaxWidth:p,barWidth:e},Nt(t,h,s,i,p,W,{text:d.textMuted,axis:d.border,grid:d.border}),P>=0&&(t.fillStyle=d.bgHover,t.globalAlpha=.5,t.fillRect(h+P*e,s,e,u),t.globalAlpha=1);const x=$.map((a,v)=>({s:a,i:v})).sort((a,v)=>(a.s.groupKey===M?1:0)-(v.s.groupKey===M?1:0));for(const{s:a,i:v}of x){const N=M===a.groupKey,ct=M!==null&&!N,gt=j(v);t.strokeStyle=gt,t.fillStyle=gt,t.lineWidth=N?2:1,t.globalAlpha=ct?.12:M?.32:.22,t.beginPath(),t.moveTo(h,i),a.counts.forEach((dt,ot)=>{const I=h+ot*e,m=i-dt/W*u;t.lineTo(I,m),t.lineTo(I+e,m)}),t.lineTo(h+p,i),t.closePath(),t.fill(),t.globalAlpha=ct?.3:1,t.beginPath(),a.counts.forEach((dt,ot)=>{const I=h+ot*e,m=i-dt/W*u;ot===0?t.moveTo(I,m):t.lineTo(I,m),t.lineTo(I+e,m)}),t.stroke()}t.globalAlpha=1;const b=a=>h+(a-K)/et*p;t.font=`${Y(-1)}px system-ui, sans-serif`;for(const[a,v]of[[J,"Lo limit"],[Z,"Hi limit"]]){if(a===void 0)continue;const N=b(a);t.strokeStyle=d.limitLine,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(N,s),t.lineTo(N,i),t.stroke(),t.setLineDash([]),t.fillStyle=d.limitLine,t.textAlign="center",t.textBaseline="bottom",t.fillText(v,N,s-2)}t.font=`${Y(-1)}px system-ui, sans-serif`,t.strokeStyle=d.border,t.lineWidth=1,t.beginPath(),t.moveTo(h,i),t.lineTo(h+p,i),t.stroke(),t.fillStyle=d.textMuted,t.textAlign="center",t.textBaseline="top";const{ticks:L,step:it}=Wt(K,tt,p,Bt(t,nt,k)),A=vt(nt,k,it||void 0);for(const a of L){const v=h+(a-K)/et*p;t.beginPath(),t.moveTo(v,i),t.lineTo(v,i+4),t.stroke(),t.fillText(A.tick(a),v,i+6)}A.unitLabel&&(t.save(),t.font=`${Y(-1)}px system-ui, sans-serif`,t.fillStyle=d.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${A.unitLabel})`,h+p,i+20),t.restore())}function O(n){const{plotX:f,plotMaxWidth:g,barWidth:t}=X;if(n<f||n>f+g)return-1;const d=Math.floor((n-f)/t);return d>=0&&d<S.length?d:-1}w.addEventListener("mousemove",n=>{const f=w.getBoundingClientRect(),g=O(n.clientX-f.left);if(g!==P&&(P=g,T()),g>=0){const t=S[g],d=$.map((h,p)=>`<span style="${se(j(p))};margin-right:4px"></span>${Mt(`${h.groupKey}: ${h.counts[g]}`)}`).join("<br>");C.innerHTML=`<strong>${Mt(`${pt(t.rangeLow,k,"engineering")} \u2013 ${pt(t.rangeHigh,k,"engineering")}`)}</strong><br>${d}`,C.style.display="block",Et(C,r,n.clientX,n.clientY)}else C.style.display="none"}),w.addEventListener("mouseleave",()=>{P!==-1&&(P=-1,T()),C.style.display="none"}),rt=Dt(r,()=>T()),T()}V();function jt(l){kt.set(l)&&(c=l,V())}function _t(l){ht.set(l)&&V()}return{card:r,setTest:jt,setAxisPrefs:_t,destroy:()=>rt?.disconnect()}}
|
|
1
|
+
import{buildTestHistogramData as qt,collectTestValues as Ht,buildTestHistogramSeries as Jt,testValueExtent as At}from"../../stats/histogram.js";import{SPACE as lt,fontPx as R,FONT as Ct,CLR as $t}from"../toolbar.js";import{fmt as gt}from"../../renderer/fmt.js";import{niceStep as Zt,fitTicks as Et}from"../../renderer/axisTicks.js";import{QUANTITY as te,categorical as ee}from"./palette.js";import{cardShell as ne,observeResize as Wt,makeTooltip as ie,attachChartTip as oe,positionChartTooltip as Bt,makeLinkedTestSelect as se,makeWaferSelect as le,makeLinkedAxisPrefs as ae,renderEmptyState as yt,chartFillHeight as Dt,applyCanvasFlow as It,makeAxisFormat as Tt,horizontalTickSpacing as Pt,PADDING as mt,robustFence as Kt,shouldIncludeLimitsByDefault as Rt,drawOffAxisLimits as re,resolveAxisRange as ce,limitLines as Ot,limitExtent as de,hasBothLimitKinds as ue,stackLabelRows as me,strokeLimitLine as fe,chartSwatchCss as he,makeSeriesLegendItem as pe,prepareCanvas as Ft}from"./chartShell.js";import{escHtml as Mt,maxOf as ge}from"../../core/utils.js";const vt=230,Nt=36,xe=18;function zt(n){const C=new Set(n.map($=>$.kind)).size;return xe+Math.max(0,C-1)*(R(-1)+2)}function Gt(n,C,$,G,et,E,s){n.font=`${R(-1)}px system-ui, sans-serif`;const h=C.filter(i=>!$.has(i.value)).map(i=>{const b=G(i.value),W=n.measureText(i.label).width;return{l:i,x:b,start:b-W/2,end:b+W/2}}),U=me(h),nt=R(-1)+2;h.forEach(({l:i,x:b},W)=>{fe(n,i,s,b,et,b,E),n.fillStyle=s,n.textAlign="center",n.textBaseline="bottom",n.fillText(i.label,b,et-2-U[W]*nt)})}function Vt(n,C,$,G,et,E,s,h=4){const U=Math.max(1,Zt(E/h)),nt=G-$;n.save(),n.font=`${R(-1)}px system-ui, sans-serif`,n.textAlign="right",n.textBaseline="middle";for(let i=0;i<=E+1e-9;i+=U){const b=G-i/(E||1)*nt;n.strokeStyle=s.grid,n.lineWidth=1,n.beginPath(),n.moveTo(C,b),n.lineTo(C+et,b),n.stroke(),n.fillStyle=s.text,n.fillText(String(Math.round(i)),C-4,b)}n.strokeStyle=s.axis,n.lineWidth=1,n.beginPath(),n.moveTo(C,$),n.lineTo(C,G),n.stroke(),n.restore()}export function renderHistogramPanel(n){const{title:C="Test value distribution",items:$,testDefs:G,onSaveImage:et,groups:E}=n,{card:s,body:h,controlsRow:U}=ne(C,et,n.ownerDocument),nt=G.filter(a=>a.testNumber!==void 0);let i=n.selectedTestNumber??nt[0]?.testNumber??null,b=null,W=0;const kt=se(nt,i,a=>{i=a,q(),n.onTestChange?.(a)},{maxWidth:"200px",ownerDocument:s.ownerDocument}),jt=kt.el;U.appendChild(jt);const ft=$.length>1?le($,b,a=>{b=a,q()},{ownerDocument:s.ownerDocument}):null;ft&&U.appendChild(ft);function _t(a){return y===a||a!==null&&!(E??[]).some(T=>T.key===a)?!1:(y=a,q(),!0)}const xt=s.ownerDocument.createElement("span");Object.assign(xt.style,{display:"inline-flex",gap:lt.lg,alignItems:"center"}),U.appendChild(xt);const at=ae(xt,n.axisPrefs,a=>{n.onAxisPrefsChange?.(a),q()},s.ownerDocument),B=ie(s);let rt=null,y=null;function it(a){const T=G.find(D=>D.testNumber===a),V=Ot(T,at.get().limits),{lo:w,hi:M}=de(V);return{unit:T?.unit,lines:V,limitLow:w,limitHigh:M,hasLimits:Ot(T).length>0,hasBothKinds:ue(T)}}function q(){const{includeLimits:a,clipOutliers:T}=at.get();if(rt?.disconnect(),rt=null,h.innerHTML="",i===null){yt(h,"No parametric test data available for a histogram.");return}const V=!!E&&E.length>0;let w=!1,M;if(V){const{limitLow:d,limitHigh:g}=it(i),x=E.flatMap(l=>l.items),{min:e,max:f}=At(x,i);if(w=a??Rt(e,f,d,g),T){const l=Ht(x,i),u=Kt(l);u&&(M={lo:Math.max(e,u.lo),hi:Math.min(f,u.hi)},W=l.reduce((Z,K)=>Z+(K<M.lo||K>M.hi?1:0),0))}M||(W=0)}const D=V?Jt(E,i,16,w?it(i).limitLow:void 0,w?it(i).limitHigh:void 0,M):null;if(ft&&(ft.style.display=D?"none":""),D){const{hasLimits:d,hasBothKinds:g}=it(i);if(at.sync(w,d,g),D.series.length===0){yt(h,"No parametric test data available for a histogram.");return}Xt(D);return}const j=b!==null?[$[b]]:$,{unit:I,lines:_,limitLow:S,limitHigh:X,hasLimits:ct,hasBothKinds:ht}=it(i),dt=Ht(j,i),{min:ut,max:pt}=At(j,i),P=a??Rt(ut,pt,S,X);at.sync(P,ct,ht);const ot=T?Kt(dt):null,O=ot?{lo:Math.max(ut,ot.lo),hi:Math.min(pt,ot.hi)}:void 0;W=O?dt.filter(d=>d<O.lo||d>O.hi).length:0;const H=qt(j,i,16,P?S:void 0,P?X:void 0,O);if(H.length===0){yt(h,"No parametric test data available for a histogram.");return}const o=Math.max(ge(H.map(d=>d.count)),1),p=s.ownerDocument.createElement("div");Object.assign(p.style,{fontSize:Ct.body,color:$t.label,marginBottom:lt.xxs}),p.textContent=`max ${o} dies/bucket`+(W?` \xB7 ${W} value${W===1?"":"s"} outside clipped range`:""),h.appendChild(p);const r=s.ownerDocument.createElement("canvas");r.style.display="block",r.style.cursor="default",h.appendChild(r);let t=-1;const c=H[0].rangeLow,m=H[H.length-1].rangeHigh,v=m-c||1,F=Math.max(Math.abs(c),Math.abs(m));function J(d){const g=mt+36,x=r.clientWidth-g-mt,e=zt(_),f=d-Nt-e;return{plotX:g,plotMaxWidth:Math.max(10,x),plotMaxHeight:f,plotTop:e}}function k(d){const{plotX:g,plotMaxWidth:x}=J(vt);if(d<g||d>g+x)return-1;const e=Math.floor((d-g)/x*H.length);return e>=0&&e<H.length?e:-1}function A(){It(r,p);const d=h.clientWidth,g=Dt(s,h,r,vt),x=Ft(r,s,d,g);if(!x)return;const{ctx:e,theme:f}=x,{plotX:l,plotMaxWidth:u,plotMaxHeight:Z,plotTop:K}=J(g),L=K+Z,tt=u/H.length;Vt(e,l,K,L,u,o,{text:f.textMuted,axis:f.border,grid:f.grid}),H.forEach((z,Q)=>{const wt=z.count/o*Z,Lt=l+Q*tt,St=L-wt;e.fillStyle=Q===t?f.text:te,e.globalAlpha=Q===t?1:.7,e.fillRect(Lt+1,St,Math.max(1,tt-2),wt),e.globalAlpha=1,Q===t&&(e.fillStyle=f.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${z.count}`,Lt+tt/2,St-2))});const st=z=>l+(z-c)/v*u;e.font=`${R(-1)}px system-ui, sans-serif`;const{offAxis:N}=ce({dataMin:c,dataMax:m,limits:_,includeLimits:!1});re(e,N,{left:l,right:l+u,top:K,bottom:L},"horizontal",f.limitLine,Tt(F,I).tick),Gt(e,_,new Set(N.map(z=>z.value)),st,K,L,f.limitLine),e.font=`${R(-1)}px system-ui, sans-serif`,e.strokeStyle=f.border,e.lineWidth=1,e.beginPath(),e.moveTo(l,L),e.lineTo(l+u,L),e.stroke(),e.fillStyle=f.textMuted,e.textAlign="center",e.textBaseline="top";const{ticks:Y,step:Ut}=Et(c,m,u,Pt(e,F,I)),bt=Tt(F,I,Ut||void 0);for(const z of Y){const Q=l+(z-c)/v*u;e.beginPath(),e.moveTo(Q,L),e.lineTo(Q,L+4),e.stroke(),e.fillText(bt.tick(z),Q,L+6)}bt.unitLabel&&(e.save(),e.font=`${R(-1)}px system-ui, sans-serif`,e.fillStyle=f.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${bt.unitLabel})`,l+u,L+20),e.restore())}r.addEventListener("mousemove",d=>{const g=r.getBoundingClientRect(),x=k(d.clientX-g.left);if(x!==t&&(t=x,A()),x>=0){const e=H[x];B.innerHTML=`<strong>${Mt(`${gt(e.rangeLow,I,"engineering")} \u2013 ${gt(e.rangeHigh,I,"engineering")}`)}</strong><br>${e.count} dies`,B.style.display="block",Bt(B,s,d.clientX,d.clientY)}else B.style.display="none"}),r.addEventListener("mouseleave",()=>{t!==-1&&(t=-1,A()),B.style.display="none"}),rt=Wt(s,()=>A()),A()}function Xt(a){const{unit:T,lines:V}=it(i),w=a.ranges,M=a.series,D=ee;y&&!M.some(o=>o.groupKey===y)&&(y=null);const j=Math.max(1,...M.flatMap(o=>o.counts)),I=s.ownerDocument.createElement("div");Object.assign(I.style,{fontSize:Ct.body,color:$t.label,marginBottom:lt.xxs}),I.textContent=`${M.length} groups \xB7 max ${j} dies/bucket`,h.appendChild(I);const _=s.ownerDocument.createElement("div");Object.assign(_.style,{display:"flex",flexWrap:"wrap",gap:lt.sm,marginTop:lt.md,marginBottom:lt.xl}),M.forEach((o,p)=>{const r=pe(o.groupKey,D(p),s.ownerDocument),t=r.el;oe(t,s,B,`${o.groupKey} \u2014 click to emphasize (dim the rest)`),r.setState({selected:y===o.groupKey,dimmed:y!==null&&y!==o.groupKey}),t.addEventListener("click",()=>{const c=y===o.groupKey?null:o.groupKey;_t(c),n.onGroupChange?.(c)}),_.appendChild(t)}),h.appendChild(_);const S=s.ownerDocument.createElement("canvas");S.style.display="block",S.style.cursor="default",h.appendChild(S);const X=w[0].rangeLow,ct=w[w.length-1].rangeHigh,ht=ct-X||1,dt=Math.max(Math.abs(X),Math.abs(ct)),ut=o=>{const p=(o.ownerDocument.defaultView??window).getComputedStyle(o);return o.offsetHeight+(parseFloat(p.marginTop)||0)+(parseFloat(p.marginBottom)||0)},pt=()=>ut(I)+ut(_);let P=-1,ot={plotX:mt+36,plotMaxWidth:10,barWidth:10};function O(){It(S,pt());const o=h.clientWidth,p=Dt(s,h,S,vt),r=Ft(S,s,o,p);if(!r)return;const{ctx:t,theme:c}=r,m=mt+36,v=Math.max(10,o-m-mt),F=zt(V),J=p-Nt-F,k=F+J,A=v/w.length;ot={plotX:m,plotMaxWidth:v,barWidth:A},Vt(t,m,F,k,v,j,{text:c.textMuted,axis:c.border,grid:c.grid}),P>=0&&(t.fillStyle=c.bgHover,t.globalAlpha=.5,t.fillRect(m+P*A,F,A,J),t.globalAlpha=1);const d=M.map((l,u)=>({s:l,i:u})).sort((l,u)=>(l.s.groupKey===y?1:0)-(u.s.groupKey===y?1:0));for(const{s:l,i:u}of d){const Z=y===l.groupKey,K=y!==null&&!Z,L=D(u);t.strokeStyle=L,t.fillStyle=L,t.lineWidth=Z?2:1,t.globalAlpha=K?.12:y?.32:.22,t.beginPath(),t.moveTo(m,k),l.counts.forEach((tt,st)=>{const N=m+st*A,Y=k-tt/j*J;t.lineTo(N,Y),t.lineTo(N+A,Y)}),t.lineTo(m+v,k),t.closePath(),t.fill(),t.globalAlpha=K?.3:1,t.beginPath(),l.counts.forEach((tt,st)=>{const N=m+st*A,Y=k-tt/j*J;st===0?t.moveTo(N,Y):t.lineTo(N,Y),t.lineTo(N+A,Y)}),t.stroke()}t.globalAlpha=1;const g=l=>m+(l-X)/ht*v;t.font=`${R(-1)}px system-ui, sans-serif`,Gt(t,V,new Set,g,F,k,c.limitLine),t.font=`${R(-1)}px system-ui, sans-serif`,t.strokeStyle=c.border,t.lineWidth=1,t.beginPath(),t.moveTo(m,k),t.lineTo(m+v,k),t.stroke(),t.fillStyle=c.textMuted,t.textAlign="center",t.textBaseline="top";const{ticks:x,step:e}=Et(X,ct,v,Pt(t,dt,T)),f=Tt(dt,T,e||void 0);for(const l of x){const u=m+(l-X)/ht*v;t.beginPath(),t.moveTo(u,k),t.lineTo(u,k+4),t.stroke(),t.fillText(f.tick(l),u,k+6)}f.unitLabel&&(t.save(),t.font=`${R(-1)}px system-ui, sans-serif`,t.fillStyle=c.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${f.unitLabel})`,m+v,k+20),t.restore())}function H(o){const{plotX:p,plotMaxWidth:r,barWidth:t}=ot;if(o<p||o>p+r)return-1;const c=Math.floor((o-p)/t);return c>=0&&c<w.length?c:-1}S.addEventListener("mousemove",o=>{const p=S.getBoundingClientRect(),r=H(o.clientX-p.left);if(r!==P&&(P=r,O()),r>=0){const t=w[r],c=M.map((m,v)=>`<span style="${he(D(v))};margin-right:4px"></span>${Mt(`${m.groupKey}: ${m.counts[r]}`)}`).join("<br>");B.innerHTML=`<strong>${Mt(`${gt(t.rangeLow,T,"engineering")} \u2013 ${gt(t.rangeHigh,T,"engineering")}`)}</strong><br>${c}`,B.style.display="block",Bt(B,s,o.clientX,o.clientY)}else B.style.display="none"}),S.addEventListener("mouseleave",()=>{P!==-1&&(P=-1,O()),B.style.display="none"}),rt=Wt(s,()=>O()),O()}q();function Yt(a){kt.set(a)&&(i=a,q())}function Qt(a){at.set(a)&&q()}return{card:s,setTest:Yt,setAxisPrefs:Qt,destroy:()=>rt?.disconnect()}}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ScatterItem, type ScatterPoint } from '../../stats/scatter.js';
|
|
2
2
|
import { type FacetItem } from '../../stats/facets.js';
|
|
3
3
|
import type { TestDef } from '../../renderer/buildWaferMap.js';
|
|
4
|
-
import { type SaveImageHandler } from './chartShell.js';
|
|
4
|
+
import { type SaveImageHandler, type AxisPrefs } from './chartShell.js';
|
|
5
5
|
type ScatterPanelItem = FacetItem & ScatterItem & {
|
|
6
6
|
label?: string;
|
|
7
7
|
};
|
|
@@ -34,6 +34,14 @@ export interface ScatterPanelOptions {
|
|
|
34
34
|
key: string;
|
|
35
35
|
items: ScatterPanelItem[];
|
|
36
36
|
}[];
|
|
37
|
+
/**
|
|
38
|
+
* The Insights tab's shared axis preferences. Scatter reads only `limits`
|
|
39
|
+
* (which limit lines to draw), and offers the "Limits:" choice when either
|
|
40
|
+
* axis's test has both test and spec limits.
|
|
41
|
+
*/
|
|
42
|
+
axisPrefs?: AxisPrefs;
|
|
43
|
+
/** Called when the user changes the "Limits:" choice here. */
|
|
44
|
+
onAxisPrefsChange?: (prefs: AxisPrefs) => void;
|
|
37
45
|
/** Document to build this panel's DOM into. Default `document` — pass the
|
|
38
46
|
* host's own `ownerDocument` when the container might live in a
|
|
39
47
|
* different document (e.g. a gallery card detached into its own popup
|
|
@@ -44,6 +52,8 @@ export interface ScatterPanelHandle {
|
|
|
44
52
|
card: HTMLElement;
|
|
45
53
|
/** Cross-panel link (e.g. from the correlation matrix): switch X/Y in place. */
|
|
46
54
|
setXY: (xTestNumber: number, yTestNumber: number) => void;
|
|
55
|
+
/** Adopt axis preferences chosen in another panel (only `limits` applies). */
|
|
56
|
+
setAxisPrefs: (prefs: AxisPrefs) => void;
|
|
47
57
|
destroy: () => void;
|
|
48
58
|
}
|
|
49
59
|
export declare function renderScatterPanel(options: ScatterPanelOptions): ScatterPanelHandle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{NO_DATA_FILL as
|
|
1
|
+
import{NO_DATA_FILL as Ce}from"../../renderer/colorMap.js";import{maxOf as J,minOf as Le}from"../../core/utils.js";import{categorical as Xe}from"./palette.js";import{buildScatterData as Ye,buildScatterDataGrouped as Ke}from"../../stats/scatter.js";import{pearsonOfPairs as Ve}from"../../stats/correlation.js";import{buildFacetTable as qe}from"../../stats/facets.js";import{SPACE as k,RADIUS as Je,fontPx as L,FONT as re,CLR as X}from"../toolbar.js";import{fitTicks as Ae}from"../../renderer/axisTicks.js";import{cardShell as Qe,observeResize as Ze,makeTooltip as et,attachChartTip as tt,makeTestSelect as nt,makeWaferSelect as it,chartFillHeight as ot,applyCanvasFlow as st,drawAxisUnit as lt,resolveChartCanvasColors as at,makeAxisFormat as Q,horizontalTickSpacing as rt,VERTICAL_TICK_SPACING_PX as ct,makeSeriesLegendItem as ft,prepareCanvas as dt,drawOffAxisLimits as Oe,limitLines as mt,hasBothLimitKinds as ut,makeLimitsSelect as pt,stackLabelRows as Pe,strokeLimitLine as De,fillTextOnHalo as Be}from"./chartShell.js";const c=52,xt=16,l=16,ht=44;export const NO_BIN_CATEGORY="none";export function binCategoryOf(h){return h===void 0?NO_BIN_CATEGORY:String(h)}export function binCategories(h){const Y=new Set;let S=!1;for(const O of h)O.hbin===void 0?S=!0:Y.add(O.hbin);const A=Array.from(Y).sort((O,Z)=>O-Z).map(String);return S&&A.push(NO_BIN_CATEGORY),A}export function renderScatterPanel(h){const{title:Y="Test scatter",items:S,testDefs:A,binColors:O,onSaveImage:Z,groups:K}=h,{card:s,body:P,controlsRow:ee}=Qe(Y,Z,h.ownerDocument),V=A.filter(e=>e.testNumber!==void 0),v=!!(K&&K.length>0),ce=(K??[]).map(e=>e.key),Ee=new Map(ce.map((e,n)=>[e,n]));let p=h.xTestNumber??V[0]?.testNumber??null,b=h.yTestNumber??V[1]?.testNumber??p,R=null;const fe=()=>R!==null?S[R]?[S[R]]:[]:S;function de(e,n,o){const r=s.ownerDocument.createElement("label");Object.assign(r.style,{display:"inline-flex",alignItems:"center",gap:k.xs,fontSize:re.body,color:X.label});const T=s.ownerDocument.createElement("span");T.textContent=e;const d=nt(V,n,o,{maxWidth:"180px",emptyText:"No tests",ownerDocument:s.ownerDocument});return r.append(T,d),{wrap:r,select:d}}const{wrap:$e,select:Re}=de("X:",p,e=>{p=e,F()}),{wrap:Ie,select:We}=de("Y:",b,e=>{b=e,F()});ee.append($e,Ie);let M=h.axisPrefs??{clipOutliers:!1};const me=s.ownerDocument.createElement("span");ee.appendChild(me);function ue(){const e=[p,b].map(n=>A.find(o=>o.testNumber===n));me.replaceChildren(...e.some(ut)?[pt(M.limits??"both",n=>{M={...M,limits:n},h.onAxisPrefsChange?.(M),E()},s.ownerDocument)]:[])}!v&&S.length>1&&ee.appendChild(it(S,R,e=>{R=e,F()},{ownerDocument:s.ownerDocument}));const I=s.ownerDocument.createElement("div");Object.assign(I.style,{color:X.warnText,background:X.warnBg,border:`1px solid ${X.warnBorder}`,borderRadius:Je.control,padding:`${k.xs} ${k.md}`,fontSize:re.body,marginBottom:k.xs,display:"none"}),s.insertBefore(I,P);function pe(){const e=v?[]:qe(fe(),{facetableOnly:!0}).filter(n=>n.splittable).map(n=>n.label);e.length>0?(I.textContent=`\u26A0 Mixed ${e.join(", ")} across these points \u2014 trends here may be confounded (Simpson's paradox). Use Group by, or the Wafer picker, to narrow to a like-for-like set.`,I.style.display=""):I.style.display="none"}pe();const te=s.ownerDocument.createElement("div");Object.assign(te.style,{color:X.label,fontSize:re.body,marginBottom:k.xs}),s.insertBefore(te,P);function ne(){const e=v?"One point per die \xB7 coloured by group \xB7 click legend to filter":"One point per die across all wafers \xB7 coloured by hard bin \xB7 click legend to filter",n=g.size===0?x:x.filter(T=>g.has(q(T))),{r:o,n:r}=Ve(n);te.textContent=o===null?`${e} \xB7 n = ${r.toLocaleString()}`:`${e} \xB7 r = ${o.toFixed(3)} \xB7 n = ${r.toLocaleString()}`}const q=e=>v?e.group??"\u2014":binCategoryOf(e.hbin),xe=e=>v?Xe(Ee.get(e)??0):e===NO_BIN_CATEGORY?Ce:O?.get(Number(e))??Ce,he=e=>v?e:e===NO_BIN_CATEGORY?"No bin data":`Bin ${e}`,g=new Set,W=s.ownerDocument.createElement("div");Object.assign(W.style,{display:"flex",flexWrap:"wrap",gap:k.sm,marginTop:k.md,marginBottom:k.xl}),P.appendChild(W);const D=s.ownerDocument.createElement("canvas");D.style.display="block",D.style.cursor="crosshair",P.appendChild(D);const Ne=et(s);let x=[],y=0,C=1,w=0,B=1;function N(e){const n=A.find(o=>o.testNumber===e);return{unit:n?.unit,lines:mt(n,M.limits)}}function Fe(){const e=P.clientWidth,n=Math.max(200,Math.min(400,e*.65)),o=ot(s,P,D,n);return{w:e,h:o,plotW:Math.max(10,e-c-xt),plotH:Math.max(10,o-l-ht)}}const ie=new Map;function be(){for(const[e,n]of ie){const o=g.has(e);n.setState({selected:o,dimmed:g.size>0&&!o})}}function ge(e){W.innerHTML="",ie.clear(),g.clear();for(const n of e){const o=ft(he(n),xe(n),s.ownerDocument),r=o.el;r.dataset.cat=n,ie.set(n,o),tt(r,s,Ne,`${he(n)} \u2014 click to filter`),r.addEventListener("click",()=>{g.has(n)?g.delete(n):g.add(n),be(),ne(),E()}),W.appendChild(r)}be()}function E(){st(D,W);const e=at(s),n=C-y,o=B-w,{w:r,h:T,plotW:d,plotH:f}=Fe(),H=dt(D,s,r,T);if(!H)return;const{ctx:t}=H;if(x.length===0){t.font=`${L()}px system-ui, sans-serif`,t.fillStyle=e.textMuted,t.textAlign="center",t.textBaseline="middle",t.fillText("No data \u2014 select two parametric tests with values.",r/2,T/2);return}t.font=`${L(-1)}px system-ui, sans-serif`;const _=p!==null?N(p).unit:void 0,ye=Ae(y,C,d,rt(t,Math.max(Math.abs(y),Math.abs(C)),_)),Te=Ae(w,B,f,()=>ct),oe=Q(Math.max(Math.abs(y),Math.abs(C)),p!==null?N(p).unit:void 0,ye.step||void 0),se=Q(Math.max(Math.abs(w),Math.abs(B)),b!==null?N(b).unit:void 0,Te.step||void 0);t.font=`${L(-1)}px system-ui, sans-serif`,t.strokeStyle=e.grid,t.lineWidth=.5,t.fillStyle=e.textMuted;for(const m of ye.ticks){const u=c+(m-y)/n*d;t.beginPath(),t.moveTo(u,l),t.lineTo(u,l+f),t.stroke(),t.textAlign="center",t.textBaseline="top",t.fillText(oe.tick(m),u,l+f+4)}for(const m of Te.ticks){const u=l+(1-(m-w)/o)*f;t.beginPath(),t.moveTo(c,u),t.lineTo(c+d,u),t.stroke(),t.textAlign="right",t.textBaseline="middle",t.fillText(se.tick(m),c-4,u)}oe.unitLabel&<(t,oe.unitLabel,c+d/2,l+f+24,e.textMuted),se.unitLabel&&(t.save(),t.font=`${L(-1)}px system-ui, sans-serif`,t.fillStyle=e.textMuted,t.textAlign="center",t.textBaseline="middle",t.translate(6,l+f/2),t.rotate(-Math.PI/2),t.fillText(`(${se.unitLabel})`,0,0),t.restore());const z=g.size===0?x:x.filter(m=>g.has(q(m))),we=z.length>5e3?Math.ceil(z.length/5e3):1;t.globalAlpha=Math.max(.15,Math.min(.7,200/(z.length/we)));for(let m=0;m<z.length;m+=we){const u=z[m],G=c+(u.x-y)/n*d,j=l+(1-(u.y-w)/o)*f;t.beginPath(),t.arc(G,j,2.5,0,Math.PI*2),t.fillStyle=xe(q(u)),t.fill()}if(t.globalAlpha=1,p!==null&&b!==null){const m=N(p),u=N(b);t.strokeStyle=e.limitLine,t.fillStyle=e.limitLine,t.lineWidth=1,t.font=`${L(-1)}px system-ui, sans-serif`,t.textBaseline="top";const G=L(-1)+2,j=m.lines.filter(i=>i.value>=y&&i.value<=C).map(i=>{const a=c+(i.value-y)/n*d;return{l:i,cx:a,start:a+2,end:a+2+t.measureText(i.label).width}}),Se=Pe(j);j.forEach(({l:i,cx:a},U)=>{De(t,i,e.limitLine,a,l,a,l+f),t.textAlign="left",Be(t,i.label,a+2,l+2+Se[U]*G,e.bg)});const le=u.lines.filter(i=>i.value>=w&&i.value<=B).map(i=>{const a=l+(1-(i.value-w)/o)*f;return{l:i,cy:a,start:a+2,end:a+2+L(-1)}}),Ge=Pe(le,1),je=J(le.map(i=>t.measureText(i.l.label).width))+8;le.forEach(({l:i,cy:a},U)=>{De(t,i,e.limitLine,c,a,c+d,a),t.textAlign="left",Be(t,i.label,c+3+Ge[U]*je,a+2,e.bg)});const ve=(i,a,U)=>i.filter($=>$.value<a||$.value>U).map($=>({value:$.value,label:$.label,side:$.value<a?"lo":"hi"})),Me={left:c,right:c+d,top:l,bottom:l+f},ae=ve(m.lines,y,C),ke=l+(j.length?(J(Se)+1)*G:0),Ue=Math.max(ae.filter(i=>i.side==="lo").length,ae.filter(i=>i.side==="hi").length);Oe(t,ae,{...Me,top:ke},"horizontal",e.limitLine,i=>Q(Math.abs(i),m.unit).tick(i),e.bg),Oe(t,ve(u.lines,w,B),{...Me,top:ke+Ue*G},"vertical",e.limitLine,i=>Q(Math.abs(i),u.unit).tick(i),e.bg),t.setLineDash([])}t.strokeStyle=e.border,t.lineWidth=1,t.beginPath(),t.moveTo(c,l),t.lineTo(c,l+f),t.moveTo(c,l+f),t.lineTo(c+d,l+f),t.stroke()}function F(){if(pe(),ue(),V.length<2||p===null||b===null){x=[],ge([]),ne(),E();return}x=v?Ke(K,p,b):Ye(fe(),p,b);let e;if(v){const n=new Set(x.map(q));e=ce.filter(o=>n.has(o))}else e=binCategories(x);if(ge(e),ne(),x.length>0){const n=x.map(_=>_.x),o=x.map(_=>_.y),r=Le(n),T=J(n),d=Le(o),f=J(o),H=(T-r)*.05||1,t=(f-d)*.05||1;y=r-H,C=T+H,w=d-t,B=f+t}E()}const He=Ze(s,()=>E());F();function _e(e,n){p=e,b=n,Re.value=String(e),We.value=String(n),F()}function ze(e){if((e.limits??"both")===(M.limits??"both")){M=e;return}M=e,ue(),E()}return{card:s,setXY:_e,setAxisPrefs:ze,destroy:()=>He.disconnect()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestTrendData as
|
|
1
|
+
import{buildTestTrendData as ke,trendCentre as we}from"../../stats/trend.js";import{isParametricTest as Ce}from"../../renderer/buildWaferMap.js";import{fmt as W}from"../../renderer/fmt.js";import{SPACE as re,fontPx as ce,FONT as Ae,CLR as Me}from"../toolbar.js";import{QUANTITY as G}from"./palette.js";import{cardShell as Pe,observeResize as Se,makeTooltip as Ne,positionChartTooltip as Ee,makeLinkedTestSelect as Ie,makeLinkedAxisPrefs as De,renderEmptyState as U,chartFillHeight as Oe,PADDING as j,resolveAxisRange as We,shouldIncludeLimitsByDefault as He,drawOffAxisLimits as $e,limitLines as me,limitExtent as de,hasBothLimitKinds as Re,stackLabelRows as Be,strokeLimitLine as Fe,makeAxisFormat as _e,VERTICAL_TICK_SPACING_PX as Xe,WAFER_MENU_HINT as ze,prepareCanvas as Ge}from"./chartShell.js";import{fitTicks as Ue}from"../../renderer/axisTicks.js";import{escHtml as K}from"../../core/utils.js";import{maxOf as je}from"../../core/utils.js";const fe=220,Ke=56,Ye=34,Qe=3.5;export function renderTrendPanel(d){const{items:Y,testDefs:ue,onSaveImage:he,onOpen:C,centreLabel:Q="mean of all wafers"}=d,pe=d.title??"Wafer-to-wafer trend",{card:a,body:u,controlsRow:V}=Pe(pe,he,d.ownerDocument);a.style.alignSelf="start";const N=ue.filter(s=>s.testNumber!==void 0&&Ce(s));let h=d.selectedTestNumber??N[0]?.testNumber??null,H=0;const q=Ie(N,h,s=>{h=s,A(),d.onTestChange?.(s)},{maxWidth:"240px",emptyText:"No parametric tests",ownerDocument:a.ownerDocument});V.appendChild(q.el);const $=a.ownerDocument.createElement("span");Object.assign($.style,{display:"inline-flex",gap:re.lg,alignItems:"center"}),V.appendChild($);const R=De($,d.axisPrefs,s=>{d.onAxisPrefsChange?.(s),A()},a.ownerDocument),w=a.ownerDocument.createElement("div");Object.assign(w.style,{color:Me.label,fontSize:Ae.body,marginBottom:re.sm}),a.insertBefore(w,u);const b=Ne(a);let E=null,i=[];function xe(s){return N.find(B=>B.testNumber===s)}function A(){const{includeLimits:s,clipOutliers:B,limits:be}=R.get();if(u.innerHTML="",E?.disconnect(),E=null,N.length===0||h===null){w.textContent="",U(u,"No parametric test data available for a trend.");return}if(Y.length<2){w.textContent="",U(u,"A wafer-to-wafer trend needs at least two wafers.");return}if(i=ke(Y,h),!i.some(o=>o.count>0)){w.textContent="",U(u,"No values recorded for this test.");return}const p=xe(h),I=me(p,be),g=we(i),J=()=>{w.textContent=`Point = wafer mean, whisker = \xB11\u03C3, dashed = ${Q} \xB7 wafers in slot order`+(C?" \xB7 click a point to open that wafer":"")+(H?` \xB7 axis clipped, ${H} outside`:"")};J();const m=a.ownerDocument.createElement("canvas");m.style.display="block",u.appendChild(m);let M=null;const Z=()=>{const o=Math.max(1,u.clientWidth),r=Math.max(fe,Oe(a,u,m,fe)),n=Ge(m,a,o,r);if(!n)return;const{ctx:e,theme:y}=n;let P=1/0,S=-1/0;const ee=[];for(const t of i)!t.count||!Number.isFinite(t.mean)||(P=Math.min(P,t.mean-t.stddev),S=Math.max(S,t.mean+t.stddev),ee.push(t.mean-t.stddev,t.mean+t.stddev));if(!Number.isFinite(P)||!Number.isFinite(S))return;const te=s??He(P,S,de(I).lo,de(I).hi);R.sync(te,me(p).length>0,Re(p));const D=We({dataMin:P,dataMax:S,limits:I,includeLimits:te,clipOutliers:B,values:ee});H=D.clippedCount,J();let x=D.lo,v=D.hi;const ne=(v-x)*.08;x-=ne,v+=ne;const c=j+Ke;e.font=`${ce(-1)}px system-ui, sans-serif`;const ie=t=>t.length>8?`${t.slice(0,7)}\u2026`:t,ge=i.length?Math.ceil(Math.max(e.measureText(ie(i[0].label)).width,e.measureText(ie(i[i.length-1].label)).width)/2):0,T=o-j-ge,O=j,L=r-Ye,k=t=>L-(t-x)/(v-x)*(L-O),se=i.length>1?(T-c)/(i.length-1):0;M={left:c,step:se};const _=t=>i.length>1?c+t*se:(c+T)/2;e.strokeStyle=y.border,e.lineWidth=1,e.beginPath(),e.moveTo(c,O),e.lineTo(c,L),e.lineTo(T,L),e.stroke(),e.fillStyle=y.text,e.textAlign="right",e.textBaseline="middle";const oe=Ue(x,v,L-O,()=>Xe),ye=_e(Math.max(Math.abs(x),Math.abs(v)),p?.unit,oe.step||void 0);for(const t of oe.ticks)e.fillText(ye.tickWithUnit(t),c-6,k(t));$e(e,D.offAxis,{left:c,right:T,top:O,bottom:L},"vertical",y.limitLine,t=>W(t,p?.unit));const le=ce(-1),X=I.filter(t=>t.value>=x&&t.value<=v).map(t=>{const l=k(t.value);return{l:t,y:l,start:l-6-le/2,end:l-6+le/2}}),ve=Be(X,1),Te=je(X.map(t=>e.measureText(t.l.label).width))+8;if(X.forEach(({l:t,y:l},f)=>{e.save(),e.globalAlpha=.7,Fe(e,t,y.limitLine,c,l,T,l),e.restore(),e.save(),e.fillStyle=y.limitLine,e.textAlign="left",e.fillText(t.label,c+3+ve[f]*Te,l-6),e.restore()}),g!==null&&g>=x&&g<=v){const t=k(g);e.save(),e.strokeStyle=y.text,e.globalAlpha=.5,e.setLineDash([3,3]),e.beginPath(),e.moveTo(c,t),e.lineTo(T,t),e.stroke(),e.restore()}e.strokeStyle=G,e.lineWidth=1.5,e.beginPath();let z=!1;i.forEach((t,l)=>{if(!t.count||!Number.isFinite(t.mean)){z=!1;return}const f=_(l),ae=k(t.mean);z?e.lineTo(f,ae):e.moveTo(f,ae),z=!0}),e.stroke(),i.forEach((t,l)=>{if(!t.count||!Number.isFinite(t.mean))return;const f=_(l);t.stddev>0&&(e.save(),e.strokeStyle=G,e.globalAlpha=.55,e.lineWidth=1,e.beginPath(),e.moveTo(f,k(t.mean-t.stddev)),e.lineTo(f,k(t.mean+t.stddev)),e.stroke(),e.restore()),e.fillStyle=G,e.beginPath(),e.arc(f,k(t.mean),Qe,0,Math.PI*2),e.fill()}),e.fillStyle=y.text,e.textBaseline="top",e.textAlign="center";const Le=Math.max(1,Math.ceil(i.length*42/Math.max(1,T-c)));i.forEach((t,l)=>{if(l%Le!==0&&l!==i.length-1)return;const f=t.label.length>8?`${t.label.slice(0,7)}\u2026`:t.label;e.fillText(f,_(l),L+6)})},F=o=>{if(!M||M.step<=0)return-1;const r=m.getBoundingClientRect(),n=Math.round((o.clientX-r.left-M.left)/M.step);return n>=0&&n<i.length&&i[n].count>0?n:-1};m.addEventListener("mousemove",o=>{const r=F(o);if(r<0){b.style.display="none",m.style.cursor="default";return}const n=i[r];m.style.cursor=C&&n.key!==void 0?"pointer":"default",b.innerHTML=`<strong>${K(n.label)}</strong><br>`+K(`mean ${W(n.mean,p?.unit)} \xB7 \u03C3 ${W(n.stddev,p?.unit)}`)+`<br>n = ${n.count.toLocaleString()}`+(g!==null?"<br>"+K(`\u0394 vs ${Q} ${n.mean-g>=0?"+":""}${W(n.mean-g,p?.unit)}`):"")+(C&&n.key!==void 0?"<br><em>click to open this wafer</em>":"")+(d.onWaferContextMenu&&n.key!==void 0?ze:""),b.style.display="block",Ee(b,a,o.clientX,o.clientY)}),m.addEventListener("mouseleave",()=>{b.style.display="none"}),m.addEventListener("click",o=>{const r=F(o);if(r<0||!C)return;const n=i[r];n.key!==void 0&&h!==null&&C(n.key,h)}),m.addEventListener("contextmenu",o=>{const r=F(o),n=r<0?void 0:i[r].key;n===void 0||!d.onWaferContextMenu||(b.style.display="none",d.onWaferContextMenu(n,h??void 0,o))}),Z(),E=Se(u,Z)}return A(),{card:a,destroy:()=>{E?.disconnect(),b.remove(),a.remove()},setTest:s=>{q.set(s)&&(h=s,A())},setAxisPrefs:s=>{R.set(s)&&A()}}}
|
|
@@ -15,7 +15,7 @@ export interface DieListDisplayOptions {
|
|
|
15
15
|
* Show a "View die list" link inside the Summary panel (`renderWaferMap`)
|
|
16
16
|
* / lot Summary panel (`renderWaferGallery`) that opens this wafer's — or,
|
|
17
17
|
* in the gallery, the whole lot's — dies as a table in a modal, with the
|
|
18
|
-
* same "Export CSV" button
|
|
18
|
+
* same "Export CSV" button every die list has. **Default
|
|
19
19
|
* `true`** — set `enabled: false` to hide it. Requires a Summary panel to
|
|
20
20
|
* be reachable at all (`RenderOptions.summaryPanel` / `GalleryOptions.summaryPanel`),
|
|
21
21
|
* since that panel is this link's only home; irrelevant otherwise.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{hasPosition as
|
|
1
|
+
import{hasPosition as F,isPositionedDie as V}from"../core/dies.js";import{testsPresent as G,testValue as H}from"../core/dieTable.js";import{classifyDie as Y}from"../core/classify.js";import{isParametricTest as _,getTestPassStatus as S}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as U}from"../stats/metadataColumns.js";import{LEADING as N,TRACKING as X,wireControlHover as Q,controlStyle as q,SPACE as J,RADIUS as P,FONT as g,CLR as d,saveTextFile as Z}from"./toolbar.js";import{csvField as k}from"./summaryPanel.js";import{fmt as j}from"../renderer/fmt.js";import{testLabel as E,markedTestLabel as M,derivedFields as ee,derivedKeyText as te,derivedTestSource as ne,isDerivedTest as ie,DERIVED_KEY as le}from"../renderer/testLabel.js";const L=(t,a)=>a?`${t} (${a})`:t,ae=5e4,z=new WeakSet;function re(t){if(z.has(t))return;z.add(t);const a=t.createElement("style");a.textContent=`
|
|
2
2
|
.wmap-dielist-table { width: 100%; border-collapse: collapse; font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
3
3
|
.wmap-dielist-th {
|
|
4
4
|
text-align: left; padding: 4px 8px; position: sticky; top: 0;
|
|
5
|
-
background: ${
|
|
6
|
-
border-bottom: 1px solid ${
|
|
5
|
+
background: ${d.panelBg}; color: ${d.label}; font-weight: 600;
|
|
6
|
+
border-bottom: 1px solid ${d.menuBorder}; white-space: nowrap;
|
|
7
7
|
}
|
|
8
8
|
.wmap-dielist-td {
|
|
9
|
-
padding: 4px 8px; color: ${
|
|
10
|
-
border-bottom: 1px solid ${
|
|
9
|
+
padding: 4px 8px; color: ${d.text};
|
|
10
|
+
border-bottom: 1px solid ${d.menuBorder}; white-space: nowrap;
|
|
11
11
|
}
|
|
12
|
-
`,t.head.appendChild(
|
|
13
|
-
`),"die-list.csv","text/csv",n.onSaveText)}),
|
|
12
|
+
`,t.head.appendChild(a)}function s(t,a,n,i){const r=t.createElement(a);return n&&Object.assign(r.style,n),i!==void 0&&(r.textContent=i),r}function oe(t){return F(t)?String(t.x):""}function se(t){return F(t)?String(t.y):""}function de(t,a){return a!==void 0?a.filter(n=>n.testNumber!==void 0):G(t,"values").map(n=>({testNumber:n,name:E(void 0,n)}))}function ce(t,a,n){if(!a)return;const i=new Map;for(const r of t){if(!V(r))continue;const u=a(r);u&&i.set(r,Y(r,u,{ringCount:n}))}return i.size>0?i:void 0}export function buildDieListSection(t,a,n={}){if(!t.length)return null;const i=n.ownerDocument??document;re(i);const r=de(t,a),u=n.maxRows??ae,C=Math.max(0,t.length-u),K=C>0?t.slice(0,u):t,m=s(i,"div",{display:"flex",flexDirection:"column",gap:J.md,flex:"1",minHeight:"0",minWidth:"0"}),v=s(i,"div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});v.appendChild(s(i,"div",{fontSize:g.meta,fontWeight:"700",letterSpacing:X,textTransform:"uppercase",color:d.label},n.title??`Die list (${t.length})`));const h=s(i,"button",{fontSize:g.body,padding:"3px 8px",borderRadius:P.control,...q("outlined"),background:d.menuBg},"Export CSV");h.type="button",Q(h),v.appendChild(h),m.appendChild(v),n.note&&m.appendChild(s(i,"div",{fontSize:g.body,color:d.label,lineHeight:N.base,flexShrink:"0"},n.note));const w=ce(t,n.getWafer,n.ringCount??4),B=t.some(e=>e.edgeExcluded===!0),O=[...n.extraColumn?[n.extraColumn.label]:[],"X","Y","Ring","Quadrant","Edge excluded","Site","Hard bin","Soft bin",...r.map(e=>L(E(e,e.testNumber),e.unit))],{columns:W,truncatedKeys:x}=U({dies:t,waferMetadata:n.waferMetadata,metadataFields:n.metadataFields,dieKeys:n.metadataColumns,waferKeys:n.waferMetadataColumns==="none"?"none":void 0,waferPlacement:n.waferMetadataColumns??"csv",reservedLabels:O}),y=[...n.extraColumn?[{label:n.extraColumn.label,get:e=>n.extraColumn.get(e)??""}]:[],{label:"X",get:oe},{label:"Y",get:se},...w?[{label:"Ring",get:e=>{const l=w.get(e);return l?String(l.ring):""}},{label:"Quadrant",get:e=>w.get(e)?.quadrant??""}]:[],...B?[{label:"Edge excluded",get:e=>e.edgeExcluded?"Yes":"No"}]:[],{label:"Site",get:e=>e.siteNum!==void 0?String(e.siteNum):""},{label:"Hard bin",get:e=>e.hbin!==void 0?String(e.hbin):""},{label:"Soft bin",get:e=>e.sbin!==void 0?String(e.sbin):""},...r.map(e=>{const l=!_(e);return{label:L(M(e,e.testNumber),e.unit),...ie(e)?{csvLabel:`${L(E(e,e.testNumber),e.unit)} [derived from ${ne(e)??le}]`}:{},get:o=>{if(l){const p=S(o,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const c=H(o,e.testNumber);if(c!==void 0)return j(c,e.unit);const b=S(o,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"},csvGet:o=>{if(l){const p=S(o,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const c=H(o,e.testNumber);if(c!==void 0)return j(c,void 0,"engineering");const b=S(o,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"}}}),...W.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],$=y.filter(e=>!e.csvOnly),A=te(r.map(e=>({label:M(e,e.testNumber),...ee(e)})));A&&m.appendChild(s(i,"div",{fontSize:g.body,color:d.label,lineHeight:N.base,flexShrink:"0"},A));const D=s(i,"div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${d.menuBorder}`,borderRadius:P.control,...n.maxHeight?{maxHeight:n.maxHeight}:{}}),f=i.createElement("table");f.className="wmap-dielist-table",f.setAttribute("aria-label",n.title??`Die list (${t.length} dies)`);const R=s(i,"thead"),T=s(i,"tr");for(const e of $){const l=i.createElement("th");l.className="wmap-dielist-th",l.scope="col",l.textContent=e.label,T.appendChild(l)}R.appendChild(T),f.appendChild(R);const I=s(i,"tbody");for(const e of K){const l=s(i,"tr");for(const o of $){const c=i.createElement("td");c.className="wmap-dielist-td",c.textContent=o.get(e),l.appendChild(c)}I.appendChild(l)}if(f.appendChild(I),D.appendChild(f),m.appendChild(D),C>0||x.length>0){const e=[];C>0&&e.push(`Showing the first ${u.toLocaleString()} of ${t.length.toLocaleString()} dies. The CSV export contains all ${t.length.toLocaleString()}.`),x.length>0&&e.push(`${x.length} further metadata field${x.length===1?"":"s"} not shown.`),m.appendChild(s(i,"div",{fontSize:g.body,color:d.label,lineHeight:N.base,flexShrink:"0"},e.join(" ")))}return h.addEventListener("click",()=>{const e=[y.map(l=>k(l.csvLabel??l.label)).join(",")];for(const l of t)e.push(y.map(o=>k((o.csvGet??o.get)(l))).join(","));Z(e.join(`
|
|
13
|
+
`),"die-list.csv","text/csv",n.onSaveText)}),m}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isYieldEligibleDie as b}from"../core/dies.js";import{isParametricTest as y}from"../renderer/buildWaferMap.js";import{buildSweepData as
|
|
1
|
+
import{isYieldEligibleDie as b}from"../core/dies.js";import{testValue as T}from"../core/dieTable.js";import{isParametricTest as y}from"../renderer/buildWaferMap.js";import{buildSweepData as C}from"../stats/sweep.js";import{renderSweepPanel as E}from"./charts/sweep.js";import{renderHistogramPanel as $}from"./charts/histogram.js";import{renderCapabilityPanel as I}from"./charts/capability.js";import{openChartExpandModal as M,populationPhrase as w}from"./charts/chartShell.js";import{buildCheckMenuEl as x,createToolbarHelpers as N,getTooltip as R,menuLayerFor as L,wireMenuA11y as V,CLR as k,FONT as F,SPACE as O}from"./toolbar.js";const P=30,v=e=>e.items.flatMap(t=>t.dies);function A(e){return e.items.map(t=>({...t,dies:t.dies.filter(a=>b(a))}))}function H(e,t,a){return(e.testDefs??[]).filter(y).map(s=>s.testNumber).filter(s=>{let o=0;for(const i of t){const n=T(i,s);if(n!==void 0&&Number.isFinite(n)&&++o>=a)return!0}return!1})}function S(e,t,a,s,o){const n=e.ownerDocument.createElement("div");n.textContent=a,n.dataset.wmapPopulation="1",Object.assign(n.style,{color:k.label,fontSize:F.body,marginBottom:O.sm});const r=e.querySelector("[data-wmap-chart-expand]")?.parentElement;r?.parentElement===e?r.after(n):e.prepend(n),M(e,t,{anchor:s,onClosed:o})}function W(e,t,a,s){const o=v(t);let i=t.notMeasuredReason??null;return!i&&!o.some(n=>b(n))&&(i="These dies are all partial or edge-excluded, which charts leave out"),i||C(o,t.testDefs,e).series.some(r=>r.points.some(d=>d.count>0))||(i="These dies have no values for this sweep\u2019s tests"),{section:"Sweep",label:e.title,unavailable:i,open:()=>{const n=E({spec:e,dies:o,population:t.population,testDefs:t.testDefs,onSaveImage:a.onSaveImage,ownerDocument:s.ownerDocument}),r=o.filter(d=>b(d)).length;M(n.card,`${e.title} \u2014 ${w(r,o.length,t.population)}`,{anchor:s,onClosed:()=>n.destroy()})}}}function B(e,t,a){const s=A(e),o=v(e).length,i=s.flatMap(l=>l.dies),n=w(i.length,o,e.population),r=(e.testDefs??[]).filter(y),d=a.ownerDocument,f=(l,c)=>e.notMeasuredReason?e.notMeasuredReason:i.length===0?"These dies are all partial or edge-excluded, which charts leave out":H(e,i,l).length>0?null:c,u={section:"Distributions",label:"Value histogram",unavailable:f(1,"These dies have no parametric test values"),open:()=>{const l=$({title:"Value histogram",items:s,testDefs:r,selectedTestNumber:e.activeTest,onSaveImage:t.onSaveImage,ownerDocument:d});S(l.card,`Value histogram \u2014 ${n}`,`Population: ${n}`,a,()=>l.destroy())}},h={section:"Distributions",label:"Process capability",unavailable:f(2,"Needs at least 2 of these dies with values for one test"),open:()=>{const l=I({title:"Process capability",items:s,testDefs:r,selectedTestNumber:e.activeTest,onSaveImage:t.onSaveImage,ownerDocument:d}),c=i.length<P?` \xB7 fewer than ${P} dies, so each Ppk is a rough estimate`:"";S(l.card,`Process capability \u2014 ${n}`,`Population: ${n}${c}`,a,()=>l.destroy())}};return[u,h]}function _(e,t,a){return[...B(e,t,a),...(t.sweeps??[]).map(s=>W(s,e,t,a))]}const g=new WeakMap;export function openDrilldownMenu(e,t,a,s){const o=t.ownerDocument,i=o.defaultView??window;g.get(o)?.();const n=_(a,s,t),r=[];let d;for(const p of n)p.section!==d&&(r.push({section:p.section}),d=p.section),r.push({label:p.label,active:!1,action:!0,enabled:p.unavailable===null,disabledHint:p.unavailable??void 0,onClick:()=>{m(),p.open()}});const{makeMenuSection:f}=N(R(o)),u=x(new DOMRect(e.x,e.y-4,0,0),r,{makeMenuSection:f},i),h=v(a).length;u.setAttribute("role","menu"),u.setAttribute("aria-label",`Open a chart of ${w(h,h,a.population)}`),u.dataset.wmapDrilldownMenu="1",L(t).appendChild(u);const l=u.getBoundingClientRect();l.bottom>i.innerHeight-4&&(u.style.top=`${Math.max(4,e.y-l.height)}px`),l.right>i.innerWidth-4&&(u.style.left=`${Math.max(4,i.innerWidth-l.width-4)}px`);let c=!1;function m(){c||(c=!0,u.remove(),o.removeEventListener("pointerdown",D,!0),i.removeEventListener("blur",m),g.get(o)===m&&g.delete(o))}function D(p){u.contains(p.target)||m()}return o.addEventListener("pointerdown",D,!0),i.addEventListener("blur",m),V(u,t,m),g.set(o,m),m}
|
|
@@ -18,20 +18,9 @@
|
|
|
18
18
|
* `suggestedName` argument of their save hooks.
|
|
19
19
|
*/
|
|
20
20
|
import { type SaveImageHandler, type SaveTextHandler } from './toolbar.js';
|
|
21
|
-
/**
|
|
22
|
-
* Tell a host that passes `downloadFilename` its meaning changes in 0.31.0.
|
|
23
|
-
* Until then it keeps its documented meaning — the whole name of the map or
|
|
24
|
-
* gallery PNG — and nothing else reads it; changing that in a patch release
|
|
25
|
-
* would break hosts that match the name their `onSaveImage` receives.
|
|
26
|
-
*/
|
|
27
|
-
export declare function noticeDownloadFilenameChange(downloadFilename: string | undefined): void;
|
|
28
21
|
/** What a renderer knows about the data behind an export. */
|
|
29
22
|
export interface ExportContext {
|
|
30
|
-
/**
|
|
31
|
-
* Leads the name, and suppresses any context it already names. Reserved for
|
|
32
|
-
* `downloadFilename` from 0.31.0; no renderer sets it before then (see
|
|
33
|
-
* `noticeDownloadFilenameChange`).
|
|
34
|
-
*/
|
|
23
|
+
/** The host's `downloadFilename`: leads the name, and suppresses any context it already names. */
|
|
35
24
|
prefix?: string;
|
|
36
25
|
/** Distinct lot IDs covered, in display order. */
|
|
37
26
|
lots: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{downloadBlob as
|
|
1
|
+
import{downloadBlob as w}from"./toolbar.js";const _=60,h=180,g=/^(con|prn|aux|nul|com\d|lpt\d)$/i;function i(e){return e.normalize("NFC").replace(/[^\p{L}\p{N}._+-]+/gu,"-").replace(/-{2,}/g,"-").replace(/^[-._]+|[-._]+$/g,"").slice(0,_).replace(/[-._]+$/g,"")}function c(e,t){return/^\d+$/.test(t)?`${e}-${t}`:t}function d(e,t){const r=`_${e.toLowerCase().replace(/[-_.\s]+/g,"_")}_`,n=`_${t.toLowerCase().replace(/[-_.\s]+/g,"_")}_`;return r.includes(n)}export function buildExportFilename(e,t){const r=t.lastIndexOf("."),n=r>=0?t.slice(r):"",f=i(r>=0?t.slice(0,r):t).toLowerCase(),s=e.prefix?.trim()?i(e.prefix):"",o=[];if(e.lots.length===1?o.push({raw:e.lots[0],text:c("lot",e.lots[0])}):e.lots.length>1&&o.push({raw:`${e.lots.length}-lots`,text:`${e.lots.length}-lots`}),e.stackedWafers!==void 0){const l=e.stackedWafers===!0?"stacked":`stacked-${e.stackedWafers}-wafer${e.stackedWafers===1?"":"s"}`;o.push({raw:l,text:l})}else e.wafer!==void 0?o.push({raw:e.wafer,text:c("wafer",e.wafer)}):e.waferCount!==void 0&&e.waferCount>1&&o.push({raw:`${e.waferCount}-wafers`,text:`${e.waferCount}-wafers`});const u=[s];for(const{raw:l,text:$}of o){const p=i($);p&&(s&&(d(s,i(l))||d(s,p))||u.push(p))}u.push(f);let a=u.filter(Boolean).join("_").slice(0,h).replace(/[-_.]+$/g,"");return a||(a="wafermap"),g.test(a)&&(a=`wafermap-${a}`),`${a}${n}`}export function withExportContext(e,t,r){return{onSaveImage:(n,f)=>{const s=buildExportFilename(e(),f);if(t)return t(n,s);w(n,s)},onSaveText:(n,f,s)=>{const o=buildExportFilename(e(),f);if(r)return r(n,o,s);w(new Blob([n],{type:s}),o)}}}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export { WMAP_VERSION, WMAP_BUILD_TIME } from './version.js';
|
|
2
|
-
export type { ToCanvasOptions,
|
|
2
|
+
export type { ToCanvasOptions, ViewportTransform } from './toCanvas.js';
|
|
3
3
|
export { renderWaferMap } from './renderWaferMap.js';
|
|
4
4
|
export type { RenderOptions, WaferMapController, WaferViewOptions, WaferPreferences, WaferDisplayState } from './renderWaferMap.js';
|
|
5
5
|
export { renderWaferGallery } from './renderWaferGallery.js';
|
|
6
6
|
export type { WaferMapDisplayItem, WaferMapDisplayItemFactory, GalleryOptions, GalleryController } from './renderWaferGallery.js';
|
|
7
7
|
export type { SummaryPanelOptions, FindingsNotice } from './summaryPanel.js';
|
|
8
|
-
export type {
|
|
8
|
+
export type { DieListDisplayOptions } from './dieList.js';
|
|
9
9
|
export { collectWarnings, severityOf } from './warnings.js';
|
|
10
10
|
export type { WarningsOptions } from './warnings.js';
|
|
11
11
|
export type { InsightsOptions, InsightsView } from './insightsTab.js';
|
|
@@ -16,5 +16,4 @@ export { openWaferMapGuide } from './toolbar.js';
|
|
|
16
16
|
export { ICONS } from './icons.js';
|
|
17
17
|
export type { OverlayHandle } from './toolbar.js';
|
|
18
18
|
export { openReportModal } from './toolbar.js';
|
|
19
|
-
export { toCanvas, buildDieListSection, } from './deprecated.js';
|
|
20
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{WMAP_VERSION,WMAP_BUILD_TIME}from"./version.js";export{renderWaferMap}from"./renderWaferMap.js";export{renderWaferGallery}from"./renderWaferGallery.js";export{collectWarnings,severityOf}from"./warnings.js";export{setDetachWindowOpener}from"./toolbar.js";export{openWaferMapGuide}from"./toolbar.js";export{ICONS}from"./icons.js";export{openReportModal}from"./toolbar.js";
|
|
1
|
+
export{WMAP_VERSION,WMAP_BUILD_TIME}from"./version.js";export{renderWaferMap}from"./renderWaferMap.js";export{renderWaferGallery}from"./renderWaferGallery.js";export{collectWarnings,severityOf}from"./warnings.js";export{setDetachWindowOpener}from"./toolbar.js";export{openWaferMapGuide}from"./toolbar.js";export{ICONS}from"./icons.js";export{openReportModal}from"./toolbar.js";
|