@wafertools/wafermap 0.30.1 → 0.30.3

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 (94) hide show
  1. package/AGENTS.md +71 -11
  2. package/CHANGELOG.md +464 -8
  3. package/README.md +6 -3
  4. package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
  5. package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
  6. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
  7. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -1
  9. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +81 -7
  12. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  13. package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
  14. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
  15. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  16. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  17. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  18. package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
  19. package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
  21. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  22. package/dist/packages/canvas-adapter/chunked.d.ts +45 -0
  23. package/dist/packages/canvas-adapter/chunked.js +1 -0
  24. package/dist/packages/canvas-adapter/dieList.js +2 -2
  25. package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
  26. package/dist/packages/canvas-adapter/drilldown.js +1 -0
  27. package/dist/packages/canvas-adapter/icons.js +1 -1
  28. package/dist/packages/canvas-adapter/insightsTab.d.ts +55 -3
  29. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  30. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +55 -0
  31. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  32. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  33. package/dist/packages/canvas-adapter/summaryPanel.d.ts +55 -9
  34. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  35. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  36. package/dist/packages/canvas-adapter/toolbar.d.ts +30 -2
  37. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  38. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  39. package/dist/packages/canvas-adapter/userGuideHtml.js +126 -3
  40. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  41. package/dist/packages/canvas-adapter/version.js +1 -1
  42. package/dist/packages/canvas-adapter/warnings.js +1 -1
  43. package/dist/packages/core/aggregates.js +1 -1
  44. package/dist/packages/core/utils.d.ts +55 -0
  45. package/dist/packages/core/utils.js +1 -1
  46. package/dist/packages/renderer/axisTicks.d.ts +32 -0
  47. package/dist/packages/renderer/axisTicks.js +1 -0
  48. package/dist/packages/renderer/binColors.d.ts +14 -0
  49. package/dist/packages/renderer/binColors.js +1 -1
  50. package/dist/packages/renderer/buildView.d.ts +11 -37
  51. package/dist/packages/renderer/buildView.js +1 -1
  52. package/dist/packages/renderer/buildWaferMap.d.ts +58 -1
  53. package/dist/packages/renderer/buildWaferMap.js +1 -1
  54. package/dist/packages/renderer/deprecate.d.ts +13 -5
  55. package/dist/packages/renderer/deprecate.js +1 -1
  56. package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
  57. package/dist/packages/renderer/derivedTests/apply.js +1 -0
  58. package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
  59. package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
  60. package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
  61. package/dist/packages/renderer/derivedTests/parser.js +2 -0
  62. package/dist/packages/renderer/fmt.d.ts +24 -2
  63. package/dist/packages/renderer/fmt.js +1 -1
  64. package/dist/packages/renderer/index.d.ts +1 -0
  65. package/dist/packages/renderer/index.js +1 -1
  66. package/dist/packages/renderer/spec.d.ts +49 -0
  67. package/dist/packages/renderer/spec.js +1 -0
  68. package/dist/packages/renderer/testLabel.d.ts +154 -0
  69. package/dist/packages/renderer/testLabel.js +1 -0
  70. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  71. package/dist/packages/stats/capability.d.ts +60 -0
  72. package/dist/packages/stats/capability.js +1 -1
  73. package/dist/packages/stats/correlation.d.ts +33 -0
  74. package/dist/packages/stats/correlation.js +1 -1
  75. package/dist/packages/stats/deprecated.d.ts +3 -0
  76. package/dist/packages/stats/deprecated.js +1 -1
  77. package/dist/packages/stats/findingsNarrative.js +1 -1
  78. package/dist/packages/stats/index.d.ts +2 -2
  79. package/dist/packages/stats/index.js +1 -1
  80. package/dist/packages/stats/math.d.ts +27 -2
  81. package/dist/packages/stats/math.js +1 -1
  82. package/dist/packages/stats/mergeTestDefs.js +1 -1
  83. package/dist/packages/stats/renderFindingsReport.js +8 -28
  84. package/dist/packages/stats/renderSummaryReport.js +22 -31
  85. package/dist/packages/stats/reportHtml.d.ts +27 -0
  86. package/dist/packages/stats/reportHtml.js +35 -14
  87. package/dist/packages/stats/sweep.d.ts +178 -0
  88. package/dist/packages/stats/sweep.js +1 -0
  89. package/dist/packages/stats/sweepXFromName.d.ts +27 -0
  90. package/dist/packages/stats/sweepXFromName.js +1 -0
  91. package/dist/packages/stats/testPassRate.js +1 -1
  92. package/dist/packages/stats/types.d.ts +28 -1
  93. package/llms.txt +14 -8
  94. package/package.json +2 -2
