@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.
Files changed (142) hide show
  1. package/AGENTS.md +6 -6
  2. package/CHANGELOG.md +212 -0
  3. package/README.md +5 -5
  4. package/dist/packages/canvas-adapter/chartPopulation.d.ts +15 -10
  5. package/dist/packages/canvas-adapter/chartPopulation.js +1 -1
  6. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +7 -0
  7. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +7 -1
  9. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +55 -3
  13. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/correlation.js +1 -2
  15. package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +2 -0
  16. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
  17. package/dist/packages/canvas-adapter/charts/histogram.d.ts +16 -0
  18. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  19. package/dist/packages/canvas-adapter/charts/palette.d.ts +2 -0
  20. package/dist/packages/canvas-adapter/charts/palette.js +1 -1
  21. package/dist/packages/canvas-adapter/charts/plotChart.d.ts +27 -0
  22. package/dist/packages/canvas-adapter/charts/plotChart.js +1 -0
  23. package/dist/packages/canvas-adapter/charts/pointInteractions.d.ts +45 -0
  24. package/dist/packages/canvas-adapter/charts/pointInteractions.js +1 -0
  25. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +2 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  27. package/dist/packages/canvas-adapter/charts/scatter.d.ts +19 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  29. package/dist/packages/canvas-adapter/charts/sweep.d.ts +12 -0
  30. package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
  31. package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +7 -0
  32. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
  33. package/dist/packages/canvas-adapter/charts/trend.d.ts +11 -0
  34. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  35. package/dist/packages/canvas-adapter/confirmDialog.d.ts +13 -0
  36. package/dist/packages/canvas-adapter/confirmDialog.js +1 -0
  37. package/dist/packages/canvas-adapter/dataTab.d.ts +131 -0
  38. package/dist/packages/canvas-adapter/dataTab.js +1 -0
  39. package/dist/packages/canvas-adapter/dieList.d.ts +39 -4
  40. package/dist/packages/canvas-adapter/dieList.js +6 -7
  41. package/dist/packages/canvas-adapter/drilldown.d.ts +20 -3
  42. package/dist/packages/canvas-adapter/drilldown.js +1 -1
  43. package/dist/packages/canvas-adapter/guideWindow.d.ts +96 -0
  44. package/dist/packages/canvas-adapter/guideWindow.js +1 -0
  45. package/dist/packages/canvas-adapter/identityHeader.d.ts +16 -0
  46. package/dist/packages/canvas-adapter/identityHeader.js +1 -1
  47. package/dist/packages/canvas-adapter/index.d.ts +3 -3
  48. package/dist/packages/canvas-adapter/index.js +1 -1
  49. package/dist/packages/canvas-adapter/insightsTab.d.ts +73 -54
  50. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  51. package/dist/packages/canvas-adapter/maplessSummary.d.ts +2 -2
  52. package/dist/packages/canvas-adapter/plotEditor.d.ts +29 -0
  53. package/dist/packages/canvas-adapter/plotEditor.js +1 -0
  54. package/dist/packages/canvas-adapter/plotItems.d.ts +32 -0
  55. package/dist/packages/canvas-adapter/plotItems.js +1 -0
  56. package/dist/packages/canvas-adapter/plotModal.d.ts +30 -0
  57. package/dist/packages/canvas-adapter/plotModal.js +1 -0
  58. package/dist/packages/canvas-adapter/plotStore.d.ts +52 -0
  59. package/dist/packages/canvas-adapter/plotStore.js +1 -0
  60. package/dist/packages/canvas-adapter/plotTab.d.ts +30 -0
  61. package/dist/packages/canvas-adapter/plotTab.js +1 -0
  62. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +3 -2
  63. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  64. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +21 -2
  65. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  66. package/dist/packages/canvas-adapter/summaryPanel.d.ts +27 -8
  67. package/dist/packages/canvas-adapter/summaryPanel.js +2 -4
  68. package/dist/packages/canvas-adapter/summaryRail.d.ts +7 -0
  69. package/dist/packages/canvas-adapter/summaryRail.js +1 -0
  70. package/dist/packages/canvas-adapter/sweepEditor.d.ts +17 -0
  71. package/dist/packages/canvas-adapter/sweepEditor.js +1 -0
  72. package/dist/packages/canvas-adapter/tableExport.d.ts +22 -0
  73. package/dist/packages/canvas-adapter/tableExport.js +3 -0
  74. package/dist/packages/canvas-adapter/toolbar.d.ts +25 -97
  75. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  76. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  77. package/dist/packages/canvas-adapter/userGuideHtml.js +337 -42
  78. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  79. package/dist/packages/canvas-adapter/version.js +1 -1
  80. package/dist/packages/canvas-adapter/viewSwitch.d.ts +13 -0
  81. package/dist/packages/canvas-adapter/viewSwitch.js +1 -0
  82. package/dist/packages/canvas-adapter/virtualTable.d.ts +44 -0
  83. package/dist/packages/canvas-adapter/virtualTable.js +1 -0
  84. package/dist/packages/core/classify.d.ts +3 -2
  85. package/dist/packages/core/classify.js +1 -1
  86. package/dist/packages/core/passBins.d.ts +17 -4
  87. package/dist/packages/core/passBins.js +1 -1
  88. package/dist/packages/core/ringCount.d.ts +22 -0
  89. package/dist/packages/core/ringCount.js +1 -0
  90. package/dist/packages/core/tableCsv.d.ts +43 -0
  91. package/dist/packages/core/tableCsv.js +3 -0
  92. package/dist/packages/renderer/binColors.d.ts +6 -6
  93. package/dist/packages/renderer/binColors.js +1 -1
  94. package/dist/packages/renderer/buildView.d.ts +1 -1
  95. package/dist/packages/renderer/buildView.js +1 -1
  96. package/dist/packages/renderer/buildWaferMap.js +1 -1
  97. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  98. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  99. package/dist/packages/stats/binPareto.d.ts +1 -1
  100. package/dist/packages/stats/binPareto.js +1 -1
  101. package/dist/packages/stats/histogram.d.ts +27 -0
  102. package/dist/packages/stats/histogram.js +1 -1
  103. package/dist/packages/stats/index.d.ts +2 -0
  104. package/dist/packages/stats/index.js +1 -1
  105. package/dist/packages/stats/lotDrift.js +1 -1
  106. package/dist/packages/stats/lotPattern.js +1 -1
  107. package/dist/packages/stats/math.d.ts +9 -6
  108. package/dist/packages/stats/math.js +1 -1
  109. package/dist/packages/stats/patternClassification.d.ts +1 -1
  110. package/dist/packages/stats/patternClassification.js +1 -1
  111. package/dist/packages/stats/plotData.d.ts +258 -0
  112. package/dist/packages/stats/plotData.js +1 -0
  113. package/dist/packages/stats/plotId.d.ts +3 -0
  114. package/dist/packages/stats/plotId.js +1 -0
  115. package/dist/packages/stats/plotSpec.d.ts +119 -0
  116. package/dist/packages/stats/plotSpec.js +4 -0
  117. package/dist/packages/stats/presentation.d.ts +2 -0
  118. package/dist/packages/stats/presentation.js +1 -1
  119. package/dist/packages/stats/regions.d.ts +6 -0
  120. package/dist/packages/stats/regions.js +1 -1
  121. package/dist/packages/stats/renderSummaryReport.d.ts +8 -3
  122. package/dist/packages/stats/renderSummaryReport.js +15 -15
  123. package/dist/packages/stats/reportHtml.d.ts +1 -1
  124. package/dist/packages/stats/reportHtml.js +36 -29
  125. package/dist/packages/stats/scatter.d.ts +6 -0
  126. package/dist/packages/stats/scatter.js +1 -1
  127. package/dist/packages/stats/sweep.d.ts +37 -1
  128. package/dist/packages/stats/sweep.js +1 -1
  129. package/dist/packages/stats/sweepText.d.ts +14 -0
  130. package/dist/packages/stats/sweepText.js +1 -0
  131. package/dist/packages/stats/sweepXFromName.d.ts +14 -0
  132. package/dist/packages/stats/sweepXFromName.js +1 -1
  133. package/dist/packages/stats/synthesis.d.ts +14 -4
  134. package/dist/packages/stats/synthesis.js +1 -1
  135. package/dist/packages/stats/testPassRate.d.ts +6 -0
  136. package/dist/packages/stats/testPassRate.js +1 -1
  137. package/dist/packages/stats/trend.js +1 -1
  138. package/dist/packages/stats/types.d.ts +1 -1
  139. package/dist/packages/stats/yield.d.ts +15 -6
  140. package/dist/packages/stats/yield.js +1 -1
  141. package/llms.txt +15 -3
  142. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { type SaveImageHandler } from '../toolbar.js';
1
+ import { type OverlayHandle, type SaveImageHandler } from '../toolbar.js';
2
2
  import { robustFence } from '../../stats/math.js';
3
3
  export type { SaveImageHandler };
4
4
  /**
@@ -19,7 +19,7 @@ export type { SaveImageHandler };
19
19
  export declare function openChartExpandModal(card: HTMLElement, title: string, opts?: {
20
20
  anchor?: Element;
21
21
  onClosed?: () => void;
22
- }): void;
22
+ }): OverlayHandle | null;
23
23
  export interface ChartCanvasColors {
24
24
  text: string;
25
25
  textMuted: string;
@@ -235,12 +235,60 @@ export declare const VERTICAL_TICK_SPACING_PX = 28;
235
235
  export declare function horizontalTickSpacing(ctx: CanvasRenderingContext2D, vRef: number, unit: string | undefined): (step: number, ticks: number[]) => number;
236
236
  /** Draw a small "(unit)" label at (x, y), restoring the context's text state afterward. */
237
237
  export declare function drawAxisUnit(ctx: CanvasRenderingContext2D, unit: string, x: number, y: number, color: string): void;
238
- export declare function saveCanvasPng(canvas: HTMLCanvasElement, filenameStem: string, onSaveImage?: SaveImageHandler): void;
238
+ /** Text for a saved image or a printed page: what a caption says about the chart, without telling the reader to click. */
239
+ export declare function captionText(text: string): string;
240
+ export interface CardContext {
241
+ controls: string[];
242
+ captions: string[];
243
+ legend: Array<{
244
+ label: string;
245
+ color: string;
246
+ }>;
247
+ }
248
+ /**
249
+ * Reads what a card currently says around its canvas: its controls' settings ("Test: vth_n_mV · 1001", "Sort: yield"),
250
+ * its caption elements (marked `data-wmap-caption`, with instructions to click or drag removed), and its legend.
251
+ */
252
+ export declare function describeCard(card: HTMLElement): CardContext;
253
+ /** What a saved chart carries besides its canvas: the legend and the statement of what it shows are DOM, not canvas. */
254
+ export interface PngDecor {
255
+ /** Which test, wafer, group or sort the card's controls are set to ("Test: vth_n_mV · 1001"): a chart saved without them is unlabelled. */
256
+ controls?: readonly string[];
257
+ /** The population and what was combined or left out, under the title: a line, or several. */
258
+ caption?: string | readonly string[];
259
+ /** The colour key of a chart coloured by category. */
260
+ legend?: ReadonlyArray<{
261
+ label: string;
262
+ color: string;
263
+ }>;
264
+ /** The colour key of a chart coloured on a gradient. */
265
+ colorbar?: {
266
+ label: string;
267
+ lo: string;
268
+ hi: string;
269
+ color: (t: number) => string;
270
+ };
271
+ }
272
+ export interface PngHeader {
273
+ /** The card the chart lives in: its colours are the saved image's, so a dark theme saves a legible dark image. */
274
+ card: HTMLElement;
275
+ title: string;
276
+ decor?: PngDecor;
277
+ }
278
+ /**
279
+ * The canvas as it is saved: under a header that names the chart, states what it shows, and carries the legend.
280
+ * A bare canvas saved with none of these is a picture of unlabelled marks: the title, the population ("6 wafers · 3,966
281
+ * dies"), and the key to the colours live in the card around the canvas, not in it.
282
+ */
283
+ export declare function composeChartPng(canvas: HTMLCanvasElement, header?: PngHeader): HTMLCanvasElement;
284
+ export declare function saveCanvasPng(canvas: HTMLCanvasElement, filenameStem: string, onSaveImage?: SaveImageHandler, header?: PngHeader): void;
239
285
  export interface CardShell {
240
286
  card: HTMLElement;
241
287
  heading: HTMLElement;
242
288
  controlsRow: HTMLElement;
243
289
  body: HTMLElement;
290
+ /** What the card's Save as PNG adds under the title, besides the canvas (a caption, a legend). Read at the moment of saving. */
291
+ setPngDecor(read: () => PngDecor | undefined): void;
244
292
  }
245
293
  /**
246
294
  * THE colour chip that stands for a data series, everywhere in the chart suite.
@@ -304,6 +352,10 @@ export declare function renderEmptyState(body: HTMLElement, message: string, sty
304
352
  export interface ListSelectOption {
305
353
  value: string;
306
354
  label: string;
355
+ /** A heading shown above the first option of each run with the same group. The list stays one flat, searchable list. */
356
+ group?: string;
357
+ /** Why this option cannot be chosen now. It stays in the list, dimmed, with the reason after it, and does nothing when picked. */
358
+ disabled?: string;
307
359
  }
