@wafertools/wafermap 0.33.0 → 0.34.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 +6 -6
- package/CHANGELOG.md +212 -0
- package/README.md +5 -5
- package/dist/packages/canvas-adapter/chartPopulation.d.ts +15 -10
- package/dist/packages/canvas-adapter/chartPopulation.js +1 -1
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +7 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/binCluster.d.ts +7 -1
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
- 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 +55 -3
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +1 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/palette.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/palette.js +1 -1
- package/dist/packages/canvas-adapter/charts/plotChart.d.ts +27 -0
- package/dist/packages/canvas-adapter/charts/plotChart.js +1 -0
- package/dist/packages/canvas-adapter/charts/pointInteractions.d.ts +45 -0
- package/dist/packages/canvas-adapter/charts/pointInteractions.js +1 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +2 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +19 -0
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.d.ts +12 -0
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
- package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +7 -0
- package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
- package/dist/packages/canvas-adapter/charts/trend.d.ts +11 -0
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/confirmDialog.d.ts +13 -0
- package/dist/packages/canvas-adapter/confirmDialog.js +1 -0
- package/dist/packages/canvas-adapter/dataTab.d.ts +131 -0
- package/dist/packages/canvas-adapter/dataTab.js +1 -0
- package/dist/packages/canvas-adapter/dieList.d.ts +39 -4
- package/dist/packages/canvas-adapter/dieList.js +6 -7
- package/dist/packages/canvas-adapter/drilldown.d.ts +20 -3
- package/dist/packages/canvas-adapter/drilldown.js +1 -1
- package/dist/packages/canvas-adapter/guideWindow.d.ts +96 -0
- package/dist/packages/canvas-adapter/guideWindow.js +1 -0
- package/dist/packages/canvas-adapter/identityHeader.d.ts +16 -0
- package/dist/packages/canvas-adapter/identityHeader.js +1 -1
- package/dist/packages/canvas-adapter/index.d.ts +3 -3
- package/dist/packages/canvas-adapter/index.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +73 -54
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/maplessSummary.d.ts +2 -2
- package/dist/packages/canvas-adapter/plotEditor.d.ts +29 -0
- package/dist/packages/canvas-adapter/plotEditor.js +1 -0
- package/dist/packages/canvas-adapter/plotItems.d.ts +32 -0
- package/dist/packages/canvas-adapter/plotItems.js +1 -0
- package/dist/packages/canvas-adapter/plotModal.d.ts +30 -0
- package/dist/packages/canvas-adapter/plotModal.js +1 -0
- package/dist/packages/canvas-adapter/plotStore.d.ts +52 -0
- package/dist/packages/canvas-adapter/plotStore.js +1 -0
- package/dist/packages/canvas-adapter/plotTab.d.ts +30 -0
- package/dist/packages/canvas-adapter/plotTab.js +1 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +3 -2
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +21 -2
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +27 -8
- package/dist/packages/canvas-adapter/summaryPanel.js +2 -4
- package/dist/packages/canvas-adapter/summaryRail.d.ts +7 -0
- package/dist/packages/canvas-adapter/summaryRail.js +1 -0
- package/dist/packages/canvas-adapter/sweepEditor.d.ts +17 -0
- package/dist/packages/canvas-adapter/sweepEditor.js +1 -0
- package/dist/packages/canvas-adapter/tableExport.d.ts +22 -0
- package/dist/packages/canvas-adapter/tableExport.js +3 -0
- package/dist/packages/canvas-adapter/toolbar.d.ts +25 -97
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +337 -42
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/viewSwitch.d.ts +13 -0
- package/dist/packages/canvas-adapter/viewSwitch.js +1 -0
- package/dist/packages/canvas-adapter/virtualTable.d.ts +44 -0
- package/dist/packages/canvas-adapter/virtualTable.js +1 -0
- package/dist/packages/core/classify.d.ts +3 -2
- package/dist/packages/core/classify.js +1 -1
- package/dist/packages/core/passBins.d.ts +17 -4
- package/dist/packages/core/passBins.js +1 -1
- package/dist/packages/core/ringCount.d.ts +22 -0
- package/dist/packages/core/ringCount.js +1 -0
- package/dist/packages/core/tableCsv.d.ts +43 -0
- package/dist/packages/core/tableCsv.js +3 -0
- package/dist/packages/renderer/binColors.d.ts +6 -6
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +1 -1
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/stats/analyzeWaferLot.js +1 -1
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/binPareto.d.ts +1 -1
- package/dist/packages/stats/binPareto.js +1 -1
- package/dist/packages/stats/histogram.d.ts +27 -0
- package/dist/packages/stats/histogram.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -0
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/lotDrift.js +1 -1
- package/dist/packages/stats/lotPattern.js +1 -1
- package/dist/packages/stats/math.d.ts +9 -6
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/patternClassification.d.ts +1 -1
- package/dist/packages/stats/patternClassification.js +1 -1
- package/dist/packages/stats/plotData.d.ts +258 -0
- package/dist/packages/stats/plotData.js +1 -0
- package/dist/packages/stats/plotId.d.ts +3 -0
- package/dist/packages/stats/plotId.js +1 -0
- package/dist/packages/stats/plotSpec.d.ts +119 -0
- package/dist/packages/stats/plotSpec.js +4 -0
- package/dist/packages/stats/presentation.d.ts +2 -0
- package/dist/packages/stats/presentation.js +1 -1
- package/dist/packages/stats/regions.d.ts +6 -0
- package/dist/packages/stats/regions.js +1 -1
- package/dist/packages/stats/renderSummaryReport.d.ts +8 -3
- package/dist/packages/stats/renderSummaryReport.js +15 -15
- package/dist/packages/stats/reportHtml.d.ts +1 -1
- package/dist/packages/stats/reportHtml.js +36 -29
- package/dist/packages/stats/scatter.d.ts +6 -0
- package/dist/packages/stats/scatter.js +1 -1
- package/dist/packages/stats/sweep.d.ts +37 -1
- package/dist/packages/stats/sweep.js +1 -1
- package/dist/packages/stats/sweepText.d.ts +14 -0
- package/dist/packages/stats/sweepText.js +1 -0
- package/dist/packages/stats/sweepXFromName.d.ts +14 -0
- package/dist/packages/stats/sweepXFromName.js +1 -1
- package/dist/packages/stats/synthesis.d.ts +14 -4
- package/dist/packages/stats/synthesis.js +1 -1
- package/dist/packages/stats/testPassRate.d.ts +6 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/trend.js +1 -1
- package/dist/packages/stats/types.d.ts +1 -1
- package/dist/packages/stats/yield.d.ts +15 -6
- package/dist/packages/stats/yield.js +1 -1
- package/llms.txt +15 -3
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
import{MEAN_WAFER_YIELD_LABEL as He}from"../stats/presentation.js";import{buildFacetTable as Fe,facetValueOf as Ne,FACET_NONE_VALUE as _e}from"../stats/facets.js";import{mergeTestDefs as de}from"../stats/mergeTestDefs.js";import{isParametricTest as Ve}from"../renderer/buildWaferMap.js";import{waferDisplayLabel as ze,waferIdentityLabel as Ue}from"../core/waferLabel.js";import{hasDrilldownTargets as ce,waferPopulation as qe}from"./chartPopulation.js";import{openDrilldownMenu as Qe}from"./drilldown.js";import{INPUT_DEFAULT_PASS_BINS as M,itemPassBins as Ke,passBinsLabel as pe}from"../core/passBins.js";import{NO_DATA_FILL as ue}from"../renderer/colorMap.js";import{describeWaferPopulation as Xe,populationStat as Je}from"../stats/population.js";import{LEADING as fe,ALPHA as Ze,SPACE as w,EDGE_GUTTER as te,FONT as O,CLR as k,RADIUS as me,SHADOW as et,controlStyle as be,wireControlHover as ne,wireTooltip as tt}from"./toolbar.js";import{ICONS as nt}from"./icons.js";import{renderCapabilityPanel as ot}from"./charts/capability.js";import{renderBoxplotPanel as at}from"./charts/boxplot.js";import{renderTrendPanel as st}from"./charts/trend.js";import{renderSweepPanel as rt}from"./charts/sweep.js";import{sweepAppliesTo as it}from"../stats/sweep.js";import{renderHistogramPanel as lt}from"./charts/histogram.js";import{renderCorrelationPanel as dt}from"./charts/correlation.js";import{renderScatterPanel as ct}from"./charts/scatter.js";import{renderBarPanel as ge}from"./charts/barPanel.js";import{renderBinClusterPanel as pt}from"./charts/binCluster.js";import{renderTestPassRatePanel as ut}from"./charts/testPassRate.js";import{QUANTITY as ft}from"./charts/palette.js";import{cardFrameStyle as mt,makeChartGridWrap as V,makeLabeledSelect as bt,makeLinkedGroupSelect as gt}from"./charts/chartShell.js";import{buildYieldData as z,buildYieldDataCombined as he}from"../stats/yield.js";import{buildBinParetoData as ht}from"../stats/binPareto.js";import{buildLotTestSectionSteps as yt,buildLotFunctionalSection as wt,buildMetadataStripBox as Ct}from"./summaryPanel.js";import{runChunked as xt}from"./chunked.js";import{buildRegionYieldData as ye,buildRingRegions as St,buildQuadrantRegions as vt}from"../stats/regions.js";import{renderRegionYieldDiagram as we}from"./charts/regionYieldDiagram.js";const Ce=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}],Tt={key:"sweeps",label:"Sweeps"};export function createInsightsTab(x){const{getItems:xe,getLotStats:Se,getBinColors:oe,getRingCount:ve,onSaveImage:T,onSaveText:U,openWafer:F,focusTest:ae}=x,Te=x.showMetadataStrip??!0,Y=x.contentInset??te,p=x.ownerDocument??document,R=p.createElement("div");Object.assign(R.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const ke=te,W=p.createElement("div");Object.assign(W.style,{paddingLeft:ke});const $=p.createElement("div");Object.assign($.style,{display:"flex",gap:w.xs,alignItems:"center",background:k.menuBg,border:`1px solid ${k.menuBorder}`,borderRadius:me.container,boxShadow:et.panel,padding:`0 ${w.lg}`,marginBottom:w.lg,marginLeft:Y,marginRight:Y}),$.setAttribute("role","tablist"),$.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=Array.from($.querySelectorAll('[role="tab"]')),o=t.indexOf(p.activeElement);if(o===-1)return;e.preventDefault();const s=t[(o+(e.key==="ArrowRight"?1:-1)+t.length)%t.length];s.focus(),s.click()});const G=p.createElement("div");Object.assign(G.style,{display:"flex",flexDirection:"column",gap:w.lg,marginTop:"0",paddingLeft:Y,paddingRight:Y}),R.appendChild(W),R.appendChild($),R.appendChild(G);const se=(x.sweeps?.length??0)>0,De=se?[...Ce,Tt]:Ce;let P=x.defaultView==="sweeps"&&!se?"overview":x.defaultView??"overview",j,E=null,L=null,H={includeLimits:void 0,clipOutliers:!1,limits:"both"};const q=new Map,re=(e,t)=>{H=e;for(const o of q.values())for(const s of o)s!==t&&s.setAxisPrefs(e)},Q=e=>{L!==e&&(L=e,_())};let N=new Map;function ie(){for(const e of N.values())e.destroy();N=new Map}function K(e,t,o){F?.(e,t,o)}function X(e){if(e.some(t=>ce(t.testDefs,x.sweeps)))return(t,o,s)=>{const r=e.find(u=>u.waferIndex===t);if(!r||!ce(r.testDefs,x.sweeps))return;s.preventDefault();const i=qe(r.dies,{waferLabel:r.identity,testDefs:r.testDefs,activeTest:o??E??void 0,waferIndex:t});Qe({x:s.clientX,y:s.clientY},s.target,i,{sweeps:x.sweeps,onSaveImage:T})}}function Be(e){return F?{open:(t,o)=>K(t,`Wafer ${e.find(s=>s.waferIndex===t)?.label??t}`,o)}:ae&&e.length===1?{open:(t,o)=>ae(o),label:"show this test on the map"}:null}function $e(){return xe().map((e,t)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:ze(e,t),identity:Ue(e),waferIndex:t,passBins:Ke(e),statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function le(e,t){Object.assign(e.style,{background:"none",border:"none",borderBottom:t?`2px solid ${k.iconActive}`:"2px solid transparent",color:t?k.iconActive:k.label,fontWeight:t?"700":"500",fontSize:O.body,padding:`${w.sm} ${te} ${w.md}`,cursor:"pointer",marginBottom:"-1px"})}function Ee(e,t){const o=p.createElement("button");o.type="button",o.textContent=t;const s=e===P;return le(o,s),o.dataset.on=s?"true":"false",ne(o,"bare"),o.dataset.wmapInsightsTab=e,o.setAttribute("role","tab"),o.setAttribute("aria-selected",s?"true":"false"),o.tabIndex=s?0:-1,o.addEventListener("click",()=>{P!==e&&(P=e,_({keepSections:!0}))}),o}function Le(e){const t=p.createElement("button");return t.type="button",t.textContent=`\u2039 ${e.label}`,le(t,!1),t.dataset.wmapInsightsBack="1",t.addEventListener("click",e.onBack),t}function Pe(e,t,o,s=!0){const r=V(p),i=oe();let u="label";const f=o??"group",g=pe(e.map(d=>d.passBins)),c=Se?.(),m=new Map(c?.lotYieldSeries.map(d=>[d.waferIndex,d.yieldPercent])),h=d=>({...d,label:d.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:m.get(d.waferIndex),key:d.waferIndex}),b=e.map(h),y=t?.map(d=>({key:d.key,items:d.items.map(h)})),S=()=>y?he(y,M,u):z(b,M,u),a={title:t?`Yield by ${f} (pass: ${g})`:`Yield by wafer (pass: ${g})`,data:S(),selfControl:{current:u,options:[["yield","Sort: yield"],["label",t?`Sort: ${f}`:"Sort: wafer ID"]],onChange:d=>(u=d,{data:S()})},barColor:()=>ft,valueLabel:d=>`${d.percent.toFixed(1)}%`,reference:d=>{if(d.length<3)return null;const D=d.map(ee=>ee.percent).sort((ee,Ge)=>ee-Ge),A=D.length%2?D[(D.length-1)/2]:(D[D.length/2-1]+D[D.length/2])/2;return{value:A,label:`median ${A.toFixed(1)}%`}},drill:y?{onOpenGroup:d=>{const D=y.find(A=>A.key===d.label)?.items??[];return{data:z(D,M,u),title:`Yield by wafer \u2014 ${f}: ${d.label} (pass: ${g})`}},onBack:()=>({data:S(),title:`Yield by ${f} (pass: ${g})`}),groupLabelText:f}:void 0,onOpen:F?d=>{typeof d.key=="number"&&K(d.key,`Wafer ${d.label}`)}:void 0,onWaferContextMenu:X(e),ownerDocument:p};let n=null;if(s&&(n=ge(a,T),n.card.style.minHeight="360px",r.appendChild(n.card)),t){const d=pt({title:"Hard bin pareto",groups:t.map(D=>({key:D.key,items:D.items.map(A=>({dies:A.dies,hardBinCounts:A.statsSummary?.stats.hardBinCounts,softBinCounts:A.statsSummary?.stats.softBinCounts}))})),onSaveImage:T,ownerDocument:p});return d.card.style.minHeight="360px",r.appendChild(d.card),{card:r,destroy:()=>{n?.destroy(),d.destroy()}}}let v="hbin";const l=d=>d===void 0?ue:(v==="hbin"?i.hard:i.soft).get(d)??ue,C=e.map(d=>({dies:d.dies,hardBinCounts:d.statsSummary?.stats.hardBinCounts,softBinCounts:d.statsSummary?.stats.softBinCounts})),I=()=>ht(C,v),B={title:"Hard bin pareto",data:I(),selfControl:{current:v,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:d=>(v=d,{data:I(),title:`${v==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:d=>l(d.binCode),ownerDocument:p},Z=ge(B,T);return Z.card.style.minHeight="360px",r.appendChild(Z.card),{card:r,destroy:()=>{n?.destroy(),Z.destroy()}}}function J(){const e=p.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...mt(),display:"flex",flexDirection:"column",gap:w.lg,minWidth:"0"}),e}function Ae(e){if(W.innerHTML="",W.style.marginBottom="0",!Te)return;const t=Ct(e.map(o=>({metadata:o.metadata})),void 0,{facetableOnly:!1});t&&(t.style.marginBottom="0",W.appendChild(t),W.style.marginBottom=w.xs)}function Ie(e,t,o,s){if(!e.length)return{elements:[],testValuesCard:null,functionalCard:null,destroy:()=>{}};const r=e.map(l=>l.wafer),i=e.map(l=>l.dies),u=e.flatMap(l=>l.dies),f=e.map(l=>l.statsSummary),g=f.every(l=>l!==void 0)?f:void 0,c=ve?.()??4,m=[],h=[],y=ut({groups:(s??[{key:"",items:e}]).map(l=>({key:l.key,items:l.items.map(C=>({dies:C.dies,testSpecYield:C.statsSummary?.stats.testSpecYield,functionalYield:C.statsSummary?.stats.functionalYield}))})),testDefs:o,onSaveImage:T,ownerDocument:p});y.card.style.minHeight="300px",m.push(y.card),h.push(y.destroy);const S=ye(i,r,c,l=>e[l].passBins,St);if(S.length){const l=we({title:"Ring yield",mode:"ring",rows:S,onSaveImage:T,ownerDocument:p});m.push(l.card),h.push(l.destroy)}const a=ye(i,r,c,l=>e[l].passBins,vt);if(a.length){const l=we({title:"Quadrant yield",mode:"quadrant",rows:a,onSaveImage:T,ownerDocument:p});m.push(l.card),h.push(l.destroy)}let n=null;if(t.length){const l=J();let C=null;const I=xt(yt(u,t,void 0,g,U,i.map(B=>({dies:B})),void 0,"full"),{onDone:B=>{C=B,l.removeAttribute("aria-busy"),B?l.replaceChildren(B):l.remove()}});if(I.done)C&&(n=l);else{const B=p.createElement("div");B.textContent="Computing test statistics\u2026",Object.assign(B.style,{color:k.label,fontSize:O.body}),l.setAttribute("aria-busy","true"),l.appendChild(B),n=l,h.push(()=>I.cancel())}}let v=null;if(o.length){const l=wt(u,o,g,U);if(l){const C=J();C.appendChild(l),v=C}}return{elements:m,testValuesCard:n,functionalCard:v,destroy:()=>{for(const l of h)l()}}}function Oe(e){const t=pe(e.map(c=>c.passBins)),o=e.length===1,s=e[0],r=J();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:w.md});const i=c=>{const m=c.lastElementChild;return m&&Object.assign(m.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),c};function u(c,m,h){const b=p.createElement("div");Object.assign(b.style,{display:"flex",alignItems:"baseline",gap:w.sm,paddingRight:w.xxl,marginRight:w.md,borderRight:`1px solid ${k.menuBorder}`});const y=p.createElement("div");y.textContent=c,Object.assign(y.style,{fontSize:O.heading,fontWeight:"700",color:k.value,lineHeight:fe.tight});const S=p.createElement("div");if(S.textContent=m,Object.assign(S.style,{fontSize:O.body,color:k.label}),b.append(y,S),h){const a=p.createElement("div");a.textContent=h,Object.assign(a.style,{fontSize:O.meta,color:k.label,opacity:Ze.muted}),b.appendChild(a)}return b}if(o){const c=z([{...s,key:s.waferIndex}],M)[0]?.percent;return c!==void 0&&r.appendChild(u(`${c.toFixed(1)}%`,`Yield \xB7 pass: ${t}`)),r.appendChild(u(String(s.dies.length),"Total dies")),i(r)}r.appendChild(u(String(e.length),"Wafers"));const f=z(e.map(c=>({...c,key:c.waferIndex})),M).map(c=>c.percent).filter(c=>Number.isFinite(c));if(f.length){const c=f.reduce((b,y)=>b+y,0)/f.length,m=he([{key:"all",items:e.map(b=>({...b,key:b.waferIndex}))}],M)[0]?.percent,h=m!==void 0&&m.toFixed(1)!==c.toFixed(1);r.appendChild(u(`${c.toFixed(1)}%`,`${He} \xB7 pass: ${t}`,h?`${m.toFixed(1)}% by dies`:void 0))}const g=e.map(c=>c.statsSummary);if(g.every(c=>c!==void 0)){const c=g.reduce((h,b)=>h+b.stats.analyzedDies,0),m=g.reduce((h,b)=>h+b.stats.excludedDies,0);r.appendChild(u(c.toLocaleString(),"Dies analysed",m?`${m.toLocaleString()} excluded`:void 0))}else r.appendChild(u(e.reduce((c,m)=>c+m.dies.length,0).toLocaleString(),"Total dies"));return i(r)}function Re(e,t,o,s,r){const i=p.createElement("div");Object.assign(i.style,{display:"flex",flexDirection:"column",gap:w.lg});const u=e.length===1&&!s;e.length&&!s&&i.appendChild(Oe(e));const f=Pe(e,s,r,!u);i.appendChild(f.card);const g=Ie(e,t,o,s);for(const c of g.elements)f.card.appendChild(c);return g.testValuesCard&&i.appendChild(g.testValuesCard),g.functionalCard&&i.appendChild(g.functionalCard),{card:i,destroy:()=>{f.destroy(),g.destroy()}}}function We(e,t,o,s){const r=V(p),i=Be(e),u=a=>n=>re(n,a()),f=a=>({...a,testStats:a.statsSummary?.stats.perTestStats}),g=e.map(f),c=o?.map(a=>({key:a.key,items:a.items.map(f)})),m=at({title:"Test value distribution",axisPrefs:H,onAxisPrefsChange:u(()=>m),onTestChange:a=>y(a),selectedTestNumber:E??void 0,items:g,testDefs:t,groups:c,groupLabelText:s,onSaveImage:T,onGroupChange:a=>Q(a),onOpen:i?.open,openActionLabel:i?.label,onWaferContextMenu:X(e),ownerDocument:p}),h=lt({title:"Value histogram",axisPrefs:H,onAxisPrefsChange:u(()=>h),onTestChange:a=>y(a),selectedTestNumber:E??void 0,items:e,testDefs:t,groups:o,onGroupChange:a=>Q(a),onSaveImage:T,ownerDocument:p}),b=st({title:"Wafer-to-wafer trend",axisPrefs:H,onAxisPrefsChange:u(()=>b),onTestChange:a=>y(a),selectedTestNumber:E??void 0,items:e.map(a=>({label:a.label,key:a.waferIndex,dies:a.dies,testStats:a.statsSummary?.stats.perTestStats})),testDefs:t,onSaveImage:T,centreLabel:Je(Xe(e.map(a=>a.wafer.metadata)),"mean"),onOpen:F?(a,n)=>K(a,`Wafer ${e.find(v=>v.waferIndex===a)?.label??a}`,n):void 0,onWaferContextMenu:X(e),ownerDocument:p}),y=a=>{E=a,m.setTest(a),h.setTest(a),b.setTest(a),S.setTest(a)};q.set("distribution",[m,h,b]);const S=ot({title:"Process capability",items:e,testDefs:t,onSaveImage:T,selectedTestNumber:E??void 0,onSelectTest:a=>y(a),ownerDocument:p});return r.append(S.card,m.card,h.card,b.card),{card:r,destroy:()=>{S.destroy(),m.destroy(),h.destroy(),b.destroy()}}}function je(e){const t=V(p),o=de(e).defs,s=e.flatMap(f=>f.dies),r=x.sweeps??[],i=r.filter(f=>!it(f,o));i.length>0&&t.appendChild(Me(i,r.length));const u=r.map(f=>rt({spec:f,dies:s,testDefs:o,onSaveImage:T,ownerDocument:p}));for(const f of u)t.appendChild(f.card);return{card:t,destroy:()=>{for(const f of u)f.destroy()}}}function Me(e,t){const o=p.createElement("div");o.setAttribute("role","status"),Object.assign(o.style,{gridColumn:"1 / -1",display:"flex",flexWrap:"wrap",alignItems:"center",gap:w.md,padding:`${w.sm} ${w.md}`,background:k.warnBg,border:`1px solid ${k.warnBorder}`,borderRadius:me.container,color:k.warnText,fontSize:O.body,lineHeight:fe.base});const s=e.length,r=p.createElement("span");if(r.style.flex="1 1 24ch",r.textContent=(s===t?s===1?"This sweep names no test in this data":"None of these sweeps names a test in this data":`${s} of ${t} sweeps name no test in this data: ${e.map(i=>i.title).join(", ")}`)+` \u2014 ${s===1?"it":"they"} may belong to another test program.`,o.appendChild(r),x.onRemoveSweeps){const i=p.createElement("button");i.type="button",Object.assign(i.style,be("outlined")),i.textContent=s===1?"Remove this sweep":`Remove these ${s} sweeps`,ne(i);const u=e.map(f=>f.id);i.addEventListener("click",()=>x.onRemoveSweeps(u)),o.appendChild(i)}return o}function Ye(e,t,o){const s=V(p),r=ct({title:"Test scatter",items:e,testDefs:t,groups:o,binColors:oe().hard,onSaveImage:T,axisPrefs:H,onAxisPrefsChange:u=>re(u,r),ownerDocument:p});q.set("correlation",[r]);const i=dt({title:"Test correlation matrix",items:e,testDefs:t,onSaveImage:T,onSaveText:U,onSelectPair:(u,f)=>r.setXY(u,f),ownerDocument:p});return s.append(i.card,r.card),{card:s,destroy:()=>{i.destroy(),r.destroy()}}}function _(e){e?.keepSections||ie(),G.innerHTML="",$.innerHTML="",x.backTab&&$.appendChild(Le(x.backTab));for(const n of De)$.appendChild(Ee(n.key,n.label));const t=$e();Ae(t);const o=Fe(t,{facetableOnly:!0}).filter(n=>n.splittable),s=p.createElement("div");Object.assign(s.style,{display:"flex",gap:w.md,alignItems:"center",marginLeft:w.xxl,paddingBottom:w.xs});let r;o.length>0?(s.appendChild(bt("Group by:",[{value:"",label:"None"},...o.map(n=>({value:n.key,label:`${n.label} (${n.values.length})`}))],j??"",n=>{j=n||void 0,L=null,_()},{hook:"group-by",ownerDocument:p})),r=o.find(n=>n.key===j)?.label):j=void 0;let i;if(j){const n=new Map,v=[];for(const l of t){const C=Ne(l.metadata,j)??_e;n.has(C)||(n.set(C,[]),v.push(C)),n.get(C).push(l)}i=v.map(l=>({key:l,items:n.get(l)}))}L!==null&&!i?.some(n=>n.key===L)&&(L=null),i&&i.length>0&&s.appendChild(gt(i.map(n=>n.key),"Show:",L,n=>Q(n),{ownerDocument:p}).el);let u=null;if(x.onOpenGuide){const n=p.createElement("button");n.type="button",n.innerHTML=nt.help,n.setAttribute("aria-label","User guide"),Object.assign(n.style,{...be("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),ne(n,"bare"),tt(n,"User guide"),n.addEventListener("click",()=>x.onOpenGuide()),Object.assign(n.style,{marginLeft:"auto"}),u=n}$.appendChild(s),u&&$.appendChild(u);const f=L!==null?i?.find(n=>n.key===L):void 0,g=f?.items??t,c=f?void 0:i,m=de(g),h=m.defs,b=h.filter(Ve);E!==null&&!b.some(n=>n.testNumber===E)&&(E=null);const y=e?.keepSections?N.get(P):void 0,S=y??(P==="overview"?Re(g,b,h,c,r):P==="distributions"?We(g,b,c,r):P==="sweeps"?je(g):Ye(g,b,c));y||N.set(P,S);const a=m.conflicts.filter(n=>n.excluded);if(a.length>0){const n=p.createElement("div");Object.assign(n.style,{color:k.label,fontSize:O.body,padding:`0 ${Y} ${w.sm}`});const v=a.slice(0,6).map(I=>I.testNumber).join(", "),l=a.length>6?`, and ${a.length-6} more`:"",C=a.length===1?"it":"them";n.textContent=`${a.length} test ${a.length===1?"number is":"numbers are"} hidden (${v}${l}) \u2014 the wafers in view describe ${C} differently, so their values cannot be pooled. `+(i&&i.length>0?`Pick one ${r??"group"} under "Show:" to analyse ${C}.`:"Load one test program at a time to analyse them."),n.dataset.wmapWithheldTests=String(a.length),G.appendChild(n)}G.appendChild(S.card)}return{el:R,render:_,destroy:()=>{ie(),R.remove()}}}
|
|
1
|
+
import{MEAN_WAFER_YIELD_LABEL as Ue}from"../stats/presentation.js";import{buildFacetTable as qe,facetValueOf as Ke,FACET_NONE_VALUE as Qe}from"../stats/facets.js";import{mergeTestDefs as he}from"../stats/mergeTestDefs.js";import{isParametricTest as we}from"../renderer/buildWaferMap.js";import{dieFailsTest as Xe}from"../stats/testPassRate.js";import{fmt as Ce}from"../renderer/fmt.js";import{testLabel as Je}from"../renderer/testLabel.js";import{waferDisplayLabel as Ze,waferIdentityLabel as et}from"../core/waferLabel.js";import{waferPopulation as tt}from"./chartPopulation.js";import{openDrilldownMenu as se}from"./drilldown.js";import{plotStoreFor as nt}from"./plotStore.js";import{itemPassBins as ot,passBinsLabel as Se}from"../core/passBins.js";import{NO_DATA_FILL as xe}from"../renderer/colorMap.js";import{describeWaferPopulation as at,populationStat as rt}from"../stats/population.js";import{LEADING as it,ALPHA as st,SPACE as x,EDGE_GUTTER as le,FONT as M,CLR as B,RADIUS as lt,SHADOW as dt,controlStyle as ct,wireControlHover as de,wireTooltip as pt}from"./toolbar.js";import{ICONS as ut}from"./icons.js";import{renderCapabilityPanel as ft}from"./charts/capability.js";import{renderBoxplotPanel as mt}from"./charts/boxplot.js";import{renderTrendPanel as bt}from"./charts/trend.js";import{renderHistogramPanel as gt}from"./charts/histogram.js";import{renderCorrelationPanel as yt}from"./charts/correlation.js";import{renderScatterPanel as ht}from"./charts/scatter.js";import{renderBarPanel as ke}from"./charts/barPanel.js";import{renderBinClusterPanel as wt}from"./charts/binCluster.js";import{renderTestPassRatePanel as Ct}from"./charts/testPassRate.js";import{QUANTITY as St}from"./charts/palette.js";import{cardFrameStyle as xt,makeChartGridWrap as ce,makeLabeledSelect as kt,makeLinkedGroupSelect as vt}from"./charts/chartShell.js";import{buildYieldData as J,buildYieldDataCombined as ve}from"../stats/yield.js";import{buildBinParetoData as Tt}from"../stats/binPareto.js";import{buildLotTestSectionSteps as Dt,buildLotFunctionalSection as Bt,buildMetadataStripBox as $t}from"./summaryPanel.js";import{runChunked as Lt}from"./chunked.js";import{buildRegionYieldData as Te,buildRingRegions as De,buildQuadrantRegions as Be,diesInRegion as It}from"../stats/regions.js";import{renderDataSection as Et}from"./dataTab.js";import{sourceFromDies as Pt}from"./plotItems.js";import{renderRegionYieldDiagram as $e}from"./charts/regionYieldDiagram.js";const Ot=[{key:"overview",label:"Overview"},{key:"distributions",label:"Distributions"},{key:"correlation",label:"Correlation"}],At={key:"data",label:"Data"},Rt={key:"plot",label:"Plot"};export function createInsightsTab($){const{getItems:Le,getLotStats:pe,getBinColors:ue,getRingCount:F,onSaveImage:k,onSaveText:A,openWafer:R,focusTest:Z,locateDie:_}=$,Ie=$.showMetadataStrip??!0,z=$.contentInset??le,m=$.ownerDocument??document,N=m.createElement("div");Object.assign(N.style,{display:"none",flexDirection:"column",width:"100%",paddingTop:"0"});const Ee=le,Y=m.createElement("div");Object.assign(Y.style,{paddingLeft:Ee});const E=m.createElement("div");Object.assign(E.style,{display:"flex",gap:x.xs,alignItems:"center",background:B.menuBg,border:`1px solid ${B.menuBorder}`,borderRadius:lt.container,boxShadow:dt.panel,padding:`0 ${x.lg}`,marginBottom:x.lg,marginLeft:z,marginRight:z}),E.setAttribute("role","tablist"),E.addEventListener("keydown",e=>{if(e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const n=Array.from(E.querySelectorAll('[role="tab"]')),a=n.indexOf(m.activeElement);if(a===-1)return;e.preventDefault();const d=n[(a+(e.key==="ArrowRight"?1:-1)+n.length)%n.length];d.focus(),d.click()});const U=m.createElement("div");Object.assign(U.style,{display:"flex",flexDirection:"column",gap:x.lg,marginTop:"0",paddingLeft:z,paddingRight:z}),N.appendChild(Y),N.appendChild(E),N.appendChild(U);const Pe=[...Ot,At,Rt],q=$.plotStore??nt($);let L=$.defaultView==="sweeps"?"plot":$.defaultView??"overview",W,fe="statistics",me="wide",P=null,O=null,K={includeLimits:void 0,clipOutliers:!1,limits:"both"};const ee=new Map,be=(e,n)=>{K=e;for(const a of ee.values())for(const d of a)d!==n&&d.setAxisPrefs(e)},te=e=>{O!==e&&(O=e,G())};let j=new Map;function ge(){for(const e of j.values())e.destroy();j=new Map}function ne(e,n,a){R?.(e,n,a)}function oe(e){return(n,a,d)=>{const r=e.find(p=>p.waferIndex===n);if(!r)return;d.preventDefault();const f=tt(r.dies,{waferLabel:r.identity,testDefs:r.testDefs,activeTest:a??P??void 0,waferIndex:n,wafer:r.wafer,passBins:r.passBins,ringCount:F()});se({x:d.clientX,y:d.clientY},d.target,f,{plots:q,onSaveImage:k,onSaveText:A,onLocateDie:_})}}function Q(e,n,a,d,r){ye({x:e.clientX,y:e.clientY},e.target,n,a,d,r)}function ye(e,n,a,d,r,f){const p=Pt(a.map(l=>({label:l.identity??l.label,dies:l.dies,waferIndex:l.waferIndex,wafer:l.wafer,passBins:l.passBins,ringCount:F()})),d,r,he(a).defs,f);p&&se(e,n,p,{plots:q,onSaveImage:k,onSaveText:A,onLocateDie:_})}function ae(e){return R?{open:(n,a)=>ne(n,`Wafer ${e.find(d=>d.waferIndex===n)?.label??n}`,a)}:Z&&e.length===1?{open:(n,a)=>Z(a),label:"show this test on the map"}:null}function Oe(){return Le().map((e,n)=>e==null?null:{metadata:e.wafer.metadata??void 0,dies:e.dies,wafer:e.wafer,label:Ze(e,n),identity:et(e),waferIndex:n,passBins:ot(e),statsSummary:e.statsSummary,testDefs:e.testDefs}).filter(e=>e!=null)}function Ae(e,n){Object.assign(e.style,{background:"none",border:"none",borderBottom:n?`2px solid ${B.iconActive}`:"2px solid transparent",color:n?B.iconActive:B.label,fontWeight:n?"700":"500",fontSize:M.body,padding:`${x.sm} ${le} ${x.md}`,cursor:"pointer",marginBottom:"-1px"})}function Re(e,n){const a=m.createElement("button");a.type="button",a.textContent=n;const d=e===L;return Ae(a,d),a.dataset.on=d?"true":"false",de(a,"bare"),a.dataset.wmapInsightsTab=e,a.setAttribute("role","tab"),a.setAttribute("aria-selected",d?"true":"false"),a.tabIndex=d?0:-1,a.addEventListener("click",()=>{L!==e&&(L=e,G({keepSections:!0}))}),a}function We(e,n,a,d=!0){const r=ce(m),f=ue();let p="label";const l=a??"group",y=Se(e.map(u=>u.passBins)),c=pe?.(),b=new Map(c?.lotYieldSeries.map(u=>[u.waferIndex,u.yieldPercent])),s=u=>({...u,label:u.label.replace(/\s*·\s*\d+(\.\d+)?%$/,""),yieldPercent:b.get(u.waferIndex),key:u.waferIndex}),g=e.map(s),h=n?.map(u=>({key:u.key,items:u.items.map(s)})),i=()=>h?ve(h,p):J(g,p),t={title:n?`Yield by ${l} (pass: ${y})`:`Yield by wafer (pass: ${y})`,data:i(),selfControl:{current:p,options:[["yield","Sort: yield"],["label",n?`Sort: ${l}`:"Sort: wafer ID"]],onChange:u=>(p=u,{data:i()})},barColor:()=>St,valueLabel:u=>`${u.percent.toFixed(1)}%`,reference:u=>{if(u.length<3)return null;const C=u.map(I=>I.percent).sort((I,V)=>I-V),D=C.length%2?C[(C.length-1)/2]:(C[C.length/2-1]+C[C.length/2])/2;return{value:D,label:`median ${D.toFixed(1)}%`}},drill:h?{onOpenGroup:u=>{const C=h.find(D=>D.key===u.label)?.items??[];return{data:J(C,p),title:`Yield by wafer \u2014 ${l}: ${u.label} (pass: ${y})`}},onBack:()=>({data:i(),title:`Yield by ${l} (pass: ${y})`}),groupLabelText:l}:void 0,onOpen:R?u=>{typeof u.key=="number"&&ne(u.key,`Wafer ${u.label}`)}:void 0,onWaferContextMenu:oe(e),ownerDocument:m};let o=null;if(d&&(o=ke(t,k),o.card.style.minHeight="360px",r.appendChild(o.card)),n){const u=wt({title:"Hard bin pareto",groups:n.map(C=>({key:C.key,items:C.items.map(D=>({dies:D.dies,hardBinCounts:D.statsSummary?.stats.hardBinCounts,softBinCounts:D.statsSummary?.stats.softBinCounts}))})),onSelectBin:(C,D)=>{const I=n.find(X=>X.key===C.groupKey);if(!I)return;const V=C.binType==="hbin"?"hard":"soft";Q(D,I.items,`in ${V} bin ${C.bin} of ${C.groupKey}`,X=>(C.binType==="hbin"?X.hbin:X.sbin)===C.bin)},onSaveImage:k,ownerDocument:m});return u.card.style.minHeight="360px",r.appendChild(u.card),{card:r,destroy:()=>{o?.destroy(),u.destroy()}}}let w="hbin";const S=u=>u===void 0?xe:(w==="hbin"?f.hard:f.soft).get(u)??xe,v=e.map(u=>({dies:u.dies,hardBinCounts:u.statsSummary?.stats.hardBinCounts,softBinCounts:u.statsSummary?.stats.softBinCounts})),H=()=>Tt(v,w),ie={title:"Hard bin pareto",data:H(),selfControl:{current:w,options:[["hbin","Hard bins"],["sbin","Soft bins"]],onChange:u=>(w=u,{data:H(),title:`${w==="hbin"?"Hard":"Soft"} bin pareto`})},barColor:u=>S(u.binCode),onSelectBar:(u,C)=>{const D=w==="hbin"?"hard":"soft",I=u.binCode;Q(C,e,I===void 0?`with no ${D} bin recorded`:`in ${D} bin ${I}`,V=>(w==="hbin"?V.hbin:V.sbin)===I)},selectLabel:"chart or tabulate its dies",ownerDocument:m},T=ke(ie,k);return T.card.style.minHeight="360px",r.appendChild(T.card),{card:r,destroy:()=>{o?.destroy(),T.destroy()}}}function re(){const e=m.createElement("div");return e.dataset.wmapChartCard="1",Object.assign(e.style,{...xt(),display:"flex",flexDirection:"column",gap:x.lg,minWidth:"0"}),e}function Me(e){if(Y.innerHTML="",Y.style.marginBottom="0",!Ie)return;const n=$t(e.map(a=>({metadata:a.metadata})),void 0,{facetableOnly:!1});n&&(n.style.marginBottom="0",Y.appendChild(n),Y.style.marginBottom=x.xs)}function Fe(e,n,a,d){if(!e.length)return{elements:[],destroy:()=>{}};const r=e.map(i=>i.wafer),f=e.map(i=>i.dies),p=F(),l=[],y=[],b=Ct({groups:(d??[{key:"",items:e}]).map(i=>({key:i.key,items:i.items.map(t=>({dies:t.dies,testSpecYield:t.statsSummary?.stats.testSpecYield,functionalYield:t.statsSummary?.stats.functionalYield}))})),testDefs:a,onSaveImage:k,onSelectTest:(i,t)=>{const o=a.find(w=>w.testNumber===i.testNumber);o&&Q(t,e,`failing ${i.label}`,w=>Xe(w,o,i.kind)===!0,we(o)?i.testNumber:void 0)},ownerDocument:m});b.card.style.minHeight="300px",l.push(b.card),y.push(b.destroy);const s=i=>(t,o)=>{const w=new Set;for(const S of e)for(const v of It(S.dies,S.wafer,p,t.key,i))w.add(v);Q(o,e,`in ${t.label}`,S=>w.has(S))},g=Te(f,r,p,i=>e[i].passBins,De);if(g.length){const i=$e({title:"Ring yield",mode:"ring",rows:g,onSelectRegion:s(De),onSaveImage:k,ownerDocument:m});l.push(i.card),y.push(i.destroy)}const h=Te(f,r,p,i=>e[i].passBins,Be);if(h.length){const i=$e({title:"Quadrant yield",mode:"quadrant",rows:h,onSelectRegion:s(Be),onSaveImage:k,ownerDocument:m});l.push(i.card),y.push(i.destroy)}return{elements:l,destroy:()=>{for(const i of y)i()}}}function Ne(e,n,a){if(!e.length)return{cards:[],destroy:()=>{}};const d=e.map(s=>s.dies),r=e.flatMap(s=>s.dies),f=e.map(s=>s.statsSummary),p=f.every(s=>s!==void 0)?f:void 0,l=[],y=[];let c=null;if(n.length){const s=re();let g=null;const h=Lt(Dt(r,n,void 0,p,A,d.map(i=>({dies:i})),void 0,"full"),{onDone:i=>{g=i,s.removeAttribute("aria-busy"),i?s.replaceChildren(i):s.remove()}});if(h.done)g&&(c=s);else{const i=m.createElement("div");i.textContent="Computing test statistics\u2026",Object.assign(i.style,{color:B.label,fontSize:M.body}),s.setAttribute("aria-busy","true"),s.appendChild(i),c=s,l.push(()=>h.cancel())}}let b=null;if(a.length){const s=Bt(r,a,p,A);if(s){const g=re();g.appendChild(s),b=g}}return c&&y.push(c),b&&y.push(b),{cards:y,destroy:()=>{for(const s of l)s()}}}function Ye(e){const n=Se(e.map(c=>c.passBins)),a=e.length===1,d=e[0],r=re();Object.assign(r.style,{flexDirection:"row",flexWrap:"wrap",gap:x.md});const f=c=>{const b=c.lastElementChild;return b&&Object.assign(b.style,{borderRight:"none",paddingRight:"0",marginRight:"0"}),c};function p(c,b,s){const g=m.createElement("div");Object.assign(g.style,{display:"flex",alignItems:"baseline",gap:x.sm,paddingRight:x.xxl,marginRight:x.md,borderRight:`1px solid ${B.menuBorder}`});const h=m.createElement("div");h.textContent=c,Object.assign(h.style,{fontSize:M.heading,fontWeight:"700",color:B.value,lineHeight:it.tight});const i=m.createElement("div");if(i.textContent=b,Object.assign(i.style,{fontSize:M.body,color:B.label}),g.append(h,i),s){const t=m.createElement("div");t.textContent=s,Object.assign(t.style,{fontSize:M.meta,color:B.label,opacity:st.muted}),g.appendChild(t)}return g}if(a){const c=J([{...d,key:d.waferIndex}])[0]?.percent;return c!==void 0&&r.appendChild(p(`${c.toFixed(1)}%`,`Yield \xB7 pass: ${n}`)),r.appendChild(p(String(d.dies.length),"Total dies")),f(r)}r.appendChild(p(String(e.length),"Wafers"));const l=J(e.map(c=>({...c,key:c.waferIndex}))).map(c=>c.percent).filter(c=>Number.isFinite(c));if(l.length){const c=l.reduce((g,h)=>g+h,0)/l.length,b=ve([{key:"all",items:e.map(g=>({...g,key:g.waferIndex}))}])[0]?.percent,s=b!==void 0&&b.toFixed(1)!==c.toFixed(1);r.appendChild(p(`${c.toFixed(1)}%`,`${Ue} \xB7 pass: ${n}`,s?`${b.toFixed(1)}% by dies`:void 0))}const y=e.map(c=>c.statsSummary);if(y.every(c=>c!==void 0)){const c=y.reduce((s,g)=>s+g.stats.analyzedDies,0),b=y.reduce((s,g)=>s+g.stats.excludedDies,0);r.appendChild(p(c.toLocaleString(),"Dies analysed",b?`${b.toLocaleString()} excluded`:void 0))}else r.appendChild(p(e.reduce((c,b)=>c+b.dies.length,0).toLocaleString(),"Total dies"));return f(r)}function je(e,n,a,d,r){const f=m.createElement("div");Object.assign(f.style,{display:"flex",flexDirection:"column",gap:x.lg});const p=e.length===1&&!d;e.length&&!d&&f.appendChild(Ye(e));const l=We(e,d,r,!p);f.appendChild(l.card);const y=Fe(e,n,a,d);for(const c of y.elements)l.card.appendChild(c);return f.appendChild(Ge()),{card:f,destroy:()=>{l.destroy(),y.destroy()}}}function Ge(){const e=m.createElement("div");Object.assign(e.style,{fontSize:M.body,color:B.label});const n=m.createElement("button");return n.type="button",n.textContent="Data tab",n.dataset.wmapOpenDataTab="1",Object.assign(n.style,{border:"none",background:"none",padding:"0",cursor:"pointer",font:"inherit",color:B.iconActive,textDecoration:"underline"}),de(n,"bare"),n.addEventListener("click",()=>{L="data",G({keepSections:!0})}),e.append("Test statistics, every die and every wafer as tables, with CSV export: see the ",n,"."),e}function He(e,n,a,d){const r=m.createElement("div");r.style.width="100%";let f,p=!1;return import("./plotTab.js").then(({createPlotSection:l})=>{if(p)return;const y=l({doc:m,store:q,items:e,testDefs:n,ringCount:F(),groupBy:a,groupLabel:d,onSaveImage:k,onSaveText:A,openWafer:R?(c,b,s)=>R(c,b,s):void 0,focusTest:Z,locateDie:_,pickPlotsFile:$.onPickPlotsFile});r.appendChild(y.card),f=y}),{card:r,destroy:()=>{p=!0,f?.destroy()}}}function Ve(e,n,a,d,r){const f=pe?.(),p=()=>{j.get("data")?.destroy(),j.delete("data"),G({keepSections:!0})};return Et({doc:m,items:e,testDefs:n,allTestDefs:a,groups:d,groupLabelText:r,ringCount:F(),yieldByWaferIndex:new Map(f?.lotYieldSeries.map(l=>[l.waferIndex,l.yieldPercent])),onSaveText:A,onLocateDie:_,onOpenWafer:R?(l,y)=>R(l,y):void 0,buildStatistics:l=>Ne(l,n,a),view:fe,onViewChange:l=>{fe=l,p()},diesLayout:me,onDiesLayoutChange:l=>{me=l}})}function _e(e,n,a,d){const r=ce(m),f=ae(e),p=t=>o=>be(o,t()),l=t=>({...t,testStats:t.statsSummary?.stats.perTestStats}),y=e.map(l),c=a?.map(t=>({key:t.key,items:t.items.map(l)})),b=mt({title:"Test value distribution",axisPrefs:K,onAxisPrefsChange:p(()=>b),onTestChange:t=>h(t),selectedTestNumber:P??void 0,items:y,testDefs:n,groups:c,groupLabelText:d,onSaveImage:k,onGroupChange:t=>te(t),onOpen:f?.open,openActionLabel:f?.label,onWaferContextMenu:oe(e),ownerDocument:m}),s=gt({title:"Value histogram",axisPrefs:K,onAxisPrefsChange:p(()=>s),onTestChange:t=>h(t),selectedTestNumber:P??void 0,items:e,testDefs:n,groups:a,onGroupChange:t=>te(t),onSaveImage:k,onSelectBucket:(t,o)=>{const w=n.find(T=>T.testNumber===t.testNumber),S=new Set(t.items.map(T=>T.item)),v=new Set(t.items.flatMap(T=>T.dies)),H=Ce(t.low,w?.unit,"engineering"),ie=Ce(t.high,w?.unit,"engineering");Q(o,e.filter(T=>S.has(T)),`with ${w?Je(w,w.testNumber):`test ${t.testNumber}`} from ${H} to ${ie}${t.group?` in ${t.group}`:""}`,T=>v.has(T),t.testNumber)},ownerDocument:m}),g=bt({title:"Wafer-to-wafer trend",axisPrefs:K,onAxisPrefsChange:p(()=>g),onTestChange:t=>h(t),selectedTestNumber:P??void 0,items:e.map(t=>({label:t.label,key:t.waferIndex,dies:t.dies,testStats:t.statsSummary?.stats.perTestStats})),testDefs:n,onSaveImage:k,centreLabel:rt(at(e.map(t=>t.wafer.metadata)),"mean"),onOpen:R?(t,o)=>ne(t,`Wafer ${e.find(w=>w.waferIndex===t)?.label??t}`,o):void 0,onSelectWafers:(t,o,w)=>{const S=new Set(t.keys);ye(o,w,e.filter(v=>S.has(v.waferIndex)),"selected in the trend",()=>!0,t.testNumber)},onWaferContextMenu:oe(e),ownerDocument:m}),h=t=>{P=t,b.setTest(t),s.setTest(t),g.setTest(t),i.setTest(t)};ee.set("distribution",[b,s,g]);const i=ft({title:"Process capability",items:e,testDefs:n,onSaveImage:k,selectedTestNumber:P??void 0,onSelectTest:t=>h(t),ownerDocument:m});return r.append(i.card,b.card,s.card,g.card),{card:r,destroy:()=>{i.destroy(),b.destroy(),s.destroy(),g.destroy()}}}function ze(e,n,a){const d=ce(m),r=ht({title:"Test scatter",items:e,testDefs:n,groups:a,binColors:ue().hard,onSaveImage:k,axisPrefs:K,onAxisPrefsChange:p=>be(p,r),onOpen:ae(e)?.open,openActionLabel:ae(e)?.label,onSelect:(p,l,y,c)=>{const b=new Map;for(const h of p){if(h.waferIndex===void 0||!h.die)continue;const i=b.get(h.waferIndex);i?i.push(h.die):b.set(h.waferIndex,[h.die])}const s=[...b].flatMap(([h,i])=>{const t=e.find(o=>o.waferIndex===h);return t?[{label:t.identity??t.label,dies:i,waferIndex:h,wafer:t.wafer,passBins:t.passBins,ringCount:F()}]:[]});if(s.length===0)return;const g=s.length===1?`selected on ${s[0].label} in the scatter`:`selected in the scatter, across ${s.length} wafers`;se(l,y,{items:s,population:g,testDefs:n,activeTest:c},{plots:q,onSaveImage:k,onSaveText:A,onLocateDie:_})},ownerDocument:m});ee.set("correlation",[r]);const f=yt({title:"Test correlation matrix",items:e,testDefs:n,onSaveImage:k,onSaveText:A,onSelectPair:(p,l)=>r.setXY(p,l),ownerDocument:m});return d.append(f.card,r.card),{card:d,destroy:()=>{f.destroy(),r.destroy()}}}function G(e){e?.keepSections||ge(),U.innerHTML="",E.innerHTML="";for(const o of Pe)E.appendChild(Re(o.key,o.label));const n=Oe();Me(n);const a=qe(n,{facetableOnly:!0}).filter(o=>o.splittable),d=m.createElement("div");Object.assign(d.style,{display:"flex",gap:x.md,alignItems:"center",marginLeft:x.xxl,paddingBottom:x.xs});let r;a.length>0?(d.appendChild(kt("Group by:",[{value:"",label:"None"},...a.map(o=>({value:o.key,label:`${o.label} (${o.values.length})`}))],W??"",o=>{W=o||void 0,O=null,G()},{hook:"group-by",ownerDocument:m})),r=a.find(o=>o.key===W)?.label):W=void 0;let f;if(W){const o=new Map,w=[];for(const S of n){const v=Ke(S.metadata,W)??Qe;o.has(v)||(o.set(v,[]),w.push(v)),o.get(v).push(S)}f=w.map(S=>({key:S,items:o.get(S)}))}O!==null&&!f?.some(o=>o.key===O)&&(O=null),f&&f.length>0&&d.appendChild(vt(f.map(o=>o.key),"Show:",O,o=>te(o),{ownerDocument:m}).el);let p=null;if($.onOpenGuide){const o=m.createElement("button");o.type="button",o.innerHTML=ut.help,o.setAttribute("aria-label","User guide"),Object.assign(o.style,{...ct("bare"),display:"inline-flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",flexShrink:"0"}),de(o,"bare"),pt(o,"User guide"),o.addEventListener("click",()=>$.onOpenGuide()),Object.assign(o.style,{marginLeft:"auto"}),p=o}E.appendChild(d),p&&E.appendChild(p);const l=O!==null?f?.find(o=>o.key===O):void 0,y=l?.items??n,c=l?void 0:f,b=he(y),s=b.defs,g=s.filter(we);P!==null&&!g.some(o=>o.testNumber===P)&&(P=null);const h=e?.keepSections?j.get(L):void 0,i=h??(L==="overview"?je(y,g,s,c,r):L==="distributions"?_e(y,g,c,r):L==="data"?Ve(y,g,s,c,r):L==="plot"?He(y,s,l?void 0:W,l?void 0:r):ze(y,g,c));h||j.set(L,i);const t=b.conflicts.filter(o=>o.excluded);if(t.length>0){const o=m.createElement("div");Object.assign(o.style,{color:B.label,fontSize:M.body,padding:`0 ${z} ${x.sm}`});const w=t.slice(0,6).map(H=>H.testNumber).join(", "),S=t.length>6?`, and ${t.length-6} more`:"",v=t.length===1?"it":"them";o.textContent=`${t.length} test ${t.length===1?"number is":"numbers are"} hidden (${w}${S}) \u2014 the wafers in view describe ${v} differently, so their values cannot be pooled. `+(f&&f.length>0?`Pick one ${r??"group"} under "Show:" to analyse ${v}.`:"Load one test program at a time to analyse them."),o.dataset.wmapWithheldTests=String(t.length),U.appendChild(o)}U.appendChild(i.card)}return{el:N,render:G,destroy:()=>{ge(),q.flush(),N.remove()}}}
|
|
@@ -7,8 +7,8 @@ export interface MaplessSummaryOptions {
|
|
|
7
7
|
activeTest?: number;
|
|
8
8
|
hbinDefs?: BinDef[];
|
|
9
9
|
sbinDefs?: BinDef[];
|
|
10
|
-
/** The map's resolved bin colours (`View.binColors`), so bars match its legend. */
|
|
11
|
-
binColors
|
|
10
|
+
/** The map's resolved bin colours (`View.binColors`), so bars match its legend and judge by the map's own pass bins. Required: there is no default to resolve them by. */
|
|
11
|
+
binColors: BinColors;
|
|
12
12
|
/** The map's value gradient name, so histogram bars match its colorbar. */
|
|
13
13
|
valueColorScheme?: string;
|
|
14
14
|
/** The map's `View.reverseValueScheme` — must travel with the name, or the
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type FieldOption } from '../stats/plotData.js';
|
|
2
|
+
import { type PlotSpec } from '../stats/plotSpec.js';
|
|
3
|
+
export interface PlotEditorOptions {
|
|
4
|
+
doc: Document;
|
|
5
|
+
plot: PlotSpec;
|
|
6
|
+
catalogue: readonly FieldOption[];
|
|
7
|
+
/** What "Follow Group by" means right now ("Split", or "a colour per wafer" with no grouping), or undefined for a single colour. */
|
|
8
|
+
followLabel: () => string | undefined;
|
|
9
|
+
/** The automatic title and axis titles, shown as placeholders so the reader sees what clearing a box gives back. */
|
|
10
|
+
autoText: () => {
|
|
11
|
+
title: string;
|
|
12
|
+
x?: string;
|
|
13
|
+
y?: string;
|
|
14
|
+
};
|
|
15
|
+
/** Called with the new spec after every change. */
|
|
16
|
+
onChange: (next: PlotSpec) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface PlotEditorHandle {
|
|
19
|
+
el: HTMLElement;
|
|
20
|
+
/** The spec changed elsewhere (undo, a rebind): show it. */
|
|
21
|
+
setPlot(plot: PlotSpec): void;
|
|
22
|
+
/** The automatic text changed (the data did): refresh the placeholders. */
|
|
23
|
+
refreshAuto(): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function inputStyle(): Partial<CSSStyleDeclaration>;
|
|
26
|
+
/** A labelled control with an optional hint beneath it: the one layout every editor field uses. */
|
|
27
|
+
export declare function fieldRow(doc: Document, label: string, control: HTMLElement, hint?: string): HTMLElement;
|
|
28
|
+
export declare function createPlotEditor(o: PlotEditorOptions): PlotEditorHandle;
|
|
29
|
+
//# sourceMappingURL=plotEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{combinationIssue as le,describeTitleDrift as oe,fieldKey as k,fieldsForRole as H,titleDrift as ae}from"../stats/plotData.js";import{sameField as j}from"../stats/plotSpec.js";import{CLR as m,FONT as S,RADIUS as V,SPACE as b,controlStyle as K,wireControlHover as _}from"./toolbar.js";import{makeListSelect as X,makeSegmented as N,makeToggle as se}from"./charts/chartShell.js";const Y="\0follow",z="\0none",re=[["scatter","Scatter"],["histogram","Histogram"],["box","Box"],["bar","Bar"],["line","Line"]],A="\0auto",G="\0count",Q={mean:"Mean",median:"Median",min:"Minimum",max:"Maximum",sum:"Sum",count:"Count",yield:"Pooled yield (passing over judged dies)"};export function inputStyle(){return{background:m.menuBg,color:m.text,border:`1px solid ${m.menuBorder}`,borderRadius:V.control,padding:`${b.xs} ${b.sm}`,fontSize:S.body,fontFamily:S.family,boxSizing:"border-box",minWidth:"0"}}export function fieldRow(c,r,B,a){const p=c.createElement("div");Object.assign(p.style,{display:"flex",flexDirection:"column",gap:b.xxs,minWidth:"0"});const x=c.createElement("div");if(x.textContent=r,Object.assign(x.style,{fontSize:S.sub,color:m.label,fontWeight:"600"}),p.append(x,B),a){const T=c.createElement("div");T.textContent=a,Object.assign(T.style,{fontSize:S.sub,color:m.label}),p.appendChild(T)}return p}export function createPlotEditor(c){const{doc:r}=c,B=t=>t.fields?t:{...t,fields:{}};let a=B(c.plot),p="setup";const x=r.createElement("div");x.dataset.wmapPlotEditor="1",Object.assign(x.style,{display:"flex",flexDirection:"column",gap:b.lg,minWidth:"0"});const T=t=>{a=t,c.onChange(t)},y=t=>{const e=JSON.parse(JSON.stringify(a));t(e),T(e)},d=(t,e,i)=>fieldRow(r,t,e,i),D=t=>{const e=t&&c.catalogue.find(i=>j(i.field,t));return e?{label:e.name,level:e.level,kind:e.kind,yield:"builtin"in e.field&&e.field.builtin==="yield"}:void 0};function Z(t,e){const i=a.fields??{},n=i.color&&!("follow"in i.color)&&!("none"in i.color)?i.color:void 0,l={x:D(i.x),y:D(i.y),color:D(n)};l[t]=D(e.field);const o=a.chart==="scatter"&&l.color?.kind==="numeric"&&!!n&&!("meta"in n);return le(a.chart,l,{aggregate:a.aggregate,level:a.level,continuousColor:o})?.short}const I=t=>{const e=a.fields?.[t],i=e&&!("follow"in e)&&!("none"in e)?e:void 0;return H(c.catalogue,a.chart,t).map(n=>{const l=i&&j(n.field,i)?void 0:Z(t,n);return{value:k(n.field),label:n.label,group:n.group,...l?{disabled:l}:{}}})},J=t=>c.catalogue.find(e=>k(e.field)===t)?.field;function v(t,e,i={}){const n=a.fields[t],l=I(t);if(i.optional&&l.unshift({value:G,label:i.optional,group:"Count"}),n&&!l.some(u=>u.value===k(n))){const u=c.catalogue.some(C=>j(C.field,n)),h="test"in n?n.name??`Test ${n.test}`:"meta"in n?n.meta:n.builtin;l.unshift({value:k(n),label:`${h} (${u?"not usable here":"not in this lot"})`,group:"Saved"})}const o=n?k(n):i.optional?G:"",s=X(l,o,u=>{if(u===G){y(C=>{delete C.fields[t]});return}const h=J(u)??a.fields[t];h&&y(C=>{C.fields[t]=h})},{maxWidth:"100%",ownerDocument:r,ariaLabel:e,emptyText:"Choose a field\u2026",searchPlaceholder:"Filter fields\u2026",hook:`plot-${t}`});return s.style.width="100%",s}const P={},F={};function ee(t){y(e=>{e.chart=t;for(const i of["x","y"]){const n=H(c.catalogue,t,i),l=h=>!!h&&n.some(C=>j(C.field,h)),o=e.fields[i];if(o&&!l(o)&&(j(o,F[i])||(P[i]=o),delete e.fields[i]),e.fields[i]||i==="y"&&t==="bar")continue;const s=P[i];if(l(s)){e.fields[i]=s,delete P[i],delete F[i];continue}const u=i==="x"&&(t==="box"||t==="bar")?{builtin:"wafer"}:i==="x"&&t==="line"?n.find(h=>h.group==="Wafer")?.field??n[0]?.field:n[0]?.field;u&&!(i==="x"&&t==="histogram")&&(e.fields[i]=u,F[i]=u)}}),E()}function te(){const t=!!a.fields.y&&"builtin"in a.fields.y&&a.fields.y.builtin==="yield",e=[{value:A,label:"Automatic"},...["mean","median","min","max","sum","count"].map(n=>({value:n,label:Q[n]}))];t&&e.push({value:"yield",label:Q.yield});const i=X(e,a.aggregate??A,n=>y(l=>{n===A?delete l.aggregate:l.aggregate=n}),{maxWidth:"100%",ownerDocument:r,ariaLabel:"Combine values by",hook:"plot-aggregate"});return i.style.width="100%",i}function ie(){const t=a.fields.color,e=c.followLabel(),i=[{value:Y,label:e?`Follow Group by (${e})`:"Follow Group by (single colour)",group:"Colour"},{value:z,label:"None",group:"Colour"},...I("color")],n=t===void 0||"follow"in t?Y:"none"in t?z:k(t),l=X(i,n,o=>{y(s=>{if(o===Y)delete s.fields.color;else if(o===z)s.fields.color={none:!0};else{const u=J(o);u&&(s.fields.color=u)}})},{maxWidth:"100%",ownerDocument:r,ariaLabel:"Colour",searchPlaceholder:"Filter fields\u2026",hook:"plot-color"});return l.style.width="100%",l}function U(t,e,i,n){const l=r.createElement("input");return l.type="text",l.value=t,l.placeholder=e,l.setAttribute("aria-label",i),Object.assign(l.style,inputStyle(),{width:"100%"}),l.addEventListener("input",()=>n(l.value)),l.addEventListener("keydown",o=>{o.key==="Escape"&&(o.stopPropagation(),l.blur())}),l}function R(t,e,i){const n=r.createElement("input");n.type="text",n.inputMode="decimal",n.value=t===void 0?"":String(t),n.placeholder="auto",n.setAttribute("aria-label",e),Object.assign(n.style,inputStyle(),{width:"100%",fontVariantNumeric:"tabular-nums"});const l=()=>{const o=n.value.trim();if(o===""){i(void 0);return}const s=Number(o);Number.isFinite(s)?i(s):n.value=t===void 0?"":String(t)};return n.addEventListener("change",l),n.addEventListener("keydown",o=>{o.key==="Enter"&&l(),o.key==="Escape"&&(o.stopPropagation(),n.value=t===void 0?"":String(t),n.blur())}),n}function q(t,e,i,n){const l=a.axes?.[t]??{},o=g=>y(f=>{f.axes=f.axes??{};const O=f.axes[t]=f.axes[t]??{};g(O);for(const $ of Object.keys(O))(O[$]===void 0||O[$]===""||$==="reverse"&&O[$]===!1)&&delete O[$];Object.keys(O).length===0&&delete f.axes[t],Object.keys(f.axes).length===0&&delete f.axes}),s=r.createElement("div");Object.assign(s.style,{display:"flex",flexDirection:"column",gap:b.md,padding:b.lg,border:`1px solid ${m.menuBorder}`,borderRadius:V.control});const u=r.createElement("div");u.textContent=e,Object.assign(u.style,{fontSize:S.body,fontWeight:"600",color:m.value}),s.appendChild(u);const h=U(l.label??"",i??"",`${e} title`,g=>o(f=>{f.label=g.trim()===""?void 0:g}));if(h.dataset.wmapPlotAxisTitle=t,s.appendChild(d("Title",h)),n.limits===!1)return s;const C=r.createElement("div");return Object.assign(C.style,{display:"grid",gridTemplateColumns:"1fr 1fr",gap:b.md}),C.append(d("Minimum",R(l.min,`${e} minimum`,g=>o(f=>{f.min=g}))),d("Maximum",R(l.max,`${e} maximum`,g=>o(f=>{f.max=g})))),s.appendChild(C),n.scale&&s.appendChild(d("Scale",N([["linear","Linear"],["log","Log"]],l.scale??"linear",g=>o(f=>{f.scale=g==="log"?"log":void 0}),r))),s.appendChild(se("Reverse direction",!!l.reverse,g=>o(f=>{f.reverse=g}),r)),s}const ne=t=>x.querySelector(`[role="tab"][data-tab="${t}"]`);let L,w;const W=new Map;function M(){if(!w)return;const t=oe(ae(a,c.catalogue));if(w.replaceChildren(),w.style.display=t?"flex":"none",!t)return;const e=r.createElement("span");e.textContent=t;const i=r.createElement("button");i.type="button",i.textContent="Use the automatic title",i.dataset.wmapPlotAutoTitle="1",Object.assign(i.style,K("outlined")),_(i),i.addEventListener("click",()=>{y(n=>{delete n.title}),E()}),w.append(e,i)}function E(){x.replaceChildren(),W.clear();const t=r.createElement("div");t.setAttribute("role","tablist"),Object.assign(t.style,{display:"flex",gap:b.sm,borderBottom:`1px solid ${m.menuBorder}`});for(const[i,n]of[["setup","Setup"],["customise","Customise"]]){const l=r.createElement("button");l.type="button",l.textContent=n,l.setAttribute("role","tab"),l.dataset.tab=i,l.setAttribute("aria-selected",String(p===i)),Object.assign(l.style,{...K("bare"),padding:`${b.sm} ${b.lg}`,fontSize:S.body,borderBottom:`2px solid ${p===i?m.iconActive:"transparent"}`,borderRadius:"0",color:p===i?m.iconActive:m.text,fontWeight:p===i?"600":"400"}),_(l,"bare"),l.tabIndex=p===i?0:-1,l.addEventListener("click",()=>{p=i,E()}),l.addEventListener("keydown",o=>{o.key!=="ArrowLeft"&&o.key!=="ArrowRight"||(o.preventDefault(),p=i==="setup"?"customise":"setup",E(),ne(p)?.focus())}),t.appendChild(l)}x.appendChild(t);const e=r.createElement("div");if(e.setAttribute("role","tabpanel"),Object.assign(e.style,{display:"flex",flexDirection:"column",gap:b.lg}),p==="setup"){e.appendChild(d("Chart type",N(re,a.chart,n=>ee(n),r,!0)));const i=a.level==="wafer"?"wafers":"dies";switch(a.chart){case"scatter":e.appendChild(d("X axis",v("x","X axis"))),e.appendChild(d("Y axis",v("y","Y axis")));break;case"histogram":e.appendChild(d("Values",v("y","Values")));break;case"box":e.appendChild(d("Categories (X axis)",v("x","Categories"),"One box per value of this field.")),e.appendChild(d("Values (Y axis)",v("y","Values")));break;case"bar":e.appendChild(d("Categories (X axis)",v("x","Categories"),"One bar per value of this field.")),e.appendChild(d("Values (Y axis)",v("y","Values",{optional:`Count of ${i}`})));break;case"line":e.appendChild(d("X axis",v("x","X axis"),"A line joins the value at each X.")),e.appendChild(d("Y axis",v("y","Y axis")));break}e.appendChild(d("Colour",ie(),"Each value of the field gets its own colour. Follow Group by takes it from the Group by control.")),(a.chart==="bar"||a.chart==="line"||a.level==="wafer")&&e.appendChild(d("Combine values by",te())),e.appendChild(d("One mark per",N([[A,"Automatic"],["die","Die"],["wafer","Wafer"]],a.level??A,n=>{y(l=>{n===A?delete l.level:l.level=n}),E()},r,!0),a.level==="wafer"?"Die-level values are combined per wafer.":"Automatic uses the finest level the chosen fields allow."))}else{L=U(a.title??"",c.autoText().title,"Plot title",o=>{y(s=>{o.trim()===""?delete s.title:Object.assign(s,{title:o})}),M()}),L.dataset.wmapPlotTitle="1",w=r.createElement("div"),w.dataset.wmapPlotTitleDrift="1",Object.assign(w.style,{display:"none",flexDirection:"column",gap:b.sm,alignItems:"flex-start",padding:`${b.sm} ${b.md}`,background:m.warnBg,border:`1px solid ${m.warnBorder}`,borderRadius:V.control,color:m.warnText,fontSize:S.body}),e.appendChild(d("Title",L)),e.appendChild(w),M();const i=c.autoText(),n=a.chart==="box"||a.chart==="bar";e.appendChild(q("x",a.chart==="histogram"?"Values axis":n?"Categories axis":"X axis",i.x,{scale:!n,limits:!n})),e.appendChild(q("y",a.chart==="histogram"?"Count axis":n||a.chart==="line"?"Values axis":"Y axis",i.y,{scale:a.chart!=="histogram"})),a.chart==="histogram"&&e.appendChild(d("Bins",R(a.bins,"Number of bins",o=>y(s=>{o===void 0||!Number.isInteger(o)||o<1?delete s.bins:s.bins=Math.min(o,200)})),"Automatic is 16 equal bins."));const l=a.fields??{};(l.x&&"test"in l.x||l.y&&"test"in l.y)&&e.appendChild(d("Limits",N([["both","Test + spec"],["test","Test"],["spec","Spec"],["none","None"]],a.limits??"both",o=>y(s=>{o==="both"?delete s.limits:s.limits=o}),r),"Drawn as dashed lines on the axis of a test that has them: short dashes for test limits, long for spec limits."));for(const o of e.querySelectorAll("[data-wmap-plot-axis-title]"))W.set(o.dataset.wmapPlotAxisTitle,o)}x.appendChild(e)}return E(),{el:x,setPlot(t){const e=B(t);JSON.stringify(e)!==JSON.stringify(a)&&(a=e,E())},refreshAuto(){const t=c.autoText();L&&(L.placeholder=t.title),M();for(const[e,i]of W)i.placeholder=(e==="x"?t.x:t.y)??""}}}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Die } from '../core/dies.js';
|
|
2
|
+
import type { Wafer } from '../core/wafer.js';
|
|
3
|
+
import type { TestDef } from '../renderer/buildWaferMap.js';
|
|
4
|
+
import type { PlotItem, PlotPoint } from '../stats/plotData.js';
|
|
5
|
+
import { type DrilldownSource } from './chartPopulation.js';
|
|
6
|
+
/** What a population hands over for each wafer: a drilldown source's items, or the Insights tab's. */
|
|
7
|
+
export interface WaferShare {
|
|
8
|
+
label: string;
|
|
9
|
+
dies: readonly Die[];
|
|
10
|
+
waferIndex?: number;
|
|
11
|
+
wafer?: Wafer;
|
|
12
|
+
passBins: readonly number[];
|
|
13
|
+
ringCount: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function toPlotItems(items: readonly WaferShare[]): PlotItem[];
|
|
16
|
+
/**
|
|
17
|
+
* The dies a drag on a plot picked out, per wafer, as a drilldown population: charts and tables of just these.
|
|
18
|
+
* `null` when the drag caught no die (a wafer-level plot has none to pick).
|
|
19
|
+
*/
|
|
20
|
+
export declare function sourceFromPoints(points: readonly PlotPoint[], items: readonly PlotItem[], testDefs: readonly TestDef[] | undefined): DrilldownSource | null;
|
|
21
|
+
/**
|
|
22
|
+
* The dies of each wafer that satisfy `keep`, as a drilldown population worded "`what` on W03" or "`what`, across N
|
|
23
|
+
* wafers". `null` when none does. Per wafer, so the tables name each die's wafer and a chart opened on it is of
|
|
24
|
+
* exactly those dies.
|
|
25
|
+
*/
|
|
26
|
+
export declare function sourceFromDies(shares: readonly WaferShare[], what: string, keep: (d: Die) => boolean, testDefs: readonly TestDef[] | undefined, activeTest?: number): DrilldownSource | null;
|
|
27
|
+
/**
|
|
28
|
+
* The dies in `bins` (hard or soft), per wafer, for a legend entry's right-click: the same population a bin pareto bar
|
|
29
|
+
* opens. `null` when no die is in them. A stack of a lot holds aggregates, not dies, so it is named as not measured.
|
|
30
|
+
*/
|
|
31
|
+
export declare function sourceFromBins(shares: readonly WaferShare[], kind: 'hard' | 'soft', bins: readonly number[], testDefs: readonly TestDef[] | undefined, activeTest?: number, stacked?: boolean): DrilldownSource | null;
|
|
32
|
+
//# sourceMappingURL=plotItems.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LOT_STACK_REASON as d}from"./chartPopulation.js";export function toPlotItems(l){return l.map(e=>({label:e.label,dies:e.dies,waferIndex:e.waferIndex,wafer:e.wafer,passBins:e.passBins,ringCount:e.ringCount,metadata:e.wafer?.metadata}))}export function sourceFromPoints(l,e,o){const s=new Map;for(const r of l){const n=s.get(r.item);if(!r.die){s.set(r.item,"all");continue}n!=="all"&&(n?n.push(r.die):s.set(r.item,[r.die]))}const t=[...s].map(([r,n])=>({label:e[r].label,dies:n==="all"?[...e[r].dies]:n,waferIndex:e[r].waferIndex,wafer:e[r].wafer,passBins:e[r].passBins,ringCount:e[r].ringCount}));if(t.length===0)return null;const a=t.length===1?`selected on ${t[0].label} in the plot`:`selected in the plot, across ${t.length} wafers`;return{items:t,population:a,testDefs:o?[...o]:void 0}}export function sourceFromDies(l,e,o,s,t){const a=l.map(n=>({it:n,dies:n.dies.filter(o)})).filter(n=>n.dies.length>0).map(({it:n,dies:f})=>({label:n.label,dies:f,waferIndex:n.waferIndex,wafer:n.wafer,passBins:n.passBins,ringCount:n.ringCount}));if(a.length===0)return null;const r=a.length===1?`${e} on ${a[0].label}`:`${e}, across ${a.length} wafers`;return{items:a,population:r,testDefs:s?[...s]:void 0,activeTest:t}}export function sourceFromBins(l,e,o,s,t,a=!1){const r=new Set(o),n=`in ${e} bin${o.length===1?"":"s"} ${o.join(", ")}`,f=sourceFromDies(l,n,i=>{const u=e==="hard"?i.hbin:i.sbin;return u!==void 0&&r.has(u)},s,t);return f&&a&&(f.notMeasuredReason=d),f}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PlotContext, PlotItem } from '../stats/plotData.js';
|
|
2
|
+
import { type PlotSpec } from '../stats/plotSpec.js';
|
|
3
|
+
import { type PlotChartOptions } from './charts/plotChart.js';
|
|
4
|
+
import { type SweepPanelOptions } from './charts/sweep.js';
|
|
5
|
+
import type { PlotStore } from './plotStore.js';
|
|
6
|
+
export interface PlotEditorWindowOptions {
|
|
7
|
+
doc: Document;
|
|
8
|
+
anchor: HTMLElement;
|
|
9
|
+
store: PlotStore;
|
|
10
|
+
plot: PlotSpec;
|
|
11
|
+
/** A draft is not in the store until added. */
|
|
12
|
+
draft: boolean;
|
|
13
|
+
items: PlotItem[];
|
|
14
|
+
ctx: PlotContext;
|
|
15
|
+
/** The label "Follow Group by" resolves to, when the tab has one. */
|
|
16
|
+
groupLabel?: string;
|
|
17
|
+
/** Pointer callbacks for the chart (open a wafer, drag-select). */
|
|
18
|
+
chart: Omit<PlotChartOptions, 'ownerDocument' | 'waferLabel'>;
|
|
19
|
+
/** What the plot is drawn over, in words, when it is not the whole tab ("selected on W03"). */
|
|
20
|
+
population?: string;
|
|
21
|
+
/** The population before any count ("selected on W03"), which a sweep's own hint puts its count in front of. */
|
|
22
|
+
rawPopulation?: string;
|
|
23
|
+
/** Click a level of a sweep's curve: the menu on the dies measured there. Sweeps only. */
|
|
24
|
+
onSelectLevel?: SweepPanelOptions['onSelectPoint'];
|
|
25
|
+
/** The window's heading. Default: Edit plot, or New plot for a draft. */
|
|
26
|
+
title?: string;
|
|
27
|
+
onClosed?: () => void;
|
|
28
|
+
}
|
|
29
|
+
export declare function openPlotEditor(o: PlotEditorWindowOptions): void;
|
|
30
|
+
//# sourceMappingURL=plotModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{fieldCatalogue as z,resolvePlot as I,plotTitle as $}from"../stats/plotData.js";import{plotToSweep as J}from"../stats/plotSpec.js";import{renderPlotChart as F}from"./charts/plotChart.js";import{renderSweepPanel as H}from"./charts/sweep.js";import{createPlotEditor as M}from"./plotEditor.js";import{createSweepEditor as Y}from"./sweepEditor.js";import{CLR as b,FONT as T,SPACE as i,controlStyle as B,openReparentedModal as q,wireControlHover as L}from"./toolbar.js";export function openPlotEditor(e){const{doc:n,store:c}=e;let o=e.plot,a=!e.draft;const W=z(e.items,e.ctx),h=t=>I(t,e.items,e.ctx),p=n.createElement("div");p.dataset.wmapPlotWindow="1",Object.assign(p.style,{display:"flex",flexWrap:"wrap",gap:i.xxl,alignItems:"flex-start",width:"100%",boxSizing:"border-box",padding:i.xl});const f=n.createElement("div");if(Object.assign(f.style,{flex:"3 1 420px",minWidth:"0",display:"flex",flexDirection:"column",gap:i.md}),e.population){const t=n.createElement("div");t.textContent=`Population: ${e.population}`,t.dataset.wmapPopulation="1",Object.assign(t.style,{color:b.label,fontSize:T.body}),f.appendChild(t)}const w=o.chart==="sweep";let r;if(w){const t=H({spec:J(o),dies:e.items.flatMap(l=>l.dies),testDefs:e.ctx.testDefs?[...e.ctx.testDefs]:void 0,population:e.rawPopulation??e.population,onSaveImage:e.chart.onSaveImage,onSelectPoint:e.onSelectLevel,ownerDocument:n});r={card:t.card,setPlot:l=>t.setSpec(J(l)),destroy:()=>t.destroy()}}else{const t=F({...e.chart,ownerDocument:n,waferLabel:l=>e.items[l]?.label??""});r={card:t.card,setPlot:l=>t.setPlot(h(l)),destroy:()=>t.destroy()}}r.card.style.alignSelf="stretch",f.appendChild(r.card);const m=n.createElement("div");Object.assign(m.style,{flex:"1 1 300px",minWidth:"260px",maxWidth:"420px",display:"flex",flexDirection:"column",gap:i.lg,maxHeight:"calc(min(92vh, 800px) - 110px)",overflowY:"auto",paddingRight:i.sm});const C=t=>{o=t,a&&c.upsert(t),y()},u=w?{...Y({doc:n,plot:o,testDefs:e.ctx.testDefs,onChange:C}),refreshAuto:()=>{}}:M({doc:n,plot:o,catalogue:W,followLabel:()=>e.groupLabel??(e.items.length>1?"a colour per wafer":void 0),autoText:()=>{const t=h(o);return{title:t.autoTitle,x:t.x?.label,y:t.y?.label}},onChange:C});m.appendChild(u.el);const g=n.createElement("div");Object.assign(g.style,{display:"flex",flexDirection:"column",gap:i.sm,color:b.label,fontSize:T.body,position:"sticky",bottom:"0",background:b.menuBg,borderTop:`1px solid ${b.menuBorder}`,paddingTop:i.sm,paddingBottom:i.md});const P=n.createElement("div");g.appendChild(P);const s=n.createElement("button");s.type="button",s.textContent="Add to my plots",s.dataset.wmapPlotAdd="1",Object.assign(s.style,{...B("outlined"),alignSelf:"flex-start"}),L(s),s.addEventListener("click",()=>{a=!0,c.upsert(o),N()});const E=JSON.parse(JSON.stringify(e.plot)),O=(t,l,R)=>{const d=n.createElement("button");return d.type="button",d.textContent=t,d.dataset[l]="1",Object.assign(d.style,B("outlined")),L(d),d.addEventListener("click",R),d},x=O("Reset","wmapPlotReset",()=>D()),k=O("Cancel","wmapPlotCancel",()=>{D(),v?.close()}),S=n.createElement("div");Object.assign(S.style,{display:"flex",gap:i.md,flexWrap:"wrap"}),S.append(x,k,s),g.appendChild(S),m.appendChild(g);const A=()=>JSON.stringify(o)!==JSON.stringify(E);function D(){o=JSON.parse(JSON.stringify(E)),a&&c.upsert(o),u.setPlot(o),y()}function N(){P.textContent=a?`Changes are saved to \u201C${w?o.title??"Sweep":$(h(o))}\u201D.`:"This is a draft: it is not kept until you add it to your plots.",s.style.display=a?"none":"",x.disabled=!A(),x.style.opacity=x.disabled?"0.5":"1"}function y(){r.setPlot(o),u.refreshAuto(),N()}y(),p.append(f,m);const j=c.subscribe(()=>{if(!a)return;const t=c.get().find(l=>l.id===o.id);if(!t){v?.close();return}JSON.stringify(t)!==JSON.stringify(o)&&(o=t,u.setPlot(t),y())}),v=q([p],{title:e.title??(a?"Edit plot":"New plot"),anchor:e.anchor,ownerDocument:n,boxSize:{width:"min(96vw, 1180px)",height:"min(92vh, 800px)"},onClosed:()=>{j(),r.destroy(),e.onClosed?.()}});v||(j(),r.destroy())}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PlotSpec } from '../stats/plotSpec.js';
|
|
2
|
+
import type { SweepSpec } from '../stats/sweep.js';
|
|
3
|
+
export interface PlotStore {
|
|
4
|
+
get(): readonly PlotSpec[];
|
|
5
|
+
/** Replaces the plot with this id, or appends it. */
|
|
6
|
+
upsert(plot: PlotSpec): void;
|
|
7
|
+
/** Inserts at a position (undo of a delete puts the plot back where it was). */
|
|
8
|
+
insertAt(index: number, plot: PlotSpec): void;
|
|
9
|
+
remove(id: string): {
|
|
10
|
+
plot: PlotSpec;
|
|
11
|
+
index: number;
|
|
12
|
+
} | undefined;
|
|
13
|
+
/** A copy with a new id, placed after the original. A typed title gains "(copy)"; an untitled plot stays untitled, so its title keeps following its fields. */
|
|
14
|
+
duplicate(id: string): PlotSpec | undefined;
|
|
15
|
+
/** Replaces the whole list (an import). A sweep the host still supplies that is not in it is reported through `onRemove`, as `remove` does. */
|
|
16
|
+
replaceAll(plots: readonly PlotSpec[]): void;
|
|
17
|
+
subscribe(fn: () => void): () => void;
|
|
18
|
+
/** Sends a change still waiting for the debounce. */
|
|
19
|
+
flush(): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The title for a copy of a plot titled `title`: the original's name with a copy mark, numbered once there are several
|
|
23
|
+
* ("Vth (copy)", "Vth (copy 2)"). A copy of a copy is a copy of the original, not a copy of a copy: marks do not pile up.
|
|
24
|
+
*/
|
|
25
|
+
export declare function copyTitle(title: string, existing: ReadonlyArray<string | undefined>): string;
|
|
26
|
+
/** Sweeps a host still supplies through `insights.sweeps`: they become sweep plots in the list. */
|
|
27
|
+
export interface LegacySweeps {
|
|
28
|
+
sweeps: readonly SweepSpec[] | undefined;
|
|
29
|
+
/** Told when the reader deletes one of them, so the host stops supplying it (else the next render brings it back). */
|
|
30
|
+
onRemove?: (ids: string[]) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The store for a host's `insights` options: its saved plots, with any `sweeps` it still supplies turned into sweep
|
|
34
|
+
* plots. A plot already saved under a sweep's id wins, so a host that keeps both does not show a sweep twice and an
|
|
35
|
+
* edit the reader made is not undone by the host's older definition.
|
|
36
|
+
*/
|
|
37
|
+
export declare function plotStoreFor(insights: {
|
|
38
|
+
plots?: PlotSpec[];
|
|
39
|
+
onPlotsChange?: (plots: PlotSpec[]) => void;
|
|
40
|
+
sweeps?: SweepSpec[];
|
|
41
|
+
onRemoveSweeps?: (ids: string[]) => void;
|
|
42
|
+
} | undefined): PlotStore;
|
|
43
|
+
export declare function createPlotStore(initial: readonly PlotSpec[] | undefined, onChange?: (plots: PlotSpec[]) => void, delayMs?: number, legacy?: LegacySweeps): PlotStore;
|
|
44
|
+
/**
|
|
45
|
+
* How a gallery hands its own store to the maps it draws on its cards. Not a public option: a card's map would
|
|
46
|
+
* otherwise build a store of its own from nothing, and the plots saved on the Plot tab would be missing from the
|
|
47
|
+
* right-click menu on every card.
|
|
48
|
+
*/
|
|
49
|
+
export interface WithPlotStore {
|
|
50
|
+
plotStore?: PlotStore;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=plotStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{sweepToPlot as O}from"../stats/plotSpec.js";import{newPlotId as g}from"../stats/plotId.js";import{noticeOnce as x}from"../renderer/deprecate.js";export function copyTitle(i,m){const l=i.replace(/(?:\s*\(copy(?: \d+)?\))+$/,"")||i,r=new Set(m);let d=`${l} (copy)`;for(let f=2;r.has(d);f++)d=`${l} (copy ${f})`;return d}export function plotStoreFor(i){return createPlotStore(i?.plots,i?.onPlotsChange,void 0,{sweeps:i?.sweeps,onRemove:i?.onRemoveSweeps})}export function createPlotStore(i,m,l=400,r){const d=new Set((i??[]).map(e=>e.id)),f=(r?.sweeps??[]).filter(e=>!d.has(e.id));(r?.sweeps?.length??0)>0&&x("insights.sweeps","`insights.sweeps` is deprecated: sweeps are plots now. Pass them in `insights.plots` as sweep plots (readPlotsFile reads a sweeps file), which the Plot tab can also save and share.");const h=new Set((r?.sweeps??[]).map(e=>e.id));let t=[...i??[],...f.map(O)].map(e=>JSON.parse(JSON.stringify(e)));const u=new Set;let p,w=!1;const S=()=>{p!==void 0&&(clearTimeout(p),p=void 0),w&&(w=!1,m?.(t.map(e=>JSON.parse(JSON.stringify(e)))))},a=()=>{for(const e of[...u])e();w=!0,p!==void 0&&clearTimeout(p),p=setTimeout(S,l)};return{get:()=>t,upsert(e){const s=t.findIndex(n=>n.id===e.id);t=s<0?[...t,e]:t.map((n,o)=>o===s?e:n),a()},insertAt(e,s){const n=Math.max(0,Math.min(e,t.length));t=[...t.slice(0,n),s,...t.slice(n)],a()},remove(e){const s=t.findIndex(o=>o.id===e);if(s<0)return;const n=t[s];return t=t.filter((o,c)=>c!==s),a(),h.has(e)&&r?.onRemove?.([e]),{plot:n,index:s}},duplicate(e){const s=t.findIndex(c=>c.id===e);if(s<0)return;const n=t[s],o=JSON.parse(JSON.stringify(n));return o.id=g(),n.title&&Object.assign(o,{title:copyTitle(n.title,t.map(c=>c.title))}),t=[...t.slice(0,s+1),o,...t.slice(s+1)],a(),o},replaceAll(e){const s=[...h].filter(n=>t.some(o=>o.id===n)&&!e.some(o=>o.id===n));t=e.map(n=>JSON.parse(JSON.stringify(n))),a(),s.length>0&&r?.onRemove?.(s)},subscribe(e){return u.add(e),()=>{u.delete(e)}},flush:S}}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Die } from '../core/dies.js';
|
|
2
|
+
import type { TestDef } from '../renderer/buildWaferMap.js';
|
|
3
|
+
import type { PlotStore } from './plotStore.js';
|
|
4
|
+
import type { InsightsItem } from './insightsTab.js';
|
|
5
|
+
import { type SaveImageHandler, type SaveTextHandler } from './toolbar.js';
|
|
6
|
+
export interface PlotSectionDeps {
|
|
7
|
+
doc: Document;
|
|
8
|
+
store: PlotStore;
|
|
9
|
+
items: InsightsItem[];
|
|
10
|
+
/** The population's reconciled test list. */
|
|
11
|
+
testDefs: TestDef[];
|
|
12
|
+
ringCount: number;
|
|
13
|
+
/** The tab's Group by key and its label, which "follow Group by" resolves to. */
|
|
14
|
+
groupBy?: string;
|
|
15
|
+
groupLabel?: string;
|
|
16
|
+
onSaveImage?: SaveImageHandler;
|
|
17
|
+
onSaveText?: SaveTextHandler;
|
|
18
|
+
/** Click a point: open its wafer, on a test's values when the plot is about one. */
|
|
19
|
+
openWafer?: (waferIndex: number, label: string, testNumber?: number) => void;
|
|
20
|
+
/** A single-wafer host's version of the same: show the test on the map that is already there. */
|
|
21
|
+
focusTest?: (testNumber: number) => void;
|
|
22
|
+
locateDie?: (die: Die, waferIndex: number | undefined) => void;
|
|
23
|
+
/** The host's own way to choose a plots file (a native dialog). Without it a file input is used. */
|
|
24
|
+
pickPlotsFile?: () => Promise<string | null>;
|
|
25
|
+
}
|
|
26
|
+
export declare function createPlotSection(d: PlotSectionDeps): {
|
|
27
|
+
card: HTMLElement;
|
|
28
|
+
destroy: () => void;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=plotTab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defaultPlot as Z,defaultSweep as ee,describeTitleDrift as te,examplePlots as oe,fieldCatalogue as ne,resolvePlot as se,titleDrift as le}from"../stats/plotData.js";import{addPlots as re,newPlotId as I,readPlotsFile as ae,writePlotsFile as ie}from"../stats/plotSpec.js";import{isYieldEligibleDie as ce}from"../core/dies.js";import{testValue as pe}from"../core/dieTable.js";import{sweepAppliesTo as de}from"../stats/sweep.js";import{plotToSweep as L}from"../stats/plotSpec.js";import{renderPlotChart as fe}from"./charts/plotChart.js";import{renderSweepPanel as ue}from"./charts/sweep.js";import{makeChartGridWrap as me}from"./charts/chartShell.js";import{confirmDialog as he}from"./confirmDialog.js";import{openDrilldownMenu as O}from"./drilldown.js";import{openPlotEditor as ge}from"./plotModal.js";import{sourceFromDies as ye,sourceFromPoints as we,toPlotItems as xe}from"./plotItems.js";import{CLR as h,FONT as S,RADIUS as F,SPACE as f,controlStyle as be,markNoPrint as E,saveTextFile as Pe,wireControlHover as De}from"./toolbar.js";const W=1e4;export function createPlotSection(l){const{doc:i,store:r}=l,c=xe(l.items.map(e=>({label:e.identity??e.label,dies:e.dies,waferIndex:e.waferIndex,wafer:e.wafer,passBins:e.passBins,ringCount:l.ringCount}))),C={testDefs:l.testDefs,groupBy:l.groupBy},U=e=>se(e,c,C),x=ne(c,C),u=i.createElement("div");u.dataset.wmapPlotTab="1",Object.assign(u.style,{display:"flex",flexDirection:"column",gap:f.lg,width:"100%"});const b=i.createElement("div");Object.assign(b.style,{display:"flex",flexWrap:"wrap",gap:f.md,alignItems:"center"});const d=(e,o,n)=>{const t=i.createElement("button");return t.type="button",t.textContent=e,t.dataset[o]="1",Object.assign(t.style,{...be("outlined"),fontSize:S.body}),De(t),t.addEventListener("click",n),t},P=d("Delete all plots\u2026","wmapPlotDeleteAll",()=>{V()});b.append(d("+ New plot","wmapPlotNew",()=>R()),d("+ New sweep","wmapPlotNewSweep",()=>M()),d("Add examples","wmapPlotExamples",()=>_()),d("Import plots\u2026","wmapPlotImport",()=>{Q()}),d("Export plots\u2026","wmapPlotExport",()=>X()),P),E(b),u.appendChild(b);const p=i.createElement("div");p.setAttribute("role","status"),Object.assign(p.style,{display:"none",flexWrap:"wrap",gap:f.md,alignItems:"center",padding:`${f.sm} ${f.md}`,background:h.menuBg,border:`1px solid ${h.menuBorder}`,borderRadius:F.container,color:h.text,fontSize:S.body}),E(p),u.appendChild(p);let m;function g(e,o,n=[]){m!==void 0&&clearTimeout(m),p.replaceChildren();const t=i.createElement("span");if(t.textContent=e,p.appendChild(t),o){const s=d(o.label,"wmapPlotNoticeAction",()=>{o.run(),$()});p.appendChild(s)}for(const s of n){const a=i.createElement("div");a.textContent=s,Object.assign(a.style,{flexBasis:"100%",color:h.label}),p.appendChild(a)}p.style.display="flex",m=setTimeout($,o?W:W*2)}function $(){m!==void 0&&(clearTimeout(m),m=void 0),p.style.display="none",p.replaceChildren()}const D=i.createElement("div");D.textContent="No plots yet. A plot is any measured value against another, or a distribution, that you choose: New plot starts one, and it is kept for the next lot. Add examples draws one of each chart type from this lot to start from.",Object.assign(D.style,{color:h.label,fontSize:S.body,padding:`${f.lg} 0`});const v=me(i);u.append(D,v);const y=new Map,k=()=>({onSaveImage:l.onSaveImage,onOpenWafer:l.openWafer?(e,o)=>l.openWafer(c[e].waferIndex??e,c[e].label,o):l.focusTest&&c.length===1?(e,o)=>{o!==void 0&&l.focusTest(o)}:void 0,openActionLabel:!l.openWafer&&l.focusTest&&c.length===1?"show this test on the map":void 0,onSelectPoints:(e,o,n)=>z(e,o,n)}),A=()=>({plots:r,onSaveImage:l.onSaveImage,onSaveText:l.onSaveText,onLocateDie:l.locateDie});function z(e,o,n){const t=we(e,c,l.testDefs);t&&O(o,n,t,A())}function T(e,o=!1){ge({doc:i,anchor:u,store:r,plot:e,draft:o,items:c,ctx:C,groupLabel:l.groupLabel,chart:k(),onSelectLevel:j})}function M(){const e=ee(l.testDefs,I());r.upsert(e),T(e)}function j(e,o){const n=s=>e.testNumbers.some(a=>{const w=pe(s,a);return w!==void 0&&Number.isFinite(w)}),t=ye(c,`measured at ${e.label}`,s=>ce(s)&&n(s),l.testDefs,e.testNumbers[0]);t&&O({x:o.clientX,y:o.clientY},o.target,t,A())}function R(){const e=Z(x,I());r.upsert(e),T(e)}function _(){const e=t=>JSON.stringify([t.chart,t.fields,t.aggregate??null,t.sweep??null]),o=new Set(r.get().map(e)),n=oe(x,c.length,I).filter(t=>!o.has(e(t)));if(n.length===0){g(x.some(t=>t.group==="Tests")?"The examples for this lot are already in your plots.":"There are no measured values in this lot to draw examples from.");return}for(const t of n)r.upsert(t);g(`Added ${n.length} example plot${n.length===1?"":"s"}: ${n.map(t=>t.chart).join(", ")}. Edit one to make it yours, or Delete what you do not need.`)}function N(e,o){const n=(t,s,a)=>e.appendChild(d(t,s,a));n("Edit","wmapPlotEdit",()=>{const t=r.get().find(s=>s.id===o.id);t&&T(t)}),n("Duplicate","wmapPlotDuplicate",()=>{r.duplicate(o.id)}),n("Delete","wmapPlotDelete",()=>{const t=r.remove(o.id);t&&g(`Deleted \u201C${t.plot.title??"plot"}\u201D.`,{label:"Undo",run:()=>r.insertAt(t.index,t.plot)})})}function H(e){return e.chart==="sweep"?Y(e):J(e)}function J(e){const o=fe({ownerDocument:i,waferLabel:s=>c[s]?.label??"",...k()});o.card.dataset.wmapPlotId=e.id,E(o.actions),N(o.actions,e);const n=i.createElement("div");n.dataset.wmapPlotTitleDrift="1",Object.assign(n.style,{display:"none",flexWrap:"wrap",gap:f.md,alignItems:"center",flexBasis:"100%",padding:`${f.xs} ${f.md}`,background:h.warnBg,border:`1px solid ${h.warnBorder}`,borderRadius:F.control,color:h.warnText,fontSize:S.body}),o.actions.appendChild(n);const t={card:o.card,actions:o.actions,json:"",driftNote:n,destroy:()=>o.destroy(),update:s=>{const a=U(s);o.setPlot(a),o.card.style.opacity=a.issues.length?"0.7":"1",G(n,s)}};return y.set(e.id,t),t}function Y(e){const o=L(e),n=c.flatMap(a=>a.dies),t=ue({spec:o,dies:n,testDefs:l.testDefs,onSaveImage:l.onSaveImage,ownerDocument:i,onSelectPoint:j});t.card.dataset.wmapPlotId=e.id,t.card.dataset.wmapSweepPlot="1",E(t.actions),N(t.actions,e);const s={card:t.card,actions:t.actions,json:"",destroy:()=>t.destroy(),update:a=>{const w=L(a);t.setSpec(w),t.card.style.opacity=de(w,l.testDefs)?"1":"0.7"}};return y.set(e.id,s),s}function G(e,o){const n=te(le(o,x));if(e.replaceChildren(),e.style.display=n?"flex":"none",!n)return;const t=i.createElement("span");t.textContent=n,e.append(t,d("Use the automatic title","wmapPlotAutoTitle",()=>{const s=r.get().find(w=>w.id===o.id);if(!s)return;const a={...s};delete a.title,r.upsert(a)}))}function B(){const e=r.get();for(const[o,n]of y)e.some(t=>t.id===o)||(n.destroy(),n.card.remove(),y.delete(o));e.forEach((o,n)=>{const t=y.get(o.id)??H(o),s=JSON.stringify(o);t.json!==s&&(t.json=s,t.update(o)),v.children[n]!==t.card&&v.insertBefore(t.card,v.children[n]??null)}),D.style.display=e.length===0?"":"none",P.disabled=e.length===0,P.style.opacity=e.length===0?"0.5":"1",P.style.cursor=e.length===0?"default":"pointer"}const K=r.subscribe(B);B();async function V(){const e=r.get().slice();if(e.length===0)return;const o=e.length;if(!await he({doc:i,anchor:u,title:"Delete all plots",message:o===1?"Delete your 1 saved plot? It is kept on this computer for the next lot, so it will not be there for the next file either. Export plots\u2026 first to keep a copy.":`Delete all ${o} saved plots, sweeps included? They are kept on this computer for the next lot, so they will not be there for the next file either. Export plots\u2026 first to keep a copy.`,confirmLabel:o===1?"Delete 1 plot":`Delete ${o} plots`}))return;const t=r.get().slice();t.length!==0&&(r.replaceAll([]),g(`Deleted ${t.length===1?"1 plot":`all ${t.length} plots`}.`,{label:"Undo",run:()=>r.replaceAll(t)}))}function X(){const e=r.get();if(e.length===0){g("There are no plots to export yet.");return}Pe(ie(e),"wafermap-plots.json","application/json",l.onSaveText)}async function q(){return l.pickPlotsFile?l.pickPlotsFile():new Promise(e=>{const o=i.createElement("input");o.type="file",o.accept=".json,application/json",o.addEventListener("change",()=>{const n=o.files?.[0];if(!n){e(null);return}n.text().then(e,()=>e(null))}),o.addEventListener("cancel",()=>e(null)),o.click()})}async function Q(){const e=await q();if(e===null)return;const o=ae(e);if(o.error){g(`Could not import: ${o.error}`);return}const{plots:n,copies:t}=re(r.get(),o.plots);r.replaceAll(n);const s=[...o.warnings,...t.length?[`Kept as copies, since a plot with the same id already existed: ${t.join(", ")}`]:[]];g(`Imported ${o.plots.length} plot${o.plots.length===1?"":"s"}.`,void 0,s)}return{card:u,destroy:()=>{K(),m!==void 0&&clearTimeout(m);for(const e of y.values())e.destroy();y.clear(),r.flush()}}}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BinColors } from '../renderer/binColors.js';
|
|
2
|
-
import { type SaveImageHandler, type SaveTextHandler
|
|
2
|
+
import { type SaveImageHandler, type SaveTextHandler } from './toolbar.js';
|
|
3
|
+
import { type UserGuideExtension } from './guideWindow.js';
|
|
3
4
|
import { type Die } from '../core/dies.js';
|
|
4
5
|
import type { WaferViewOptions } from './renderWaferMap.js';
|
|
5
6
|
import type { RenderableWaferMap } from './renderWaferMap.js';
|
|
@@ -113,7 +114,7 @@ export interface GalleryOptions {
|
|
|
113
114
|
* A prefix for every file the gallery, its cards and their detached windows
|
|
114
115
|
* save — PNGs and CSVs alike. The lots, wafer or wafer count, and content
|
|
115
116
|
* follow it (`<prefix>_25-wafers_gallery-hard-bin.png`), except any the prefix
|
|
116
|
-
* already names. See docs/api.md §5.4.5.
|
|
117
|
+
* already names. See docs/api/render-map.md §5.4.5.
|
|
117
118
|
*/
|
|
118
119
|
downloadFilename?: string;
|
|
119
120
|
/**
|