@@ -72,7 +72,7 @@ export const USER_GUIDE_HTML=`<div class="wmap-guide">
72
72
  .wmap-guide-toc{display:none!important}
73
73
  }
74
74
  </style>
75
- <h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-09-17T07:11:09.912Z">v0.30.1</span></h1>
75
+ <h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-09-23T17:22:32.765Z">v0.30.3</span></h1>
76
76
  <p class="wmap-guide-online-link">This is a quick reference. <a href="https://wafertools.github.io/wafermap/user-guide/" target="_blank" rel="noopener">View the full illustrated guide online \u2197</a></p>
77
77
  <p>This guide describes the display and analysis features of the wafer map viewer.
78
78
  It is written for users who may be semiconductor test engineers, device engineers, and yield engineers
@@ -248,11 +248,25 @@ no gradient to fall back to). <strong>Test pass/fail</strong> is also available
248
248
  Overlays option on an ordinary parametric test when it carries recorded
249
249
  verdicts, as an alternative to spec-limit judgement.</p>
250
250
  <div data-wmap-demo="value-heatmap" class="wmap-demo"></div><div data-wmap-demo="spec-passfail" class="wmap-demo"></div><hr>
251
+ <h3 id="derived-tests">Derived tests (\u2020)</h3>
252
+ <p>A test name marked <strong>\u2020</strong> in front is a <em>derived</em> test: its value was calculated
253
+ from other tests on the same die \u2014 a difference, a ratio, a margin \u2014 rather
254
+ than measured by the tester. It behaves like any other test: you can map it,
255
+ chart it and find patterns in it.</p>
256
+ <p>The dagger appears wherever the test is named \u2014 the map title, tooltips, the
257
+ plot-mode test list, Insights charts and their test pickers, findings, the
258
+ Summary panel, the die list and reports \u2014 always in front of the name, so in a
259
+ list the derived tests line up in a column down the left edge. The words
260
+ <strong>\u2020 Derived, not measured</strong> always appear nearby. Hovering over a derived
261
+ value, or reading the key line under a table or findings list, shows the
262
+ expression it was computed from, e.g. <code>t[1020] - t[1010]</code>. The numbers in
263
+ square brackets are the test numbers it reads. In a CSV export the expression
264
+ appears in a <strong>Derived from</strong> column instead.</p>
251
265
  <h2 id="3-toolbar-controls">3. Toolbar controls</h2>
252
266
  <p>The toolbar sits at the top-right of the map and is always visible. Controls that
253
267
  are not applicable to the current mode are hidden automatically. (Hovering the map
254
268
  shows die tooltips \u2014 a separate thing from the toolbar, which is always present.)</p>
255
- <p><img src="data:image/png;base64,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" alt="Single-map toolbar"></p>
269
+ <p><img src="data:image/png;base64,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" alt="Single-map toolbar"></p>
256
270
  <h3 id="single-map">Single map</h3>
257
271
  <table>
258
272
  <thead>
@@ -328,6 +342,11 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
328
342
  <td>Click and drag to select a rectangular group of dies (see <a href="#43-box-select" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'43-box-select\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 4.3</a>).</td>
329
343
  </tr>
330
344
  <tr>
345
+ <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMyAzdjE4aDE4Ii8+PHBhdGggZD0iTTcgMTZjMy0xIDQtOSA3LTlzMyA1IDYgNSIvPjwvc3ZnPgo=" width="20" height="20"></td>
346
+ <td>Chart</td>
347
+ <td>Opens a menu of charts drawn from the selected dies, or from the whole wafer when nothing is selected (see <a href="#44-charting-dies-and-wafers" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'44-charting-dies-and-wafers\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 4.4</a>). Only shown when there is something to chart.</td>
348
+ </tr>
349
+ <tr>
331
350
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMyAzdjE4aDE4Ii8+PHBhdGggZD0iTTE4IDE3VjkiLz48cGF0aCBkPSJNMTMgMTdWNSIvPjxwYXRoIGQ9Ik04IDE3di0zIi8+PC9zdmc+Cg==" width="20" height="20"></td>
332
351
  <td>Insights</td>
333
352
  <td>Swaps the map for this wafer&#39;s own chart suite \u2014 yield, bin breakdown, process capability, and more (see <a href="#8-insights-tab" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'8-insights-tab\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 8</a>). Only shown when the application has enabled it.</td>
@@ -414,6 +433,10 @@ highlighting one bin at a time.</p>
414
433
  <td>Add a die to the current selection</td>
415
434
  </tr>
416
435
  <tr>
436
+ <td><strong>Right-click</strong>, <strong>Menu key</strong> or <strong>Shift + F10</strong></td>
437
+ <td>Chart the selected dies, or the whole wafer (see <a href="#44-charting-dies-and-wafers" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'44-charting-dies-and-wafers\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 4.4</a>)</td>
438
+ </tr>
439
+ <tr>
417
440
  <td><strong>Mouse wheel / scroll</strong></td>
418
441
  <td>Zoom in and out at the cursor</td>
419
442
  </tr>
@@ -515,6 +538,9 @@ highlighting one bin at a time.</p>
515
538
  <td>Opens this guide.</td>
516
539
  </tr>
517
540
  </tbody></table>
541
+ <p>Right-click a card anywhere outside its map to chart that wafer on its own \u2014 a
542
+ histogram, process capability or a sweep (see
543
+ <a href="#44-charting-dies-and-wafers" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'44-charting-dies-and-wafers\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 4.4</a>).</p>
518
544
  <p>Click a card&#39;s expand button to detach it into its own separate window with the
519
545
  complete single-map toolbar (falls back to a floating window inside the page if
520
546
  separate windows aren&#39;t available in your environment). The vacated grid card
@@ -546,6 +572,48 @@ Use <strong>Ctrl / Cmd + click</strong> to add individual dies to the current se
546
572
  Press <strong>Esc</strong> to clear.</p>
547
573
  <div data-wmap-demo="box-select" class="wmap-demo"></div><p><em>A block of dies near the centre is shown pre-selected. Choose Box select in the
548
574
  toolbar and drag to make your own selection, or Ctrl/Cmd + click individual dies.</em></p>
575
+ <h3 id="44-charting-dies-and-wafers">4.4 Charting dies and wafers</h3>
576
+ <p><strong>Right-click</strong> a population to open a menu of charts drawn from just those
577
+ dies. Picking one opens it in a window over the map. This is how you look at a
578
+ cluster, a scratch or an edge region on its own, or at one wafer out of a lot.</p>
579
+ <p>What you right-click decides the population:</p>
580
+ <ul>
581
+ <li><strong>Selected dies</strong> \u2014 select some dies, then right-click on the map. Right-
582
+ clicking a die that is <strong>not</strong> selected selects that die alone first;
583
+ right-clicking a selected die, or empty space, keeps the selection.</li>
584
+ <li><strong>A whole wafer</strong> \u2014 right-click empty map space with nothing selected, a
585
+ gallery card anywhere outside its map (its header, the space around the map),
586
+ or one wafer&#39;s bar, box or point in an Insights chart (<em>Yield by wafer</em>,
587
+ <em>Test value distribution</em>, <em>Wafer-to-wafer trend</em> \u2014 their tooltips say
588
+ &quot;right-click to chart this wafer&quot;).</li>
589
+ </ul>
590
+ <p>On the map you can also press the <strong>Menu</strong> key or <strong>Shift + F10</strong>, or use the
591
+ <strong>Chart</strong> toolbar button, which charts the selection when there is one and the
592
+ wafer otherwise.</p>
593
+ <p>The menu offers:</p>
594
+ <ul>
595
+ <li><strong>Value histogram</strong> \u2014 one test&#39;s distribution, opening on the test the map
596
+ is showing.</li>
597
+ <li><strong>Process capability</strong> \u2014 every test normalised to its spec limits, with its
598
+ Ppk. Below 30 dies the chart says each Ppk is a rough estimate: a Ppk from a
599
+ handful of dies can be far from the process&#39;s real capability.</li>
600
+ <li><strong>Sweeps</strong> your application has defined (see <a href="#sweep-cards" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'sweep-cards\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Sweep cards</a>).</li>
601
+ </ul>
602
+ <p>What every chart opened this way does:</p>
603
+ <ul>
604
+ <li>It states its population \u2014 how many dies and which wafer they came from \u2014 in
605
+ its title and above the plot, so it cannot be mistaken for a chart of the
606
+ whole lot. Partial and edge-excluded dies are left out, as they are
607
+ everywhere else, and the chart says how many that was.</li>
608
+ <li>It is a <strong>snapshot</strong>: changing the selection afterwards does not change a
609
+ chart that is already open.</li>
610
+ <li>A chart that cannot be drawn from the population stays in the menu, greyed,
611
+ with the reason \u2014 for instance when none of the dies has values for its tests,
612
+ or when the map is a lot stack, whose dies are averages across wafers rather
613
+ than measured dies.</li>
614
+ </ul>
615
+ <p>A map with only bin data and no sweeps has nothing to chart, and right-click
616
+ there does what it normally does.</p>
549
617
  <hr>
550
618
  <h2 id="5-findings-panel">5. Findings panel</h2>
551
619
  <p>When spatial analysis has been run, the <strong>Findings</strong> panel lists statistically
@@ -756,7 +824,12 @@ toolbar. Click it again to return to the map. Independent of the Findings
756
824
  sidebar (Section 6) \u2014 the two toggle independently, and opening one never
757
825
  hides the other&#39;s toolbar button, since Findings has nothing to highlight
758
826
  against once the map is replaced.</p>
759
- <p>Insights is organized into three sub-tabs:</p>
827
+ <p>Any chart mark that stands for one wafer \u2014 a bar in <em>Yield by wafer</em>, a box in
828
+ <em>Test value distribution</em>, a point in <em>Wafer-to-wafer trend</em> \u2014 can be
829
+ right-clicked to chart that wafer on its own (see
830
+ <a href="#44-charting-dies-and-wafers" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'44-charting-dies-and-wafers\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 4.4</a>); its tooltip says so.</p>
831
+ <p>Insights is organised into three sub-tabs, plus a fourth, <strong>Sweeps</strong>, when
832
+ the application has defined any sweeps (see <em>Sweep cards</em> below):</p>
760
833
  <ul>
761
834
  <li><p><strong>Overview</strong> \u2014 a <strong>test pass rate</strong> chart showing which test fails most (and,
762
835
  with &quot;Group by&quot; active, whether it fails more in one split than another). It
@@ -830,6 +903,56 @@ box drives the box plot and histogram onto that test.</em></p>
830
903
  positive, orange = negative; intensity = strength) and a die-level scatter
831
904
  plot coloured by hard bin. Clicking a matrix cell drives the scatter plot
832
905
  onto that pair.</em></p>
906
+ <h3 id="sweep-cards">Sweep cards</h3>
907
+ <p>A <strong>sweep</strong> appears in its own <strong>Sweeps</strong> sub-tab when your application has
908
+ defined one. It exists for a test program that measures the same quantity at a
909
+ series of drive levels and records each level as its own test number \u2014 often one
910
+ block sweeping up and another sweeping down. Read one test at a time that is a
911
+ row of unrelated distributions; read as a sweep it is a pair of response curves,
912
+ and the card measures the pair.</p>
913
+ <p><strong>Each line is the population median, with a shaded p10\u2013p90 band</strong> \u2014 not one
914
+ trace per die. A lot is thousands of dies, so per-die traces would be a solid
915
+ block of ink. The band is the spread across the dies currently in scope, so a
916
+ band that widens at one end of the sweep is telling you the population disagrees
917
+ most at that level.</p>
918
+ <p><strong>The dies behind it are whatever Insights is scoped to.</strong> A sweep names tests,
919
+ not dies \u2014 so the group or wafer selected in the panels above it decides the
920
+ population, and the card&#39;s die count states what that came to. To sweep a
921
+ hand-picked set of dies instead, select them on a map and right-click (see
922
+ <a href="#44-charting-dies-and-wafers" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'44-charting-dies-and-wafers\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 4.4</a>).</p>
923
+ <p>Two measurements are drawn on the plot and restated underneath:</p>
924
+ <ul>
925
+ <li><strong>Crossing</strong> \u2014 where the first two curves meet. If they cross more than once
926
+ the card says so rather than reporting the first as though it were the only
927
+ one.</li>
928
+ <li><strong>Separation</strong> \u2014 the horizontal distance between the two curves at the levels
929
+ the application asked about. With one curve rising and one falling the pair
930
+ traces a V, and this is the width of that V. A level that one of the curves
931
+ never reaches reads <strong>not measurable</strong>, naming the curve \u2014 never <code>0</code>, which
932
+ would read as &quot;they meet here&quot;.</li>
933
+ </ul>
934
+ <p>Only the <strong>first two</strong> curves are measured. Any further ones are drawn for
935
+ context.</p>
936
+ <p><strong>The x axis is the sweep, not the lot</strong> \u2014 that is what distinguishes this card
937
+ from the wafer-to-wafer trend, which walks one test across wafers. If the
938
+ application supplied the real swept quantity, the axis is in those units (dBm,
939
+ volts, \xB0C) and the crossing is reported in them. If it did not, the axis is the
940
+ position in the sequence, labelled by test, because test numbers are identifiers
941
+ \u2014 treating them as a scale would invent spacing the data never claimed.</p>
942
+ <p>Some sweeps step by multiples \u2014 thresholds of 1k, 2k, 5k \u2026 1M \u2014 and are drawn
943
+ on a <strong>logarithmic</strong> x axis, so each step gets the same room. There the crossing
944
+ is found along the log axis, and a width is given as a <strong>ratio</strong> between the two
945
+ curves, with the level each reaches: <em>\xD72.49 (15.9 k\u03A9 \u2192 39.7 k\u03A9)</em>. On a log axis
946
+ the same shift is the same multiple anywhere along it, which a difference in \u03A9
947
+ would hide.</p>
948
+ <p>The card footer reports anything that limits what you are seeing: tests in the
949
+ sweep that have no definition, functional (pass/fail) tests inside a sweep,
950
+ which have no value to plot, and series whose tests carry different units.</p>
951
+ <p>If the swept values no longer line up with the tests \u2014 usually because a test in
952
+ the sweep is missing from this data \u2014 the footer lists the tests it did find, and
953
+ the card is drawn in test order with <strong>the crossing and widths not measured</strong>.
954
+ Pairing the remaining values with the remaining tests would put every later point
955
+ at the wrong level, which gives a plausible curve and a wrong crossing.</p>
833
956
  <h3 id="exporting-a-chart">Exporting a chart</h3>
834
957
  <p>Every chart panel has a <strong>camera</strong> button that saves the current view as a PNG at the displayed
835
958
  resolution. To get a clean full-resolution render, use the panel&#39;s expand (corner-arrows)
@@ -1,3 +1,3 @@
1
- export declare const WMAP_VERSION = "0.30.1";
2
- export declare const WMAP_BUILD_TIME = "2026-09-17T07:12:45.499Z";
1
+ export declare const WMAP_VERSION = "0.30.3";
2
+ export declare const WMAP_BUILD_TIME = "2026-09-23T17:24:44.719Z";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- export const WMAP_VERSION="0.30.1",WMAP_BUILD_TIME="2026-09-17T07:12:45.499Z";
1
+ export const WMAP_VERSION="0.30.3",WMAP_BUILD_TIME="2026-09-23T17:24:44.719Z";
@@ -1 +1 @@
1
- import{SHADOW as S,TRACKING as O,SPACE as s,RADIUS as v,FONT as y,CLR as d,Z_BASE as B}from"./toolbar.js";const E={error:0,warning:1,info:2};export function severityOf(t){return t.severity??"warning"}export function collectWarnings(t){const o=[],i=new Set,n=e=>{if(!e?.message)return;const r=`${e.code}\0${e.message}`;i.has(r)||(i.add(r),o.push(e))};for(const e of t.result?.warnings??[])n(e);for(const e of t.statsSummary?.stats.warnings??[])n(e);for(const e of t.lotStatsSummary?.perWafer??[])for(const r of e.summary.stats.warnings??[])n(r);for(const e of t.extra??[])n(e);return o.sort((e,r)=>E[severityOf(e)]-E[severityOf(r)])}function $(t){return t==="error"?{bg:d.errBg,border:d.errBorder,text:d.errText}:{bg:d.warnBg,border:d.warnBorder,text:d.warnText}}function w(t){return t==="error"?"\u26D4":t==="info"?"\u2139":"\u26A0"}export const SHORT_LABEL={"partial-coverage":"Partial wafer coverage","geometry-conflict":"Geometry conflict","non-standard-diameter":"Non-standard wafer diameter","diameter-exceeds-die-extent":"Diameter exceeds die extent","edge-exclusion-exceeds-radius":"Edge exclusion exceeds radius","test-count-capped":"Test analysis skipped","test-def-collision":"Test definitions disagree","analysis-option-corrected":"Analysis option corrected","bin-colors-shared":"Bins share a colour","pass-bins-mixed":"Pass bins differ by wafer","ring-count-mixed":"Ring counts differ by wafer","input-field-removed":"Removed input name ignored","input-values-not-numbers":"Bins or test values are text"};function C(t){const o=t.code?SHORT_LABEL[t.code]:void 0;if(o)return o;const i=t.message.split(/(?<=\.)\s/)[0]??t.message;return i.length>60?`${i.slice(0,57)}\u2026`:i}export function buildWarningsBanner(t,o=document){const i=o.createElement("div");Object.assign(i.style,{marginBottom:s.lg});for(const n of t){const e=severityOf(n),r=$(e),m=o.createElement("div");Object.assign(m.style,{background:r.bg,border:`1px solid ${r.border}`,borderRadius:v.control,marginBottom:s.sm,fontSize:y.body,color:r.text,lineHeight:"1.5"}),m.setAttribute("role","status");const a=o.createElement("button");a.type="button",Object.assign(a.style,{display:"flex",alignItems:"center",gap:s.sm,width:"100%",background:"none",border:"none",font:"inherit",color:"inherit",textAlign:"left",padding:`${s.sm} ${s.lg}`,cursor:"pointer"});const g=o.createElement("span");Object.assign(g.style,{flex:"1",minWidth:"0"}),g.textContent=`${w(e)} ${C(n)}`;const l=o.createElement("span");Object.assign(l.style,{flexShrink:"0"}),a.append(g,l);const p=o.createElement("div");Object.assign(p.style,{padding:`0 ${s.lg} ${s.sm}`}),p.textContent=n.message;let c=!1;const f=()=>{l.textContent=c?"\u25B4":"\u25BE",p.style.display=c?"block":"none",a.setAttribute("aria-expanded",String(c)),a.setAttribute("aria-label",`${C(n)}. ${c?"Click to collapse.":"Click to expand for detail."}`)};f(),a.addEventListener("click",()=>{c=!c,f()}),a.addEventListener("mouseenter",()=>{l.style.opacity="0.65"}),a.addEventListener("mouseleave",()=>{l.style.opacity="1"}),m.append(a,p),i.appendChild(m)}return i}export function buildWarningsMenuEl(t,o,i=window){const n=i.document,e=n.createElement("div"),r=280,a=t.left+r<=(i.innerWidth??1/0)?t.left:Math.max(4,t.right-r);Object.assign(e.style,{position:"fixed",top:`${t.bottom+4}px`,left:`${a}px`,background:d.menuBg,border:`1px solid ${d.menuBorder}`,borderRadius:v.control,boxShadow:S.menu,zIndex:B,width:`${r}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),e.setAttribute("role","dialog"),e.setAttribute("aria-label","Data warnings");const g=n.createElement("div");Object.assign(g.style,{padding:`${s.sm} ${s.xl} ${s.xs}`,fontSize:y.body,fontWeight:"700",letterSpacing:O,textTransform:"uppercase",color:d.label}),g.textContent=o.length===1?"1 warning":`${o.length} warnings`,e.appendChild(g);for(const l of o){const p=severityOf(l),c=$(p),f=n.createElement("div");Object.assign(f.style,{display:"flex",gap:s.md,padding:`${s.md} ${s.xl}`,fontSize:y.body,lineHeight:"1.5",color:d.text,borderTop:`1px solid ${d.menuBorder}`,alignItems:"flex-start"});const x=n.createElement("span");Object.assign(x.style,{color:c.text,flexShrink:"0"}),x.textContent=w(p),f.appendChild(x);const u=n.createElement("div"),b=n.createElement("div");Object.assign(b.style,{fontWeight:"700",color:c.text,marginBottom:s.xxs}),b.textContent=l.code,u.appendChild(b);const h=n.createElement("div");h.textContent=l.message,u.appendChild(h),f.appendChild(u),e.appendChild(f)}return e}
1
+ import{SHADOW as S,TRACKING as O,SPACE as s,RADIUS as v,FONT as y,CLR as d,Z_BASE as B}from"./toolbar.js";const E={error:0,warning:1,info:2};export function severityOf(t){return t.severity??"warning"}export function collectWarnings(t){const o=[],i=new Set,n=e=>{if(!e?.message)return;const r=`${e.code}\0${e.message}`;i.has(r)||(i.add(r),o.push(e))};for(const e of t.result?.warnings??[])n(e);for(const e of t.statsSummary?.stats.warnings??[])n(e);for(const e of t.lotStatsSummary?.perWafer??[])for(const r of e.summary.stats.warnings??[])n(r);for(const e of t.extra??[])n(e);return o.sort((e,r)=>E[severityOf(e)]-E[severityOf(r)])}function $(t){return t==="error"?{bg:d.errBg,border:d.errBorder,text:d.errText}:{bg:d.warnBg,border:d.warnBorder,text:d.warnText}}function w(t){return t==="error"?"\u26D4":t==="info"?"\u2139":"\u26A0"}export const SHORT_LABEL={"partial-coverage":"Partial wafer coverage","geometry-conflict":"Geometry conflict","non-standard-diameter":"Non-standard wafer diameter","diameter-exceeds-die-extent":"Diameter exceeds die extent","edge-exclusion-exceeds-radius":"Edge exclusion exceeds radius","test-count-capped":"Test analysis skipped","test-def-collision":"Test definitions disagree","analysis-option-corrected":"Analysis option corrected","bin-colors-shared":"Bins share a colour","pass-bins-mixed":"Pass bins differ by wafer","ring-count-mixed":"Ring counts differ by wafer","input-field-removed":"Removed input name ignored","input-values-not-numbers":"Bins or test values are text","derived-test-invalid":"Derived test not applied"};function C(t){const o=t.code?SHORT_LABEL[t.code]:void 0;if(o)return o;const i=t.message.split(/(?<=\.)\s/)[0]??t.message;return i.length>60?`${i.slice(0,57)}\u2026`:i}export function buildWarningsBanner(t,o=document){const i=o.createElement("div");Object.assign(i.style,{marginBottom:s.lg});for(const n of t){const e=severityOf(n),r=$(e),m=o.createElement("div");Object.assign(m.style,{background:r.bg,border:`1px solid ${r.border}`,borderRadius:v.control,marginBottom:s.sm,fontSize:y.body,color:r.text,lineHeight:"1.5"}),m.setAttribute("role","status");const a=o.createElement("button");a.type="button",Object.assign(a.style,{display:"flex",alignItems:"center",gap:s.sm,width:"100%",background:"none",border:"none",font:"inherit",color:"inherit",textAlign:"left",padding:`${s.sm} ${s.lg}`,cursor:"pointer"});const p=o.createElement("span");Object.assign(p.style,{flex:"1",minWidth:"0"}),p.textContent=`${w(e)} ${C(n)}`;const l=o.createElement("span");Object.assign(l.style,{flexShrink:"0"}),a.append(p,l);const g=o.createElement("div");Object.assign(g.style,{padding:`0 ${s.lg} ${s.sm}`}),g.textContent=n.message;let c=!1;const f=()=>{l.textContent=c?"\u25B4":"\u25BE",g.style.display=c?"block":"none",a.setAttribute("aria-expanded",String(c)),a.setAttribute("aria-label",`${C(n)}. ${c?"Click to collapse.":"Click to expand for detail."}`)};f(),a.addEventListener("click",()=>{c=!c,f()}),a.addEventListener("mouseenter",()=>{l.style.opacity="0.65"}),a.addEventListener("mouseleave",()=>{l.style.opacity="1"}),m.append(a,g),i.appendChild(m)}return i}export function buildWarningsMenuEl(t,o,i=window){const n=i.document,e=n.createElement("div"),r=280,a=t.left+r<=(i.innerWidth??1/0)?t.left:Math.max(4,t.right-r);Object.assign(e.style,{position:"fixed",top:`${t.bottom+4}px`,left:`${a}px`,background:d.menuBg,border:`1px solid ${d.menuBorder}`,borderRadius:v.control,boxShadow:S.menu,zIndex:B,width:`${r}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),e.setAttribute("role","dialog"),e.setAttribute("aria-label","Data warnings");const p=n.createElement("div");Object.assign(p.style,{padding:`${s.sm} ${s.xl} ${s.xs}`,fontSize:y.body,fontWeight:"700",letterSpacing:O,textTransform:"uppercase",color:d.label}),p.textContent=o.length===1?"1 warning":`${o.length} warnings`,e.appendChild(p);for(const l of o){const g=severityOf(l),c=$(g),f=n.createElement("div");Object.assign(f.style,{display:"flex",gap:s.md,padding:`${s.md} ${s.xl}`,fontSize:y.body,lineHeight:"1.5",color:d.text,borderTop:`1px solid ${d.menuBorder}`,alignItems:"flex-start"});const x=n.createElement("span");Object.assign(x.style,{color:c.text,flexShrink:"0"}),x.textContent=w(g),f.appendChild(x);const u=n.createElement("div"),b=n.createElement("div");Object.assign(b.style,{fontWeight:"700",color:c.text,marginBottom:s.xxs}),b.textContent=l.code,u.appendChild(b);const h=n.createElement("div");h.textContent=l.message,u.appendChild(h),f.appendChild(u),e.appendChild(f)}return e}
@@ -1 +1 @@
1
- import{getDieKey as g}from"./dies.js";export function aggregateValues(u,s,l=0){if(!u.length)return[];const n=new Map,t=new Map;for(const d of u)for(const o of d){const e=g(o),f=o.testValues?.[l];f!==void 0?(n.has(e)||(n.set(e,[]),t.set(e,o)),n.get(e).push(f)):t.has(e)||t.set(e,o)}const r=[];for(const[d,o]of t){const e=n.get(d);if(!e?.length){r.push({...o,testValues:void 0,testPass:void 0});continue}let f;if(s==="mean")f=e.reduce((i,a)=>i+a,0)/e.length;else if(s==="median"){const i=[...e].sort((c,h)=>c-h),a=Math.floor(i.length/2);f=i.length%2?i[a]:(i[a-1]+i[a])/2}else if(s==="stddev"){if(e.length<2){r.push({...o,testValues:{0:0},testPass:void 0});continue}const i=e.reduce((a,c)=>a+c,0)/e.length;f=Math.sqrt(e.reduce((a,c)=>a+(c-i)**2,0)/(e.length-1))}else s==="min"?f=Math.min(...e):s==="max"?f=Math.max(...e):f=e.length;r.push({...o,testValues:{0:f},testPass:void 0})}return r}export function getUniqueBins(u,s="hard"){const l=new Set;for(const n of u){const t=s==="soft"?n.sbin:n.hbin;t!==void 0&&l.add(t)}return[...l].sort((n,t)=>n-t)}export function aggregateBinCounts(u,s,l="hard"){if(!u.length)return[];const n=new Map;for(const t of u)for(const r of t)if((l==="soft"?r.sbin:r.hbin)===s){const o=g(r);n.set(o,(n.get(o)??0)+1)}return(u[0]??[]).map(t=>({...t,testValues:{0:n.get(g(t))??0},testPass:void 0,...l==="soft"?{sbin:s}:{hbin:s}}))}
1
+ import{minOf as p,maxOf as m}from"./utils.js";import{getDieKey as g}from"./dies.js";export function aggregateValues(a,s,l=0){if(!a.length)return[];const n=new Map,t=new Map;for(const d of a)for(const o of d){const e=g(o),f=o.testValues?.[l];f!==void 0?(n.has(e)||(n.set(e,[]),t.set(e,o)),n.get(e).push(f)):t.has(e)||t.set(e,o)}const r=[];for(const[d,o]of t){const e=n.get(d);if(!e?.length){r.push({...o,testValues:void 0,testPass:void 0});continue}let f;if(s==="mean")f=e.reduce((i,u)=>i+u,0)/e.length;else if(s==="median"){const i=[...e].sort((c,h)=>c-h),u=Math.floor(i.length/2);f=i.length%2?i[u]:(i[u-1]+i[u])/2}else if(s==="stddev"){if(e.length<2){r.push({...o,testValues:{0:0},testPass:void 0});continue}const i=e.reduce((u,c)=>u+c,0)/e.length;f=Math.sqrt(e.reduce((u,c)=>u+(c-i)**2,0)/(e.length-1))}else s==="min"?f=p(e):s==="max"?f=m(e):f=e.length;r.push({...o,testValues:{0:f},testPass:void 0})}return r}export function getUniqueBins(a,s="hard"){const l=new Set;for(const n of a){const t=s==="soft"?n.sbin:n.hbin;t!==void 0&&l.add(t)}return[...l].sort((n,t)=>n-t)}export function aggregateBinCounts(a,s,l="hard"){if(!a.length)return[];const n=new Map;for(const t of a)for(const r of t)if((l==="soft"?r.sbin:r.hbin)===s){const o=g(r);n.set(o,(n.get(o)??0)+1)}return(a[0]??[]).map(t=>({...t,testValues:{0:n.get(g(t))??0},testPass:void 0,...l==="soft"?{sbin:s}:{hbin:s}}))}
@@ -25,6 +25,21 @@ export declare function compareNatural(a: string, b: string): number;
25
25
  */
26
26
  export declare function prettyKey(key: string): string;
27
27
  /** Clamp `v` into [0, 1] — the normalization range every colour scale expects. */
28
+ /**
29
+ * Smallest / largest of `values`, by iteration rather than `Math.min(...values)`.
30
+ *
31
+ * **Never spread a per-die array into `Math.min`/`Math.max`.** Spread passes one
32
+ * argument per element, and V8 throws `RangeError: Maximum call stack size
33
+ * exceeded` somewhere above ~125k arguments — so the idiom works on every test
34
+ * fixture and every small wafer, then fails outright on a real production lot.
35
+ * It did: `buildWaferMap` could not build a 400k-die map at all, and the aggregate
36
+ * min/max stacks had the same latent fault.
37
+ *
38
+ * Returns 0 for an empty array, matching the callers that treat "no values" as no
39
+ * extent rather than as ±Infinity.
40
+ */
41
+ export declare function minOf(values: readonly number[]): number;
42
+ export declare function maxOf(values: readonly number[]): number;
28
43
  export declare function clamp01(v: number): number;
29
44
  /**
30
45
  * Arithmetic mean. Returns **NaN** for an empty array — deliberately, not 0.
@@ -80,4 +95,44 @@ export declare function csvField(value: string): string;
80
95
  * where only the reports used it, while every tooltip interpolated raw names.
81
96
  */
82
97
  export declare function escHtml(value: string): string;
98
+ /**
99
+ * Shallow structural equality for the three collection shapes the library's
100
+ * shared display options are built from — the one copy of each rule.
101
+ *
102
+ * These exist for a single purpose: deciding whether a recomputed option is
103
+ * actually DIFFERENT before pushing it to every live card. A gallery resolves
104
+ * its wafers one at a time and re-derives the lot-wide options (bin colours,
105
+ * value range, metadata order) after each one, and each of those derivations
106
+ * allocates a fresh object — so a reference check always reports "changed" and
107
+ * every card rebuilds its view and redraws. Measured on 50 wafers x 8,000 dies,
108
+ * that pushed the same unchanged bin-colour map 1,275 times and cost 24 s of
109
+ * the load. Identity is the wrong question; value is the right one.
110
+ *
111
+ * Deliberately shallow: every value compared here is a number, a string or a
112
+ * colour, never a nested object. A generic deep-equal would invite use on
113
+ * `Die` or `View`, where it would be both wrong and ruinously slow.
114
+ */
115
+ export declare function arrayEqual<T>(a: readonly T[] | undefined, b: readonly T[] | undefined): boolean;
116
+ /** See {@link arrayEqual}. Same keys, same values — insertion order is not compared. */
117
+ export declare function mapEqual<K, V>(a: ReadonlyMap<K, V> | undefined, b: ReadonlyMap<K, V> | undefined): boolean;
118
+ /** See {@link arrayEqual}. Same members — order is meaningless in a Set. */
119
+ export declare function setEqual<T>(a: ReadonlySet<T> | undefined, b: ReadonlySet<T> | undefined): boolean;
120
+ /**
121
+ * A computation written as a generator so its caller decides how much of it to
122
+ * run at once: each `yield` is a point at which the work may be paused and the
123
+ * thread handed back. The generator's return value is the finished result, so a
124
+ * chunked computation has exactly ONE implementation — a caller that does not
125
+ * care runs it to completion with {@link drain} and cannot observe the
126
+ * difference, while a caller on the main thread drives it a slice at a time
127
+ * (see `runChunked` in `canvas-adapter/chunked.ts`).
128
+ *
129
+ * This shape, rather than a callback or an `async` rewrite, because the work
130
+ * here is synchronous and CPU-bound: there is nothing to await, only somewhere
131
+ * to stop. A second "progressive" copy of the same builder is the alternative,
132
+ * and two copies of a rule is the bug this library keeps finding.
133
+ */
134
+ export type Chunked<T> = Generator<void, T, void>;
135
+ /** Run a {@link Chunked} computation to completion without yielding, and
136
+ * return its result — the synchronous entry point for any chunked builder. */
137
+ export declare function drain<T>(work: Chunked<T>): T;
83
138
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- export function compareNatural(t,e){return t.localeCompare(e,"en",{numeric:!0})}export function prettyKey(t){return t.replace(/([A-Z])/g," $1").replace(/_/g," ").trim().replace(/^./,e=>e.toUpperCase())}export function clamp01(t){return Math.max(0,Math.min(1,t))}export function mean(t){if(t.length===0)return NaN;let e=0;for(const n of t)e+=n;return e/t.length}export function medianOfSorted(t){const e=t.length;if(e===0)return 0;const n=Math.floor(e/2);return e%2===1?t[n]:(t[n-1]+t[n])/2}export function median(t){return medianOfSorted([...t].sort((e,n)=>e-n))}export function percentile98(t){if(t.length===0)return 0;const e=[...t].sort((o,r)=>o-r),n=Math.floor(e.length*.98);return e[Math.min(n,e.length-1)]}export function modeOf(t){if(!t.length)return null;const e=new Map;for(const r of t)e.set(r,(e.get(r)??0)+1);let n=0,o=t[0];for(const[r,c]of e)c>n&&(n=c,o=r);return o}const p=/^[=+\-@]/;export function csvField(t){const e=p.test(t)&&Number.isNaN(Number(t))?`'${t}`:t;return/[",\n\r]/.test(e)?`"${e.replace(/"/g,'""')}"`:e}export function escHtml(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}
1
+ export function compareNatural(t,e){return t.localeCompare(e,"en",{numeric:!0})}export function prettyKey(t){return t.replace(/([A-Z])/g," $1").replace(/_/g," ").trim().replace(/^./,e=>e.toUpperCase())}export function minOf(t){if(t.length===0)return 0;let e=t[0];for(let r=1;r<t.length;r++)t[r]<e&&(e=t[r]);return e}export function maxOf(t){if(t.length===0)return 0;let e=t[0];for(let r=1;r<t.length;r++)t[r]>e&&(e=t[r]);return e}export function clamp01(t){return Math.max(0,Math.min(1,t))}export function mean(t){if(t.length===0)return NaN;let e=0;for(const r of t)e+=r;return e/t.length}export function medianOfSorted(t){const e=t.length;if(e===0)return 0;const r=Math.floor(e/2);return e%2===1?t[r]:(t[r-1]+t[r])/2}export function median(t){return medianOfSorted([...t].sort((e,r)=>e-r))}export function percentile98(t){if(t.length===0)return 0;const e=Float64Array.from(t);e.sort();const r=Math.floor(e.length*.98);return e[Math.min(r,e.length-1)]}export function modeOf(t){if(!t.length)return null;const e=new Map;for(const o of t)e.set(o,(e.get(o)??0)+1);let r=0,n=t[0];for(const[o,f]of e)f>r&&(r=f,n=o);return n}const i=/^[=+\-@]/;export function csvField(t){const e=i.test(t)&&Number.isNaN(Number(t))?`'${t}`:t;return/[",\n\r]/.test(e)?`"${e.replace(/"/g,'""')}"`:e}export function escHtml(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export function arrayEqual(t,e){if(t===e)return!0;if(!t||!e||t.length!==e.length)return!1;for(let r=0;r<t.length;r++)if(t[r]!==e[r])return!1;return!0}export function mapEqual(t,e){if(t===e)return!0;if(!t||!e||t.size!==e.size)return!1;for(const[r,n]of t)if(!e.has(r)||e.get(r)!==n)return!1;return!0}export function setEqual(t,e){if(t===e)return!0;if(!t||!e||t.size!==e.size)return!1;for(const r of t)if(!e.has(r))return!1;return!0}export function drain(t){let e=t.next();for(;!e.done;)e=t.next();return e.value}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Round a raw spacing to the nearest 1, 2 or 5 × 10ⁿ — THE rounding rule for
3
+ * every tick grid in the library, the wafer map's own mm axes included.
4
+ */
5
+ export declare function niceStep(raw: number): number;
6
+ /**
7
+ * The decimal places that show every multiple of `step` exactly — 2 → 0,
8
+ * 0.5 → 1, 0.25 → 2, 0.002 → 3. Capped at 9, past which the step is noise in
9
+ * a double and more digits would print it.
10
+ */
11
+ export declare function stepDecimals(step: number): number;
12
+ /**
13
+ * The densest round tick grid that fits: walks the 1-2-5 ladder from fine to
14
+ * coarse and returns the first step whose ticks sit at least
15
+ * `minSpacingPx(step, ticks)` apart on an axis `lengthPx` long.
16
+ *
17
+ * Density comes from the space, never from a fixed tick count. A fixed count
18
+ * was the first version of this, and on a 200 px box plot it rounded the step
19
+ * up to leave three ticks — 1.0 / 1.2 / 1.4 on data spanning 0.863–1.56, fewer
20
+ * and coarser than the unrounded five it replaced. A horizontal axis passes the
21
+ * widest label its step would draw plus a gap, so the step is as fine as the
22
+ * labels allow; a vertical axis passes a line spacing.
23
+ *
24
+ * Ticks are only returned inside the range: an axis maps `[lo, hi]` to its
25
+ * pixels, and extending it to reach a round number would change what the plot
26
+ * shows, not just how it is labelled.
27
+ */
28
+ export declare function fitTicks(lo: number, hi: number, lengthPx: number, minSpacingPx: (step: number, ticks: number[]) => number): {
29
+ ticks: number[];
30
+ step: number;
31
+ };
32
+ //# sourceMappingURL=axisTicks.d.ts.map
@@ -0,0 +1 @@
1
+ export function niceStep(i){if(!(i>0)||!Number.isFinite(i))return 0;const n=Math.pow(10,Math.floor(Math.log10(i))),t=i/n;return(t<1.5?1:t<3.5?2:t<7.5?5:10)*n}export function stepDecimals(i){if(!(i>0)||!Number.isFinite(i))return 0;for(let n=0;n<=9;n++){const t=i*10**n;if(Math.abs(t-Math.round(t))<1e-6*Math.max(1,t))return n}return 9}function M(i,n,t){const c=Math.ceil(i/t-1e-9)*t,e=[];for(let r=c;r<=n+t*1e-9;r+=t)e.push(Number((Math.round(r/t)*t).toFixed(stepDecimals(t)+2)));return e}export function fitTicks(i,n,t,c){const e=n-i;if(!(e>0)||!Number.isFinite(e)||!(t>0))return{ticks:Number.isFinite(i)?[i]:[],step:0};const r=[1,2,5];let f=Math.floor(Math.log10(e/Math.max(1,t/4))),u=0;for(let a=0;a<60;a++){const o=Number((r[u]*10**f).toPrecision(12)),s=M(i,n,o);if(s.length<=1||o/e*t>=c(o,s))return{ticks:s,step:o};++u===r.length&&(u=0,f++)}return{ticks:[i,n],step:e}}
@@ -169,4 +169,18 @@ export declare function binColorsCover(colors: BinColors, dies: Iterable<Die>):
169
169
  * degraded, which is what that level means.
170
170
  */
171
171
  export declare function binColorWarning(colors: BinColors, plotMode: string | undefined): WaferWarning | null;
172
+ /**
173
+ * Do two resolved assignments say the same thing? Compares every field of
174
+ * `BinColors` by value — the colour maps, the clash lists and the pass sets.
175
+ *
176
+ * The gallery's check before it pushes `binColors` to every card. Every
177
+ * `resolveBinColors*` call allocates fresh Maps and Sets even when the
178
+ * population's bins have not changed, so a reference comparison can only ever
179
+ * say "different", and each false positive costs a `buildView` plus a full
180
+ * canvas redraw on every card on screen. `pass` is compared as well as
181
+ * `hard`/`soft`: it is the verdict the colours were chosen with, it drives bin
182
+ * ORDER and soft-bin yield downstream, and a lot can change which bins pass
183
+ * (`pass-bins-mixed`) without any colour landing on a different value.
184
+ */
185
+ export declare function binColorsEqual(a: BinColors | undefined, b: BinColors | undefined): boolean;
172
186
  //# sourceMappingURL=binColors.d.ts.map
@@ -1 +1 @@
1
- import{diePassStatus as g}from"../core/dies.js";import{INPUT_DEFAULT_PASS_BINS as B,itemPassBins as m}from"../core/passBins.js";import{getBinColorScheme as w}from"./colorSchemes.js";export function resolveBinColors(s,n={}){return resolveBinColorsByWafer([{dies:s,passBins:n.passBins}],n).colors}export function binColorsForMaps(s,n={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],n).colors}export function resolveBinColorsForMaps(s,n={}){return resolveBinColorsByWafer(s.map(e=>({dies:e.dies,passBins:m(e)})),{...n,hbinDefs:mergeBinDefs(s.map(e=>e.hbinDefs)),sbinDefs:mergeBinDefs(s.map(e=>e.sbinDefs))})}export function mergeBinDefs(s){const n=new Set,e=[];for(const a of s)for(const o of a??[])n.has(o.bin)||(n.add(o.bin),e.push(o));return e}export function resolveBinColorsByWafer(s,n={}){const e=w(n.binColorScheme),a=n.useDefinedBinColors??!0,o=h(s),f=b(o.hardBins,i=>o.pass.hard.has(i),e,a?n.hbinDefs:void 0,!1),t=b(o.softBins,i=>o.pass.soft.has(i),e,a?n.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:o.pass},mixedHardBins:o.mixedHardBins}}export function binPassSets(s,n){return h([{dies:s,passBins:n}]).pass}export function binPassSetsByWafer(s){return h(s).pass}function h(s){const n=new Map,e=new Map,a=new Set;for(const t of s){const i=new Set(t.passBins??B);for(const l of t.dies){const r=g(l,i)===!0;l.hbin!=null&&(n.set(l.hbin,(n.get(l.hbin)??!0)&&r),r&&a.add(l.hbin)),l.sbin!=null&&e.set(l.sbin,(e.get(l.sbin)??!0)&&r)}}const o=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:o(n),soft:o(e)};return{hardBins:[...n.keys()],softBins:[...e.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function u(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const n=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${n?"bin":"bins"} ${u(s)} ${n?"passes":"pass"} on some wafers and ${n?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${n?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,n){if(!s.pass)return!1;for(const e of n)if(e.hbin!=null&&!s.hard.has(e.hbin)||e.sbin!=null&&!s.soft.has(e.sbin))return!1;return!0}export function binColorWarning(s,n){const e=n==="softBin"?"soft":n==="hardBin"?"hard":null;if(!e)return null;const a=s.shared[e];if(!a.length)return null;const o=u(a);return{code:"bin-colors-shared",severity:"warning",message:`${e==="hard"?"Hard":"Soft"} bins ${o} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const S=1,x=2;function p(s,n,e,a){const o=s.length,f=a?Math.floor(o/2):0;return s[((n-e+f)%o+o)%o]}function b(s,n,e,a,o){const f=new Map;for(const r of a??[])r.color&&f.set(r.bin,r.color);const t=new Map;for(const r of s)t.set(r,f.get(r)??(n(r)?p(e.pass,r,S,o):p(e.fail,r,x,o)));const i=new Map;for(const[r,c]of t){const d=c.toLowerCase();i.set(d,[...i.get(d)??[],r])}const l=[...i.values()].filter(r=>r.length>1).flat().sort((r,c)=>r-c);return{colors:t,shared:l}}
1
+ import{diePassStatus as w}from"../core/dies.js";import{INPUT_DEFAULT_PASS_BINS as S,itemPassBins as x}from"../core/passBins.js";import{getBinColorScheme as y}from"./colorSchemes.js";import{arrayEqual as u,mapEqual as p,setEqual as b}from"../core/utils.js";export function resolveBinColors(s,n={}){return resolveBinColorsByWafer([{dies:s,passBins:n.passBins}],n).colors}export function binColorsForMaps(s,n={}){return resolveBinColorsForMaps(Array.isArray(s)?s:[s],n).colors}export function resolveBinColorsForMaps(s,n={}){return resolveBinColorsByWafer(s.map(e=>({dies:e.dies,passBins:x(e)})),{...n,hbinDefs:mergeBinDefs(s.map(e=>e.hbinDefs)),sbinDefs:mergeBinDefs(s.map(e=>e.sbinDefs))})}export function mergeBinDefs(s){const n=new Set,e=[];for(const a of s)for(const r of a??[])n.has(r.bin)||(n.add(r.bin),e.push(r));return e}export function resolveBinColorsByWafer(s,n={}){const e=y(n.binColorScheme),a=n.useDefinedBinColors??!0,r=d(s),f=B(r.hardBins,i=>r.pass.hard.has(i),e,a?n.hbinDefs:void 0,!1),t=B(r.softBins,i=>r.pass.soft.has(i),e,a?n.sbinDefs:void 0,!0);return{colors:{hard:f.colors,soft:t.colors,shared:{hard:f.shared,soft:t.shared},pass:r.pass},mixedHardBins:r.mixedHardBins}}export function binPassSets(s,n){return d([{dies:s,passBins:n}]).pass}export function binPassSetsByWafer(s){return d(s).pass}function d(s){const n=new Map,e=new Map,a=new Set;for(const t of s){const i=new Set(t.passBins??S);for(const l of t.dies){const o=w(l,i)===!0;l.hbin!=null&&(n.set(l.hbin,(n.get(l.hbin)??!0)&&o),o&&a.add(l.hbin)),l.sbin!=null&&e.set(l.sbin,(e.get(l.sbin)??!0)&&o)}}const r=t=>new Set([...t].filter(([,i])=>i).map(([i])=>i)),f={hard:r(n),soft:r(e)};return{hardBins:[...n.keys()],softBins:[...e.keys()],pass:f,mixedHardBins:[...a].filter(t=>!f.hard.has(t)).sort((t,i)=>t-i)}}function g(s){return s.length>8?`${s.slice(0,8).join(", ")} and ${s.length-8} more`:s.join(", ")}export function mixedPassBinsWarning(s){if(!s.length)return null;const n=s.length===1;return{code:"pass-bins-mixed",severity:"warning",message:`Hard ${n?"bin":"bins"} ${g(s)} ${n?"passes":"pass"} on some wafers and ${n?"fails":"fail"} on others: these wafers were built with different pass bins. Each wafer's own yield and die verdicts are correct, but a bin has one colour and one legend row, so ${n?"it is":"they are"} shown as failing there.`}}export function binColorsCover(s,n){if(!s.pass)return!1;for(const e of n)if(e.hbin!=null&&!s.hard.has(e.hbin)||e.sbin!=null&&!s.soft.has(e.sbin))return!1;return!0}export function binColorWarning(s,n){const e=n==="softBin"?"soft":n==="hardBin"?"hard":null;if(!e)return null;const a=s.shared[e];if(!a.length)return null;const r=g(a);return{code:"bin-colors-shared",severity:"warning",message:`${e==="hard"?"Hard":"Soft"} bins ${r} are drawn in a colour another bin also has, so colour alone cannot tell them apart. There are more bins than the colour scheme has distinct colours (or a bin definition repeats one). Use the legend, the tooltip, or highlight one bin at a time.`}}const C=1,P=2;function m(s,n,e,a){const r=s.length,f=a?Math.floor(r/2):0;return s[((n-e+f)%r+r)%r]}function B(s,n,e,a,r){const f=new Map;for(const o of a??[])o.color&&f.set(o.bin,o.color);const t=new Map;for(const o of s)t.set(o,f.get(o)??(n(o)?m(e.pass,o,C,r):m(e.fail,o,P,r)));const i=new Map;for(const[o,h]of t){const c=h.toLowerCase();i.set(c,[...i.get(c)??[],o])}const l=[...i.values()].filter(o=>o.length>1).flat().sort((o,h)=>o-h);return{colors:t,shared:l}}export function binColorsEqual(s,n){return s===n?!0:!s||!n?!1:p(s.hard,n.hard)&&p(s.soft,n.soft)&&u(s.shared.hard,n.shared.hard)&&u(s.shared.soft,n.shared.soft)&&b(s.pass.hard,n.pass.hard)&&b(s.pass.soft,n.pass.soft)}
@@ -5,6 +5,9 @@ import type { DieMetadata, WaferMetadata } from '../core/metadata.js';
5
5
  import { type Affine } from '../core/transforms.js';
6
6
  import { type BinColors } from './binColors.js';
7
7
  import type { TestDef, BinDef, MetadataFieldDef, ReticleConfig } from './buildWaferMap.js';
8
+ import { classifySpec, type SpecCategory } from './spec.js';
9
+ export { classifySpec };
10
+ export type { SpecCategory };
8
11
  /** Resolve a toolbar activeTest cursor to the canonical test number for getDieTestValue. */
9
12
  export declare function resolveTestNumber(activeTest: number, testDefs?: TestDef[]): {
10
13
  testNumber: number;
@@ -447,26 +450,6 @@ interface ColorFns {
447
450
  forValue: (t: number) => string;
448
451
  forBin: (bin: number) => string;
449
452
  }
450
- /** A die's spec classification for `value` mode against the active test's limits. */
451
- export type SpecCategory = 'pass' | 'failHigh' | 'failLow';
452
- /**
453
- * Classify a value against the active test's spec limits, using the SAME rules as the value-mode
454
- * die colouring in `pushDieRectangles`. Returns null when there is no value. Shared by the colour
455
- * branch and the spec-count tally so the two never diverge.
456
- *
457
- * Out-of-spec *classification* depends ONLY on whether limits are defined — never on
458
- * `colorbarRangeMode`. An out-of-spec die is always flagged when limits exist, regardless of
459
- * how the colorbar is scaled. The *form* of the indication, decided in `pushDieRectangles`,
460
- * depends only on the effective `passFailDisplay`: under `'spec'` (pass/fail mode) the die gets a solid
461
- * green/blue/red categorical fill; in normal value/gradient mode it keeps the value gradient
462
- * fill (like every other die, so the distribution stays readable and out-of-spec colours don't
463
- * collide with the scheme) and is flagged with a ▽/△ marker (`ViewRect.specMark`). Either way
464
- * the die is never drawn as plain in-spec — the silent correctness bug this guards against.
465
- */
466
- export declare function classifySpec(value: number | undefined, activeTestDef: {
467
- limitLow?: number;
468
- limitHigh?: number;
469
- } | undefined): SpecCategory | null;
470
453
  /** Options for {@link buildHoverText}. All fields are optional. */
471
454
  export interface HoverTextOptions {
472
455
  testDefs?: TestDef[];
@@ -499,24 +482,15 @@ export declare function buildHoverText(die: Die, plotMode: PlotMode, opts?: Hove
499
482
  export interface MapTitleParts {
500
483
  primary: string;
501
484
  secondary: string;
485
+ /**
486
+ * The derived-test key, `"† Derived, not measured"`, when the test the
487
+ * map shows is derived — drawn on its own line below `secondary`, because
488
+ * `primary` then carries the `†` and a glyph must never appear without its
489
+ * key. Absent for a measured test. Deliberately NOT part of the export file
490
+ * name, which reads `primary` and `secondary` only.
491
+ */
492
+ note?: string;
502
493
  }
503
- /**
504
- * Build the on-canvas map title for any plot mode, derived from the View (+ optional bin defs so a
505
- * single-bin stacked card can name its bin). Returns a primary/secondary split so the renderer can
506
- * place the key identifier above the scale and the supporting context below it — keeping each line
507
- * short and never obscured.
508
- *
509
- * Primary / secondary per mode:
510
- * - value → "{name} ({unit})" / —
511
- * - stackedValues → "{name} ({unit}) · {method}" / "stacked ({n} wafers)"
512
- * - hardBin/softBin → "Hard Bin" / "Soft Bin" / —
513
- * - stackedBins → "Hard Bin {bin}" (or "Hard Bin") / "stacked ({n} wafers)"
514
- * - stackedSoftBins → "Soft Bin {bin}" (or "Soft Bin") / "stacked ({n} wafers)"
515
- *
516
- * @param fallbackFormat unitless value formatting — matches the colorbar tick formatting.
517
- * @param binDefs the active bin defs (hbinDefs for hard modes, sbinDefs for soft) — used to name
518
- * the bin on single-bin stacked cards.
519
- */
520
494
  export declare function buildMapTitle(view: View, fallbackFormat?: 'si' | 'engineering', binDefs?: BinDef[]): MapTitleParts;
521
495
  export declare function generateTextOverlay(dies: PositionedDie[], txCoords: Float64Array | null, options: {
522
496
  plotMode: PlotMode;