308
360
  /**
309
361
  * A themed single-select picker: a trigger button plus an on-demand popup
@@ -1 +1 @@
1
- import{SHADOW as N,LEADING as j,wireControlHover as L,controlStyle as F,SPACE as m,RADIUS as C,fontPx as k,FONT as S,CLR as f,Z_BASE as K,menuLayerFor as Y,wireListNavigation as Z,MENU_SEARCH_THRESHOLD as W,makeMenuSearchBox as J,markMenuTrigger as R,saveImageBlob as Q,openReparentedModal as ee}from"../toolbar.js";import{ICONS as _}from"../icons.js";import{minOf as te,maxOf as ne}from"../../core/utils.js";import{robustFence as P}from"../../stats/math.js";import{fmt as oe,fmtColorbarAxis as ie}from"../../renderer/fmt.js";import{markedTestLabel as re,DERIVED_LANE_PAD as le}from"../../renderer/testLabel.js";export function openChartExpandModal(e,o,n={}){for(const s of e.querySelectorAll('div[style*="position: absolute"]'))s.style.display==="block"&&s.style.pointerEvents==="none"&&(s.style.display="none");const t=e.querySelector("[data-wmap-chart-expand]"),i=e.getAttribute("style")??"",r=e.dataset.wmapChartGrow??"plot";Object.assign(e.style,{flex:"1",minHeight:"0",alignSelf:"stretch",border:"none",borderRadius:"0"}),e.dataset.wmapExpanded="1";const l=ee([e],{title:o,anchor:n.anchor,ownerDocument:e.ownerDocument,boxSize:se[r],onClosed:()=>{delete e.dataset.wmapExpanded,e.setAttribute("style",i),t&&(t.style.display="flex"),n.onClosed?.()}});if(!l){delete e.dataset.wmapExpanded,e.setAttribute("style",i);return}if(t&&(t.style.display="none"),r==="rows"){const s=()=>{const a=Number(e.dataset.wmapChartFullH);if(!a)return;const p=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(p+a)}px)`)};s(),(e.ownerDocument.defaultView??window).requestAnimationFrame(s)}}const se={plot:{width:"min(94vw, 1100px)",height:"min(90vh, 760px)"},rows:{width:"min(94vw, 960px)",height:"min(90vh, 700px)"},square:{width:"min(94vw, 820px)",height:"min(90vh, 860px)"}};export const GRID_ALPHA=.4;export function withAlpha(e,o){const n=e.trim();let t,i,r,l=1;const s=n.match(/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i),a=n.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/i);if(s){const p=s[1].length===3?s[1].split("").map(c=>c+c).join(""):s[1];[t,i,r]=[0,2,4].map(c=>parseInt(p.slice(c,c+2),16)),p.length===8&&(l=parseInt(p.slice(6,8),16)/255)}else if(a)[t,i,r]=[a[1],a[2],a[3]].map(Number),a[4]!==void 0&&(l=a[4].endsWith("%")?parseFloat(a[4])/100:Number(a[4]));else return e;return`rgba(${t}, ${i}, ${r}, ${+(l*o).toFixed(3)})`}const ae={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},z={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(z)){const i=o.getPropertyValue(`--wmap-${z[t]}`).trim();n[t]=i||ae[t]}return n.grid=withAlpha(n.border,GRID_ALPHA),n}export const PADDING=12,VALUE_WIDTH=100;export function observeResize(e,o){const n=e.ownerDocument.defaultView??window;let t=0;const i=new n.ResizeObserver(()=>{t||(t=n.requestAnimationFrame(()=>{t=0,o()}))});return i.observe(e),{disconnect:()=>{t&&n.cancelAnimationFrame(t),t=0,i.disconnect()}}}export function growCardToFitContent(e,o,n){const t=`${H(e,o)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function H(e,o){const n=e.ownerDocument.defaultView??window,t=n.getComputedStyle(e);let i=0;for(const r of Array.from(e.children)){if(r===o)continue;const l=r,s=n.getComputedStyle(l);s.display==="none"||s.position==="absolute"||s.position==="fixed"||(i+=l.offsetHeight+(parseFloat(s.marginTop)||0)+(parseFloat(s.marginBottom)||0))}return t.boxSizing==="border-box"&&(i+=(parseFloat(t.paddingTop)||0)+(parseFloat(t.paddingBottom)||0)+(parseFloat(t.borderTopWidth)||0)+(parseFloat(t.borderBottomWidth)||0)),i}function V(e,o){const n=e.ownerDocument.defaultView??window;let t=0;for(const i of Array.from(e.children)){if(i===o)continue;const r=i,l=n.getComputedStyle(r);l.display==="none"||l.position==="absolute"||l.position==="fixed"||(t+=r.offsetHeight+(parseFloat(l.marginTop)||0)+(parseFloat(l.marginBottom)||0))}return t}export const WAFER_MENU_HINT="<br><em>right-click to chart this wafer</em>";export function populationPhrase(e,o,n){const t=e.toLocaleString(),i=e===1?"die":"dies";return n?e===o?`${t} ${i} ${n}`:`${t} of ${o.toLocaleString()} dies ${n} (partial and edge-excluded dies left out)`:`${t} ${i}`}export function setChartGrow(e,o){e.dataset.wmapChartGrow=o}export function isExpandedCard(e){return e.dataset.wmapExpanded==="1"}export function bodyRoom(e,o,n){const i=(e.ownerDocument.defaultView??window).getComputedStyle(e),r=e.clientHeight-(parseFloat(i.paddingTop)||0)-(parseFloat(i.paddingBottom)||0),l=i.boxSizing==="border-box"?(parseFloat(i.paddingTop)||0)+(parseFloat(i.paddingBottom)||0)+(parseFloat(i.borderTopWidth)||0)+(parseFloat(i.borderBottomWidth)||0):0;return r-(H(e,o)-l)-(n?V(o,n):0)}export function fitRowsHeight(e,o,n,t,i=0){if(growCardToFitContent(e,o,i+n),e.dataset.wmapChartFullH=String(Math.ceil(H(e,o)+i+t)),!isExpandedCard(e))return n;const r=bodyRoom(e,o)-i;return Math.max(n,Math.min(t,Math.floor(r)))}export function chartFillHeight(e,o,n,t){const i=V(o,n);return growCardToFitContent(e,o,i+t),Math.max(t,o.clientHeight-i)}export function chartDpr(e){return(e.ownerDocument.defaultView??window).devicePixelRatio||1}export function prepareCanvas(e,o,n,t){const i=chartDpr(e);e.width=Math.max(1,Math.floor(n*i)),e.height=Math.max(1,Math.floor(t*i)),e.style.width=`${n}px`,e.style.height=`${t}px`;const r=e.getContext("2d");return r?(r.setTransform(i,0,0,i,0,0),r.clearRect(0,0,n,t),r.font=`${k(-1)}px system-ui, sans-serif`,r.textBaseline="middle",{ctx:r,theme:resolveChartCanvasColors(o)}):null}export function applyCanvasFlow(e,o=0){let n;if(typeof o=="number")n=o;else{const t=(o.ownerDocument.defaultView??window).getComputedStyle(o);n=o.offsetTop+o.offsetHeight+(parseFloat(t.marginBottom)||0)}e.style.position="absolute",e.style.left="0",e.style.right="0",e.style.top=`${n}px`,e.style.bottom="0"}export function formatValue(e){return Number.isFinite(e)?Number.isInteger(e)?`${e}`:oe(e,void 0,"engineering"):"\u2014"}export function makeAxisFormat(e,o,n){if(!o&&n===void 0)return{tick:formatValue,tickWithUnit:formatValue,unitLabel:""};const{tickFmt:t,axisLabel:i,scaledUnit:r}=ie(e,null,o,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:i}}const ce=8;export const VERTICAL_TICK_SPACING_PX=28;export function horizontalTickSpacing(e,o,n){return(t,i)=>{const r=makeAxisFormat(o,n,t).tick;return i.reduce((l,s)=>Math.max(l,e.measureText(r(s)).width),0)+ce}}export function drawAxisUnit(e,o,n,t,i){const r={textAlign:e.textAlign,textBaseline:e.textBaseline,fillStyle:e.fillStyle,font:e.font};e.font=`${k(-1)}px system-ui, sans-serif`,e.textAlign="left",e.textBaseline="middle",e.fillStyle=i,e.fillText(`(${o})`,n,t),Object.assign(e,r)}export function saveCanvasPng(e,o,n){const t=e.ownerDocument.createElement("canvas");t.width=e.width,t.height=e.height;const i=t.getContext("2d");i.fillStyle="#ffffff",i.fillRect(0,0,t.width,t.height),i.drawImage(e,0,0),t.toBlob(r=>{r&&Q(r,o,n)},"image/png")}const G="10px";export function chartSwatchCss(e,o="solid"){return["display:inline-block",`width:${G}`,`height:${G}`,`border-radius:${C.pill}`,`background:${o==="outline"?"transparent":e}`,`border:1px ${o==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const de="0.35";export function makeSeriesLegendItem(e,o,n=document){const t=n.createElement("button");t.type="button",Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:m.xs,padding:`${m.xs} ${m.md}`,borderRadius:C.pill,border:`1px solid ${f.menuBorder}`,background:"none",cursor:"pointer",fontSize:S.body,color:f.text,whiteSpace:"nowrap"}),L(t,"bare");const i=n.createElement("span");return i.style.cssText=chartSwatchCss(o),t.append(i,n.createTextNode(e)),{el:t,setState({selected:r,dimmed:l}){t.style.borderColor=r?f.iconActive:f.menuBorder,t.style.fontWeight=r?"600":"400",t.style.opacity=l?de:"1",t.setAttribute("aria-pressed",r?"true":"false")}}}export function cardFrameStyle(){return{background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:C.container,padding:m.xl,fontVariantNumeric:"tabular-nums"}}export function cardShell(e,o,n=document){const t=n.createElement("div");t.dataset.wmapChartCard="1",t.dataset.wmapChartTitle=e,Object.assign(t.style,{display:"flex",flexDirection:"column",...cardFrameStyle(),minWidth:"0",minHeight:"0",flex:"1 1 0",position:"relative"});const i=t.ownerDocument.createElement("div");Object.assign(i.style,{display:"flex",alignItems:"center",gap:m.sm,paddingBottom:"6px",marginBottom:m.md,borderBottom:`1px solid ${f.menuBorder}`});const r=t.ownerDocument.createElement("div");r.textContent=e,Object.assign(r.style,{color:f.value,fontSize:S.heading,fontWeight:"600",flex:"1"}),i.appendChild(r);const l=makeTooltip(t),s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Save as PNG"),s.innerHTML=_.download,Object.assign(s.style,{...F("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(s),attachChartTip(s,t,l,"Save as PNG"),s.addEventListener("click",()=>{const d=t.querySelector("canvas");d&&saveCanvasPng(d,e,o)}),i.appendChild(s);const a=t.ownerDocument.createElement("button");a.type="button",a.setAttribute("aria-label","Expand"),a.innerHTML=_.expand,Object.assign(a.style,{...F("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:j.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),L(a),attachChartTip(a,t,l,"Expand"),a.dataset.wmapChartExpand="1",a.addEventListener("click",()=>openChartExpandModal(t,r.textContent??e)),i.appendChild(a),t.appendChild(i);const p=t.ownerDocument.createElement("div");Object.assign(p.style,{display:"flex",gap:m.sm,marginBottom:m.md,flexWrap:"wrap",alignItems:"center"}),t.appendChild(p);const c=t.ownerDocument.createElement("div");return Object.assign(c.style,{overflowX:"hidden",overflowY:"hidden",minHeight:"0",flex:"1",position:"relative"}),t.appendChild(c),{card:t,heading:r,controlsRow:p,body:c}}export function makeSegmented(e,o,n,t=document,i=!1){const r=t.createElement("div");r.setAttribute("role","radiogroup"),Object.assign(r.style,{display:"inline-flex",border:`1px solid ${f.menuBorder}`,borderRadius:C.control,overflow:"hidden"});const l=`seg-${Math.random().toString(36).slice(2,9)}`,s=[];return e.forEach(([a,p],c)=>{const d=t.createElement("label");Object.assign(d.style,{display:"inline-flex",alignItems:"center",fontSize:S.body,padding:i?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:c>0?`1px solid ${f.menuBorder}`:"none"}),L(d,"bare");const u=t.createElement("input");u.type="radio",u.name=l,u.value=a,u.checked=a===o,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const w=()=>{d.style.background=u.checked?f.bgActive:f.menuBg,d.style.color=u.checked?f.iconActive:f.text,d.dataset.on=u.checked?"true":"false"};s.push(w),w(),u.addEventListener("change",()=>{if(u.checked){for(const v of s)v();n(a)}}),d.append(u,t.createTextNode(p)),r.appendChild(d)}),r}export function makeBackButton(e,o=document){const n=o.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...F("outlined"),background:f.menuBg,color:f.label}),L(n),n.addEventListener("click",e),n}export function renderEmptyState(e,o,n){const t=e.ownerDocument.createElement("div");t.textContent=o,Object.assign(t.style,{color:f.label,fontSize:S.body,padding:"8px 0"}),n&&Object.assign(t.style,n),e.appendChild(t)}export function makeListSelect(e,o,n,t={}){const{maxWidth:i="200px",ownerDocument:r=document,ariaLabel:l,emptyText:s,searchPlaceholder:a="Filter\u2026",hook:p}=t;let c=o;const d=x=>e.find(O=>O.value===x)?.label??"",u=r.createElement("button");u.type="button",p&&(u.dataset.wmapSelect=p),l&&u.setAttribute("aria-label",l),Object.assign(u.style,{fontSize:S.body,padding:`${m.xxs} ${m.sm}`,background:f.menuBg,color:f.text,border:`1px solid ${f.menuBorder}`,borderRadius:C.control,maxWidth:i,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:m.sm,justifyContent:"space-between"}),L(u),R(u,!1);const w=r.createElement("span");Object.assign(w.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const v=r.createElement("span");v.textContent="\u25BE",v.style.flex="0 0 auto",u.append(w,v);const E=()=>{w.textContent=e.length===0?s??"(none)":d(c)||s||"(none)"};if(E(),e.length===0)return u.disabled=!0,u.style.opacity="0.6",u.style.cursor="default",Object.defineProperty(u,"value",{get:()=>c,set:x=>{c=x,E()}});let b=null;const B=()=>{b&&(b.remove(),b=null,R(u,!1))},$=()=>{B(),u.focus()};function U(){const x=u.getBoundingClientRect(),O=r.defaultView??window,D=Math.max(x.width,220),q=Math.min(x.left,Math.max(4,(O.innerWidth??1/0)-D-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:N.menu,zIndex:K,minWidth:`${D}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const T=[],I=()=>T.filter(h=>h.row.style.display!=="none").map(h=>h.row);if(e.length>W){const h=J(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(),I()[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",X=>{X.stopPropagation(),c=h.value,E(),$(),n(h.value)}),b.appendChild(g),T.push({row:g,label:h.label.toLowerCase()})}if(Z(b,I,$),Y(u).appendChild(b),R(u,!0),e.length<=W){const h=e.findIndex(y=>y.value===c);I()[h>=0?h:0]?.focus()}}u.addEventListener("click",x=>{if(x.stopPropagation(),b){B();return}U()});const M=x=>{if(!u.isConnected){r.removeEventListener("click",M);return}b&&!b.contains(x.target)&&!u.contains(x.target)&&B()};return r.addEventListener("click",M),Object.defineProperty(u,"value",{get:()=>c,set:x=>{c=x,E()}})}export function testOptionLabels(e){const o=e.some(n=>n.derived);return e.map(n=>(o&&!n.derived?le:"")+re(n,n.testNumber))}export function makeTestSelect(e,o,n,t={}){const{maxWidth:i="200px",emptyText:r="No parametric tests",ownerDocument:l=document}=t;return makeListSelect(testOptionLabels(e).map((s,a)=>({value:String(e[a].testNumber),label:s})),o!==null?String(o):"",s=>n(Number(s)),{maxWidth:i,ownerDocument:l,ariaLabel:"Test",emptyText:r,searchPlaceholder:"Filter tests\u2026"})}export function makeLinkedAxisPrefs(e,o,n,t=document){let i=o?.includeLimits,r=o?.clipOutliers??!1,l=o?.limits??"both";const s=()=>n({includeLimits:i,clipOutliers:r,limits:l});return{get:()=>({includeLimits:i,clipOutliers:r,limits:l}),sync(a,p,c=!1){e.innerHTML="",c&&e.appendChild(makeLimitsSelect(l,d=>{l=d,s()},t)),p&&e.appendChild(makeToggle("Axis includes limits",a,d=>{i=d,s()},t)),e.appendChild(makeToggle("Clip outliers",r,d=>{r=d,s()},t))},set(a){const p=a.limits??"both";return a.includeLimits===i&&a.clipOutliers===r&&p===l?!1:(i=a.includeLimits,r=a.clipOutliers,l=p,!0)}}}export function makeLinkedTestSelect(e,o,n,t={}){let i=o;const r=makeTestSelect(e,i,l=>{l!==i&&(i=l,n(l))},t);return{el:r,get:()=>i,set(l){return i===l||!e.some(s=>s.testNumber===l)?!1:(i=l,r.value=String(l),!0)}}}export const ALL_GROUPS="\0all";export function makeLinkedGroupSelect(e,o,n,t,i={}){let r=n;const l=c=>c??ALL_GROUPS,s=c=>c===ALL_GROUPS?null:c,a=makeLabeledSelect(o,[{value:ALL_GROUPS,label:"All groups"},...e.map(c=>({value:c,label:c}))],l(r),c=>{const d=s(c);d!==r&&(r=d,t(d))},{...i,hook:"group-scope"}),p=a.querySelector('[data-wmap-select="group-scope"]');return{el:a,get:()=>r,set(c){return r===c||c!==null&&!e.includes(c)?!1:(r=c,p&&(p.value=l(c)),!0)}}}export function makeWaferSelect(e,o,n,t={}){const{maxWidth:i="160px",allLabel:r="All wafers",ownerDocument:l=document}=t,s="\0all";return makeListSelect([{value:s,label:r},...e.map((a,p)=>({value:String(p),label:a.label??`#${p}`}))],o===null?s:String(o),a=>n(a===s?null:Number(a)),{maxWidth:i,ownerDocument:l,ariaLabel:r,searchPlaceholder:"Filter wafers\u2026"})}export function makeToggle(e,o,n,t=document){const i=t.createElement("label");Object.assign(i.style,{display:"inline-flex",alignItems:"center",gap:m.xs,fontSize:S.body,color:f.label,cursor:"pointer",userSelect:"none"}),L(i,"bare");const r=t.createElement("input");return r.type="checkbox",r.checked=o,r.style.cssText="margin:0;cursor:pointer;",r.addEventListener("change",()=>n(r.checked)),i.append(r,t.createTextNode(e)),i}export function makeLabeledSelect(e,o,n,t,i={}){const{maxWidth:r="160px",hook:l,ownerDocument:s=document}=i,a=s.createElement("label");a.textContent=e,Object.assign(a.style,{color:f.label,fontSize:S.body,display:"flex",alignItems:"center",gap:m.xs});const p=makeListSelect(o,n,t,{maxWidth:r,ownerDocument:s,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return a.appendChild(p),a}export function makeChartGridWrap(e=document){const o=e.createElement("div");return o.dataset.wmapChartGrid="1",Object.assign(o.style,{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(420px, 1fr))",gap:m.lg,flex:"0 0 auto"}),o}export function makeTooltip(e){const o=e.ownerDocument.createElement("div");return Object.assign(o.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"50",background:"rgba(30, 32, 40, 0.93)",color:"#f0f0f2",border:"1px solid rgba(255,255,255,0.10)",borderRadius:C.control,padding:`${m.sm} ${m.xl}`,fontSize:S.sub,fontFamily:S.family,maxWidth:"320px",whiteSpace:"normal",overflowWrap:"break-word",boxShadow:N.menu}),e.appendChild(o),o}export function attachChartTip(e,o,n,t){e.removeAttribute("title"),e.textContent?.trim()||(e.ariaLabel=t),e.addEventListener("mouseenter",i=>{n.textContent=t,n.style.display="block",positionChartTooltip(n,o,i.clientX,i.clientY)}),e.addEventListener("mousemove",i=>{n.style.display==="block"&&positionChartTooltip(n,o,i.clientX,i.clientY)}),e.addEventListener("mouseleave",()=>{n.style.display="none"}),e.addEventListener("click",()=>{n.style.display="none"}),e.addEventListener("focus",()=>{n.textContent=t,n.style.display="block";const i=e.getBoundingClientRect();positionChartTooltip(n,o,i.left+i.width/2,i.bottom)}),e.addEventListener("blur",()=>{n.style.display="none"})}export function positionChartTooltip(e,o,n,t){const i=o.getBoundingClientRect(),r=14,l=4;e.style.left="0",e.style.top="0";const s=e.offsetWidth,a=e.offsetHeight;let p=n-i.left+r,c=t-i.top+r;p+s+l>i.width&&(p=n-i.left-r-s),c+a+l>i.height&&(c=t-i.top-r-a),p=Math.max(l,p),c=Math.max(l,c),e.style.left=`${p}px`,e.style.top=`${c}px`}export const LIMIT_LABEL={test:{lo:"Lo limit",hi:"Hi limit"},spec:{lo:"LSL",hi:"USL"}},LIMIT_DASH={test:[3,3],spec:[10,4]},LIMITS_SHOWN_OPTIONS=[{value:"both",label:"Test + spec"},{value:"test",label:"Test limits"},{value:"spec",label:"Spec limits (LSL/USL)"},{value:"none",label:"None"}];function A(e,o){const[n,t]=o==="test"?[e?.limitLow,e?.limitHigh]:[e?.specLow,e?.specHigh],i=[];return n!==void 0&&Number.isFinite(n)&&i.push({value:n,kind:o,end:"lo",label:LIMIT_LABEL[o].lo}),t!==void 0&&Number.isFinite(t)&&i.push({value:t,kind:o,end:"hi",label:LIMIT_LABEL[o].hi}),i}export function hasBothLimitKinds(e){return A(e,"test").length>0&&A(e,"spec").length>0}export function limitLines(e,o="both"){if(o==="none")return[];const n=A(e,"test"),t=A(e,"spec"),i=o==="test"?n:o==="spec"?t:[...n,...t];return i.length?i:[...n,...t]}export function limitExtent(e){if(!e.length)return{};const o=e.map(n=>n.value);return{lo:te(o),hi:ne(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{P as robustFence};export function shouldIncludeLimitsByDefault(e,o,n,t,i=1/3){if(n===void 0&&t===void 0||!Number.isFinite(e)||!Number.isFinite(o))return!1;const r=Math.min(e,n??e),s=Math.max(o,t??o)-r;if(s<=0)return!0;const a=o-e;return a===0?!0:a/s>=i}export function resolveAxisRange(e){const{dataMin:o,dataMax:n,limits:t=[],includeLimits:i,clipOutliers:r,values:l}=e;let s=o,a=n,p=0;if(r&&l?.length){const d=P(l);if(d){const u=Math.max(s,d.lo),w=Math.min(a,d.hi);w>u&&(p=l.filter(v=>Number.isFinite(v)&&(v<u||v>w)).length,s=u,a=w)}}if(i)for(const d of t)s=Math.min(s,d.value),a=Math.max(a,d.value);s===a&&(s-=1,a+=1);const c=[];for(const d of t)d.value<s?c.push({value:d.value,label:d.label,side:"lo"}):d.value>a&&c.push({value:d.value,label:d.label,side:"hi"});return{lo:s,hi:a,offAxis:c,clippedCount:p}}export function limitLabelSide(e,o,n,t,i,r=3){const l=i?-1:1;return(l<0?e-r-o>=n:e+r+o<=t)?l:-l}export function drawOffAxisLimits(e,o,n,t,i,r,l){if(!o.length)return;e.save(),e.fillStyle=i,e.font=`${k(-1)}px system-ui, sans-serif`;const s={lo:0,hi:0},a=k(-1)+2;for(const{value:p,label:c,side:d}of o){const u=s[d]++*a,w=t==="horizontal"?d==="lo"?"\u2190":"\u2192":d==="lo"?"\u2193":"\u2191",v=d==="lo"?`${w} ${c} ${r(p)}`:`${c} ${r(p)} ${w}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=d==="lo"?"left":"right",fillTextOnHalo(e,v,d==="lo"?n.left+2:n.right-2,n.top+2+u,l)):(e.textBaseline=d==="lo"?"bottom":"top",e.textAlign="left",fillTextOnHalo(e,v,n.left+4,d==="lo"?n.bottom-2-u:n.top+2+u,l))}e.restore()}
1
+ import{SHADOW as V,LEADING as G,wireControlHover as B,controlStyle as j,SPACE as w,RADIUS as T,fontPx as F,FONT as L,CLR as f,Z_BASE as oe,menuLayerFor as ie,wireListNavigation as re,MENU_SEARCH_THRESHOLD as U,makeMenuSearchBox as le,markMenuTrigger as N,markNoPrint as P,saveImageBlob as se,openReparentedModal as ae}from"../toolbar.js";import{ICONS as X}from"../icons.js";import{minOf as ce,maxOf as de}from"../../core/utils.js";import{robustFence as K}from"../../stats/math.js";import{fmt as ue,fmtColorbarAxis as pe}from"../../renderer/fmt.js";import{markedTestLabel as fe,DERIVED_LANE_PAD as me}from"../../renderer/testLabel.js";export function openChartExpandModal(e,i,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]"),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=ae([e],{title:i,anchor:n.anchor,ownerDocument:e.ownerDocument,boxSize:he[r],onClosed:()=>{delete e.dataset.wmapExpanded,e.setAttribute("style",o),t&&(t.style.display="flex"),n.onClosed?.()}});if(!l)return delete e.dataset.wmapExpanded,e.setAttribute("style",o),null;if(t&&(t.style.display="none"),r==="rows"){const s=()=>{const a=Number(e.dataset.wmapChartFullH);if(!a)return;const p=l.box.offsetHeight-l.contentWrap.clientHeight;l.setNaturalHeight(`min(90vh, ${Math.ceil(p+a)}px)`)};s(),(e.ownerDocument.defaultView??window).requestAnimationFrame(s)}return l}const he={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,i){const n=e.trim();let t,o,r,l=1;const s=n.match(/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i),a=n.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:\s*[,/]\s*([\d.]+%?))?\s*\)$/i);if(s){const p=s[1].length===3?s[1].split("").map(c=>c+c).join(""):s[1];[t,o,r]=[0,2,4].map(c=>parseInt(p.slice(c,c+2),16)),p.length===8&&(l=parseInt(p.slice(6,8),16)/255)}else if(a)[t,o,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}, ${o}, ${r}, ${+(l*i).toFixed(3)})`}const ge={text:"#333",textMuted:"#66788a",border:"rgba(0,0,0,0.12)",bg:"#fff",bgHover:"#edf0f8",track:"#fafbfc",warnBorder:"#f0c040",accent:"#1a66cc",limitLine:"#b45309"},Y={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(Y)){const o=i.getPropertyValue(`--wmap-${Y[t]}`).trim();n[t]=o||ge[t]}return n.grid=withAlpha(n.border,GRID_ALPHA),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=`${W(e,i)+n}px`;e.style.minHeight!==t&&(e.style.minHeight=t)}function W(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,s=n.getComputedStyle(l);s.display==="none"||s.position==="absolute"||s.position==="fixed"||(o+=l.offsetHeight+(parseFloat(s.marginTop)||0)+(parseFloat(s.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 Z(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-(W(e,i)-l)-(n?Z(i,n):0)}export function fitRowsHeight(e,i,n,t,o=0){if(growCardToFitContent(e,i,o+n),e.dataset.wmapChartFullH=String(Math.ceil(W(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=Z(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}`:ue(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}=pe(e,null,i,"engineering",n);return{tick:t,tickWithUnit:r?l=>`${t(l)} ${r}`:t,unitLabel:o}}const xe=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,s)=>Math.max(l,e.measureText(r(s)).width),0)+xe}}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 captionText(e){return e.split(" \xB7 ").map(i=>i.replace(/[\s·]+$/,"").trim()).filter(i=>i&&!/\b(click|clicks|drag|hover|right-click)\b/i.test(i)&&!/\(.*(click|drag).*\)/i.test(i)).join(" \xB7 ")}const M=(e,i)=>{for(let n=e;n&&n!==i.parentElement;n=n.parentElement)if(n.style.display==="none")return!1;return!0},be=/\s*[▾▼]\s*$/,_=new WeakMap;export function describeCard(e){const i=[],n=e.querySelector("[data-wmap-controls]");if(n){for(const l of n.querySelectorAll("button[aria-haspopup]")){if(!M(l,n))continue;const s=(l.textContent??"").replace(be,"").trim();if(!s)continue;const a=l.closest("label"),c=(a?(a.textContent??"").replace(l.textContent??"","").replace(/[:\s]+$/,"").trim():"")||l.getAttribute("aria-label")||"";i.push(c&&c!==s?`${c}: ${s}`:s)}for(const l of n.querySelectorAll('[role="radiogroup"]')){if(!M(l,n))continue;const a=l.querySelector("input:checked")?.closest("label")?.textContent?.trim();a&&i.push(a)}for(const l of n.querySelectorAll('input[type="checkbox"]:checked')){if(!M(l,n))continue;const s=l.closest("label")?.textContent?.trim();s&&i.push(s)}}const t=[];for(const l of e.querySelectorAll("[data-wmap-caption]")){if(!M(l,e))continue;const s=captionText((l.textContent??"").replace(/\s+/g," ").trim());s&&t.push(s)}const o=[];for(const l of e.querySelectorAll("button[aria-pressed]")){const s=l.querySelector("span"),a=s?.style.backgroundColor||s?.style.borderColor,p=(l.textContent??"").trim();p&&a&&a!=="transparent"&&o.push({label:p,color:a})}for(const l of e.querySelectorAll("[data-wmap-legend] > span")){const s=l.firstElementChild,a=s?.style.backgroundColor||s?.style.borderColor,p=(l.textContent??"").trim();p&&a&&a!=="transparent"&&o.push({label:p,color:a})}const r=_.get(e)?.();return r&&(r.controls&&i.push(...r.controls),r.caption!==void 0&&(t.length=0,t.push(...typeof r.caption=="string"?[r.caption]:r.caption)),r.legend&&(o.length=0,o.push(...r.legend))),{controls:i,captions:t,legend:o}}function ye(e){const i=describeCard(e),n=_.get(e)?.();return{controls:i.controls,caption:i.captions,legend:i.legend.length?i.legend:void 0,colorbar:n?.colorbar}}function J(e){for(const i of e.querySelectorAll("[data-wmap-chart-card]")){const n=i.querySelector(":scope > [data-wmap-print-only]");if(!n)continue;const t=describeCard(i);n.replaceChildren();const o=[t.controls.length?t.controls.join(" \xB7 "):"",...t.captions].filter(Boolean);for(const r of o){const l=e.createElement("div");l.textContent=r,n.appendChild(l)}for(const r of i.querySelectorAll("[data-wmap-caption]"))r.setAttribute("data-wmap-noprint","1")}}const Q=new WeakSet;function we(e){if(Q.has(e))return;Q.add(e);const i=e.createElement("style");i.textContent="@media print{[data-wmap-print-only]{display:block!important}}",e.head.appendChild(i);const n=e.defaultView;n?.addEventListener("beforeprint",()=>J(e));try{n?.matchMedia?.("print").addEventListener?.("change",t=>{t.matches&&J(e)})}catch{}}function Ce(e){const i=e.ownerDocument.defaultView;for(let n=e;n&&i;n=n.parentElement){const t=i.getComputedStyle(n).backgroundColor;if(t&&t!=="transparent"&&!/^rgba\(.*,\s*0\)$/.test(t))return t}return"#ffffff"}export function composeChartPng(e,i){const n=e.ownerDocument,t=n.createElement("canvas"),o=Math.max(1,e.width/(e.clientWidth||e.width)),r=e.width,l=n.createElement("canvas").getContext("2d"),s=12*o,a=(g,y="400")=>`${y} ${g*o}px system-ui, -apple-system, "Segoe UI", sans-serif`,p=(g,y)=>l?(l.font=y,l.measureText(g).width):g.length*7*o,c=[];let d=0;if(i){d=s,c.push({kind:"text",text:i.title,x:s,y:d+14*o,font:a(16,"600"),alpha:1}),d+=24*o;const g=i.decor,y=[];g?.controls?.length&&y.push({text:g.controls.join(" \xB7 "),alpha:1});for(const m of g?.caption===void 0?[]:typeof g.caption=="string"?[g.caption]:g.caption)y.push({text:m,alpha:.75});const x=a(12);for(const{text:m,alpha:C}of y){let b="";for(const E of m.split(" ")){const O=b?`${b} ${E}`:E;b&&p(O,x)>r-2*s?(c.push({kind:"text",text:b,x:s,y:d+11*o,font:x,alpha:C}),d+=17*o,b=E):b=O}b&&(c.push({kind:"text",text:b,x:s,y:d+11*o,font:x,alpha:C}),d+=17*o)}if(g?.legend?.length){d+=4*o;const m=a(12);let C=s;for(const b of g.legend){const E=14*o+p(b.label,m)+14*o;C>s&&C+E>r-s&&(C=s,d+=18*o),c.push({kind:"dot",x:C+5*o,y:d+7*o,r:5*o,color:b.color}),c.push({kind:"text",text:b.label,x:C+14*o,y:d+11*o,font:m,alpha:1}),C+=E}d+=20*o}if(g?.colorbar){d+=4*o;const m=a(12),C=g.colorbar;let b=s;c.push({kind:"text",text:C.label,x:b,y:d+10*o,font:m,alpha:1}),b+=p(C.label,m)+10*o,c.push({kind:"text",text:C.lo,x:b,y:d+10*o,font:m,alpha:.75}),b+=p(C.lo,m)+6*o,c.push({kind:"strip",x:b,y:d+1*o,w:200*o,h:10*o,color:C.color}),b+=206*o,c.push({kind:"text",text:C.hi,x:b,y:d+10*o,font:m,alpha:.75}),d+=20*o}d+=6*o}t.width=r,t.height=Math.ceil(e.height+d);const u=t.getContext("2d");if(u.fillStyle=i?Ce(i.card):"#ffffff",u.fillRect(0,0,t.width,t.height),i){const g=n.defaultView,y=g&&g.getComputedStyle(i.card).color||"#222222";u.textBaseline="alphabetic";for(const x of c)if(x.kind==="text")u.font=x.font,u.fillStyle=y,u.globalAlpha=x.alpha,u.fillText(x.text,x.x,x.y),u.globalAlpha=1;else if(x.kind==="dot")u.fillStyle=x.color,u.beginPath(),u.arc(x.x,x.y,x.r,0,Math.PI*2),u.fill();else for(let m=0;m<x.w;m++)u.fillStyle=x.color(m/Math.max(1,x.w-1)),u.fillRect(x.x+m,x.y,1,x.h)}return u.drawImage(e,0,d),t}export function saveCanvasPng(e,i,n,t){composeChartPng(e,t).toBlob(o=>{o&&se(o,i,n)},"image/png")}const ee="10px";export function chartSwatchCss(e,i="solid"){return["display:inline-block",`width:${ee}`,`height:${ee}`,`border-radius:${T.pill}`,`background:${i==="outline"?"transparent":e}`,`border:1px ${i==="outline"?"dashed":"solid"} ${e}`,"box-sizing:border-box","flex-shrink:0"].join(";")}const Se="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:w.xs,padding:`${w.xs} ${w.md}`,borderRadius:T.pill,border:`1px solid ${f.menuBorder}`,background:"none",cursor:"pointer",fontSize:L.body,color:f.text,whiteSpace:"nowrap"}),B(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?f.iconActive:f.menuBorder,t.style.fontWeight=r?"600":"400",t.style.opacity=l?Se:"1",t.setAttribute("aria-pressed",r?"true":"false")}}}export function cardFrameStyle(){return{background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:T.container,padding:w.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:w.sm,paddingBottom:"6px",marginBottom:w.md,borderBottom:`1px solid ${f.menuBorder}`});const r=t.ownerDocument.createElement("div");r.textContent=e,Object.assign(r.style,{color:f.value,fontSize:L.heading,fontWeight:"600",flex:"1"}),o.appendChild(r);const l=makeTooltip(t),s=t.ownerDocument.createElement("button");s.type="button",s.setAttribute("aria-label","Save as PNG"),s.innerHTML=X.download,Object.assign(s.style,{...j("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:G.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),B(s),attachChartTip(s,t,l,"Save as PNG"),s.addEventListener("click",()=>{const u=t.querySelector("canvas");u&&saveCanvasPng(u,r.textContent||e,i,{card:t,title:r.textContent||e,decor:ye(t)})}),P(s),o.appendChild(s);const a=t.ownerDocument.createElement("button");a.type="button",a.setAttribute("aria-label","Expand"),a.innerHTML=X.expand,Object.assign(a.style,{...j("outlined"),color:f.label,padding:"0",width:"22px",height:"22px",lineHeight:G.none,flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center"}),B(a),attachChartTip(a,t,l,"Expand"),a.dataset.wmapChartExpand="1",a.addEventListener("click",()=>openChartExpandModal(t,r.textContent??e)),P(a),o.appendChild(a),t.appendChild(o);const p=t.ownerDocument.createElement("div");p.dataset.wmapPrintOnly="1",Object.assign(p.style,{display:"none",color:f.label,fontSize:L.body,marginBottom:w.sm}),t.appendChild(p),we(t.ownerDocument);const c=t.ownerDocument.createElement("div");c.dataset.wmapControls="1",Object.assign(c.style,{display:"flex",gap:w.sm,marginBottom:w.md,flexWrap:"wrap",alignItems:"center"}),P(c),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,setPngDecor:u=>{_.set(t,u)}}}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 ${f.menuBorder}`,borderRadius:T.control,overflow:"hidden"});const l=`seg-${Math.random().toString(36).slice(2,9)}`,s=[];return e.forEach(([a,p],c)=>{const d=t.createElement("label");Object.assign(d.style,{display:"inline-flex",alignItems:"center",fontSize:L.body,padding:o?"1px 6px":"3px 10px",cursor:"pointer",userSelect:"none",borderLeft:c>0?`1px solid ${f.menuBorder}`:"none"}),B(d,"bare");const u=t.createElement("input");u.type="radio",u.name=l,u.value=a,u.checked=a===i,Object.assign(u.style,{position:"absolute",opacity:"0",width:"0",height:"0"});const g=()=>{d.style.background=u.checked?f.bgActive:f.menuBg,d.style.color=u.checked?f.iconActive:f.text,d.dataset.on=u.checked?"true":"false"};s.push(g),g(),u.addEventListener("change",()=>{if(u.checked){for(const y of s)y();n(a)}}),d.append(u,t.createTextNode(p)),r.appendChild(d)}),r}export function makeBackButton(e,i=document){const n=i.createElement("button");return n.type="button",n.textContent="\u2190 Back",Object.assign(n.style,{...j("outlined"),background:f.menuBg,color:f.label}),B(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:f.label,fontSize:L.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:s,searchPlaceholder:a="Filter\u2026",hook:p}=t;let c=i;const d=k=>e.find(H=>H.value===k)?.label??"",u=r.createElement("button");u.type="button",p&&(u.dataset.wmapSelect=p),l&&u.setAttribute("aria-label",l),Object.assign(u.style,{fontSize:L.body,padding:`${w.xxs} ${w.sm}`,background:f.menuBg,color:f.text,border:`1px solid ${f.menuBorder}`,borderRadius:T.control,maxWidth:o,textAlign:"left",cursor:"pointer",display:"inline-flex",alignItems:"center",gap:w.sm,justifyContent:"space-between"}),B(u),N(u,!1);const g=r.createElement("span");Object.assign(g.style,{overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});const y=r.createElement("span");y.textContent="\u25BE",y.style.flex="0 0 auto",u.append(g,y);const x=()=>{g.textContent=e.length===0?s??"(none)":d(c)||s||"(none)"};if(x(),e.length===0)return u.disabled=!0,u.style.opacity="0.6",u.style.cursor="default",Object.defineProperty(u,"value",{get:()=>c,set:k=>{c=k,x()}});let m=null;const C=()=>{m&&(m.remove(),m=null,N(u,!1))},b=()=>{C(),u.focus()};function E(){const k=u.getBoundingClientRect(),H=r.defaultView??window,z=Math.max(k.width,220),te=Math.min(k.left,Math.max(4,(H.innerWidth??1/0)-z-4));m=r.createElement("div"),m.setAttribute("role","listbox"),m.setAttribute("aria-label",l??"Options"),Object.assign(m.style,{position:"fixed",top:`${k.bottom+4}px`,left:`${te}px`,background:f.menuBg,border:`1px solid ${f.menuBorder}`,borderRadius:T.control,boxShadow:V.menu,zIndex:oe,minWidth:`${z}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"});const A=[],D=()=>A.filter(h=>h.row.style.display!=="none").map(h=>h.row),I=[],ne=()=>{for(const h of I)h.el.style.display=A.slice(h.first,h.last+1).some(S=>S.row.style.display!=="none")?"":"none"};if(e.length>U){const h=le(S=>{for(const v of A)v.row.style.display=v.label.includes(S)?"":"none";ne()},a,r);Object.assign(h.style,{position:"sticky",top:"0",zIndex:"1",background:f.menuBg}),h.addEventListener("keydown",S=>{if(S.key==="Escape"){b();return}S.key==="ArrowDown"&&(S.preventDefault(),D()[0]?.focus())}),m.appendChild(h)}let q;for(const h of e){if(h.group!==void 0&&h.group!==q){const $=r.createElement("div");$.textContent=h.group,$.setAttribute("role","presentation"),Object.assign($.style,{padding:`${w.sm} ${w.xl} ${w.xxs}`,fontSize:L.sub,color:f.label,fontWeight:"600"}),m.appendChild($),I.push({el:$,first:A.length,last:A.length})}q=h.group;const S=h.value===c,v=r.createElement("div");v.textContent=h.disabled?`${h.label} \u2014 ${h.disabled}`:h.label,v.setAttribute("role","option"),v.setAttribute("aria-selected",S?"true":"false"),h.disabled&&v.setAttribute("aria-disabled","true"),v.tabIndex=-1,Object.assign(v.style,{padding:`${w.sm} ${w.xl}`,fontSize:L.body,cursor:h.disabled?"default":"pointer",color:h.disabled?f.label:S?f.iconActive:f.text,fontWeight:S?"700":"400",opacity:h.disabled?"0.7":"",background:S?f.menuActive:"transparent",whiteSpace:"nowrap"}),v.addEventListener("mouseenter",()=>{!S&&!h.disabled&&(v.style.background=f.menuHover)}),v.addEventListener("mouseleave",()=>{S||(v.style.background="transparent")}),v.addEventListener("click",$=>{$.stopPropagation(),!h.disabled&&(c=h.value,x(),b(),n(h.value))}),m.appendChild(v),A.push({row:v,label:h.label.toLowerCase()}),I.length&&(I[I.length-1].last=A.length-1)}if(re(m,D,b),ie(u).appendChild(m),N(u,!0),e.length<=U){const h=e.findIndex(S=>S.value===c);D()[h>=0?h:0]?.focus()}}u.addEventListener("click",k=>{if(k.stopPropagation(),m){C();return}E()});const O=k=>{if(!u.isConnected){r.removeEventListener("click",O);return}m&&!m.contains(k.target)&&!u.contains(k.target)&&C()};return r.addEventListener("click",O),Object.defineProperty(u,"value",{get:()=>c,set:k=>{c=k,x()}})}export function testOptionLabels(e){const i=e.some(n=>n.derived);return e.map(n=>(i&&!n.derived?me:"")+fe(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((s,a)=>({value:String(e[a].testNumber),label:s})),i!==null?String(i):"",s=>n(Number(s)),{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,l=i?.limits??"both";const s=()=>n({includeLimits:o,clipOutliers:r,limits:l});return{get:()=>({includeLimits:o,clipOutliers:r,limits:l}),sync(a,p,c=!1){e.innerHTML="",c&&e.appendChild(makeLimitsSelect(l,d=>{l=d,s()},t)),p&&e.appendChild(makeToggle("Axis includes limits",a,d=>{o=d,s()},t)),e.appendChild(makeToggle("Clip outliers",r,d=>{r=d,s()},t))},set(a){const p=a.limits??"both";return a.includeLimits===o&&a.clipOutliers===r&&p===l?!1:(o=a.includeLimits,r=a.clipOutliers,l=p,!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(s=>s.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=c=>c??ALL_GROUPS,s=c=>c===ALL_GROUPS?null:c,a=makeLabeledSelect(i,[{value:ALL_GROUPS,label:"All groups"},...e.map(c=>({value:c,label:c}))],l(r),c=>{const d=s(c);d!==r&&(r=d,t(d))},{...o,hook:"group-scope"}),p=a.querySelector('[data-wmap-select="group-scope"]');return{el:a,get:()=>r,set(c){return r===c||c!==null&&!e.includes(c)?!1:(r=c,p&&(p.value=l(c)),!0)}}}export function makeWaferSelect(e,i,n,t={}){const{maxWidth:o="160px",allLabel:r="All wafers",ownerDocument:l=document}=t,s="\0all";return makeListSelect([{value:s,label:r},...e.map((a,p)=>({value:String(p),label:a.label??`#${p}`}))],i===null?s:String(i),a=>n(a===s?null:Number(a)),{maxWidth:o,ownerDocument:l,ariaLabel:"Wafer",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:w.xs,fontSize:L.body,color:f.label,cursor:"pointer",userSelect:"none"}),B(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:s=document}=o,a=s.createElement("label");a.textContent=e,Object.assign(a.style,{color:f.label,fontSize:L.body,display:"flex",alignItems:"center",gap:w.xs});const p=makeListSelect(i,n,t,{maxWidth:r,ownerDocument:s,hook:l,ariaLabel:e.replace(/:\s*$/,"")});return a.appendChild(p),a}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:w.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:T.control,padding:`${w.sm} ${w.xl}`,fontSize:L.sub,fontFamily:L.family,maxWidth:"320px",whiteSpace:"normal",overflowWrap:"break-word",boxShadow:V.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 s=e.offsetWidth,a=e.offsetHeight;let p=n-o.left+r,c=t-o.top+r;p+s+l>o.width&&(p=n-o.left-r-s),c+a+l>o.height&&(c=t-o.top-r-a),p=Math.max(l,p),c=Math.max(l,c),e.style.left=`${p}px`,e.style.top=`${c}px`}export const LIMIT_LABEL={test:{lo:"Lo limit",hi:"Hi limit"},spec:{lo:"LSL",hi:"USL"}},LIMIT_DASH={test:[3,3],spec:[10,4]},LIMITS_SHOWN_OPTIONS=[{value:"both",label:"Test + spec"},{value:"test",label:"Test limits"},{value:"spec",label:"Spec limits (LSL/USL)"},{value:"none",label:"None"}];function R(e,i){const[n,t]=i==="test"?[e?.limitLow,e?.limitHigh]:[e?.specLow,e?.specHigh],o=[];return n!==void 0&&Number.isFinite(n)&&o.push({value:n,kind:i,end:"lo",label:LIMIT_LABEL[i].lo}),t!==void 0&&Number.isFinite(t)&&o.push({value:t,kind:i,end:"hi",label:LIMIT_LABEL[i].hi}),o}export function hasBothLimitKinds(e){return R(e,"test").length>0&&R(e,"spec").length>0}export function limitLines(e,i="both"){if(i==="none")return[];const n=R(e,"test"),t=R(e,"spec"),o=i==="test"?n:i==="spec"?t:[...n,...t];return o.length?o:[...n,...t]}export function limitExtent(e){if(!e.length)return{};const i=e.map(n=>n.value);return{lo:ce(i),hi:de(i)}}export function stackLabelRows(e,i=4){const n=e.map((r,l)=>({...r,i:l})).sort((r,l)=>r.start-l.start),t=[],o=new Array(e.length).fill(0);for(const r of n){let l=t.findIndex(s=>r.start>=s+i);l===-1?(l=t.length,t.push(r.end)):t[l]=r.end,o[r.i]=l}return o}export function fillTextOnHalo(e,i,n,t,o){if(o){const r=e.measureText(i),l=2,s=e.fillStyle;e.fillStyle=o,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(i,n,t)}export function strokeLimitLine(e,i,n,t,o,r,l){e.save(),e.strokeStyle=n,e.lineWidth=1,e.setLineDash(LIMIT_DASH[i.kind]),e.beginPath(),e.moveTo(t,o),e.lineTo(r,l),e.stroke(),e.restore()}export function makeLimitsSelect(e,i,n=document){return makeLabeledSelect("Limits:",LIMITS_SHOWN_OPTIONS,e,t=>i(t),{maxWidth:"170px",hook:"limits-shown",ownerDocument:n})}export{K as robustFence};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),s=Math.max(i,t??i)-r;if(s<=0)return!0;const a=i-e;return a===0?!0:a/s>=o}export function resolveAxisRange(e){const{dataMin:i,dataMax:n,limits:t=[],includeLimits:o,clipOutliers:r,values:l}=e;let s=i,a=n,p=0;if(r&&l?.length){const d=K(l);if(d){const u=Math.max(s,d.lo),g=Math.min(a,d.hi);g>u&&(p=l.filter(y=>Number.isFinite(y)&&(y<u||y>g)).length,s=u,a=g)}}if(o)for(const d of t)s=Math.min(s,d.value),a=Math.max(a,d.value);s===a&&(s-=1,a+=1);const c=[];for(const d of t)d.value<s?c.push({value:d.value,label:d.label,side:"lo"}):d.value>a&&c.push({value:d.value,label:d.label,side:"hi"});return{lo:s,hi:a,offAxis:c,clippedCount:p}}export function limitLabelSide(e,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,l){if(!i.length)return;e.save(),e.fillStyle=o,e.font=`${F(-1)}px system-ui, sans-serif`;const s={lo:0,hi:0},a=F(-1)+2;for(const{value:p,label:c,side:d}of i){const u=s[d]++*a,g=t==="horizontal"?d==="lo"?"\u2190":"\u2192":d==="lo"?"\u2193":"\u2191",y=d==="lo"?`${g} ${c} ${r(p)}`:`${c} ${r(p)} ${g}`;t==="horizontal"?(e.textBaseline="top",e.textAlign=d==="lo"?"left":"right",fillTextOnHalo(e,y,d==="lo"?n.left+2:n.right-2,n.top+2+u,l)):(e.textBaseline=d==="lo"?"bottom":"top",e.textAlign="left",fillTextOnHalo(e,y,n.left+4,d==="lo"?n.bottom-2-u:n.top+2+u,l))}e.restore()}
@@ -1,2 +1 @@
1
- import{buildCorrelationMatrix as vt,filterCorrelationMatrix as Lt,correlationSampleNote as It}from"../../stats/correlation.js";import{csvField as K,maxOf as $t}from"../../core/utils.js";import{CORRELATION_POSITIVE as ct,CORRELATION_NEGATIVE as dt}from"./palette.js";import{buildFacetTable as St}from"../../stats/facets.js";import{wireControlHover as Tt,controlStyle as Dt,SPACE as v,RADIUS as st,fontPx as ut,FONT as j,CLR as b,saveTextFile as Rt}from"../toolbar.js";import{attachChartTip as At,cardShell as kt,setChartGrow as _t,isExpandedCard as Bt,bodyRoom as Nt,observeResize as Ot,makeTooltip as Wt,positionChartTooltip as Ht,makeWaferSelect as jt,renderEmptyState as Xt,resolveChartCanvasColors as Ft,prepareCanvas as zt,chartDpr as ft}from"./chartShell.js";import{escHtml as q}from"../../core/utils.js";import{unmarkedLabel as mt,derivedCsvCell as pt,DERIVED_CSV_HEADER as ht,DERIVED_MARK as Vt,DERIVED_KEY as Yt}from"../../renderer/testLabel.js";const xt=5,bt=100,gt=20;function lt(g){const d=g.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)/);if(d)return[+d[1],+d[2],+d[3]];const p=g.trim().replace("#","");return p.length===6?[parseInt(p.slice(0,2),16),parseInt(p.slice(2,4),16),parseInt(p.slice(4,6),16)]:null}function Pt(g){const d=lt(g);return d?(d[0]*299+d[1]*587+d[2]*114)/1e3>140?"#111":"#fff":"#000"}function Gt(g,d,p){const N=lt(g);if(!N)return g;const J=Math.round(d[0]+(N[0]-d[0])*p),O=Math.round(d[1]+(N[1]-d[1])*p),l=Math.round(d[2]+(N[2]-d[2])*p);return`rgb(${J},${O},${l})`}export function renderCorrelationPanel(g){const{title:d="Test correlation matrix",items:p,testDefs:N,onSaveImage:J,onSelectPair:O}=g,{card:l,body:L,controlsRow:Q}=kt(d,J,g.ownerDocument);_t(l,"square"),L.style.overflowX="auto",l.style.alignSelf="start";let V=gt,X=null;const F=l.ownerDocument.createElement("label");F.textContent="Max tests:",Object.assign(F.style,{color:b.label,fontSize:j.body,display:"flex",alignItems:"center",gap:v.xs});const w=l.ownerDocument.createElement("input");w.type="number",w.min=String(xt),w.max=String(bt),w.value=String(V),Object.assign(w.style,{width:"52px",fontSize:j.body,padding:`${v.xxs} ${v.xs}`,background:b.menuBg,color:b.value,border:`1px solid ${b.menuBorder}`,borderRadius:st.control}),w.addEventListener("change",()=>{const e=Math.max(xt,Math.min(bt,parseInt(w.value,10)||gt));w.value=String(e),e!==V&&(V=e,tt())}),F.appendChild(w),Q.appendChild(F),p.length>1&&Q.appendChild(jt(p,X,e=>{X=e,tt()},{ownerDocument:l.ownerDocument}));function yt(){return X!==null?p[X]?[p[X]]:[]:p}if(g.onSaveText){const e=l.ownerDocument.createElement("button");e.type="button",e.textContent="Correlation CSV",Object.assign(e.style,{...Dt("outlined"),color:b.text}),Tt(e),e.addEventListener("click",()=>{const o=rt;if(!o)return;const a=o.tests.some(f=>f.derived),u=["Test X,Test X number,Test Y,Test Y number,r,n"+(a?`,Test X ${ht.toLowerCase()},Test Y ${ht.toLowerCase()}`:"")];for(let f=0;f<o.tests.length;f++)for(let i=f+1;i<o.tests.length;i++){const y=o.cells.find(h=>h.xIndex===i&&h.yIndex===f);y&&u.push([K(mt(o.tests[i].label)),String(o.tests[i].testNumber),K(mt(o.tests[f].label)),String(o.tests[f].testNumber),y.r===null?"":y.r.toFixed(6),String(y.n),...a?[K(pt(o.tests[i])),K(pt(o.tests[f]))]:[]].join(","))}Rt(u.join(`
2
- `),"test-correlation.csv","text/csv",g.onSaveText)}),Q.appendChild(e)}const k=l.ownerDocument.createElement("div");Object.assign(k.style,{display:"flex",flexDirection:"column",gap:v.xs,marginBottom:v.sm}),l.insertBefore(k,L);const I=Wt(l);At(F,l,I,"Cap on how many tests the matrix includes (strongest correlations kept first)");let Z=()=>{};function Et(e,o,a,u,f,i,y,h){k.innerHTML="";const _=It(y);if(_){const x=l.ownerDocument.createElement("div");x.textContent=`${_} \u2014 r is an estimate, not the whole population.`,Object.assign(x.style,{color:b.label,fontSize:j.body}),k.appendChild(x)}if(f.length>0){const x=l.ownerDocument.createElement("div");x.textContent=`\u26A0 Mixed ${f.join(", ")} within this set \u2014 correlations may be misleading (Simpson's paradox). Use Group by, or the Wafer picker, to narrow to a like-for-like set.`,Object.assign(x.style,{color:b.warnText,background:b.warnBg,border:`1px solid ${b.warnBorder}`,borderRadius:st.control,padding:`${v.xs} ${v.md}`,fontSize:j.body}),k.appendChild(x)}const M=l.ownerDocument.createElement("span");Object.assign(M.style,{display:"inline-flex",alignItems:"center",gap:v.sm,color:b.label,fontSize:j.body,flexWrap:"wrap"});const W=l.ownerDocument.createElement("span");W.textContent=(i!==null?`Pearson r \xB7 n \u2248 ${i.toLocaleString()} dies per pair \xB7 click a cell to view that pair in scatter \xB7`:"Pearson r \xB7 click a cell to view that pair in scatter \xB7")+(h?` ${Vt} ${Yt} \xB7`:""),M.appendChild(W);const r=l.ownerDocument.createElement("span");Object.assign(r.style,{display:"inline-flex",alignItems:"center",gap:v.xs});const $=l.ownerDocument.createElement("span");$.textContent="\u22121";const Y=l.ownerDocument.createElement("span");Object.assign(Y.style,{display:"inline-block",width:"64px",height:"8px",borderRadius:st.control,border:`1px solid ${b.menuBorder}`,background:`linear-gradient(to right, ${dt}, ${b.menuBg}, ${ct})`});const z=l.ownerDocument.createElement("span");z.textContent="+1",r.append($,Y,z),M.appendChild(r),k.appendChild(M);const S=l.ownerDocument.createElement("span");if(Object.assign(S.style,{color:b.value,fontSize:j.body,fontWeight:"500"}),e===0&&o===0)S.textContent=u?`No significant correlations found \u2014 strongest pair: ${u.xLabel} \u2194 ${u.yLabel} (r = ${u.r.toFixed(2)})`:"No significant correlations found";else{const x=[];e>0&&x.push(`${e} strong (|r| \u2265 0.7)`),o>0&&x.push(`${o} moderate (0.4\u20130.7)`);const P=e+o;let s=x.join(", ")+` pair${P!==1?"s":""} found`;a>0&&(s+=` \xB7 ${a} weak pair${a!==1?"s":""} not shown`),S.textContent=s}k.appendChild(S)}function Mt(e){if(L.innerHTML="",e.tests.length<2)return Xt(L,"Need at least two parametric tests for a correlation matrix."),()=>{};const o=l.ownerDocument.createElement("canvas");o.style.display="block",o.style.cursor="default",L.appendChild(o);const a=e.tests.length,u=s=>s.label.split(" (#")[0],f=Math.min(14,$t(e.tests.map(s=>u(s).length))),i=f*6.5+8,y=10,h=Math.round(y*6.5*Math.sin(Math.PI/4))+14,_=14,M=28,W=44;let r=-1,$=-1;const Y=96;function z(s){const c=Math.max(0,s-i),m=Math.floor(c/a);if(!Bt(l))return Math.max(_,Math.min(W,m));const n=Math.floor((Nt(l,L,o)-h-4)/a);return Math.max(_,Math.min(Y,m,n))}const S=new Map;for(const s of e.cells){let c=S.get(s.yIndex);c||(c=[],S.set(s.yIndex,c)),c.push(s)}function x(){const s=Ft(l),c=lt(s.bg)??[255,255,255],m=L.clientWidth,n=z(m),H=n*a,T=h+n*a+4,D=i+H,R=zt(o,l,D,T);if(!R)return;const{ctx:t}=R;t.font=`${ut(-1)}px system-ui, sans-serif`,t.fillStyle=s.text,e.tests.forEach((A,E)=>{const C=u(A),B=C.length>y?`${C.slice(0,y-1)}\u2026`:C,et=i+E*n+n/2;t.save(),t.translate(et,h-4),t.rotate(-Math.PI/4),t.textAlign="left",t.textBaseline="bottom",t.fillStyle=E===r?s.text:s.textMuted,t.fillText(B,0,0),t.restore()}),e.tests.forEach((A,E)=>{const C=u(A),B=h+E*n,et=B+n/2;t.font=`${ut(-1)}px system-ui, sans-serif`,t.textAlign="right",t.textBaseline="middle",t.fillStyle=E===$?s.text:s.textMuted,t.fillText(C.length>f?`${C.slice(0,f-1)}\u2026`:C,i-4,et),(S.get(E)??[]).forEach(it=>{const nt=it.xIndex,G=i+nt*n,wt=nt===r&&E===$,at=nt===E,U=it.r;if(U===null){t.fillStyle=s.border,t.globalAlpha=.4,t.fillRect(G+1,B+1,n-2,n-2),t.globalAlpha=1;return}let ot=s.bgHover;at?t.fillStyle=s.bgHover:(ot=Gt(U>=0?ct:dt,c,Math.abs(U)),t.fillStyle=ot),t.fillRect(G+1,B+1,n-2,n-2),n>=M&&!at&&(t.font=`${Math.min(10,n*.35)}px system-ui, sans-serif`,t.fillStyle=Pt(ot),t.textAlign="center",t.textBaseline="middle",t.fillText(U.toFixed(2),G+n/2,B+n/2)),wt&&(t.strokeStyle=s.text,t.lineWidth=2,t.strokeRect(G+1,B+1,n-2,n-2),t.lineWidth=1)})}),t.strokeStyle=s.bg,t.lineWidth=1;for(let A=0;A<=a;A++){const E=i+A*n;t.beginPath(),t.moveTo(E,h),t.lineTo(E,h+a*n),t.stroke();const C=h+A*n;t.beginPath(),t.moveTo(i,C),t.lineTo(i+a*n,C),t.stroke()}}function P(s){const c=o.getBoundingClientRect(),m=L.clientWidth,n=z(m),H=(s.clientX-c.left)*(o.width/ft(o)/c.width),T=(s.clientY-c.top)*(o.height/ft(o)/c.height),D=Math.floor((H-i)/n),R=Math.floor((T-h)/n);return D>=0&&D<a&&R>=0&&R<a?{xi:D,yi:R}:null}return o.addEventListener("mousemove",s=>{const c=P(s);if(!c){I.style.display="none",o.style.cursor="default";return}const{xi:m,yi:n}=c,H=m===n;o.style.cursor=H||!O?"default":"pointer";const T=e.cells.find(t=>t.xIndex===m&&t.yIndex===n),D=e.tests[m].label,R=e.tests[n].label;H?I.innerHTML=`<strong>${q(D)}</strong>`:T?.r!==null&&T?.r!==void 0?I.innerHTML=`<strong>${q(u(e.tests[n]))}</strong> (#${e.tests[n].testNumber}) vs <strong>${q(u(e.tests[m]))}</strong> (#${e.tests[m].testNumber})<br>r = ${T.r.toFixed(4)} \xB7 n = ${T.n.toLocaleString()}${O?"<br><em>click to view in scatter</em>":""}`:I.innerHTML=`${q(`${R} vs ${D}`)}<br><em>insufficient data</em>`,I.style.display="block",Ht(I,l,s.clientX,s.clientY)}),o.addEventListener("mouseleave",()=>{I.style.display="none"}),o.addEventListener("click",s=>{const c=P(s);if(!c||!O)return;const{xi:m,yi:n}=c;m!==n&&(r=m,$=n,x(),O(e.tests[m].testNumber,e.tests[n].testNumber))}),x}let rt=null;function tt(){const e=yt(),o=e.flatMap(r=>r.dies??[]),a=vt(o,N),{matrix:u,strongPairs:f,moderatePairs:i,hiddenWeakPairs:y,strongestPair:h}=Lt(a,{minTests:6,maxTests:V}),_=St(e,{facetableOnly:!0}).filter(r=>r.splittable).map(r=>r.label),M=u.cells.filter(r=>r.xIndex!==r.yIndex).map(r=>r.n).sort((r,$)=>r-$),W=M.length?M[Math.floor(M.length/2)]:null;rt=u,Et(f,i,y,h,_,W,a.sample,u.tests.some(r=>r.derived)),Z=Mt(u),Z()}const Ct=Ot(l,()=>Z());return tt(),{card:l,destroy:()=>Ct.disconnect()}}
1
+ import{buildCorrelationMatrix as vt,filterCorrelationMatrix as It,correlationSampleNote as $t}from"../../stats/correlation.js";import{maxOf as Tt}from"../../core/utils.js";import{CORRELATION_POSITIVE as st,CORRELATION_NEGATIVE as lt}from"./palette.js";import{buildFacetTable as St}from"../../stats/facets.js";import{wireControlHover as Dt,controlStyle as Rt,SPACE as v,RADIUS as rt,fontPx as dt,FONT as X,CLR as g}from"../toolbar.js";import{exportCsv as At}from"../tableExport.js";import{attachChartTip as kt,cardShell as _t,setChartGrow as Bt,isExpandedCard as Nt,bodyRoom as Ot,observeResize as Wt,makeTooltip as Ht,positionChartTooltip as Xt,makeWaferSelect as jt,renderEmptyState as zt,resolveChartCanvasColors as Ft,prepareCanvas as Vt,chartDpr as ut}from"./chartShell.js";import{escHtml as K}from"../../core/utils.js";import{unmarkedLabel as mt,derivedCsvCell as pt,DERIVED_CSV_HEADER as ft,DERIVED_MARK as Pt,DERIVED_KEY as Yt}from"../../renderer/testLabel.js";const ht=5,xt=100,gt=20;function q(b){const d=b.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)/);if(d)return[+d[1],+d[2],+d[3]];const h=b.trim().replace("#","");return h.length===6?[parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16)]:null}function Gt(b){const d=q(b);return d?(d[0]*299+d[1]*587+d[2]*114)/1e3>140?"#111":"#fff":"#000"}function bt(b,d,h){const O=q(b);if(!O)return b;const J=Math.round(d[0]+(O[0]-d[0])*h),W=Math.round(d[1]+(O[1]-d[1])*h),r=Math.round(d[2]+(O[2]-d[2])*h);return`rgb(${J},${W},${r})`}export function renderCorrelationPanel(b){const{title:d="Test correlation matrix",items:h,testDefs:O,onSaveImage:J,onSelectPair:W}=b,{card:r,body:I,controlsRow:Q,setPngDecor:yt}=_t(d,J,b.ownerDocument);yt(()=>{const e=q(r.ownerDocument.defaultView?.getComputedStyle(r).backgroundColor??"")??[255,255,255];return{colorbar:{label:"Pearson r",lo:"\u22121",hi:"+1",color:o=>bt(o<.5?lt:st,e,Math.abs(2*o-1))}}}),Bt(r,"square"),I.style.overflowX="auto",r.style.alignSelf="start";let V=gt,j=null;const z=r.ownerDocument.createElement("label");z.textContent="Max tests:",Object.assign(z.style,{color:g.label,fontSize:X.body,display:"flex",alignItems:"center",gap:v.xs});const E=r.ownerDocument.createElement("input");E.type="number",E.min=String(ht),E.max=String(xt),E.value=String(V),Object.assign(E.style,{width:"52px",fontSize:X.body,padding:`${v.xxs} ${v.xs}`,background:g.menuBg,color:g.value,border:`1px solid ${g.menuBorder}`,borderRadius:rt.control}),E.addEventListener("change",()=>{const e=Math.max(ht,Math.min(xt,parseInt(E.value,10)||gt));E.value=String(e),e!==V&&(V=e,tt())}),z.appendChild(E),Q.appendChild(z),h.length>1&&Q.appendChild(jt(h,j,e=>{j=e,tt()},{ownerDocument:r.ownerDocument}));function Ct(){return j!==null?h[j]?[h[j]]:[]:h}if(b.onSaveText){const e=r.ownerDocument.createElement("button");e.type="button",e.textContent="Correlation CSV",Object.assign(e.style,{...Rt("outlined"),color:g.text}),Dt(e),e.addEventListener("click",()=>{const o=it;if(!o)return;const u=o.tests.some(s=>s.derived),m=[];for(let s=0;s<o.tests.length;s++)for(let a=s+1;a<o.tests.length;a++){const y=o.cells.find(x=>x.xIndex===a&&x.yIndex===s);y&&m.push({x:o.tests[a],y:o.tests[s],r:y.r,n:y.n})}At([{header:"Test X",get:s=>mt(s.x.label)},{header:"Test X number",get:s=>s.x.testNumber},{header:"Test Y",get:s=>mt(s.y.label)},{header:"Test Y number",get:s=>s.y.testNumber},{header:"r",get:s=>s.r===null?"":s.r.toFixed(6)},{header:"n",get:s=>s.n},...u?[{header:`Test X ${ft.toLowerCase()}`,get:s=>pt(s.x)},{header:`Test Y ${ft.toLowerCase()}`,get:s=>pt(s.y)}]:[]],m,"test-correlation.csv",b.onSaveText)}),Q.appendChild(e)}const k=r.ownerDocument.createElement("div");Object.assign(k.style,{display:"flex",flexDirection:"column",gap:v.xs,marginBottom:v.sm}),r.insertBefore(k,I);const $=Ht(r);kt(z,r,$,"Cap on how many tests the matrix includes (strongest correlations kept first)");let Z=()=>{};function wt(e,o,u,m,s,a,y,x){k.innerHTML="";const _=$t(y);if(_){const p=r.ownerDocument.createElement("div");p.dataset.wmapCaption="1",p.textContent=`${_} \u2014 r is an estimate, not the whole population.`,Object.assign(p.style,{color:g.label,fontSize:X.body}),k.appendChild(p)}if(s.length>0){const p=r.ownerDocument.createElement("div");p.dataset.wmapCaption="1",p.textContent=`\u26A0 Mixed ${s.join(", ")} within this set \u2014 correlations may be misleading (Simpson's paradox). Use Group by, or the Wafer picker, to narrow to a like-for-like set.`,Object.assign(p.style,{color:g.warnText,background:g.warnBg,border:`1px solid ${g.warnBorder}`,borderRadius:rt.control,padding:`${v.xs} ${v.md}`,fontSize:X.body}),k.appendChild(p)}const w=r.ownerDocument.createElement("span");Object.assign(w.style,{display:"inline-flex",alignItems:"center",gap:v.sm,color:g.label,fontSize:X.body,flexWrap:"wrap"});const B=r.ownerDocument.createElement("span");B.dataset.wmapCaption="1",B.textContent=(a!==null?`Pearson r \xB7 n \u2248 ${a.toLocaleString()} dies per pair \xB7 click a cell to view that pair in scatter \xB7`:"Pearson r \xB7 click a cell to view that pair in scatter \xB7")+(x?` ${Pt} ${Yt} \xB7`:""),w.appendChild(B);const i=r.ownerDocument.createElement("span");Object.assign(i.style,{display:"inline-flex",alignItems:"center",gap:v.xs});const T=r.ownerDocument.createElement("span");T.textContent="\u22121";const P=r.ownerDocument.createElement("span");Object.assign(P.style,{display:"inline-block",width:"64px",height:"8px",borderRadius:rt.control,border:`1px solid ${g.menuBorder}`,background:`linear-gradient(to right, ${lt}, ${g.menuBg}, ${st})`});const F=r.ownerDocument.createElement("span");F.textContent="+1",i.append(T,P,F),w.appendChild(i),k.appendChild(w);const L=r.ownerDocument.createElement("span");if(L.dataset.wmapCaption="1",Object.assign(L.style,{color:g.value,fontSize:X.body,fontWeight:"500"}),e===0&&o===0)L.textContent=m?`No significant correlations found \u2014 strongest pair: ${m.xLabel} \u2194 ${m.yLabel} (r = ${m.r.toFixed(2)})`:"No significant correlations found";else{const p=[];e>0&&p.push(`${e} strong (|r| \u2265 0.7)`),o>0&&p.push(`${o} moderate (0.4\u20130.7)`);const Y=e+o;let l=p.join(", ")+` pair${Y!==1?"s":""} found`;u>0&&(l+=` \xB7 ${u} weak pair${u!==1?"s":""} not shown`),L.textContent=l}k.appendChild(L)}function Mt(e){if(I.innerHTML="",e.tests.length<2)return zt(I,"Need at least two parametric tests for a correlation matrix."),()=>{};const o=r.ownerDocument.createElement("canvas");o.style.display="block",o.style.cursor="default",I.appendChild(o);const u=e.tests.length,m=l=>l.label.split(" (#")[0],s=Math.min(14,Tt(e.tests.map(l=>m(l).length))),a=s*6.5+8,y=10,x=Math.round(y*6.5*Math.sin(Math.PI/4))+14,_=14,w=28,B=44;let i=-1,T=-1;const P=96;function F(l){const c=Math.max(0,l-a),f=Math.floor(c/u);if(!Nt(r))return Math.max(_,Math.min(B,f));const n=Math.floor((Ot(r,I,o)-x-4)/u);return Math.max(_,Math.min(P,f,n))}const L=new Map;for(const l of e.cells){let c=L.get(l.yIndex);c||(c=[],L.set(l.yIndex,c)),c.push(l)}function p(){const l=Ft(r),c=q(l.bg)??[255,255,255],f=I.clientWidth,n=F(f),H=n*u,S=x+n*u+4,D=a+H,R=Vt(o,r,D,S);if(!R)return;const{ctx:t}=R;t.font=`${dt(-1)}px system-ui, sans-serif`,t.fillStyle=l.text,e.tests.forEach((A,C)=>{const M=m(A),N=M.length>y?`${M.slice(0,y-1)}\u2026`:M,et=a+C*n+n/2;t.save(),t.translate(et,x-4),t.rotate(-Math.PI/4),t.textAlign="left",t.textBaseline="bottom",t.fillStyle=C===i?l.text:l.textMuted,t.fillText(N,0,0),t.restore()}),e.tests.forEach((A,C)=>{const M=m(A),N=x+C*n,et=N+n/2;t.font=`${dt(-1)}px system-ui, sans-serif`,t.textAlign="right",t.textBaseline="middle",t.fillStyle=C===T?l.text:l.textMuted,t.fillText(M.length>s?`${M.slice(0,s-1)}\u2026`:M,a-4,et),(L.get(C)??[]).forEach(at=>{const nt=at.xIndex,G=a+nt*n,Lt=nt===i&&C===T,ct=nt===C,U=at.r;if(U===null){t.fillStyle=l.border,t.globalAlpha=.4,t.fillRect(G+1,N+1,n-2,n-2),t.globalAlpha=1;return}let ot=l.bgHover;ct?t.fillStyle=l.bgHover:(ot=bt(U>=0?st:lt,c,Math.abs(U)),t.fillStyle=ot),t.fillRect(G+1,N+1,n-2,n-2),n>=w&&!ct&&(t.font=`${Math.min(10,n*.35)}px system-ui, sans-serif`,t.fillStyle=Gt(ot),t.textAlign="center",t.textBaseline="middle",t.fillText(U.toFixed(2),G+n/2,N+n/2)),Lt&&(t.strokeStyle=l.text,t.lineWidth=2,t.strokeRect(G+1,N+1,n-2,n-2),t.lineWidth=1)})}),t.strokeStyle=l.bg,t.lineWidth=1;for(let A=0;A<=u;A++){const C=a+A*n;t.beginPath(),t.moveTo(C,x),t.lineTo(C,x+u*n),t.stroke();const M=x+A*n;t.beginPath(),t.moveTo(a,M),t.lineTo(a+u*n,M),t.stroke()}}function Y(l){const c=o.getBoundingClientRect(),f=I.clientWidth,n=F(f),H=(l.clientX-c.left)*(o.width/ut(o)/c.width),S=(l.clientY-c.top)*(o.height/ut(o)/c.height),D=Math.floor((H-a)/n),R=Math.floor((S-x)/n);return D>=0&&D<u&&R>=0&&R<u?{xi:D,yi:R}:null}return o.addEventListener("mousemove",l=>{const c=Y(l);if(!c){$.style.display="none",o.style.cursor="default";return}const{xi:f,yi:n}=c,H=f===n;o.style.cursor=H||!W?"default":"pointer";const S=e.cells.find(t=>t.xIndex===f&&t.yIndex===n),D=e.tests[f].label,R=e.tests[n].label;H?$.innerHTML=`<strong>${K(D)}</strong>`:S?.r!==null&&S?.r!==void 0?$.innerHTML=`<strong>${K(m(e.tests[n]))}</strong> (#${e.tests[n].testNumber}) vs <strong>${K(m(e.tests[f]))}</strong> (#${e.tests[f].testNumber})<br>r = ${S.r.toFixed(4)} \xB7 n = ${S.n.toLocaleString()}${W?"<br><em>click to view in scatter</em>":""}`:$.innerHTML=`${K(`${R} vs ${D}`)}<br><em>insufficient data</em>`,$.style.display="block",Xt($,r,l.clientX,l.clientY)}),o.addEventListener("mouseleave",()=>{$.style.display="none"}),o.addEventListener("click",l=>{const c=Y(l);if(!c||!W)return;const{xi:f,yi:n}=c;f!==n&&(i=f,T=n,p(),W(e.tests[f].testNumber,e.tests[n].testNumber))}),p}let it=null;function tt(){const e=Ct(),o=e.flatMap(i=>i.dies??[]),u=vt(o,O),{matrix:m,strongPairs:s,moderatePairs:a,hiddenWeakPairs:y,strongestPair:x}=It(u,{minTests:6,maxTests:V}),_=St(e,{facetableOnly:!0}).filter(i=>i.splittable).map(i=>i.label),w=m.cells.filter(i=>i.xIndex!==i.yIndex).map(i=>i.n).sort((i,T)=>i-T),B=w.length?w[Math.floor(w.length/2)]:null;it=m,wt(s,a,y,x,_,B,u.sample,m.tests.some(i=>i.derived)),Z=Mt(m),Z()}const Et=Wt(r,()=>Z());return tt(),{card:r,destroy:()=>Et.disconnect()}}
@@ -35,6 +35,8 @@ export interface GroupedBarPlotOptions {
35
35
  /** Tooltip HTML for a hovered sub-bar. */
