@wafertools/wafermap 0.30.3 → 0.31.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 +9 -19
- package/CHANGELOG.md +140 -0
- package/README.md +2 -2
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +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 +16 -2
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.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/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.d.ts +11 -1
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +4 -7
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +15 -30
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.js +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +49 -43
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/aggregates.d.ts +0 -6
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/dies.d.ts +1 -3
- 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/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +1 -9
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +54 -34
- 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/deprecate.d.ts +1 -1
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.js +1 -1
- package/dist/packages/renderer/derivedTests/parser.js +1 -1
- package/dist/packages/renderer/index.d.ts +6 -5
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +17 -0
- package/dist/packages/renderer/spec.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +4 -2
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/clusterDetection.d.ts +2 -1
- package/dist/packages/stats/correlation.d.ts +1 -1
- package/dist/packages/stats/facets.d.ts +1 -1
- package/dist/packages/stats/index.d.ts +6 -16
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/mergeTestDefs.d.ts +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +2 -1
- package/dist/packages/stats/regions.d.ts +2 -1
- package/dist/packages/stats/renderFindingsReport.d.ts +0 -4
- package/dist/packages/stats/renderFindingsReport.js +1 -21
- package/dist/packages/stats/renderSummaryReport.js +11 -11
- package/dist/packages/stats/sweep.d.ts +8 -0
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/types.d.ts +9 -3
- 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 j,LEADING as H,wireControlHover as L,controlStyle as T,SPACE as m,RADIUS as E,fontPx as F,FONT as v,CLR as p,Z_BASE as U,menuLayerFor as X,wireListNavigation as Y,MENU_SEARCH_THRESHOLD as N,makeMenuSearchBox as K,markMenuTrigger as M,saveImageBlob as Z,openReparentedModal as J}from"../toolbar.js";import{ICONS as P}from"../icons.js";import{fmt as Q,fmtColorbarAxis as ee}from"../../renderer/fmt.js";import{markedTestLabel as te,DERIVED_LANE_PAD as ne}from"../../renderer/testLabel.js";export function openChartExpandModal(e,i,n={}){for(const a of e.querySelectorAll('div[style*="position: absolute"]'))a.style.display==="block"&&a.style.pointerEvents==="none"&&(a.style.display="none");const t=e.querySelector("[data-wmap-chart-expand]"),o=e.getAttribute("style")??"",r=e.dataset.wmapChartGrow??"plot";Object.assign(e.style,{flex:"1",minHeight:"0",alignSelf:"stretch",border:"none",borderRadius:"0"}),e.dataset.wmapExpanded="1";const l=J([e],{title:i,anchor:n.anchor,ownerDocument:e.ownerDocument,boxSize:oe[r],onClosed:()=>{delete e.dataset.wmapExpanded,e.setAttribute("style",o),t&&(t.style.display="flex"),n.onClosed?.()}});if(!l){delete e.dataset.wmapExpanded,e.setAttribute("style",o);return}if(t&&(t.style.display="none"),r==="rows"){const a=()=>{const s=Number(e.dataset.wmapChartFullH);if(!s)return;const c=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(c+s)}px)`)};a(),(e.ownerDocument.defaultView??window).requestAnimationFrame(a)}}const oe={plot:{width:"min(94vw, 1100px)",height:"min(90vh, 760px)"},rows:{width:"min(94vw, 960px)",height:"min(90vh, 700px)"},square:{width:"min(94vw, 820px)",height:"min(90vh, 860px)"}},ie={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},W={text:"text",textMuted:"text-muted",border:"border",bg:"surface",bgHover:"bg-hover",track:"panel-bg",warnBorder:"warn-border",accent:"icon-active",limitLine:"warn-text"};export function resolveChartCanvasColors(e){const i=getComputedStyle(e),n={};for(const t of Object.keys(W)){const o=i.getPropertyValue(`--wmap-${W[t]}`).trim();n[t]=o||ie[t]}return n}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,i){const n=e.ownerDocument.defaultView??window;let t=0;const o=new n.ResizeObserver(()=>{t||(t=n.requestAnimationFrame(()=>{t=0,i()}))});return o.observe(e),{disconnect:()=>{t&&n.cancelAnimationFrame(t),t=0,o.disconnect()}}}export function growCardToFitContent(e,i,n){const t=`${R(e,i)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function R(e,i){const n=e.ownerDocument.defaultView??window,t=n.getComputedStyle(e);let o=0;for(const r of Array.from(e.children)){if(r===i)continue;const l=r,a=n.getComputedStyle(l);a.display==="none"||a.position==="absolute"||a.position==="fixed"||(o+=l.offsetHeight+(parseFloat(a.marginTop)||0)+(parseFloat(a.marginBottom)||0))}return t.boxSizing==="border-box"&&(o+=(parseFloat(t.paddingTop)||0)+(parseFloat(t.paddingBottom)||0)+(parseFloat(t.borderTopWidth)||0)+(parseFloat(t.borderBottomWidth)||0)),o}function _(e,i){const n=e.ownerDocument.defaultView??window;let t=0;for(const o of Array.from(e.children)){if(o===i)continue;const r=o,l=n.getComputedStyle(r);l.display==="none"||l.position==="absolute"||l.position==="fixed"||(t+=r.offsetHeight+(parseFloat(l.marginTop)||0)+(parseFloat(l.marginBottom)||0))}return t}export const WAFER_MENU_HINT="<br><em>right-click to chart this wafer</em>";export function populationPhrase(e,i,n){const t=e.toLocaleString(),o=e===1?"die":"dies";return n?e===i?`${t} ${o} ${n}`:`${t} of ${i.toLocaleString()} dies ${n} (partial and edge-excluded dies left out)`:`${t} ${o}`}export function setChartGrow(e,i){e.dataset.wmapChartGrow=i}export function isExpandedCard(e){return e.dataset.wmapExpanded==="1"}export function bodyRoom(e,i,n){const o=(e.ownerDocument.defaultView??window).getComputedStyle(e),r=e.clientHeight-(parseFloat(o.paddingTop)||0)-(parseFloat(o.paddingBottom)||0),l=o.boxSizing==="border-box"?(parseFloat(o.paddingTop)||0)+(parseFloat(o.paddingBottom)||0)+(parseFloat(o.borderTopWidth)||0)+(parseFloat(o.borderBottomWidth)||0):0;return r-(R(e,i)-l)-(n?_(i,n):0)}export function fitRowsHeight(e,i,n,t,o=0){if(growCardToFitContent(e,i,o+n),e.dataset.wmapChartFullH=String(Math.ceil(R(e,i)+o+t)),!isExpandedCard(e))return n;const r=bodyRoom(e,i)-o;return Math.max(n,Math.min(t,Math.floor(r)))}export function chartFillHeight(e,i,n,t){const o=_(i,n);return growCardToFitContent(e,i,o+t),Math.max(t,i.clientHeight-o)}export function chartDpr(e){return(e.ownerDocument.defaultView??window).devicePixelRatio||1}export function prepareCanvas(e,i,n,t){const o=chartDpr(e);e.width=Math.max(1,Math.floor(n*o)),e.height=Math.max(1,Math.floor(t*o)),e.style.width=`${n}px`,e.style.height=`${t}px`;const r=e.getContext("2d");return r?(r.setTransform(o,0,0,o,0,0),r.clearRect(0,0,n,t),r.font=`${F(-1)}px system-ui, sans-serif`,r.textBaseline="middle",{ctx:r,theme:resolveChartCanvasColors(i)}):null}export function applyCanvasFlow(e,i=0){let n;if(typeof i=="number")n=i;else{const t=(i.ownerDocument.defaultView??window).getComputedStyle(i);n=i.offsetTop+i.offsetHeight+(parseFloat(t.marginBottom)||0)}e.style.position="absolute",e.style.left="0",e.style.right="0",e.style.top=`${n}px`,e.style.bottom="0"}export function formatValue(e){return Number.isFinite(e)?Number.isInteger(e)?`${e}`:Q(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,i,n){if(!i&&n===void 0)return{tick:formatValue,tickWithUnit:formatValue,unitLabel:""};const{tickFmt:t,axisLabel:o,scaledUnit:r}=ee(e,null,i,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:o}}const re=8;export const VERTICAL_TICK_SPACING_PX=28;export function horizontalTickSpacing(e,i,n){return(t,o)=>{const r=makeAxisFormat(i,n,t).tick;return o.reduce((l,a)=>Math.max(l,e.measureText(r(a)).width),0)+re}}export function drawAxisUnit(e,i,n,t,o){const r={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font=`${F(-1)}px system-ui, sans-serif`,e.textAlign="left",e.textBaseline="middle",e.fillStyle=o,e.fillText(`(${i})`,n,t),Object.assign(e,r)}export function saveCanvasPng(e,i,n){const t=e.ownerDocument.createElement("canvas");t.width=e.width,t.height=e.height;const o=t.getContext("2d");o.fillStyle="#ffffff",o.fillRect(0,0,t.width,t.height),o.drawImage(e,0,0),t.toBlob(r=>{r&&Z(r,i,n)},"image/png")}const z="10px";export function chartSwatchCss(e,i="solid"){return["display:inline-block",`width:${z}`,`height:${z}`,`border-radius:${E.pill}`,`background:${i==="outline"?"transparent":e}`,`border:1px ${i==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const le="0.35";export function makeSeriesLegendItem(e,i,n=document){const t=n.createElement("button");t.type="button",Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:m.xs,padding:`${m.xs} ${m.md}`,borderRadius:E.pill,border:`1px solid ${p.menuBorder}`,background:"none",cursor:"pointer",fontSize:v.body,color:p.text,whiteSpace:"nowrap"}),L(t,"bare");const o=n.createElement("span");return o.style.cssText=chartSwatchCss(i),t.append(o,n.createTextNode(e)),{el:t,setState({selected:r,dimmed:l}){t.style.borderColor=r?p.iconActive:p.menuBorder,t.style.fontWeight=r?"600":"400",t.style.opacity=l?le:"1",t.setAttribute("aria-pressed",r?"true":"false")}}}export function cardFrameStyle(){return{background:p.menuBg,border:`1px solid ${p.menuBorder}`,borderRadius:E.container,padding:m.xl,fontVariantNumeric:"tabular-nums"}}export function cardShell(e,i,n=document){const t=n.createElement("div");t.dataset.wmapChartCard="1",t.dataset.wmapChartTitle=e,Object.assign(t.style,{display:"flex",flexDirection:"column",...cardFrameStyle(),minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const o=t.ownerDocument.createElement("div");Object.assign(o.style,{display:"flex",alignItems:"center",gap:m.sm,paddingBottom:"6px",marginBottom:m.md,borderBottom:`1px solid ${p.menuBorder}`});const r=t.ownerDocument.createElement("div");r.textContent=e,Object.assign(r.style,{color:p.value,fontSize:v.heading,fontWeight:"600",flex:"1"}),o.appendChild(r);const l=makeTooltip(t),a=t.ownerDocument.createElement("button");a.type="button",a.setAttribute("aria-label","Save as PNG"),a.innerHTML=P.download,Object.assign(a.style,{...T("outlined"),color:p.label,padding:"0",width:"22px",height:"22px",lineHeight:H.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(a),attachChartTip(a,t,l,"Save as PNG"),a.addEventListener("click",()=>{const f=t.querySelector("canvas");f&&saveCanvasPng(f,e,i)}),o.appendChild(a);const s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Expand"),s.innerHTML=P.expand,Object.assign(s.style,{...T("outlined"),color:p.label,padding:"0",width:"22px",height:"22px",lineHeight:H.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(s),attachChartTip(s,t,l,"Expand"),s.dataset.wmapChartExpand="1",s.addEventListener("click",()=>openChartExpandModal(t,r.textContent??e)),o.appendChild(s),t.appendChild(o);const c=t.ownerDocument.createElement("div");Object.assign(c.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(c);const d=t.ownerDocument.createElement("div");return Object.assign(d.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),t.appendChild(d),{card:t,heading:r,controlsRow:c,body:d}}export function makeSegmented(e,i,n,t=document,o=!1){const r=t.createElement("div");r.setAttribute("role","radiogroup"),Object.assign(r.style,{display:"inline-flex",border:`1px solid ${p.menuBorder}`,borderRadius:E.control,overflow:"hidden"});const l=`seg-${Math.random().toString(36).slice(2,9)}`,a=[];return e.forEach(([s,c],d)=>{const f=t.createElement("label");Object.assign(f.style,{display:"inline-flex",alignItems:"center",fontSize:v.body,padding:o?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:d>0?`1px solid ${p.menuBorder}`:"none"}),L(f,"bare");const u=t.createElement("input");u.type="radio",u.name=l,u.value=s,u.checked=s===i,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{f.style.background=u.checked?p.bgActive:p.menuBg,f.style.color=u.checked?p.iconActive:p.text,f.dataset.on=u.checked?"true":"false"};a.push(w),w(),u.addEventListener("change",()=>{if(u.checked){for(const C of a)C();n(s)}}),f.append(u,t.createTextNode(c)),r.appendChild(f)}),r}export function makeBackButton(e,i=document){const n=i.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...T("outlined"),background:p.menuBg,color:p.label}),L(n),n.addEventListener("click",e),n}export function renderEmptyState(e,i,n){const t=e.ownerDocument.createElement("div");t.textContent=i,Object.assign(t.style,{color:p.label,fontSize:v.body,padding:"8px 0"}),n&&Object.assign(t.style,n),e.appendChild(t)}export function makeListSelect(e,i,n,t={}){const{maxWidth:o="200px",ownerDocument:r=document,ariaLabel:l,emptyText:a,searchPlaceholder:s="Filter\u2026",hook:c}=t;let d=i;const f=b=>e.find($=>$.value===b)?.label??"",u=r.createElement("button");u.type="button",c&&(u.dataset.wmapSelect=c),l&&u.setAttribute("aria-label",l),Object.assign(u.style,{fontSize:v.body,padding:`${m.xxs} ${m.sm}`,background:p.menuBg,color:p.text,border:`1px solid ${p.menuBorder}`,borderRadius:E.control,maxWidth:o,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:m.sm,justifyContent:"space-between"}),L(u),M(u,!1);const w=r.createElement("span");Object.assign(w.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const C=r.createElement("span");C.textContent="\u25BE",C.style.flex="0 0 auto",u.append(w,C);const S=()=>{w.textContent=e.length===0?a??"(none)":f(d)||a||"(none)"};if(S(),e.length===0)return u.disabled=!0,u.style.opacity="0.6",u.style.cursor="default",Object.defineProperty(u,"value",{get:()=>d,set:b=>{d=b,S()}});let x=null;const k=()=>{x&&(x.remove(),x=null,M(u,!1))},A=()=>{k(),u.focus()};function V(){const b=u.getBoundingClientRect(),$=r.defaultView??window,D=Math.max(b.width,220),G=Math.min(b.left,Math.max(4,($.innerWidth??1/0)-D-4));x=r.createElement("div"),x.setAttribute("role","listbox"),x.setAttribute("aria-label",l??"Options"),Object.assign(x.style,{position:"fixed",top:`${b.bottom+4}px`,left:`${G}px`,background:p.menuBg,border:`1px solid ${p.menuBorder}`,borderRadius:E.control,boxShadow:j.menu,zIndex:U,minWidth:`${D}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const B=[],O=()=>B.filter(h=>h.row.style.display!=="none").map(h=>h.row);if(e.length>N){const h=K(y=>{for(const g of B)g.row.style.display=g.label.includes(y)?"":"none"},s,r);Object.assign(h.style,{position:"sticky",top:"0",zIndex:"1",background:p.menuBg}),h.addEventListener("keydown",y=>{if(y.key==="Escape"){A();return}y.key==="ArrowDown"&&(y.preventDefault(),O()[0]?.focus())}),x.appendChild(h)}for(const h of e){const y=h.value===d,g=r.createElement("div");g.textContent=h.label,g.setAttribute("role","option"),g.setAttribute("aria-selected",y?"true":"false"),g.tabIndex=-1,Object.assign(g.style,{padding:`${m.sm} ${m.xl}`,fontSize:v.body,cursor:"pointer",color:y?p.iconActive:p.text,fontWeight:y?"700":"400",background:y?p.menuActive:"transparent",whiteSpace:"nowrap"}),g.addEventListener("mouseenter",()=>{y||(g.style.background=p.menuHover)}),g.addEventListener("mouseleave",()=>{y||(g.style.background="transparent")}),g.addEventListener("click",q=>{q.stopPropagation(),d=h.value,S(),A(),n(h.value)}),x.appendChild(g),B.push({row:g,label:h.label.toLowerCase()})}if(Y(x,O,A),X(u).appendChild(x),M(u,!0),e.length<=N){const h=e.findIndex(y=>y.value===d);O()[h>=0?h:0]?.focus()}}u.addEventListener("click",b=>{if(b.stopPropagation(),x){k();return}V()});const I=b=>{if(!u.isConnected){r.removeEventListener("click",I);return}x&&!x.contains(b.target)&&!u.contains(b.target)&&k()};return r.addEventListener("click",I),Object.defineProperty(u,"value",{get:()=>d,set:b=>{d=b,S()}})}export function testOptionLabels(e){const i=e.some(n=>n.derived);return e.map(n=>(i&&!n.derived?ne:"")+te(n,n.testNumber))}export function makeTestSelect(e,i,n,t={}){const{maxWidth:o="200px",emptyText:r="No parametric tests",ownerDocument:l=document}=t;return makeListSelect(testOptionLabels(e).map((a,s)=>({value:String(e[s].testNumber),label:a})),i!==null?String(i):"",a=>n(Number(a)),{maxWidth:o,ownerDocument:l,ariaLabel:"Test",emptyText:r,searchPlaceholder:"Filter tests\u2026"})}export function makeLinkedAxisPrefs(e,i,n,t=document){let o=i?.includeLimits,r=i?.clipOutliers??!1;const l=()=>n({includeLimits:o,clipOutliers:r});return{get:()=>({includeLimits:o,clipOutliers:r}),sync(a,s){e.innerHTML="",s&&e.appendChild(makeToggle("Axis includes limits",a,c=>{o=c,l()},t)),e.appendChild(makeToggle("Clip outliers",r,c=>{r=c,l()},t))},set(a){return a.includeLimits===o&&a.clipOutliers===r?!1:(o=a.includeLimits,r=a.clipOutliers,!0)}}}export function makeLinkedTestSelect(e,i,n,t={}){let o=i;const r=makeTestSelect(e,o,l=>{l!==o&&(o=l,n(l))},t);return{el:r,get:()=>o,set(l){return o===l||!e.some(a=>a.testNumber===l)?!1:(o=l,r.value=String(l),!0)}}}export const ALL_GROUPS="\0all";export function makeLinkedGroupSelect(e,i,n,t,o={}){let r=n;const l=d=>d??ALL_GROUPS,a=d=>d===ALL_GROUPS?null:d,s=makeLabeledSelect(i,[{value:ALL_GROUPS,label:"All groups"},...e.map(d=>({value:d,label:d}))],l(r),d=>{const f=a(d);f!==r&&(r=f,t(f))},{...o,hook:"group-scope"}),c=s.querySelector('[data-wmap-select="group-scope"]');return{el:s,get:()=>r,set(d){return r===d||d!==null&&!e.includes(d)?!1:(r=d,c&&(c.value=l(d)),!0)}}}export function makeWaferSelect(e,i,n,t={}){const{maxWidth:o="160px",allLabel:r="All wafers",ownerDocument:l=document}=t,a="\0all";return makeListSelect([{value:a,label:r},...e.map((s,c)=>({value:String(c),label:s.label??`#${c}`}))],i===null?a:String(i),s=>n(s===a?null:Number(s)),{maxWidth:o,ownerDocument:l,ariaLabel:r,searchPlaceholder:"Filter wafers\u2026"})}export function makeToggle(e,i,n,t=document){const o=t.createElement("label");Object.assign(o.style,{display:"inline-flex",alignItems:"center",gap:m.xs,fontSize:v.body,color:p.label,cursor:"pointer",userSelect:"none"}),L(o,"bare");const r=t.createElement("input");return r.type="checkbox",r.checked=i,r.style.cssText="margin:0;cursor:pointer;",r.addEventListener("change",()=>n(r.checked)),o.append(r,t.createTextNode(e)),o}export function makeLabeledSelect(e,i,n,t,o={}){const{maxWidth:r="160px",hook:l,ownerDocument:a=document}=o,s=a.createElement("label");s.textContent=e,Object.assign(s.style,{color:p.label,fontSize:v.body,display:"flex",alignItems:"center",gap:m.xs});const c=makeListSelect(i,n,t,{maxWidth:r,ownerDocument:a,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return s.appendChild(c),s}export function makeChartGridWrap(e=document){const i=e.createElement("div");return i.dataset.wmapChartGrid="1",Object.assign(i.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:m.lg,flex:"0 0 auto"}),i}export function makeTooltip(e){const i=e.ownerDocument.createElement("div");return Object.assign(i.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:"rgba(30, 32, 40, 0.93)",color:"#f0f0f2",border:"1px solid rgba(255,255,255,0.10)",borderRadius:E.control,padding:`${m.sm} ${m.xl}`,fontSize:v.sub,fontFamily:v.family,maxWidth:"320px",whiteSpace:"normal",overflowWrap:"break-word",boxShadow:j.menu}),e.appendChild(i),i}export function attachChartTip(e,i,n,t){e.removeAttribute("title"),e.textContent?.trim()||(e.ariaLabel=t),e.addEventListener("mouseenter",o=>{n.textContent=t,n.style.display="block",positionChartTooltip(n,i,o.clientX,o.clientY)}),e.addEventListener("mousemove",o=>{n.style.display==="block"&&positionChartTooltip(n,i,o.clientX,o.clientY)}),e.addEventListener("mouseleave",()=>{n.style.display="none"}),e.addEventListener("click",()=>{n.style.display="none"}),e.addEventListener("focus",()=>{n.textContent=t,n.style.display="block";const o=e.getBoundingClientRect();positionChartTooltip(n,i,o.left+o.width/2,o.bottom)}),e.addEventListener("blur",()=>{n.style.display="none"})}export function positionChartTooltip(e,i,n,t){const o=i.getBoundingClientRect(),r=14,l=4;e.style.left="0",e.style.top="0";const a=e.offsetWidth,s=e.offsetHeight;let c=n-o.left+r,d=t-o.top+r;c+a+l>o.width&&(c=n-o.left-r-a),d+s+l>o.height&&(d=t-o.top-r-s),c=Math.max(l,c),d=Math.max(l,d),e.style.left=`${c}px`,e.style.top=`${d}px`}export function robustFence(e,i=1.5){const n=Float64Array.from(e.filter(a=>Number.isFinite(a)));if(n.sort(),n.length<8)return null;const t=a=>{const s=(n.length-1)*a,c=Math.floor(s),d=Math.ceil(s);return c===d?n[c]:n[c]+(n[d]-n[c])*(s-c)},o=t(.25),r=t(.75),l=r-o;return l===0?null:{lo:o-i*l,hi:r+i*l}}export function shouldIncludeLimitsByDefault(e,i,n,t,o=1/3){if(n===void 0&&t===void 0||!Number.isFinite(e)||!Number.isFinite(i))return!1;const r=Math.min(e,n??e),a=Math.max(i,t??i)-r;if(a<=0)return!0;const s=i-e;return s===0?!0:s/a>=o}export function resolveAxisRange(e){const{dataMin:i,dataMax:n,limitLow:t,limitHigh:o,includeLimits:r,clipOutliers:l,values:a}=e;let s=i,c=n,d=0;if(l&&a?.length){const u=robustFence(a);if(u){const w=Math.max(s,u.lo),C=Math.min(c,u.hi);C>w&&(d=a.filter(S=>Number.isFinite(S)&&(S<w||S>C)).length,s=w,c=C)}}r&&(t!==void 0&&(s=Math.min(s,t)),o!==void 0&&(c=Math.max(c,o))),s===c&&(s-=1,c+=1);const f=[];return t!==void 0&&t<s&&f.push({value:t,label:"LSL",side:"lo"}),t!==void 0&&t>c&&f.push({value:t,label:"LSL",side:"hi"}),o!==void 0&&o>c&&f.push({value:o,label:"USL",side:"hi"}),o!==void 0&&o<s&&f.push({value:o,label:"USL",side:"lo"}),{lo:s,hi:c,offAxis:f,clippedCount:d}}export function limitLabelSide(e,i,n,t,o,r=3){const l=o?-1:1;return(l<0?e-r-i>=n:e+r+i<=t)?l:-l}export function drawOffAxisLimits(e,i,n,t,o,r){if(i.length){e.save(),e.fillStyle=o,e.font=`${F(-1)}px system-ui, sans-serif`;for(const{value:l,label:a,side:s}of i){const c=t==="horizontal"?s==="lo"?"\u2190":"\u2192":s==="lo"?"\u2193":"\u2191",d=s==="lo"?`${c} ${a} ${r(l)}`:`${a} ${r(l)} ${c}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=s==="lo"?"left":"right",e.fillText(d,s==="lo"?n.left+2:n.right-2,n.top+2)):(e.textBaseline=s==="lo"?"bottom":"top",e.textAlign="left",e.fillText(d,n.left+4,s==="lo"?n.bottom-2:n.top+2))}e.restore()}}
|
|
1
|
+
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{fmt as ne,fmtColorbarAxis as oe}from"../../renderer/fmt.js";import{markedTestLabel as ie,DERIVED_LANE_PAD as re}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:le[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 d=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(d+a)}px)`)};s(),(e.ownerDocument.defaultView??window).requestAnimationFrame(s)}}const le={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 d=s[1].length===3?s[1].split("").map(c=>c+c).join(""):s[1];[t,i,r]=[0,2,4].map(c=>parseInt(d.slice(c,c+2),16)),d.length===8&&(l=parseInt(d.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 se={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||se[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=`${M(e,o)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function M(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-(M(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(M(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}`:ne(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}=oe(e,null,o,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:i}}const ae=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)+ae}}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 ce="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?ce:"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 u=t.querySelector("canvas");u&&saveCanvasPng(u,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 d=t.ownerDocument.createElement("div");Object.assign(d.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(d);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:d,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,d],c)=>{const u=t.createElement("label");Object.assign(u.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(u,"bare");const p=t.createElement("input");p.type="radio",p.name=l,p.value=a,p.checked=a===o,Object.assign(p.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{u.style.background=p.checked?f.bgActive:f.menuBg,u.style.color=p.checked?f.iconActive:f.text,u.dataset.on=p.checked?"true":"false"};s.push(w),w(),p.addEventListener("change",()=>{if(p.checked){for(const v of s)v();n(a)}}),u.append(p,t.createTextNode(d)),r.appendChild(u)}),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:d}=t;let c=o;const u=x=>e.find(O=>O.value===x)?.label??"",p=r.createElement("button");p.type="button",d&&(p.dataset.wmapSelect=d),l&&p.setAttribute("aria-label",l),Object.assign(p.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(p),R(p,!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",p.append(w,v);const E=()=>{w.textContent=e.length===0?s??"(none)":u(c)||s||"(none)"};if(E(),e.length===0)return p.disabled=!0,p.style.opacity="0.6",p.style.cursor="default",Object.defineProperty(p,"value",{get:()=>c,set:x=>{c=x,E()}});let b=null;const B=()=>{b&&(b.remove(),b=null,R(p,!1))},$=()=>{B(),p.focus()};function G(){const x=p.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(p).appendChild(b),R(p,!0),e.length<=W){const h=e.findIndex(y=>y.value===c);F()[h>=0?h:0]?.focus()}}p.addEventListener("click",x=>{if(x.stopPropagation(),b){B();return}G()});const H=x=>{if(!p.isConnected){r.removeEventListener("click",H);return}b&&!b.contains(x.target)&&!p.contains(x.target)&&B()};return r.addEventListener("click",H),Object.defineProperty(p,"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?re:"")+ie(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,d,c=!1){e.innerHTML="",c&&e.appendChild(makeLimitsSelect(l,u=>{l=u,s()},t)),d&&e.appendChild(makeToggle("Axis includes limits",a,u=>{i=u,s()},t)),e.appendChild(makeToggle("Clip outliers",r,u=>{r=u,s()},t))},set(a){const d=a.limits??"both";return a.includeLimits===i&&a.clipOutliers===r&&d===l?!1:(i=a.includeLimits,r=a.clipOutliers,l=d,!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 u=s(c);u!==r&&(r=u,t(u))},{...i,hook:"group-scope"}),d=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,d&&(d.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,d)=>({value:String(d),label:a.label??`#${d}`}))],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 d=makeListSelect(o,n,t,{maxWidth:r,ownerDocument:s,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return a.appendChild(d),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 d=n-i.left+r,c=t-i.top+r;d+s+l>i.width&&(d=n-i.left-r-s),c+a+l>i.height&&(c=t-i.top-r-a),d=Math.max(l,d),c=Math.max(l,c),e.style.left=`${d}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(s=>Number.isFinite(s)));if(n.sort(),n.length<8)return null;const t=s=>{const a=(n.length-1)*s,d=Math.floor(a),c=Math.ceil(a);return d===c?n[d]:n[d]+(n[c]-n[d])*(a-d)},i=t(.25),r=t(.75),l=r-i;return l===0?null:{lo:i-o*l,hi:r+o*l}}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,d=0;if(r&&l?.length){const u=robustFence(l);if(u){const p=Math.max(s,u.lo),w=Math.min(a,u.hi);w>p&&(d=l.filter(v=>Number.isFinite(v)&&(v<p||v>w)).length,s=p,a=w)}}if(i)for(const u of t)s=Math.min(s,u.value),a=Math.max(a,u.value);s===a&&(s-=1,a+=1);const c=[];for(const u of t)u.value<s?c.push({value:u.value,label:u.label,side:"lo"}):u.value>a&&c.push({value:u.value,label:u.label,side:"hi"});return{lo:s,hi:a,offAxis:c,clippedCount:d}}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:d,label:c,side:u}of o){const p=s[u]++*a,w=t==="horizontal"?u==="lo"?"\u2190":"\u2192":u==="lo"?"\u2193":"\u2191",v=u==="lo"?`${w} ${c} ${r(d)}`:`${c} ${r(d)} ${w}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=u==="lo"?"left":"right",fillTextOnHalo(e,v,u==="lo"?n.left+2:n.right-2,n.top+2+p,l)):(e.textBaseline=u==="lo"?"bottom":"top",e.textAlign="left",fillTextOnHalo(e,v,n.left+4,u==="lo"?n.bottom-2-p:n.top+2+p,l))}e.restore()}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestHistogramData as Ue,collectTestValues as He,buildTestHistogramSeries as Xe,testValueExtent as Ae}from"../../stats/histogram.js";import{SPACE as se,fontPx as X,FONT as Ce,CLR as $e}from"../toolbar.js";import{fmt as pe}from"../../renderer/fmt.js";import{niceStep as Ye,fitTicks as We}from"../../renderer/axisTicks.js";import{QUANTITY as Qe,categorical as qe}from"./palette.js";import{cardShell as Je,observeResize as De,makeTooltip as Ze,attachChartTip as et,positionChartTooltip as Ee,makeLinkedTestSelect as tt,makeWaferSelect as nt,makeLinkedAxisPrefs as it,renderEmptyState as Te,chartFillHeight as Pe,applyCanvasFlow as Ie,makeAxisFormat as Le,horizontalTickSpacing as Be,PADDING as ue,robustFence as Re,shouldIncludeLimitsByDefault as Oe,drawOffAxisLimits as ot,resolveAxisRange as lt,chartSwatchCss as st,makeSeriesLegendItem as at,prepareCanvas as Fe}from"./chartShell.js";import{escHtml as ve,maxOf as rt}from"../../core/utils.js";const Me=230,Ke=36,xe=18;function Ne(o,z,G,Y,be,R,r,y=4){const ae=Math.max(1,Ye(R/y)),me=Y-G;o.save(),o.font=`${X(-1)}px system-ui, sans-serif`,o.textAlign="right",o.textBaseline="middle";for(let c=0;c<=R+1e-9;c+=ae){const F=Y-c/(R||1)*me;o.strokeStyle=r.grid,o.lineWidth=1,o.beginPath(),o.moveTo(z,F),o.lineTo(z+be,F),o.stroke(),o.fillStyle=r.text,o.fillText(String(Math.round(c)),z-4,F)}o.strokeStyle=r.axis,o.lineWidth=1,o.beginPath(),o.moveTo(z,G),o.lineTo(z,Y),o.stroke(),o.restore()}export function renderHistogramPanel(o){const{title:z="Test value distribution",items:G,testDefs:Y,onSaveImage:be,groups:R}=o,{card:r,body:y,controlsRow:ae}=Je(z,be,o.ownerDocument),me=Y.filter(l=>l.testNumber!==void 0);let c=o.selectedTestNumber??me[0]?.testNumber??null,F=null,Q=0;const Se=tt(me,c,l=>{c=l,V(),o.onTestChange?.(l)},{maxWidth:"200px",ownerDocument:r.ownerDocument}),ze=Se.el;ae.appendChild(ze);const fe=G.length>1?nt(G,F,l=>{F=l,V()},{ownerDocument:r.ownerDocument}):null;fe&&ae.appendChild(fe);function Ge(l){return v===l||l!==null&&!(R??[]).some(S=>S.key===l)?!1:(v=l,V(),!0)}const ye=r.ownerDocument.createElement("span");Object.assign(ye.style,{display:"inline-flex",gap:se.lg,alignItems:"center"}),ae.appendChild(ye);const he=it(ye,o.axisPrefs,l=>{o.onAxisPrefsChange?.(l),V()},r.ownerDocument),C=Ze(r);let re=null,v=null;function q(l){const S=Y.find(J=>J.testNumber===l);return{unit:S?.unit,limitLow:S?.limitLow,limitHigh:S?.limitHigh}}function V(){const{includeLimits:l,clipOutliers:S}=he.get();if(re?.disconnect(),re=null,y.innerHTML="",c===null){Te(y,"No parametric test data available for a histogram.");return}const J=!!R&&R.length>0;let Z=!1,k;if(J){const{limitLow:s,limitHigh:u}=q(c),i=R.flatMap(b=>b.items),{min:t,max:x}=Ae(i,c);if(Z=l??Oe(t,x,s,u),S){const b=He(i,c),M=Re(b);M&&(k={lo:Math.max(t,M.lo),hi:Math.min(x,M.hi)},Q=b.reduce((ie,A)=>ie+(A<k.lo||A>k.hi?1:0),0))}k||(Q=0)}const $=J?Xe(R,c,16,Z?q(c).limitLow:void 0,Z?q(c).limitHigh:void 0,k):null;if(fe&&(fe.style.display=$?"none":""),$){const{limitLow:s,limitHigh:u}=q(c);if(he.sync(Z,s!==void 0||u!==void 0),$.series.length===0){Te(y,"No parametric test data available for a histogram.");return}Ve($);return}const j=F!==null?[G[F]]:G,{unit:W,limitLow:D,limitHigh:E}=q(c),w=He(j,c),{min:K,max:ee}=Ae(j,c),te=l??Oe(K,ee,D,E);he.sync(te,D!==void 0||E!==void 0);const ne=S?Re(w):null,_=ne?{lo:Math.max(K,ne.lo),hi:Math.min(ee,ne.hi)}:void 0;Q=_?w.filter(s=>s<_.lo||s>_.hi).length:0;const H=Ue(j,c,16,te?D:void 0,te?E:void 0,_);if(H.length===0){Te(y,"No parametric test data available for a histogram.");return}const P=Math.max(rt(H.map(s=>s.count)),1),U=r.ownerDocument.createElement("div");Object.assign(U.style,{fontSize:Ce.body,color:$e.label,marginBottom:se.xxs}),U.textContent=`max ${P} dies/bucket`+(Q?` \xB7 ${Q} value${Q===1?"":"s"} outside clipped range`:""),y.appendChild(U);const T=r.ownerDocument.createElement("canvas");T.style.display="block",T.style.cursor="default",y.appendChild(T);let O=-1;const n=H[0].rangeLow,f=H[H.length-1].rangeHigh,g=f-n||1,e=Math.max(Math.abs(n),Math.abs(f));function d(s){const u=ue+36,i=T.clientWidth-u-ue,t=s-Ke-xe;return{plotX:u,plotMaxWidth:Math.max(10,i),plotMaxHeight:t,plotTop:xe}}function h(s){const{plotX:u,plotMaxWidth:i}=d(Me);if(s<u||s>u+i)return-1;const t=Math.floor((s-u)/i*H.length);return t>=0&&t<H.length?t:-1}function p(){Ie(T,U);const s=y.clientWidth,u=Pe(r,y,T,Me),i=Fe(T,r,s,u);if(!i)return;const{ctx:t,theme:x}=i,{plotX:b,plotMaxWidth:M,plotMaxHeight:ie,plotTop:A}=d(u),a=A+ie,L=M/H.length;Ne(t,b,A,a,M,P,{text:x.textMuted,axis:x.border,grid:x.border}),H.forEach((m,B)=>{const le=m.count/P*ie,ke=b+B*L,we=a-le;t.fillStyle=B===O?x.text:Qe,t.globalAlpha=B===O?1:.7,t.fillRect(ke+1,we,Math.max(1,L-2),le),t.globalAlpha=1,B===O&&(t.fillStyle=x.text,t.textAlign="center",t.textBaseline="bottom",t.fillText(`${m.count}`,ke+L/2,we-2))});const N=m=>b+(m-n)/g*M;t.font=`${X(-1)}px system-ui, sans-serif`;const{offAxis:ce}=lt({dataMin:n,dataMax:f,limitLow:D,limitHigh:E,includeLimits:!1});ot(t,ce,{left:b,right:b+M,top:A,bottom:a},"horizontal",x.limitLine,Le(e,W).tick);const ge=new Set(ce.map(m=>m.value));for(const[m,B]of[[D,"LSL"],[E,"USL"]]){if(m===void 0||ge.has(m))continue;const le=N(m);t.strokeStyle=x.limitLine,t.setLineDash([3,3]),t.lineWidth=1,t.beginPath(),t.moveTo(le,A),t.lineTo(le,a),t.stroke(),t.setLineDash([]),t.fillStyle=x.limitLine,t.textAlign="center",t.textBaseline="bottom",t.fillText(B,le,A-2)}t.font=`${X(-1)}px system-ui, sans-serif`,t.strokeStyle=x.border,t.lineWidth=1,t.beginPath(),t.moveTo(b,a),t.lineTo(b+M,a),t.stroke(),t.fillStyle=x.textMuted,t.textAlign="center",t.textBaseline="top";const{ticks:de,step:oe}=We(n,f,M,Be(t,e,W)),I=Le(e,W,oe||void 0);for(const m of de){const B=b+(m-n)/g*M;t.beginPath(),t.moveTo(B,a),t.lineTo(B,a+4),t.stroke(),t.fillText(I.tick(m),B,a+6)}I.unitLabel&&(t.save(),t.font=`${X(-1)}px system-ui, sans-serif`,t.fillStyle=x.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${I.unitLabel})`,b+M,a+20),t.restore())}T.addEventListener("mousemove",s=>{const u=T.getBoundingClientRect(),i=h(s.clientX-u.left);if(i!==O&&(O=i,p()),i>=0){const t=H[i];C.innerHTML=`<strong>${ve(`${pe(t.rangeLow,W,"engineering")} \u2013 ${pe(t.rangeHigh,W,"engineering")}`)}</strong><br>${t.count} dies`,C.style.display="block",Ee(C,r,s.clientX,s.clientY)}else C.style.display="none"}),T.addEventListener("mouseleave",()=>{O!==-1&&(O=-1,p()),C.style.display="none"}),re=De(r,()=>p()),p()}function Ve(l){const{unit:S,limitLow:J,limitHigh:Z}=q(c),k=l.ranges,$=l.series,j=qe;v&&!$.some(n=>n.groupKey===v)&&(v=null);const W=Math.max(1,...$.flatMap(n=>n.counts)),D=r.ownerDocument.createElement("div");Object.assign(D.style,{fontSize:Ce.body,color:$e.label,marginBottom:se.xxs}),D.textContent=`${$.length} groups \xB7 max ${W} dies/bucket`,y.appendChild(D);const E=r.ownerDocument.createElement("div");Object.assign(E.style,{display:"flex",flexWrap:"wrap",gap:se.sm,marginTop:se.md,marginBottom:se.xl}),$.forEach((n,f)=>{const g=at(n.groupKey,j(f),r.ownerDocument),e=g.el;et(e,r,C,`${n.groupKey} \u2014 click to emphasize (dim the rest)`),g.setState({selected:v===n.groupKey,dimmed:v!==null&&v!==n.groupKey}),e.addEventListener("click",()=>{const d=v===n.groupKey?null:n.groupKey;Ge(d),o.onGroupChange?.(d)}),E.appendChild(e)}),y.appendChild(E);const w=r.ownerDocument.createElement("canvas");w.style.display="block",w.style.cursor="default",y.appendChild(w);const K=k[0].rangeLow,ee=k[k.length-1].rangeHigh,te=ee-K||1,ne=Math.max(Math.abs(K),Math.abs(ee)),_=n=>{const f=(n.ownerDocument.defaultView??window).getComputedStyle(n);return n.offsetHeight+(parseFloat(f.marginTop)||0)+(parseFloat(f.marginBottom)||0)},H=()=>_(D)+_(E);let P=-1,U={plotX:ue+36,plotMaxWidth:10,barWidth:10};function T(){Ie(w,H());const n=y.clientWidth,f=Pe(r,y,w,Me),g=Fe(w,r,n,f);if(!g)return;const{ctx:e,theme:d}=g,h=ue+36,p=Math.max(10,n-h-ue),s=xe,u=f-Ke-xe,i=s+u,t=p/k.length;U={plotX:h,plotMaxWidth:p,barWidth:t},Ne(e,h,s,i,p,W,{text:d.textMuted,axis:d.border,grid:d.border}),P>=0&&(e.fillStyle=d.bgHover,e.globalAlpha=.5,e.fillRect(h+P*t,s,t,u),e.globalAlpha=1);const x=$.map((a,L)=>({s:a,i:L})).sort((a,L)=>(a.s.groupKey===v?1:0)-(L.s.groupKey===v?1:0));for(const{s:a,i:L}of x){const N=v===a.groupKey,ce=v!==null&&!N,ge=j(L);e.strokeStyle=ge,e.fillStyle=ge,e.lineWidth=N?2:1,e.globalAlpha=ce?.12:v?.32:.22,e.beginPath(),e.moveTo(h,i),a.counts.forEach((de,oe)=>{const I=h+oe*t,m=i-de/W*u;e.lineTo(I,m),e.lineTo(I+t,m)}),e.lineTo(h+p,i),e.closePath(),e.fill(),e.globalAlpha=ce?.3:1,e.beginPath(),a.counts.forEach((de,oe)=>{const I=h+oe*t,m=i-de/W*u;oe===0?e.moveTo(I,m):e.lineTo(I,m),e.lineTo(I+t,m)}),e.stroke()}e.globalAlpha=1;const b=a=>h+(a-K)/te*p;e.font=`${X(-1)}px system-ui, sans-serif`;for(const[a,L]of[[J,"LSL"],[Z,"USL"]]){if(a===void 0)continue;const N=b(a);e.strokeStyle=d.limitLine,e.setLineDash([3,3]),e.lineWidth=1,e.beginPath(),e.moveTo(N,s),e.lineTo(N,i),e.stroke(),e.setLineDash([]),e.fillStyle=d.limitLine,e.textAlign="center",e.textBaseline="bottom",e.fillText(L,N,s-2)}e.font=`${X(-1)}px system-ui, sans-serif`,e.strokeStyle=d.border,e.lineWidth=1,e.beginPath(),e.moveTo(h,i),e.lineTo(h+p,i),e.stroke(),e.fillStyle=d.textMuted,e.textAlign="center",e.textBaseline="top";const{ticks:M,step:ie}=We(K,ee,p,Be(e,ne,S)),A=Le(ne,S,ie||void 0);for(const a of M){const L=h+(a-K)/te*p;e.beginPath(),e.moveTo(L,i),e.lineTo(L,i+4),e.stroke(),e.fillText(A.tick(a),L,i+6)}A.unitLabel&&(e.save(),e.font=`${X(-1)}px system-ui, sans-serif`,e.fillStyle=d.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${A.unitLabel})`,h+p,i+20),e.restore())}function O(n){const{plotX:f,plotMaxWidth:g,barWidth:e}=U;if(n<f||n>f+g)return-1;const d=Math.floor((n-f)/e);return d>=0&&d<k.length?d:-1}w.addEventListener("mousemove",n=>{const f=w.getBoundingClientRect(),g=O(n.clientX-f.left);if(g!==P&&(P=g,T()),g>=0){const e=k[g],d=$.map((h,p)=>`<span style="${st(j(p))};margin-right:4px"></span>${ve(`${h.groupKey}: ${h.counts[g]}`)}`).join("<br>");C.innerHTML=`<strong>${ve(`${pe(e.rangeLow,S,"engineering")} \u2013 ${pe(e.rangeHigh,S,"engineering")}`)}</strong><br>${d}`,C.style.display="block",Ee(C,r,n.clientX,n.clientY)}else C.style.display="none"}),w.addEventListener("mouseleave",()=>{P!==-1&&(P=-1,T()),C.style.display="none"}),re=De(r,()=>T()),T()}V();function je(l){Se.set(l)&&(c=l,V())}function _e(l){he.set(l)&&V()}return{card:r,setTest:je,setAxisPrefs:_e,destroy:()=>re?.disconnect()}}
|
|
1
|
+
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,10 +1,14 @@
|
|
|
1
|
-
import { type ScatterItem } from '../../stats/scatter.js';
|
|
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
|
};
|
|
8
|
+
export declare const NO_BIN_CATEGORY = "none";
|
|
9
|
+
export declare function binCategoryOf(hbin: number | undefined): string;
|
|
10
|
+
/** Legend order: bins ascending, then "No bin data" last. */
|
|
11
|
+
export declare function binCategories(points: readonly Pick<ScatterPoint, 'hbin'>[]): string[];
|
|
8
12
|
export interface ScatterPanelOptions {
|
|
9
13
|
title?: string;
|
|
10
14
|
items: ScatterPanelItem[];
|
|
@@ -30,6 +34,14 @@ export interface ScatterPanelOptions {
|
|
|
30
34
|
key: string;
|
|
31
35
|
items: ScatterPanelItem[];
|
|
32
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;
|
|
33
45
|
/** Document to build this panel's DOM into. Default `document` — pass the
|
|
34
46
|
* host's own `ownerDocument` when the container might live in a
|
|
35
47
|
* different document (e.g. a gallery card detached into its own popup
|
|
@@ -40,6 +52,8 @@ export interface ScatterPanelHandle {
|
|
|
40
52
|
card: HTMLElement;
|
|
41
53
|
/** Cross-panel link (e.g. from the correlation matrix): switch X/Y in place. */
|
|
42
54
|
setXY: (xTestNumber: number, yTestNumber: number) => void;
|
|
55
|
+
/** Adopt axis preferences chosen in another panel (only `limits` applies). */
|
|
56
|
+
setAxisPrefs: (prefs: AxisPrefs) => void;
|
|
43
57
|
destroy: () => void;
|
|
44
58
|
}
|
|
45
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{buildSweepData as ce}from"../../stats/sweep.js";import{fmt as C}from"../../renderer/fmt.js";import{SPACE as ne,fontPx as
|
|
1
|
+
import{buildSweepData as ce,sweepAppliesTo as fe}from"../../stats/sweep.js";import{fmt as C}from"../../renderer/fmt.js";import{SPACE as ne,fontPx as pe,FONT as oe,CLR as G,ALPHA as de}from"../toolbar.js";import{categorical as he}from"./palette.js";import{cardShell as me,observeResize as ue,makeTooltip as xe,positionChartTooltip as ge,renderEmptyState as se,chartFillHeight as be,populationPhrase as ye,PADDING as Y,prepareCanvas as $e,makeAxisFormat as Te,VERTICAL_TICK_SPACING_PX as ve}from"./chartShell.js";import{fitTicks as Se}from"../../renderer/axisTicks.js";import{escHtml as m}from"../../core/utils.js";import{unmarkedLabel as Me,derivedTestNote as Le,DERIVED_MARK as J,DERIVED_KEY as we}from"../../renderer/testLabel.js";function _(u,a){return a?C(u,a).replace(/(\.\d*?)0+(?=\D|$)/,"$1").replace(/\.(?=\D|$)/,""):Number.isInteger(u)?String(u):String(Number(u.toPrecision(3)))}function Pe(u){const a=Me(u.label),S=a.length>9?`${a.slice(0,8)}\u2026`:a;return u.derived?`${J} ${S}`:S}const ie=260,ke=62,Ae=56,Ee=3;function Ie(u,a){const S=u.series[0]?.points[0]?.testNumber;return S===void 0?void 0:a?.find(j=>j.testNumber===S)?.unit}export function renderSweepPanel(u){const{spec:a,testDefs:S,onSaveImage:j,population:re}=u;let z=u.dies;const{card:y,body:M}=me(a.title,j,u.ownerDocument);y.style.alignSelf="start";const V=y.ownerDocument,N=V.createElement("div");Object.assign(N.style,{color:G.label,fontSize:oe.body,marginBottom:ne.sm}),y.insertBefore(N,M);const W=V.createElement("div");Object.assign(W.style,{color:G.label,fontSize:oe.body,marginTop:ne.sm}),y.appendChild(W);const L=xe(y);let X=null,E=null,$;const H=n=>a.series[n]?.color??he(n);function Q(){if(M.innerHTML="",W.innerHTML="",X?.disconnect(),X=null,a.series.length===0){N.textContent="",se(M,"This sweep defines no series.");return}if(E=ce(z,S,a),$=Ie(E,S),E.series.filter(i=>i.points.some(T=>T.count>0)).length===0){N.textContent="",se(M,fe(a,S)?"No values recorded for the tests in this sweep.":"None of this sweep\u2019s tests are in this data \u2014 it may belong to another test program."),Z();return}N.textContent=`Line = median across ${ye(E.dieCount,z.length,re)}, band = p10\u2013p90`+(a.series.length>2?" \xB7 crossing and separation measured between the first two series":"");const c=V.createElement("canvas");c.style.display="block",M.appendChild(c);let o=null;const w=()=>{const i=E;if(i===null)return;const T=Math.max(1,M.clientWidth),B=Math.max(ie,be(y,M,c,ie)),F=$e(c,y,T,B);if(!F)return;const{ctx:e,theme:x}=F,g=t=>i.xScale==="log"?Math.log10(t):t;let f=1/0,h=-1/0,b=1/0,v=-1/0;for(const t of i.series)for(const s of t.points)s.count!==0&&(f=Math.min(f,s.p10),h=Math.max(h,s.p90),b=Math.min(b,g(s.x)),v=Math.max(v,g(s.x)));for(const t of i.separations)f=Math.min(f,t.y),h=Math.max(h,t.y);if(!Number.isFinite(f)||!Number.isFinite(h))return;f===h&&(f-=1,h+=1);const l=(h-f)*.08;if(f-=l,h+=l,b===v&&(b-=.5,v+=.5),i.xScale==="log"){const t=(v-b)*.03;b-=t,v+=t}e.font=`${pe(-1)}px system-ui, sans-serif`;const r=Y+ke,p=T-Y-8,R=Y,P=B-Ae,k=t=>P-(t-f)/(h-f)*(P-R),D=t=>r+(g(t)-b)/(v-b)*(p-r);o={left:r,right:p,xLo:b,xHi:v,yOf:k,ax:g},e.strokeStyle=x.border,e.lineWidth=1,e.beginPath(),e.moveTo(r,R),e.lineTo(r,P),e.lineTo(p,P),e.stroke(),e.fillStyle=x.text,e.textAlign="right",e.textBaseline="middle";const q=Se(f,h,P-R,()=>ve),le=Te(Math.max(Math.abs(f),Math.abs(h)),$,q.step||void 0);for(const t of q.ticks)e.fillText(le.tickWithUnit(t),r-6,k(t));for(const t of i.separations){const s=k(t.y);e.save(),e.strokeStyle=x.limitLine,e.globalAlpha=.55,e.setLineDash([4,3]),e.beginPath(),e.moveTo(r,s),e.lineTo(p,s),e.stroke(),e.restore()}if(i.series.forEach((t,s)=>{const A=t.points.filter(d=>d.count>0);if(!(A.length<2)){e.save(),e.fillStyle=H(s),e.globalAlpha=.14,e.beginPath(),A.forEach((d,I)=>{const O=D(d.x),te=k(d.p90);I===0?e.moveTo(O,te):e.lineTo(O,te)});for(let d=A.length-1;d>=0;d--){const I=A[d];e.lineTo(D(I.x),k(I.p10))}e.closePath(),e.fill(),e.restore()}}),i.series.forEach((t,s)=>{e.strokeStyle=H(s),e.lineWidth=1.75,e.beginPath();let A=!1;for(const d of t.points){if(d.count===0){A=!1;continue}const I=D(d.x),O=k(d.median);A?e.lineTo(I,O):e.moveTo(I,O),A=!0}e.stroke(),e.fillStyle=H(s);for(const d of t.points)d.count!==0&&(e.beginPath(),e.arc(D(d.x),k(d.median),Ee,0,Math.PI*2),e.fill())}),i.crossing){const t=D(i.crossing.x),s=k(i.crossing.y);e.save(),e.strokeStyle=x.text,e.globalAlpha=.45,e.setLineDash([3,3]),e.beginPath(),e.moveTo(t,s),e.lineTo(t,P),e.stroke(),e.restore(),e.save(),e.strokeStyle=x.text,e.lineWidth=2,e.beginPath(),e.arc(t,s,5.5,0,Math.PI*2),e.stroke(),e.restore()}e.fillStyle=x.text,e.textBaseline="top",e.textAlign="center";const K=i.xIsPhysical?[...new Set(i.series.flatMap(t=>t.points.map(s=>s.x)))].sort((t,s)=>t-s).map(t=>({x:t,label:_(t,i.xUnit)})):(i.series[0]?.points??[]).map(t=>({x:t.x,label:Pe(t)})),ae=Math.max(1,Math.ceil(K.length*46/Math.max(1,p-r)));K.forEach((t,s)=>{s%ae!==0&&s!==K.length-1||e.fillText(t.label,D(t.x),P+6)}),e.save(),e.fillStyle=x.text,e.globalAlpha=.75,e.textAlign="center",e.textBaseline="bottom",e.fillText(i.xLabel,(r+p)/2,B-4),e.restore(),e.save(),e.fillStyle=x.text,e.globalAlpha=.75,e.translate(12,(R+P)/2),e.rotate(-Math.PI/2),e.textBaseline="top",e.fillText(i.yLabel+($?` (${$})`:""),0,0),e.restore(),e.textAlign="left",e.textBaseline="middle";let U=r+4;const ee=R+8;i.series.forEach((t,s)=>{e.fillStyle=H(s),e.fillRect(U,ee-4,10,3),U+=14,e.fillStyle=x.text,e.fillText(t.label,U,ee),U+=e.measureText(t.label).width+16})};w(),X=ue(M,w),c.addEventListener("mousemove",i=>{const T=E;if(T===null||o===null){L.style.display="none";return}const B=c.getBoundingClientRect(),F=i.clientX-B.left,e=o.xLo+(F-o.left)/Math.max(1,o.right-o.left)*(o.xHi-o.xLo),x=o.ax;let g=null;for(const l of T.series)for(const r of l.points){if(r.count===0)continue;const p=Math.abs(x(r.x)-e);(g===null||p<g.dist)&&(g={x:r.x,dist:p})}if(g===null){L.style.display="none";return}const f=T.series.map(l=>l.points.find(r=>r.x===g.x&&r.count>0)),h=T.series.map((l,r)=>{const p=f[r];return p===void 0?"":`<span style="color:${H(r)}">\u25A0</span> ${p.derived?`${J} `:""}${m(l.label)}: <strong>${m(C(p.median,$))}</strong><br><span style="opacity:${de.muted}">p10 ${m(C(p.p10,$))} \u2013 p90 ${m(C(p.p90,$))} \xB7 n=${p.count.toLocaleString()}</span>`}).filter(Boolean);if(h.length===0){L.style.display="none";return}const b=T.series.flatMap(l=>l.points).find(l=>l.x===g.x&&l.count>0),v=[...new Set(f.map(l=>Le(l)).filter(l=>l!==void 0))];L.innerHTML=`<strong>${m(b?.label??"")}</strong><br>`+h.join("<br>")+v.map(l=>`<br><em>${m(l)}</em>`).join(""),L.style.display="block",ge(L,y,i.clientX,i.clientY)}),c.addEventListener("mouseleave",()=>{L.style.display="none"}),Z()}function Z(){const n=E;if(n===null)return;const c=[];n.notMeasured!==void 0?c.push(`<strong>Crossing and widths:</strong> not measured \u2014 ${m(n.notMeasured)}`):n.crossing?c.push(`<strong>Crossing:</strong> ${m(`${n.xLabel} = ${_(n.crossing.x,n.xUnit)}`)} \xB7 ${m(`${n.yLabel} ${C(n.crossing.y,$)}`)}`+(n.crossing.multiple?" <em>(first of several \u2014 the curves cross more than once)</em>":"")):(a.crossing??!0)&&a.series.length>=2&&c.push("<strong>Crossing:</strong> the first two series do not cross over the swept range");for(const o of n.separations)if(o.distance!==null){const w=o.ratio!==void 0&&o.from!==void 0&&o.to!==void 0?`\xD7${Number(o.ratio.toPrecision(3))} on the ${n.xLabel} axis (${_(o.from,n.xUnit)} \u2192 ${_(o.to,n.xUnit)})`:`${_(o.distance,n.xUnit)} on the ${n.xLabel} axis`;c.push(`<strong>Width at ${m(C(o.y,$))}:</strong> ${m(w)}`)}else{const w=o.reason==="second-series-never-reaches"?n.series[1]?.label:n.series[0]?.label;c.push(`<strong>Width at ${m(C(o.y,$))}:</strong> not measurable \u2014 ${m(w??"a series")} never reaches this level`)}n.series.some(o=>o.points.some(w=>w.derived))&&c.push(`${J} ${m(we)} \u2014 hover a point for the expression`);for(const o of n.warnings)c.push(`<span style="color:${G.errText}">${m(o)}</span>`);W.innerHTML=c.join("<br>")}return Q(),{card:y,destroy:()=>{X?.disconnect(),L.remove(),y.remove()},setDies:n=>{z=n,Q()}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildTestPassRateData as F,hasJudgeableTests as B}from"../../stats/testPassRate.js";import{categorical as I,QUANTITY as N}from"./palette.js";import{SPACE as H,FONT as _,CLR as
|
|
1
|
+
import{buildTestPassRateData as F,hasJudgeableTests as B}from"../../stats/testPassRate.js";import{categorical as I,QUANTITY as N}from"./palette.js";import{SPACE as H,FONT as _,CLR as S}from"../toolbar.js";import{cardShell as A,makeTooltip as O,makeSegmented as j,renderEmptyState as y}from"./chartShell.js";import{renderGroupedBarPlot as M}from"./groupedBarPlot.js";import{escHtml as g}from"../../core/utils.js";const k=100,G=8,K={spec:"Test limits",testFlag:"Tester flag",functional:"Functional"},U={spec:"Parametric pass rate \xB7 test limits",testFlag:"Parametric pass rate \xB7 tester flag",functional:"Functional test pass rate"};export function renderTestPassRatePanel(f){const{groups:u,testDefs:b,onSaveImage:$}=f,p=f.ownerDocument??document,P=u.flatMap(t=>t.items.flatMap(a=>a.dies??[])),d=["spec","testFlag","functional"].filter(t=>B(b,t,P));let s=d[0]??"spec";const h=t=>U[t],{card:o,heading:v,body:i,controlsRow:x}=A(h(s),$,p);o.style.alignSelf="start",d.length>1&&x.appendChild(j(d.map(t=>[t,K[t]]),s,t=>{s=t,v.textContent=h(s),T()},p));const n=p.createElement("div");Object.assign(n.style,{color:S.label,fontSize:_.body,marginBottom:H.sm}),o.insertBefore(n,i);const D=O(o);let m=null;function T(){if(m?.destroy(),m=null,i.innerHTML="",d.length===0){n.textContent="",y(i,"No tests with a pass/fail verdict \u2014 parametric tests need test limits, functional tests need recorded results.");return}const t=F(u,b,s);if(t.rows.length===0){n.textContent="",y(i,s==="spec"?"No parametric test has test limits to judge against.":s==="testFlag"?"No parametric test has a recorded tester verdict.":"No functional test has recorded results.");return}const a=t.groups.length>1,R=s==="spec"?"Dies within test limits, per test":s==="testFlag"?"Dies the tester marked pass, per test":"Dies passing the recorded verdict, per test";if(n.textContent=R+(a?" \xB7 one bar per group":"")+" \xB7 worst first",t.disagreementDies){const e=p.createElement("span");e.textContent=` \xB7 ${t.disagreementDies.toLocaleString()} die${t.disagreementDies===1?"":"s"} judged differently by limits and tester flag`,Object.assign(e.style,{color:S.warnText}),n.appendChild(e)}const L=t.rows,C=e=>a?I(e):N;m=M(o,i,D,{rows:L.map(e=>({label:e.label,bars:e.byGroup.map(l=>({fraction:l.passRatePercent===null?null:l.passRatePercent/100})),trailing:e.overall.passRatePercent===null?"\u2014":`${e.overall.passRatePercent.toFixed(1)}%`})),groups:t.groups,labelWidth:k,maxVisibleRows:G,showLegend:a,maxLabelChars:14,emptyBarText:"no data",colorOf:C,tooltipHtml:(e,l)=>{const r=L[e],c=r.byGroup[l],w=a?` \xB7 ${g(String(t.groups[l]))}`:"";if(c.passRatePercent===null)return`<strong>${g(r.label)}</strong>${w}<br>no dies with a verdict`;const E=s==="spec"&&!a&&(r.failLowDies||r.failHighDies)?`<br>${r.failLowDies??0} below the low limit \xB7 ${r.failHighDies??0} above the high limit`:"";return`<strong>${g(r.label)}</strong>${w}<br>${c.passRatePercent.toFixed(1)}% pass<br>${c.passDies.toLocaleString()} pass \xB7 ${c.failDies.toLocaleString()} fail \xB7 n = ${c.totalDies.toLocaleString()}`+E}})}return T(),{card:o,destroy:()=>{m?.destroy(),D.remove(),o.remove()}}}
|
|
@@ -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.
|
|
@@ -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";
|
|
@@ -2,7 +2,7 @@ import type { LotStatsSummary } from '../stats/types.js';
|
|
|
2
2
|
import type { WaferMapDisplayItem } from './renderWaferGallery.js';
|
|
3
3
|
import type { BinColors } from '../renderer/binColors.js';
|
|
4
4
|
import { type SaveImageHandler, type SaveTextHandler } from './toolbar.js';
|
|
5
|
-
import type
|
|
5
|
+
import { type SweepSpec } from '../stats/sweep.js';
|
|
6
6
|
export type InsightsView = 'overview' | 'distributions' | 'correlation' | 'sweeps';
|
|
7
7
|
/** Public option shape for `RenderOptions.insights`/`GalleryOptions.insights`. */
|
|
8
8
|
export interface InsightsOptions {
|
|
@@ -65,6 +65,14 @@ export interface InsightsOptions {
|
|
|
65
65
|
* the syntax is one syntax library-wide.
|
|
66
66
|
*/
|
|
67
67
|
sweeps?: SweepSpec[];
|
|
68
|
+
/**
|
|
69
|
+
* Offers a "Remove" button on the Sweeps tab's notice about sweeps that name
|
|
70
|
+
* none of the data's tests — typically sweeps a host kept from another test
|
|
71
|
+
* program. Called with those sweeps' ids; the host drops them and re-renders
|
|
72
|
+
* with the remaining `sweeps`. Without it the notice still shows, with no
|
|
73
|
+
* button. The library never edits the host's sweep list itself.
|
|
74
|
+
*/
|
|
75
|
+
onRemoveSweeps?: (ids: string[]) => void;
|
|
68
76
|
/**
|
|
69
77
|
* Open the Insights view on mount instead of starting on the map/grid.
|
|
70
78
|
* Default false — the tab is offered, the map is what you see first.
|
|
@@ -123,6 +131,8 @@ export interface InsightsTabDeps {
|
|
|
123
131
|
defaultView?: InsightsView;
|
|
124
132
|
/** Sweep definitions to render in the Distributions view — see `InsightsOptions.sweeps`. */
|
|
125
133
|
sweeps?: SweepSpec[];
|
|
134
|
+
/** See `InsightsOptions.onRemoveSweeps`. */
|
|
135
|
+
onRemoveSweeps?: (ids: string[]) => void;
|
|
126
136
|
/**
|
|
127
137
|
* When provided, the tab bar gets a leading "‹ Map"/"‹ Gallery" tab that
|
|
128
138
|
* exits Insights back to the host's normal view — one visible navigation
|