36
36
  /** HTML for the hover tooltip. Escape every label with `escHtml` (core/utils.ts): they come from input files. */
37
37
  tooltipHtml: (rowIndex: number, groupIndex: number) => string;
38
+ /** Click a sub-bar: called with its row, its group and the click, so the host can open a menu there. */
39
+ onRowClick?: (rowIndex: number, groupIndex: number, e: MouseEvent) => void;
38
40
  }
39
41
  export interface GroupedBarPlotHandle {
40
42
  /** Redraw — call when the theme changes or data is replaced. */
@@ -1 +1 @@
1
- import{categorical as Y}from"./palette.js";import{SPACE as E,FONT as q,CLR as J}from"../toolbar.js";import{observeResize as K,positionChartTooltip as Q,fitRowsHeight as Z,setChartGrow as tt,prepareCanvas as et,chartSwatchCss as nt,PADDING as c,VALUE_WIDTH as D}from"./chartShell.js";const lt=8,w=1,a=14,ot=8;export function renderGroupedBarPlot(h,C,p,U){const{rows:v,groups:L,labelWidth:T,tooltipHtml:$,maxVisibleRows:j=ot,showLegend:k=!0,maxLabelChars:H,emptyBarText:R,colorOf:B=Y}=U,f=h.ownerDocument;tt(h,"rows");const A=Math.max(1,L.length),M=A*a+(A-1)*w,u=M+lt;let W=0;if(k){const e=f.createElement("div");Object.assign(e.style,{display:"flex",flexWrap:"wrap",gap:`${E.xs} ${E.xl}`,marginBottom:E.xs}),L.forEach((l,n)=>{const t=f.createElement("span");Object.assign(t.style,{display:"inline-flex",alignItems:"center",gap:E.sm,fontSize:q.body,color:J.text});const o=f.createElement("span");o.style.cssText=nt(B(n));const i=f.createElement("span");i.textContent=l,t.append(o,i),e.appendChild(t)}),C.appendChild(e),W=e.offsetHeight}const d=f.createElement("div"),G=c*2+Math.min(v.length,j)*u;Object.assign(d.style,{overflowX:"hidden",overflowY:"auto",minHeight:"0",flex:"1",maxHeight:`${G}px`,scrollbarGutter:"stable"}),C.appendChild(d);const s=f.createElement("canvas");s.style.display="block",s.style.cursor="default",d.appendChild(s);let r=null;function _(){const e=c+T,l=Math.max(10,s.clientWidth-e-D-c);return{barX:e,barMaxWidth:l}}function z(e,l){const n=Math.floor((l-c)/u);if(n<0||n>=v.length)return null;const t=l-c-n*u,o=Math.floor(t/(a+w));if(o<0||o>=A)return null;const{barX:i,barMaxWidth:m}=_();return e<i||e>i+m?null:{row:n,group:o}}function g(){const e=c*2+v.length*u;d.style.maxHeight=`${Z(h,C,G,e,W)}px`;const l=d.clientWidth,n=et(s,h,l,e);if(!n)return;const{ctx:t,theme:o}=n,{barX:i,barMaxWidth:m}=_();v.forEach((x,P)=>{const b=c+P*u;if(r&&r.row===P){const y=b+r.group*(a+w);t.fillStyle=o.bgHover,t.fillRect(0,y-1,l,a+2)}t.fillStyle=o.text,t.textAlign="right";const F=H!==void 0&&x.label.length>H?`${x.label.slice(0,H-1)}\u2026`:x.label;t.fillText(F,c+T-8,b+M/2),x.bars.forEach((y,X)=>{const S=b+X*(a+w);if(t.fillStyle=o.track,t.fillRect(i,S,m,a),y.fraction===null){R&&(t.fillStyle=o.textMuted,t.textAlign="left",t.fillText(R,i+4,S+a/2));return}const N=Math.max(y.fraction>0?1:0,y.fraction*m);t.fillStyle=B(X),t.fillRect(i,S,N,a)}),t.fillStyle=o.text,t.textAlign="right",t.fillText(x.trailing,i+m+D,b+M/2)})}const I=e=>{const l=s.getBoundingClientRect(),n=z(e.clientX-l.left,e.clientY-l.top),t=n?.row!==r?.row||n?.group!==r?.group;if(r=n,t&&g(),!n){p.style.display="none";return}p.innerHTML=$(n.row,n.group),p.style.display="block",Q(p,h,e.clientX,e.clientY)},O=()=>{r&&(r=null,g()),p.style.display="none"};s.addEventListener("mousemove",I),s.addEventListener("mouseleave",O);const V=K(h,()=>g());return g(),{draw:g,destroy:()=>{V?.disconnect(),s.removeEventListener("mousemove",I),s.removeEventListener("mouseleave",O)}}}
1
+ import{categorical as q}from"./palette.js";import{SPACE as E,FONT as J,CLR as K}from"../toolbar.js";import{observeResize as Q,positionChartTooltip as Z,fitRowsHeight as tt,setChartGrow as et,prepareCanvas as nt,chartSwatchCss as lt,PADDING as c,VALUE_WIDTH as $}from"./chartShell.js";const ot=8,C=1,a=14,it=8;export function renderGroupedBarPlot(f,L,h,v){const{rows:w,groups:S,labelWidth:T,tooltipHtml:j,maxVisibleRows:z=it,showLegend:V=!0,maxLabelChars:R,emptyBarText:B,colorOf:k=q}=v,d=f.ownerDocument;et(f,"rows");const H=Math.max(1,S.length),A=H*a+(H-1)*C,u=A+ot;let W=0;if(V){const t=d.createElement("div");t.dataset.wmapLegend="1",Object.assign(t.style,{display:"flex",flexWrap:"wrap",gap:`${E.xs} ${E.xl}`,marginBottom:E.xs}),S.forEach((l,n)=>{const e=d.createElement("span");Object.assign(e.style,{display:"inline-flex",alignItems:"center",gap:E.sm,fontSize:J.body,color:K.text});const i=d.createElement("span");i.style.cssText=lt(k(n));const s=d.createElement("span");s.textContent=l,e.append(i,s),t.appendChild(e)}),L.appendChild(t),W=t.offsetHeight}const g=d.createElement("div"),G=c*2+Math.min(w.length,z)*u;Object.assign(g.style,{overflowX:"hidden",overflowY:"auto",minHeight:"0",flex:"1",maxHeight:`${G}px`,scrollbarGutter:"stable"}),L.appendChild(g);const o=d.createElement("canvas");o.style.display="block",o.style.cursor="default",g.appendChild(o);let r=null;function X(){const t=c+T,l=Math.max(10,o.clientWidth-t-$-c);return{barX:t,barMaxWidth:l}}function _(t,l){const n=Math.floor((l-c)/u);if(n<0||n>=w.length)return null;const e=l-c-n*u,i=Math.floor(e/(a+C));if(i<0||i>=H)return null;const{barX:s,barMaxWidth:m}=X();return t<s||t>s+m?null:{row:n,group:i}}function p(){const t=c*2+w.length*u;g.style.maxHeight=`${tt(f,L,G,t,W)}px`;const l=g.clientWidth,n=nt(o,f,l,t);if(!n)return;const{ctx:e,theme:i}=n,{barX:s,barMaxWidth:m}=X();w.forEach((x,D)=>{const b=c+D*u;if(r&&r.row===D){const y=b+r.group*(a+C);e.fillStyle=i.bgHover,e.fillRect(0,y-1,l,a+2)}e.fillStyle=i.text,e.textAlign="right";const F=R!==void 0&&x.label.length>R?`${x.label.slice(0,R-1)}\u2026`:x.label;e.fillText(F,c+T-8,b+A/2),x.bars.forEach((y,U)=>{const M=b+U*(a+C);if(e.fillStyle=i.track,e.fillRect(s,M,m,a),y.fraction===null){B&&(e.fillStyle=i.textMuted,e.textAlign="left",e.fillText(B,s+4,M+a/2));return}const N=Math.max(y.fraction>0?1:0,y.fraction*m);e.fillStyle=k(U),e.fillRect(s,M,N,a)}),e.fillStyle=i.text,e.textAlign="right",e.fillText(x.trailing,s+m+$,b+A/2)})}const I=t=>{const l=o.getBoundingClientRect(),n=_(t.clientX-l.left,t.clientY-l.top),e=n?.row!==r?.row||n?.group!==r?.group;if(r=n,e&&p(),!n){h.style.display="none";return}h.innerHTML=j(n.row,n.group),h.style.display="block",Z(h,f,t.clientX,t.clientY)},O=()=>{r&&(r=null,p()),h.style.display="none"},P=t=>{const l=o.getBoundingClientRect(),n=_(t.clientX-l.left,t.clientY-l.top);n&&v.onRowClick&&(h.style.display="none",v.onRowClick(n.row,n.group,t))};o.addEventListener("mousemove",I),o.addEventListener("mouseleave",O),v.onRowClick&&(o.style.cursor="pointer",o.addEventListener("click",P));const Y=Q(f,()=>p());return p(),{draw:p,destroy:()=>{Y?.disconnect(),o.removeEventListener("mousemove",I),o.removeEventListener("mouseleave",O),o.removeEventListener("click",P)}}}
@@ -1,5 +1,6 @@
1
1
  import { type HistogramItem } from '../../stats/histogram.js';
2
2
  import type { TestDef } from '../../renderer/buildWaferMap.js';
3
+ import type { Die } from '../../core/dies.js';
3
4
  import { type SaveImageHandler, type AxisPrefs } from './chartShell.js';
4
5
  export interface HistogramPanelOptions {
5
6
  /** Initial axis toggles, shared with the sibling distribution panels. */
@@ -27,6 +28,21 @@ export interface HistogramPanelOptions {
27
28
  /** Fired when the user narrows to one group by clicking it in the legend.
28
29
  * The Insights tab owns the scope; this panel only reports the gesture. */
29
30
  onGroupChange?: (key: string | null) => void;
31
+ /**
32
+ * Click a bar: the dies it counts, per item, with the bar's range and the click, so the host can open a menu on them.
33
+ * On the overlaid group view the bar is the bucket across every group, or across the one the legend has emphasised
34
+ * (`group` then says which): there is no one bar to click, so the column is the target.
35
+ */
36
+ onSelectBucket?: (sel: {
37
+ testNumber: number;
38
+ low: number;
39
+ high: number;
40
+ items: Array<{
41
+ item: HistogramItem;
42
+ dies: Die[];
43
+ }>;
44
+ group?: string;
45
+ }, e: MouseEvent) => void;
30
46
  /** Document to build this panel's DOM into. Default `document` — pass the
31
47
  * host's own `ownerDocument` when the container might live in a
32
48
  * different document (e.g. a gallery card detached into its own popup
@@ -1 +1 @@
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
+ import{buildTestHistogramData as Zt,collectTestValues as Ct,buildTestHistogramSeries as te,testValueExtent as Bt,diesInBucket as At}from"../../stats/histogram.js";import{SPACE as lt,fontPx as N,FONT as $t,CLR as Et}from"../toolbar.js";import{fmt as xt}from"../../renderer/fmt.js";import{niceStep as ee,fitTicks as Wt}from"../../renderer/axisTicks.js";import{QUANTITY as ne,categorical as ie}from"./palette.js";import{cardShell as oe,observeResize as Dt,makeTooltip as se,attachChartTip as le,positionChartTooltip as It,makeLinkedTestSelect as ae,makeWaferSelect as re,makeLinkedAxisPrefs as ce,renderEmptyState as kt,chartFillHeight as Pt,applyCanvasFlow as Rt,makeAxisFormat as Tt,horizontalTickSpacing as Kt,PADDING as mt,robustFence as Nt,shouldIncludeLimitsByDefault as Ot,drawOffAxisLimits as ue,resolveAxisRange as de,limitLines as Ft,limitExtent as me,hasBothLimitKinds as he,stackLabelRows as fe,strokeLimitLine as ge,chartSwatchCss as pe,makeSeriesLegendItem as xe,prepareCanvas as zt}from"./chartShell.js";import{escHtml as wt,maxOf as be}from"../../core/utils.js";const Mt=230,Gt=36,ye=18;function Vt(n){const $=new Set(n.map(E=>E.kind)).size;return ye+Math.max(0,$-1)*(N(-1)+2)}function jt(n,$,E,V,et,S,l){n.font=`${N(-1)}px system-ui, sans-serif`;const x=$.filter(i=>!E.has(i.value)).map(i=>{const k=V(i.value),W=n.measureText(i.label).width;return{l:i,x:k,start:k-W/2,end:k+W/2}}),U=fe(x),nt=N(-1)+2;x.forEach(({l:i,x:k},W)=>{ge(n,i,l,k,et,k,S),n.fillStyle=l,n.textAlign="center",n.textBaseline="bottom",n.fillText(i.label,k,et-2-U[W]*nt)})}function Xt(n,$,E,V,et,S,l,x=4){const U=Math.max(1,ee(S/x)),nt=V-E;n.save(),n.font=`${N(-1)}px system-ui, sans-serif`,n.textAlign="right",n.textBaseline="middle";for(let i=0;i<=S+1e-9;i+=U){const k=V-i/(S||1)*nt;n.strokeStyle=l.grid,n.lineWidth=1,n.beginPath(),n.moveTo($,k),n.lineTo($+et,k),n.stroke(),n.fillStyle=l.text,n.fillText(String(Math.round(i)),$-4,k)}n.strokeStyle=l.axis,n.lineWidth=1,n.beginPath(),n.moveTo($,E),n.lineTo($,V),n.stroke(),n.restore()}export function renderHistogramPanel(n){const{title:$="Test value distribution",items:E,testDefs:V,onSaveImage:et,groups:S}=n,{card:l,body:x,controlsRow:U}=oe($,et,n.ownerDocument),nt=V.filter(c=>c.testNumber!==void 0);let i=n.selectedTestNumber??nt[0]?.testNumber??null,k=null,W=0,ht;const vt=ae(nt,i,c=>{i=c,q(),n.onTestChange?.(c)},{maxWidth:"200px",ownerDocument:l.ownerDocument}),_t=vt.el;U.appendChild(_t);const ft=E.length>1?re(E,k,c=>{k=c,q()},{ownerDocument:l.ownerDocument}):null;ft&&U.appendChild(ft);function Yt(c){return b===c||c!==null&&!(S??[]).some(L=>L.key===c)?!1:(b=c,q(),!0)}const bt=l.ownerDocument.createElement("span");Object.assign(bt.style,{display:"inline-flex",gap:lt.lg,alignItems:"center"}),U.appendChild(bt);const at=ce(bt,n.axisPrefs,c=>{n.onAxisPrefsChange?.(c),q()},l.ownerDocument),C=se(l);let rt=null,b=null;function it(c){const L=V.find(I=>I.testNumber===c),j=Ft(L,at.get().limits),{lo:T,hi:w}=me(j);return{unit:L?.unit,lines:j,limitLow:T,limitHigh:w,hasLimits:Ft(L).length>0,hasBothKinds:he(L)}}function q(){const{includeLimits:c,clipOutliers:L}=at.get();if(rt?.disconnect(),rt=null,x.innerHTML="",i===null){kt(x,"No parametric test data available for a histogram.");return}const j=!!S&&S.length>0;let T=!1,w;if(j){const{limitLow:u,limitHigh:m}=it(i),g=S.flatMap(r=>r.items),{min:e,max:p}=Bt(g,i);if(T=c??Ot(e,p,u,m),L){const r=Ct(g,i),h=Nt(r);h&&(w={lo:Math.max(e,h.lo),hi:Math.min(p,h.hi)},W=r.reduce((Z,K)=>Z+(K<w.lo||K>w.hi?1:0),0))}w||(W=0),ht=w}const I=j?te(S,i,16,T?it(i).limitLow:void 0,T?it(i).limitHigh:void 0,w):null;if(ft&&(ft.style.display=I?"none":""),I){const{hasLimits:u,hasBothKinds:m}=it(i);if(at.sync(T,u,m),I.series.length===0){kt(x,"No parametric test data available for a histogram.");return}Qt(I);return}const O=k!==null?[E[k]]:E,{unit:D,lines:X,limitLow:M,limitHigh:_,hasLimits:ct,hasBothKinds:gt}=it(i),ut=Ct(O,i),{min:dt,max:pt}=Bt(O,i),P=c??Ot(dt,pt,M,_);at.sync(P,ct,gt);const ot=L?Nt(ut):null,R=ot?{lo:Math.max(dt,ot.lo),hi:Math.min(pt,ot.hi)}:void 0;W=R?ut.filter(u=>u<R.lo||u>R.hi).length:0,ht=R;const y=Zt(O,i,16,P?M:void 0,P?_:void 0,R);if(y.length===0){kt(x,"No parametric test data available for a histogram.");return}const o=Math.max(be(y.map(u=>u.count)),1),d=l.ownerDocument.createElement("div");d.dataset.wmapCaption="1",Object.assign(d.style,{fontSize:$t.body,color:Et.label,marginBottom:lt.xxs}),d.textContent=`max ${o} dies/bucket`+(W?` \xB7 ${W} value${W===1?"":"s"} outside clipped range`:"")+(n.onSelectBucket?" \xB7 click a bar to chart or tabulate its dies":""),x.appendChild(d);const s=l.ownerDocument.createElement("canvas");s.style.display="block",s.style.cursor="default",x.appendChild(s);let t=-1;const a=y[0].rangeLow,f=y[y.length-1].rangeHigh,H=f-a||1,F=Math.max(Math.abs(a),Math.abs(f));function J(u){const m=mt+36,g=s.clientWidth-m-mt,e=Vt(X),p=u-Gt-e;return{plotX:m,plotMaxWidth:Math.max(10,g),plotMaxHeight:p,plotTop:e}}function v(u){const{plotX:m,plotMaxWidth:g}=J(Mt);if(u<m||u>m+g)return-1;const e=Math.floor((u-m)/g*y.length);return e>=0&&e<y.length?e:-1}function A(){Rt(s,d);const u=x.clientWidth,m=Pt(l,x,s,Mt),g=zt(s,l,u,m);if(!g)return;const{ctx:e,theme:p}=g,{plotX:r,plotMaxWidth:h,plotMaxHeight:Z,plotTop:K}=J(m),B=K+Z,tt=h/y.length;Xt(e,r,K,B,h,o,{text:p.textMuted,axis:p.border,grid:p.grid}),y.forEach((G,Q)=>{const St=G.count/o*Z,Lt=r+Q*tt,Ht=B-St;e.fillStyle=Q===t?p.text:ne,e.globalAlpha=Q===t?1:.7,e.fillRect(Lt+1,Ht,Math.max(1,tt-2),St),e.globalAlpha=1,Q===t&&(e.fillStyle=p.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(`${G.count}`,Lt+tt/2,Ht-2))});const st=G=>r+(G-a)/H*h;e.font=`${N(-1)}px system-ui, sans-serif`;const{offAxis:z}=de({dataMin:a,dataMax:f,limits:X,includeLimits:!1});ue(e,z,{left:r,right:r+h,top:K,bottom:B},"horizontal",p.limitLine,Tt(F,D).tick),jt(e,X,new Set(z.map(G=>G.value)),st,K,B,p.limitLine),e.font=`${N(-1)}px system-ui, sans-serif`,e.strokeStyle=p.border,e.lineWidth=1,e.beginPath(),e.moveTo(r,B),e.lineTo(r+h,B),e.stroke(),e.fillStyle=p.textMuted,e.textAlign="center",e.textBaseline="top";const{ticks:Y,step:Jt}=Wt(a,f,h,Kt(e,F,D)),yt=Tt(F,D,Jt||void 0);for(const G of Y){const Q=r+(G-a)/H*h;e.beginPath(),e.moveTo(Q,B),e.lineTo(Q,B+4),e.stroke(),e.fillText(yt.tick(G),Q,B+6)}yt.unitLabel&&(e.save(),e.font=`${N(-1)}px system-ui, sans-serif`,e.fillStyle=p.textMuted,e.textAlign="right",e.textBaseline="top",e.fillText(`(${yt.unitLabel})`,r+h,B+20),e.restore())}s.addEventListener("mousemove",u=>{const m=s.getBoundingClientRect(),g=v(u.clientX-m.left);if(g!==t&&(t=g,A()),g>=0){const e=y[g];C.innerHTML=`<strong>${wt(`${xt(e.rangeLow,D,"engineering")} \u2013 ${xt(e.rangeHigh,D,"engineering")}`)}</strong><br>${e.count} dies`+(n.onSelectBucket&&e.count>0?"<br><em>click to chart or tabulate these dies</em>":""),C.style.display="block",It(C,l,u.clientX,u.clientY)}else C.style.display="none"}),s.addEventListener("mouseleave",()=>{t!==-1&&(t=-1,A()),C.style.display="none"}),n.onSelectBucket&&(s.style.cursor="pointer",s.addEventListener("click",u=>{const m=v(u.clientX-s.getBoundingClientRect().left);if(m<0||i===null||y[m].count===0)return;const g=At(O,i,y,m,ht);g.length!==0&&(C.style.display="none",n.onSelectBucket({testNumber:i,low:y[m].rangeLow,high:y[m].rangeHigh,items:g},u))})),rt=Dt(l,()=>A()),A()}function Qt(c){const{unit:L,lines:j}=it(i),T=c.ranges,w=c.series,I=ie;b&&!w.some(o=>o.groupKey===b)&&(b=null);const O=Math.max(1,...w.flatMap(o=>o.counts)),D=l.ownerDocument.createElement("div");D.dataset.wmapCaption="1",Object.assign(D.style,{fontSize:$t.body,color:Et.label,marginBottom:lt.xxs}),D.textContent=`${w.length} groups \xB7 max ${O} dies/bucket`+(n.onSelectBucket?" \xB7 click a column to chart or tabulate its dies":""),x.appendChild(D);const X=l.ownerDocument.createElement("div");Object.assign(X.style,{display:"flex",flexWrap:"wrap",gap:lt.sm,marginTop:lt.md,marginBottom:lt.xl}),w.forEach((o,d)=>{const s=xe(o.groupKey,I(d),l.ownerDocument),t=s.el;le(t,l,C,`${o.groupKey} \u2014 click to emphasize (dim the rest)`),s.setState({selected:b===o.groupKey,dimmed:b!==null&&b!==o.groupKey}),t.addEventListener("click",()=>{const a=b===o.groupKey?null:o.groupKey;Yt(a),n.onGroupChange?.(a)}),X.appendChild(t)}),x.appendChild(X);const M=l.ownerDocument.createElement("canvas");M.style.display="block",M.style.cursor="default",x.appendChild(M);const _=T[0].rangeLow,ct=T[T.length-1].rangeHigh,gt=ct-_||1,ut=Math.max(Math.abs(_),Math.abs(ct)),dt=o=>{const d=(o.ownerDocument.defaultView??window).getComputedStyle(o);return o.offsetHeight+(parseFloat(d.marginTop)||0)+(parseFloat(d.marginBottom)||0)},pt=()=>dt(D)+dt(X);let P=-1,ot={plotX:mt+36,plotMaxWidth:10,barWidth:10};function R(){Rt(M,pt());const o=x.clientWidth,d=Pt(l,x,M,Mt),s=zt(M,l,o,d);if(!s)return;const{ctx:t,theme:a}=s,f=mt+36,H=Math.max(10,o-f-mt),F=Vt(j),J=d-Gt-F,v=F+J,A=H/T.length;ot={plotX:f,plotMaxWidth:H,barWidth:A},Xt(t,f,F,v,H,O,{text:a.textMuted,axis:a.border,grid:a.grid}),P>=0&&(t.fillStyle=a.bgHover,t.globalAlpha=.5,t.fillRect(f+P*A,F,A,J),t.globalAlpha=1);const u=w.map((r,h)=>({s:r,i:h})).sort((r,h)=>(r.s.groupKey===b?1:0)-(h.s.groupKey===b?1:0));for(const{s:r,i:h}of u){const Z=b===r.groupKey,K=b!==null&&!Z,B=I(h);t.strokeStyle=B,t.fillStyle=B,t.lineWidth=Z?2:1,t.globalAlpha=K?.12:b?.32:.22,t.beginPath(),t.moveTo(f,v),r.counts.forEach((tt,st)=>{const z=f+st*A,Y=v-tt/O*J;t.lineTo(z,Y),t.lineTo(z+A,Y)}),t.lineTo(f+H,v),t.closePath(),t.fill(),t.globalAlpha=K?.3:1,t.beginPath(),r.counts.forEach((tt,st)=>{const z=f+st*A,Y=v-tt/O*J;st===0?t.moveTo(z,Y):t.lineTo(z,Y),t.lineTo(z+A,Y)}),t.stroke()}t.globalAlpha=1;const m=r=>f+(r-_)/gt*H;t.font=`${N(-1)}px system-ui, sans-serif`,jt(t,j,new Set,m,F,v,a.limitLine),t.font=`${N(-1)}px system-ui, sans-serif`,t.strokeStyle=a.border,t.lineWidth=1,t.beginPath(),t.moveTo(f,v),t.lineTo(f+H,v),t.stroke(),t.fillStyle=a.textMuted,t.textAlign="center",t.textBaseline="top";const{ticks:g,step:e}=Wt(_,ct,H,Kt(t,ut,L)),p=Tt(ut,L,e||void 0);for(const r of g){const h=f+(r-_)/gt*H;t.beginPath(),t.moveTo(h,v),t.lineTo(h,v+4),t.stroke(),t.fillText(p.tick(r),h,v+6)}p.unitLabel&&(t.save(),t.font=`${N(-1)}px system-ui, sans-serif`,t.fillStyle=a.textMuted,t.textAlign="right",t.textBaseline="top",t.fillText(`(${p.unitLabel})`,f+H,v+20),t.restore())}function y(o){const{plotX:d,plotMaxWidth:s,barWidth:t}=ot;if(o<d||o>d+s)return-1;const a=Math.floor((o-d)/t);return a>=0&&a<T.length?a:-1}M.addEventListener("mousemove",o=>{const d=M.getBoundingClientRect(),s=y(o.clientX-d.left);if(s!==P&&(P=s,R()),s>=0){const t=T[s],a=w.map((f,H)=>`<span style="${pe(I(H))};margin-right:4px"></span>${wt(`${f.groupKey}: ${f.counts[s]}`)}`).join("<br>");C.innerHTML=`<strong>${wt(`${xt(t.rangeLow,L,"engineering")} \u2013 ${xt(t.rangeHigh,L,"engineering")}`)}</strong><br>${a}`,C.style.display="block",It(C,l,o.clientX,o.clientY)}else C.style.display="none"}),M.addEventListener("mouseleave",()=>{P!==-1&&(P=-1,R()),C.style.display="none"}),n.onSelectBucket&&S&&(M.style.cursor="pointer",M.addEventListener("click",o=>{const d=y(o.clientX-M.getBoundingClientRect().left);if(d<0||i===null)return;const s=b?S.filter(a=>a.key===b):S,t=At(s.flatMap(a=>a.items),i,T,d,ht);t.length!==0&&(C.style.display="none",n.onSelectBucket({testNumber:i,low:T[d].rangeLow,high:T[d].rangeHigh,items:t,...b?{group:b}:{}},o))})),rt=Dt(l,()=>R()),R()}q();function Ut(c){vt.set(c)&&(i=c,q())}function qt(c){at.set(c)&&q()}return{card:l,setTest:Ut,setAxisPrefs:qt,destroy:()=>rt?.disconnect()}}
@@ -1,6 +1,8 @@
1
1
  /** Neutral single fill for "how much" encodings (bars, boxes, histogram). */
2
2
  export declare const QUANTITY = "#4e79a7";
3
3
  export declare function categorical(i: number): string;
4
+ /** How many series colours are distinct from one another before the palette repeats. */
5
+ export declare const CATEGORICAL_COUNT: number;
4
6
  /**
5
7
  * Sequential fill for a yield percentage on a **fixed** domain — light at
6
8
  * ≤50%, deepest at 100% — so the same yield is always the same colour
@@ -1 +1 @@
1
- import{clamp01 as p}from"../../core/utils.js";export const QUANTITY="#4e79a7";const n=["#0072B2","#E69F00","#009E73","#CC79A7","#56B4E9","#D55E00","#8C6D31","#999999"];export function categorical(t){return n[(t%n.length+n.length)%n.length]}function E(t,e,c){const s=[parseInt(t.slice(1,3),16),parseInt(t.slice(3,5),16),parseInt(t.slice(5,7),16)],l=[parseInt(e.slice(1,3),16),parseInt(e.slice(3,5),16),parseInt(e.slice(5,7),16)],r=s.map((o,i)=>Math.round(o+(l[i]-o)*c));return`rgb(${r[0]},${r[1]},${r[2]})`}export function yieldFill(t){const e=p((t-50)/50);return E("#e7eef6","#2f6395",e)}export function capabilityColor(t){return t===null?"#999999":t>=1.33?"#009E73":t>=1?"#E69F00":"#D55E00"}export const CORRELATION_POSITIVE="#0072B2",CORRELATION_NEGATIVE="#D55E00";
1
+ import{clamp01 as i}from"../../core/utils.js";export const QUANTITY="#4e79a7";const E=["#0072B2","#E69F00","#009E73","#CC79A7","#56B4E9","#D55E00","#8C6D31","#999999"],p=["#1f1ff9","#f91ff9","#05d105","#65188b","#f91f68","#607afb","#8b1818","#1f4951"],n=[...E,...p];export function categorical(t){return n[(t%n.length+n.length)%n.length]}export const CATEGORICAL_COUNT=n.length;function I(t,e,c){const s=[parseInt(t.slice(1,3),16),parseInt(t.slice(3,5),16),parseInt(t.slice(5,7),16)],l=[parseInt(e.slice(1,3),16),parseInt(e.slice(3,5),16),parseInt(e.slice(5,7),16)],r=s.map((o,f)=>Math.round(o+(l[f]-o)*c));return`rgb(${r[0]},${r[1]},${r[2]})`}export function yieldFill(t){const e=i((t-50)/50);return I("#e7eef6","#2f6395",e)}export function capabilityColor(t){return t===null?"#999999":t>=1.33?"#009E73":t>=1?"#E69F00":"#D55E00"}export const CORRELATION_POSITIVE="#0072B2",CORRELATION_NEGATIVE="#D55E00";
@@ -0,0 +1,27 @@
1
+ import { type PlotPoint, type ResolvedPlot } from '../../stats/plotData.js';
2
+ import { type SaveImageHandler } from './chartShell.js';
3
+ export interface PlotChartOptions {
4
+ ownerDocument?: Document;
5
+ onSaveImage?: SaveImageHandler;
6
+ /** The label of the wafer an item index names, for tooltips. */
7
+ waferLabel: (item: number) => string;
8
+ /** Click a point: open its wafer, on `testNumber` (the plot's own measurement) when it has one. Omit where there is no wafer to open. */
9
+ onOpenWafer?: (item: number, testNumber?: number) => void;
10
+ /** What a click on a point does, in the reader's words. Default `open this wafer`. */
11
+ openActionLabel?: string;
12
+ /** Drag a rectangle over a scatter: the dies inside it, the pointer position and the canvas to anchor a menu on. */
13
+ onSelectPoints?: (points: PlotPoint[], at: {
14
+ x: number;
15
+ y: number;
16
+ }, anchor: HTMLElement) => void;
17
+ }
18
+ export interface PlotChartHandle {
19
+ card: HTMLElement;
20
+ /** The row under the title, for the host's own buttons (Edit, Duplicate, Delete). */
21
+ actions: HTMLElement;
22
+ /** Draw `resolved`, replacing whatever was shown. */
23
+ setPlot(resolved: ResolvedPlot): void;
24
+ destroy(): void;
25
+ }
26
+ export declare function renderPlotChart(options: PlotChartOptions): PlotChartHandle;
27
+ //# sourceMappingURL=plotChart.d.ts.map
@@ -0,0 +1 @@
1
+ import{escHtml as I,maxOf as fe,minOf as We}from"../../core/utils.js";import{fiveNumberSummary as Be}from"../../stats/math.js";import{fmt as R}from"../../renderer/fmt.js";import{fitTicks as Te}from"../../renderer/axisTicks.js";import{plotFootnote as Fe,plotTestNumber as Oe,plotTitle as et}from"../../stats/plotData.js";import{SPACE as ee,RADIUS as tt,fontPx as me,FONT as we,CLR as xe,markNoPrint as ze}from"../toolbar.js";import{cardShell as nt,observeResize as ot,makeTooltip as it,attachChartTip as lt,chartFillHeight as st,applyCanvasFlow as ke,prepareCanvas as Me,positionChartTooltip as Le,resolveChartCanvasColors as ve,makeAxisFormat as at,horizontalTickSpacing as rt,VERTICAL_TICK_SPACING_PX as ct,makeSeriesLegendItem as ft,renderEmptyState as Re,limitLines as mt,limitExtent as ht,stackLabelRows as qe,strokeLimitLine as He,drawOffAxisLimits as ut,limitLabelSide as dt,shouldIncludeLimitsByDefault as gt}from"./chartShell.js";import{categorical as pt}from"./palette.js";import{resolveValueColorFn as $e}from"../../renderer/colorSchemes.js";import{wirePointInteractions as yt}from"./pointInteractions.js";const M=64,Xe=16,k=12,he=52,bt=16,Pe=5e3;function te(b,P,$={}){const q=P.scale==="log";let w=b.length?We(b):0,O=b.length?fe(b):1;q&&(w=Math.log10(Math.max(w,Number.MIN_VALUE)),O=Math.log10(Math.max(O,Number.MIN_VALUE)));const ne=(O-w)*.05||(q?.5:1);w=$.floorAtZero&&!q&&w>=0?0:w-ne,O+=ne;const s=x=>x===void 0||!Number.isFinite(x)?void 0:q?x>0?Math.log10(x):void 0:x,g=s(P.min),H=s(P.max);g!==void 0&&(w=g),H!==void 0&&(O=H),O>w||(O=w+1);const C=O-w,V=!!P.reverse,Z=x=>{const Y=((q?Math.log10(Math.max(x,Number.MIN_VALUE)):x)-w)/C;return V?1-Y:Y};return{lo:q?10**w:w,hi:q?10**O:O,log:q,reverse:V,frac:Z}}function _e(b,P,$,q){if(P/b<10)return Te(b,P,$,q);const w=Math.floor(Math.log10(b)),O=Math.ceil(Math.log10(P)),ne=g=>{const H=[];for(let C=w;C<=O;C++)for(const V of g){const Z=V*10**C;Z>=b&&Z<=P&&H.push(Z)}return H},L=ne([1]);return{ticks:L.length>=4?L:ne([1,2,5]),step:0}}export function renderPlotChart(b){const P=b.ownerDocument??document,{card:$,heading:q,body:w,controlsRow:O,setPngDecor:ne}=nt("Plot",b.onSaveImage,P);$.dataset.wmapPlotCard="1";const L=it($);let s,g,H,C,V,Z=[];const x=new Set,Y=new Set;let ue=null,X=-1,ce=[],z,K,G,_=null,J=[],D=-1,de=he;const oe=e=>pt(e),ge=e=>s?.groups[e]||"";function pe(){const e=w.clientWidth,l=Math.max(220,Math.min(420,e*.62))+(de-he),c=st($,w,g,l);return{w:e,h:c,plotW:Math.max(10,e-M-Xe),plotH:Math.max(10,c-k-de)}}const ie=e=>mt(e?.limits,s?.spec.limits??"both").filter(l=>e?.scale!=="log"||l.value>0),ae=(e,l)=>{const c=e.filter(Number.isFinite);if(!l.length||c.length===0)return e;const{lo:r,hi:i}=ht(l);return gt(We(c),fe(c),r,i)?[...e,...l.map(o=>o.value)]:e};function re(e,l,c,r,i,o,t,n){if(o.length===0)return;const f=o.filter(h=>h.value>=i.lo&&h.value<=i.hi),p=o.filter(h=>!f.includes(h)).map(h=>({value:h.value,label:h.label,side:h.value<i.lo?"lo":"hi"}));e.save(),e.font=`${me(-1)}px system-ui, sans-serif`;const d=h=>`${h.label} ${R(h.value,t.unit)}`,a=me(-1)+2;if(n==="y"){const h=f.map(m=>{const y=k+(1-i.frac(m.value))*r;return{l:m,y,start:y-6-a/2,end:y-6+a/2}}),S=qe(h,1),u=h.length?fe(h.map(m=>e.measureText(d(m.l)).width))+8:0;h.forEach(({l:m,y},v)=>{e.globalAlpha=.7,He(e,m,l,M,y,M+c,y),e.globalAlpha=1,e.fillStyle=l,e.textAlign="left",e.textBaseline="alphabetic",e.fillText(d(m),M+3+S[v]*u,y-4)})}else{const h=f.map(u=>{const m=M+i.frac(u.value)*c,y=e.measureText(d(u)).width,v=dt(m,y,M,M+c,u.end==="lo");return{l:u,x:m,side:v,start:v<0?m-3-y:m+3,end:v<0?m-3:m+3+y}}),S=qe(h,4);h.forEach(({l:u,x:m,side:y},v)=>{e.globalAlpha=.7,He(e,u,l,m,k,m,k+r),e.globalAlpha=1,e.fillStyle=l,e.textAlign=y<0?"right":"left",e.textBaseline="top",e.fillText(d(u),m+y*3,k+2+S[v]*a)})}e.restore(),ut(e,p,{left:M,right:M+c,top:k,bottom:k+r},n==="y"?"vertical":"horizontal",l,h=>R(h,t.unit))}function Ee(e,l,c,r){return at(Math.max(Math.abs(l),Math.abs(c)),e.unit,r)}function Ce(e,l){return l?`${e.label} (${l})`:e.label}function ye(e,l,c,r,i,o,t,n,f,p){e.font=`${me(-1)}px system-ui, sans-serif`;const d=()=>ct,a=n.log?_e(n.lo,n.hi,o,d):Te(n.lo,n.hi,o,d),h=Ee(p,n.lo,n.hi,a.step||void 0);e.strokeStyle=l.grid,e.lineWidth=.5,e.fillStyle=l.textMuted;let S="";if("labels"in t){const u=t.labels.length;t.labels.forEach((m,y)=>{const v=M+(y+.5)/u*i,T=m.length>22?`${m.slice(0,21)}\u2026`:m;t.rotate?(e.save(),e.translate(v,k+o+6),e.rotate(-Math.PI/4),e.textAlign="right",e.textBaseline="middle",e.fillText(T,0,0),e.restore()):(e.textAlign="center",e.textBaseline="top",e.fillText(T,v,k+o+4))})}else{const u=rt(e,Math.max(Math.abs(t.lo),Math.abs(t.hi)),f.unit),m=t.log?_e(t.lo,t.hi,i,u):Te(t.lo,t.hi,i,u),y=Ee(f,t.lo,t.hi,m.step||void 0);S=y.unitLabel;for(const v of m.ticks){const T=M+t.frac(v)*i;e.beginPath(),e.moveTo(T,k),e.lineTo(T,k+o),e.stroke(),e.textAlign="center",e.textBaseline="top",e.fillText(y.tick(v),T,k+o+4)}}for(const u of a.ticks){const m=k+(1-n.frac(u))*o;e.beginPath(),e.moveTo(M,m),e.lineTo(M+i,m),e.stroke(),e.textAlign="right",e.textBaseline="middle",e.fillText(h.tick(u),M-4,m)}e.font=`${me(0)}px system-ui, sans-serif`,e.fillStyle=l.text,e.textAlign="center",e.textBaseline="bottom",e.fillText(Ce(f,S),M+i/2,r-6),e.save(),e.translate(14,k+o/2),e.rotate(-Math.PI/2),e.textBaseline="top",e.fillText(Ce(p,h.unitLabel),0,0),e.restore(),e.strokeStyle=l.border,e.lineWidth=1,e.beginPath(),e.moveTo(M,k),e.lineTo(M,k+o),e.moveTo(M,k+o),e.lineTo(M+i,k+o),e.stroke()}function je(){if(!s||!g||s.marks?.type!=="scatter"||!s.x||!s.y)return;ke(g,C??0);const e=ve($),{w:l,h:c,plotW:r,plotH:i}=pe(),o=Me(g,$,l,c);if(!o)return;const{ctx:t}=o,n=s.marks.points,f=x.size===0?n:n.filter(A=>x.has(A.group)),p=s.colorScale,d=$e(),a=ie(s.x),h=ie(s.y),S=te(ae(n.map(A=>A.x),a),s.x),u=te(ae(n.map(A=>A.y),h),s.y);z={x:S,y:u,plotW:r,plotH:i},ye(t,e,l,c,r,i,S,u,s.x,s.y),re(t,e.limitLine,r,i,S,a,s.x,"x"),re(t,e.limitLine,r,i,u,h,s.y,"y");const m=f.length>Pe?Math.ceil(f.length/Pe):1;t.globalAlpha=Math.max(.15,Math.min(.7,200/(f.length/m))),ce=[];const y=M,v=M+r,T=k,le=k+i;for(let A=0;A<f.length;A+=m){const N=f[A],se=M+S.frac(N.x)*r,U=k+(1-u.frac(N.y))*i;se<y||se>v||U<T||U>le||(ce.push({p:N,cx:se,cy:U}),t.beginPath(),t.arc(se,U,2.5,0,Math.PI*2),t.fillStyle=p&&N.value!==void 0?d((N.value-p.lo)/(p.hi-p.lo||1)):oe(N.group),t.fill())}t.globalAlpha=1;const E=ue?ce.find(A=>A.p===ue):void 0;if(E&&(t.beginPath(),t.arc(E.cx,E.cy,5,0,Math.PI*2),t.lineWidth=2,t.strokeStyle=e.text,t.stroke()),Y.size>0){t.lineWidth=1.5,t.strokeStyle=e.text;let A=0;for(const N of Y)if(!(x.size>0&&!x.has(N.group))){if(++A>Pe)break;t.beginPath(),t.arc(M+S.frac(N.x)*r,k+(1-u.frac(N.y))*i,4,0,Math.PI*2),t.stroke()}}}function Ue(){if(K=void 0,!s||s.marks?.type!=="histogram"||!s.x)return;const e=s.marks.values,l=e.flat();if(l.length===0)return;const c=te(ae(l,ie(s.x)),s.x),r=Math.max(1,s.spec.bins??bt),i=c.log?Math.log10(c.lo):c.lo,o=c.log?Math.log10(c.hi):c.hi,t=Array.from({length:r+1},(f,p)=>{const d=i+(o-i)*p/r;return c.log?10**d:d}),n=e.map(()=>new Array(r).fill(0));e.forEach((f,p)=>{for(const d of f){const a=c.log?d>0?Math.log10(d):NaN:d;if(!Number.isFinite(a)||a<i||a>o)continue;const h=Math.min(r-1,Math.floor((a-i)/(o-i)*r));n[p][h]++}}),K={edges:t,counts:n,maxCount:Math.max(1,...n.map(f=>fe(f)))}}function Ye(){if(!s||!g||!K||!s.x||!s.y)return;ke(g,C??0);const e=ve($),{w:l,h:c,plotW:r,plotH:i}=pe(),o=Me(g,$,l,c);if(!o)return;const{ctx:t}=o,{edges:n,counts:f,maxCount:p}=K,d=te([n[0],n[n.length-1]],{...s.x,min:n[0],max:n[n.length-1]}),a=te([0,p],{label:s.y.label,scale:"linear",min:0,max:p*1.05});z={x:d,y:a,plotW:r,plotH:i},ye(t,e,l,c,r,i,d,a,s.x,s.y),re(t,e.limitLine,r,i,d,ie(s.x),s.x,"x");const h=f.map((u,m)=>m).filter(u=>x.size===0||x.has(u)),S=h.length>1;for(const u of h)t.globalAlpha=S?.55:.85,t.fillStyle=oe(u),f[u].forEach((m,y)=>{if(m===0)return;const v=M+Math.min(d.frac(n[y]),d.frac(n[y+1]))*r,T=M+Math.max(d.frac(n[y]),d.frac(n[y+1]))*r,le=k+(1-a.frac(m))*i;t.fillRect(v,le,Math.max(1,T-v-1),k+i-le)});if(t.globalAlpha=1,X>=0){const u=M+Math.min(d.frac(n[X]),d.frac(n[X+1]))*r,m=M+Math.max(d.frac(n[X]),d.frac(n[X+1]))*r;t.lineWidth=1.5,t.strokeStyle=e.text,t.strokeRect(u,k,Math.max(1,m-u),i)}}function Ge(){const e=s?.marks;if(!s||!g||!s.x||!s.y||!e||e.type!=="box"&&e.type!=="bar")return;ke(g,C??0);const l=ve($),c=g.getContext("2d");let r=0;c&&(c.font=`${me(-1)}px system-ui, sans-serif`,r=fe(e.categories.map(E=>c.measureText(E.length>22?`${E.slice(0,21)}\u2026`:E).width)));const i=Math.max(10,w.clientWidth-M-Xe)/Math.max(1,e.categories.length),o=r+8>i;de=o?he+Math.min(110,r*.72):he;const{w:t,h:n,plotW:f,plotH:p}=pe(),d=Me(g,$,t,n);if(!d)return;const{ctx:a}=d,h=E=>E.filter(Number.isFinite),S=ie(s.y),u=te(ae(e.type==="bar"?[...h(e.values.flat()),0]:h(e.cells.flat(2)),S),s.y,{floorAtZero:e.type==="bar"});z={x:u,y:u,plotW:f,plotH:p},ye(a,l,t,n,f,p,{labels:e.categories,rotate:o},u,s.x,s.y),re(a,l.limitLine,f,p,u,S,s.y,"y");const m=e.categories.length,y=s.groups.length,v=f/m,T=v*.8/y;G={slotW:v,gw:T,n:m,groups:y,plotW:f};const le=u.log?k+p:k+(1-u.frac(0))*p;for(let E=0;E<y;E++){if(x.size>0&&!x.has(E))continue;const A=oe(E);for(let N=0;N<m;N++){const se=M+N*v+v*.1+E*T;if(e.type==="bar"){const U=e.values[E][N];if(!Number.isFinite(U))continue;const j=k+(1-u.frac(U))*p;a.fillStyle=A,a.globalAlpha=.9,a.fillRect(se+1,Math.min(j,le),Math.max(1,T-2),Math.max(1,Math.abs(le-j))),a.globalAlpha=1}else{const U=e.cells[E][N];if(U.length===0)continue;const j=se+T/2,Q=Math.max(3,Math.min(T*.7,46)),B=Be(Float64Array.from(U)),F=be=>k+(1-u.frac(be))*p;if(a.strokeStyle=A,a.fillStyle=A,a.lineWidth=1.5,U.length===1){a.beginPath(),a.arc(j,F(B.median),3,0,Math.PI*2),a.fill();continue}a.beginPath(),a.moveTo(j,F(B.min)),a.lineTo(j,F(B.max)),a.stroke();for(const be of[B.min,B.max])a.beginPath(),a.moveTo(j-Q/4,F(be)),a.lineTo(j+Q/4,F(be)),a.stroke();a.globalAlpha=.35,a.fillRect(j-Q/2,Math.min(F(B.q1),F(B.q3)),Q,Math.max(1,Math.abs(F(B.q1)-F(B.q3)))),a.globalAlpha=1,a.strokeRect(j-Q/2,Math.min(F(B.q1),F(B.q3)),Q,Math.max(1,Math.abs(F(B.q1)-F(B.q3)))),a.lineWidth=2.5,a.beginPath(),a.moveTo(j-Q/2,F(B.median)),a.lineTo(j+Q/2,F(B.median)),a.stroke()}}}if(_){const E=M+_.c*v+v*.1+_.g*T;a.lineWidth=1.5,a.strokeStyle=l.text,a.strokeRect(E,k,T,p)}}function Ae(e){if(!G||!g)return null;const l=g.getBoundingClientRect(),c=e.clientX-l.left-M;if(c<0||c>G.plotW)return null;const r=Math.min(G.n-1,Math.floor(c/G.slotW)),i=Math.floor((c-r*G.slotW-G.slotW*.1)/G.gw);return i<0||i>=G.groups?null:{g:i,c:r}}function De(e){const l=s,c=l.marks,r=l.level==="die"?"dies":"wafers",i=c.cells[e.g][e.c],o=`<strong>${I(c.categories[e.c])}${l.groups.length>1?` \xB7 ${I(ge(e.g))}`:""}</strong><br>`,t=l.y?.unit;if(i.length===0)return`${o}no ${r}`;const n=`${i.length.toLocaleString("en-GB")} ${r}`,f=c.categoryItems&&b.onOpenWafer?`<br><em>click to ${I(b.openActionLabel??"open this wafer")}</em>`:"";if(c.type==="bar"){const d=c.values[e.g][e.c];return`${o}${I(`${l.y.label}: ${R(d,t)}`)}<br>${I(n)}${f}`}const p=Be(Float64Array.from(i));return`${o}${["max","q3","median","q1","min"].map(d=>I(`${{max:"max",q3:"upper quartile",median:"median",q1:"lower quartile",min:"min"}[d]}: ${R(p[d],t)}`)).join("<br>")}<br>${I(n)}${f}`}function Ve(){const e=s?.marks;if(!s||!g||!s.x||!s.y||!e||e.type!=="line")return;ke(g,C??0);const l=ve($),{w:c,h:r,plotW:i,plotH:o}=pe(),t=Me(g,$,c,r);if(!t)return;const{ctx:n}=t,f=ie(s.x),p=ie(s.y),d=te(ae(e.xs,f),s.x),a=te(ae(e.values.flat().filter(Number.isFinite),p),s.y);z={x:d,y:a,plotW:i,plotH:o},ye(n,l,c,r,i,o,d,a,s.x,s.y),re(n,l.limitLine,i,o,d,f,s.x,"x"),re(n,l.limitLine,i,o,a,p,s.y,"y"),J=e.xs.map(h=>M+d.frac(h)*i),e.values.forEach((h,S)=>{if(x.size>0&&!x.has(S))return;n.strokeStyle=oe(S),n.fillStyle=oe(S),n.lineWidth=2;let u=!1;n.beginPath(),h.forEach((m,y)=>{if(!Number.isFinite(m)){u=!1;return}const v=J[y],T=k+(1-a.frac(m))*o;u?n.lineTo(v,T):n.moveTo(v,T),u=!0}),n.stroke(),h.forEach((m,y)=>{Number.isFinite(m)&&(n.beginPath(),n.arc(J[y],k+(1-a.frac(m))*o,D===y?4.5:3,0,Math.PI*2),n.fill())})}),D>=0&&(n.lineWidth=1,n.strokeStyle=l.textMuted,n.beginPath(),n.moveTo(J[D],k),n.lineTo(J[D],k+o),n.stroke())}function Ie(e){const l=s?.marks;if(!l||l.type!=="line"||!g||!s?.x||J.length===0)return null;const c=e.clientX-g.getBoundingClientRect().left;let r=-1,i=24;if(J.forEach((n,f)=>{const p=Math.abs(n-c);p<i&&(i=p,r=f)}),D=r,r<0)return null;const o=s.level==="die"?"dies":"wafers",t=l.values.map((n,f)=>({g:f,v:n[r],n:l.counts[f][r]})).filter(n=>Number.isFinite(n.v)&&(x.size===0||x.has(n.g)));return`<strong>${I(`${s.x.label}: ${R(l.xs[r],s.x.unit)}`)}</strong><br>`+t.map(n=>I(`${s.groups.length>1?`${ge(n.g)}: `:""}${R(n.v,s.y.unit)} (${n.n.toLocaleString("en-GB")} ${o})`)).join("<br>")}function W(){const e=s?.marks?.type;e==="scatter"?je():e==="histogram"?Ye():e==="box"||e==="bar"?Ge():e==="line"&&Ve()}function Ze(e){const l=s,c=b.waferLabel(e.item),r=e.die&&e.die.x!==void 0?`die (${e.die.x}, ${e.die.y})`:"",i=[c,r].filter(Boolean).join(" \xB7 "),o=(t,n)=>`${t.label}: ${R(n,t.unit)}`;return(i?`<strong>${I(i)}</strong><br>`:"")+I(o(l.x,e.x))+"<br>"+I(o(l.y,e.y))+(l.colorScale&&e.value!==void 0?`<br>${I(`${l.colorScale.label}: ${R(e.value,l.colorScale.unit)}`)}`:"")+(l.groups.length>1?`<br>${I(ge(e.group))}`:"")+(b.onOpenWafer?`<br><em>click to ${I(b.openActionLabel??"open this wafer")}</em>`:"")}function Ne(e){if(!K||!z||!s?.x)return null;const l=g.getBoundingClientRect(),r=(e.clientX-l.left-M)/z.plotW;if(r<0||r>1)return null;const i=z.x.reverse?1-r:r,{edges:o,counts:t}=K,n=o.length-1,f=Math.min(n-1,Math.max(0,Math.floor(i*n)));X=f;const p=s.x,d=t.map((a,h)=>({g:h,n:a[f]})).filter(a=>a.n>0&&(x.size===0||x.has(a.g)));return`<strong>${I(`${R(o[f],p.unit)} to ${R(o[f+1],p.unit)}`)}</strong><br>`+(d.length?d.map(a=>I(`${s.groups.length>1?`${ge(a.g)}: `:""}${a.n.toLocaleString("en-GB")} ${s.level==="die"?"dies":"wafers"}`)).join("<br>"):"none")}function Ke(e){const l=P.createElement("div");l.dataset.wmapPlotColorbar="1",Object.assign(l.style,{display:"flex",alignItems:"center",gap:ee.md,marginTop:ee.md,marginBottom:ee.xl,fontSize:we.body,color:xe.label});const c=P.createElement("canvas");c.width=200,c.height=10,Object.assign(c.style,{width:"200px",height:"10px",borderRadius:tt.control,flex:"0 0 auto"});const r=c.getContext("2d"),i=$e();if(r)for(let f=0;f<200;f++)r.fillStyle=i(f/199),r.fillRect(f,0,1,10);const o=P.createElement("span");o.textContent=R(e.lo,e.unit);const t=P.createElement("span");t.textContent=R(e.hi,e.unit);const n=P.createElement("span");return n.textContent=e.label,n.style.color=xe.text,l.append(n,o,c,t),l}const Se=(e,l,c)=>l.map(r=>{const{item:i,die:o}=e(r);return{x:0,y:0,group:c,item:i,die:o}});function Je(){V?.destroy(),V=void 0,w.innerHTML="",g=void 0,H=void 0,C=void 0,Z=[],ue=null,X=-1,ce=[],z=void 0,G=void 0,_=null,J=[],D=-1,de=he,x.clear(),Y.clear();const e=s;if(!e)return;if(q.textContent=et(e),$.dataset.wmapChartTitle=q.textContent,e.issues.length||!e.marks){for(const i of e.issues)Re(w,i);return}const l=P.createElement("div");Object.assign(l.style,{color:xe.label,fontSize:we.body,marginBottom:ee.xs});const c=b.openActionLabel??"open this wafer",r=e.marks.type==="scatter"?(b.onOpenWafer?` \xB7 click a point to ${c}`:"")+(b.onSelectPoints?` \xB7 drag to select ${e.level==="die"?"dies":"wafers"}`:""):e.marks.type==="box"||e.marks.type==="bar"?(b.onOpenWafer&&e.marks.categoryItems?` \xB7 click to ${c}`:b.onSelectPoints?" \xB7 click to chart or tabulate what it counts":"")+(e.marks.type==="box"?" \xB7 box: quartiles; whiskers: minimum and maximum":""):e.marks.type==="histogram"&&b.onSelectPoints?" \xB7 click a bar to chart or tabulate its dies":e.marks.type==="line"&&b.onSelectPoints?" \xB7 click a point to chart or tabulate what it averages":"";l.textContent=Fe(e)+r,ze(l),C=P.createElement("div"),C.style.display="flow-root",w.appendChild(C),C.appendChild(l);for(const i of e.notes){const o=P.createElement("div");ze(o),o.textContent=i,Object.assign(o.style,{color:xe.warnText,fontSize:we.body,marginBottom:ee.xs}),C.appendChild(o)}if(e.plotted===0){Re(w,"Nothing to plot: no mark has every value this plot needs.");return}if(e.groups.length>1&&(H=P.createElement("div"),Object.assign(H.style,{display:"flex",flexWrap:"wrap",gap:ee.sm,marginTop:ee.md,marginBottom:ee.xl}),e.groups.forEach((i,o)=>{const t=ft(i||"(none)",oe(o),P);lt(t.el,$,L,`${i||"(none)"} \u2014 click to filter`),t.el.addEventListener("click",()=>{x.has(o)?x.delete(o):x.add(o),Z.forEach((n,f)=>n.setState({selected:x.has(f),dimmed:x.size>0&&!x.has(f)})),W()}),Z.push(t),H.appendChild(t.el)}),C.appendChild(H)),e.colorScale&&(H=Ke(e.colorScale),C.appendChild(H)),g=P.createElement("canvas"),g.style.display="block",g.style.cursor=e.marks.type==="scatter"?"crosshair":"default",w.appendChild(g),e.marks.type==="scatter"){const i=e.marks,o=!!b.onSelectPoints;V=yt({card:$,canvas:g,tooltip:L,drawn:()=>ce,describe:Ze,clickable:()=>!!b.onOpenWafer,onOpen:t=>b.onOpenWafer?.(t.item,Oe(e.spec)),onHover:t=>{ue=t,W()},select:o?{pick:({x0:t,y0:n,x1:f,y1:p})=>z?(x.size===0?i.points:i.points.filter(a=>x.has(a.group))).filter(a=>{const h=M+z.x.frac(a.x)*z.plotW,S=k+(1-z.y.frac(a.y))*z.plotH;return h>=t&&h<=f&&S>=n&&S<=p}):[],onPicked:(t,n)=>{Y.clear();for(const f of t)Y.add(f);W(),t.length>0&&b.onSelectPoints(t,n,g)},onClear:()=>{Y.clear(),W()},hasSelection:()=>Y.size>0}:void 0})}else if(e.marks.type==="histogram"){Ue(),g.addEventListener("mousemove",o=>{const t=Ne(o);if(!t){L.style.display="none",X>=0&&(X=-1,W());return}L.innerHTML=t,L.style.display="block",Le(L,$,o.clientX,o.clientY),W()}),g.addEventListener("mouseleave",()=>{L.style.display="none",X>=0&&(X=-1,W())});const i=e.marks;b.onSelectPoints&&(g.style.cursor="pointer",g.addEventListener("click",o=>{if(!K||Ne(o)===null)return;const t=X,{edges:n}=K,f=z?.x.log,p=f?Math.log10(n[0]):n[0],d=f?Math.log10(n[n.length-1]):n[n.length-1],a=n.length-1,h=[];i.values.forEach((S,u)=>{if(x.size>0&&!x.has(u))return;const m=[];S.forEach((y,v)=>{const T=f?y>0?Math.log10(y):NaN:y;!Number.isFinite(T)||T<p||T>d||Math.min(a-1,Math.floor((T-p)/(d-p)*a))===t&&m.push(i.units[u][v])}),h.push(...Se(i.unit,m,u))}),h.length>0&&(L.style.display="none",b.onSelectPoints(h,{x:o.clientX,y:o.clientY},g))}))}else if(e.marks.type==="box"||e.marks.type==="bar"){const i=e.marks;g.addEventListener("mousemove",o=>{const t=Ae(o);if(g.style.cursor=t&&(i.categoryItems&&b.onOpenWafer||b.onSelectPoints&&i.cellUnits[t.g][t.c].length>0)?"pointer":"default",!t){L.style.display="none",_&&(_=null,W());return}L.innerHTML=De(t),L.style.display="block",Le(L,$,o.clientX,o.clientY),(!_||_.g!==t.g||_.c!==t.c)&&(_=t,W())}),g.addEventListener("mouseleave",()=>{L.style.display="none",_&&(_=null,W())}),g.addEventListener("click",o=>{const t=Ae(o);if(!t)return;const n=i.categoryItems?.[t.c];if(n!==void 0&&n>=0&&b.onOpenWafer){b.onOpenWafer(n,Oe(e.spec));return}const f=i.cellUnits[t.g][t.c];f.length>0&&b.onSelectPoints&&(L.style.display="none",b.onSelectPoints(Se(i.unit,f,t.g),{x:o.clientX,y:o.clientY},g))})}else if(e.marks.type==="line"){g.addEventListener("mousemove",o=>{const t=Ie(o);if(!t){L.style.display="none",W();return}L.innerHTML=t,L.style.display="block",Le(L,$,o.clientX,o.clientY),W()}),g.addEventListener("mouseleave",()=>{L.style.display="none",D>=0&&(D=-1,W())});const i=e.marks;b.onSelectPoints&&(g.style.cursor="pointer",g.addEventListener("click",o=>{if(Ie(o)===null||D<0)return;const t=i.cellUnits.flatMap((n,f)=>x.size>0&&!x.has(f)?[]:Se(i.unit,n[D]??[],f));t.length>0&&(L.style.display="none",b.onSelectPoints(t,{x:o.clientX,y:o.clientY},g))}))}W()}ne(()=>{const e=s;if(!e||!e.marks)return;const l=$e();return{caption:[Fe(e),...e.notes],legend:e.groups.length>1?e.groups.map((c,r)=>({label:c||"(none)",color:oe(r)})):void 0,colorbar:e.colorScale?{label:e.colorScale.label,lo:R(e.colorScale.lo,e.colorScale.unit),hi:R(e.colorScale.hi,e.colorScale.unit),color:l}:void 0}});const Qe=ot($,()=>W());return{card:$,actions:O,setPlot(e){s=e,Je()},destroy(){Qe.disconnect(),V?.destroy()}}}
@@ -0,0 +1,45 @@
1
+ /** A point as drawn, with its pixel position inside the canvas. */
2
+ export interface DrawnPoint<P> {
3
+ p: P;
4
+ cx: number;
5
+ cy: number;
6
+ }
7
+ export interface PointInteractionOptions<P> {
8
+ card: HTMLElement;
9
+ canvas: HTMLCanvasElement;
10
+ tooltip: HTMLElement;
11
+ /** The points as last drawn. Only these can be hit: a chart that samples a large population hides the rest. */
12
+ drawn: () => ReadonlyArray<DrawnPoint<P>>;
13
+ /** Finds the point a pointer is on by its own rule, in place of the nearest drawn point within a few pixels: a chart
14
+ * whose points are columns (one per wafer) is hit anywhere along the column, not only on the dot. */
15
+ locate?: (e: MouseEvent) => P | null;
16
+ /** Tooltip HTML for a point (already escaped). */
17
+ describe: (p: P) => string;
18
+ /** Whether a click on this point does anything. */
19
+ clickable: (p: P) => boolean;
20
+ onOpen?: (p: P) => void;
21
+ /** The point under the pointer changed (null when it left all points): redraw. */
22
+ onHover: (p: P | null) => void;
23
+ /** Present to enable drag-to-select. */
24
+ select?: {
25
+ /** The points inside a rectangle given in canvas pixels. */
26
+ pick: (rect: {
27
+ x0: number;
28
+ y0: number;
29
+ x1: number;
30
+ y1: number;
31
+ }) => P[];
32
+ /** A drag finished: the chart records the selection, redraws and tells its host. `at` is the pointer in the viewport. */
33
+ onPicked: (picked: P[], at: {
34
+ x: number;
35
+ y: number;
36
+ }) => void;
37
+ /** A click on empty space while a selection stands. */
38
+ onClear: () => void;
39
+ hasSelection: () => boolean;
40
+ };
41
+ }
42
+ export declare function wirePointInteractions<P>(o: PointInteractionOptions<P>): {
43
+ destroy: () => void;
44
+ };
45
+ //# sourceMappingURL=pointInteractions.d.ts.map
@@ -0,0 +1 @@
1
+ import{CLR as E}from"../toolbar.js";import{positionChartTooltip as L}from"./chartShell.js";const M=7,k=4;export function wirePointInteractions(n){const{card:u,canvas:r,tooltip:a}=n;let d=null;const x=t=>{if(n.locate)return n.locate(t);const e=r.getBoundingClientRect(),s=t.clientX-e.left,l=t.clientY-e.top;let p=null,i=M*M;for(const o of n.drawn()){const c=o.cx-s,h=o.cy-l,y=c*c+h*h;y<=i&&(i=y,p=o.p)}return p};r.addEventListener("mousemove",t=>{const e=x(t);if(r.style.cursor=e&&n.clickable(e)?"pointer":"crosshair",!e){a.style.display="none",d&&(d=null,n.onHover(null));return}a.innerHTML=n.describe(e),a.style.display="block",L(a,u,t.clientX,t.clientY),d!==e&&(d=e,n.onHover(e))}),r.addEventListener("mouseleave",()=>{a.style.display="none",d&&(d=null,n.onHover(null))});let b=!1;const m=u.ownerDocument.createElement("div");Object.assign(m.style,{position:"absolute",display:"none",pointerEvents:"none",zIndex:"40",boxSizing:"border-box",border:`1px dashed ${E.iconActive}`,background:"rgba(120,150,200,0.15)"}),u.appendChild(m);const f=n.select;return f&&r.addEventListener("mousedown",t=>{if(t.button!==0)return;b=!1;const e=r.getBoundingClientRect(),s=t.clientX-e.left,l=t.clientY-e.top;let p=!1,i=s,o=l;const c=u.ownerDocument,h=v=>{if(i=Math.min(Math.max(v.clientX-e.left,0),e.width),o=Math.min(Math.max(v.clientY-e.top,0),e.height),!p&&Math.hypot(i-s,o-l)<k)return;p=!0,a.style.display="none";const g=u.getBoundingClientRect();Object.assign(m.style,{display:"block",left:`${e.left-g.left+Math.min(s,i)}px`,top:`${e.top-g.top+Math.min(l,o)}px`,width:`${Math.abs(i-s)}px`,height:`${Math.abs(o-l)}px`})},y=v=>{c.removeEventListener("mousemove",h),c.removeEventListener("mouseup",y),m.style.display="none",p&&(b=!0,f.onPicked(f.pick({x0:Math.min(s,i),y0:Math.min(l,o),x1:Math.max(s,i),y1:Math.max(l,o)}),{x:v.clientX,y:v.clientY}))};c.addEventListener("mousemove",h),c.addEventListener("mouseup",y)}),r.addEventListener("click",t=>{if(b){b=!1;return}const e=x(t);if(!e){f?.hasSelection()&&f.onClear();return}n.clickable(e)&&n.onOpen?.(e)}),{destroy:()=>m.remove()}}
@@ -6,6 +6,8 @@ export interface RegionYieldDiagramOptions {
6
6
  mode: RegionYieldMode;
7
7
  /** Ring mode: ordered ring 1 (core) → ring N (edge), matching `buildRegionYieldData`'s output for `buildRingRegions`. Quadrant mode: any order — each row's quadrant is read from its `key` (`quadrant:NE` etc.), not position. */
8
8
  rows: RegionYieldDatum[];
9
+ /** Click a region: called with it and the click, so the host can pick out the dies it counts and open a menu there. */
10
+ onSelectRegion?: (row: RegionYieldDatum, e: MouseEvent) => void;
9
11
  onSaveImage?: SaveImageHandler;
10
12
  /** Document to build this panel's DOM into. Default `document` — pass the
11
13
  * host's own `ownerDocument` when the container might live in a