@wafertools/wafermap 0.26.1 → 0.27.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 (88) hide show
  1. package/CHANGELOG.md +360 -0
  2. package/README.md +46 -3
  3. package/dist/packages/canvas-adapter/canvasTheme.d.ts +4 -0
  4. package/dist/packages/canvas-adapter/canvasTheme.js +1 -1
  5. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +16 -0
  6. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  7. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +13 -1
  9. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/capability.d.ts +10 -10
  11. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +337 -3
  13. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/correlation.d.ts +8 -9
  15. package/dist/packages/canvas-adapter/charts/correlation.js +2 -1
  16. package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +50 -0
  17. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -0
  18. package/dist/packages/canvas-adapter/charts/histogram.d.ts +12 -1
  19. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  20. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  21. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  22. package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +20 -0
  23. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -0
  24. package/dist/packages/canvas-adapter/charts/trend.d.ts +30 -0
  25. package/dist/packages/canvas-adapter/charts/trend.js +1 -0
  26. package/dist/packages/canvas-adapter/dieList.js +7 -7
  27. package/dist/packages/canvas-adapter/identityHeader.d.ts +46 -0
  28. package/dist/packages/canvas-adapter/identityHeader.js +1 -0
  29. package/dist/packages/canvas-adapter/index.d.ts +1 -1
  30. package/dist/packages/canvas-adapter/insightsTab.d.ts +27 -3
  31. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  32. package/dist/packages/canvas-adapter/maplessSummary.js +1 -1
  33. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +15 -2
  34. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -20
  35. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +72 -17
  36. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  37. package/dist/packages/canvas-adapter/summaryPanel.d.ts +219 -33
  38. package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
  39. package/dist/packages/canvas-adapter/toCanvas.d.ts +6 -0
  40. package/dist/packages/canvas-adapter/toCanvas.js +1 -1
  41. package/dist/packages/canvas-adapter/toolbar.d.ts +310 -6
  42. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  43. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  44. package/dist/packages/canvas-adapter/userGuideHtml.js +97 -34
  45. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  46. package/dist/packages/canvas-adapter/version.js +1 -1
  47. package/dist/packages/canvas-adapter/warnings.d.ts +0 -4
  48. package/dist/packages/canvas-adapter/warnings.js +1 -1
  49. package/dist/packages/core/utils.d.ts +11 -0
  50. package/dist/packages/core/utils.js +1 -1
  51. package/dist/packages/renderer/buildView.d.ts +34 -1
  52. package/dist/packages/renderer/buildView.js +1 -1
  53. package/dist/packages/renderer/buildWaferMap.d.ts +104 -5
  54. package/dist/packages/renderer/buildWaferMap.js +1 -1
  55. package/dist/packages/renderer/colorSchemes.js +1 -1
  56. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  57. package/dist/packages/stats/binPareto.d.ts +15 -0
  58. package/dist/packages/stats/binPareto.js +1 -1
  59. package/dist/packages/stats/clusterDetection.js +1 -1
  60. package/dist/packages/stats/connectedComponents.d.ts +14 -0
  61. package/dist/packages/stats/connectedComponents.js +1 -0
  62. package/dist/packages/stats/correlation.d.ts +26 -0
  63. package/dist/packages/stats/correlation.js +1 -1
  64. package/dist/packages/stats/facets.js +1 -1
  65. package/dist/packages/stats/filterFindings.d.ts +18 -0
  66. package/dist/packages/stats/filterFindings.js +1 -1
  67. package/dist/packages/stats/findingsNarrative.js +1 -1
  68. package/dist/packages/stats/histogram.d.ts +19 -1
  69. package/dist/packages/stats/histogram.js +1 -1
  70. package/dist/packages/stats/index.d.ts +6 -0
  71. package/dist/packages/stats/index.js +1 -1
  72. package/dist/packages/stats/mergeTestDefs.d.ts +44 -0
  73. package/dist/packages/stats/mergeTestDefs.js +1 -0
  74. package/dist/packages/stats/metadataColumns.d.ts +20 -4
  75. package/dist/packages/stats/metadataColumns.js +1 -1
  76. package/dist/packages/stats/patternClassification.js +1 -1
  77. package/dist/packages/stats/renderFindingsReport.js +14 -14
  78. package/dist/packages/stats/renderSummaryReport.js +32 -29
  79. package/dist/packages/stats/reportHtml.js +38 -13
  80. package/dist/packages/stats/testPassRate.d.ts +85 -0
  81. package/dist/packages/stats/testPassRate.js +1 -0
  82. package/dist/packages/stats/trend.d.ts +40 -0
  83. package/dist/packages/stats/trend.js +1 -0
  84. package/dist/packages/stats/types.d.ts +117 -6
  85. package/llms.txt +6 -0
  86. package/package.json +7 -5
  87. package/dist/packages/canvas-adapter/metadataBadge.d.ts +0 -30
  88. package/dist/packages/canvas-adapter/metadataBadge.js +0 -1
@@ -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-08-28T14:50:01.535Z">v0.26.1</span></h1>
75
+ <h1 id="wafer-map-user-guide">Wafer Map \u2014 User Guide<span class="wmap-guide-version" title="Built 2026-09-09T07:38:59.875Z">v0.27.0</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
@@ -250,7 +250,7 @@ verdicts, as an alternative to spec-limit judgement.</p>
250
250
  <p>The toolbar sits at the top-right of the map and is always visible. Controls that
251
251
  are not applicable to the current mode are hidden automatically. (Hovering the map
252
252
  shows die tooltips \u2014 a separate thing from the toolbar, which is always present.)</p>
253
- <p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA6YAAAA+CAIAAAC3PZEPAAAQAElEQVR4nOydB1wURxvG90DpCCIISFEsqFiwYu+9l6iIvXeNJcaoGEvUL3ZNrDHGgr0X7L03rKggSu+9wwEC33M7sDm5A7njymLm/93Pbzn2LnPH7swzz/vOO2WSk5MZhsnNzS3wr/gBhUKhUCgUCoXCcwQCgfiB+L9lGFbacmJXqvalUCgUCoVCoVB4DqdxIWI5+UueKZMrRhZLTk4OjvEvQ6FQKBQKhUKhlB40NDQgcPFvWRZO+JbhBG5GRkZmZiZDoVAoFAqFQqGUTohpm52dDRtXS0tLR0dHwCJyefFsWloaOcPAwEBXV1dbW1tTU5OhUCgqJ1mYkyrMFmblZPMg0KKpweiU1dDX0TTU0WAoFAqFQuE9kLWwcdPT01NSUmDmfvnyRU9PD7JWEBcXJxQK8RR+MDU1hRZmKBSKOsjKzo1OykrP5GMOva6WwKxc2bKaAoZCoVAolNIA9G1MTAwUMPF6NeD6knwGMzMzqncpFDUSlchTvQvQMDSPoVAoFAqllABZCzMXB8TrFUle/GBoaKitrc1QKBQ1kZyeLczidY0UNA+NZCgUCoVCKSVA9RoYGDCs6tUgKbx6enoMhUJRH8nCUlAjpVQ0kkKhUCgUDiJxc3Nzy5Diu9TipVDUS+aXUqAmS0UjKRQKhULh0NLSYtgyDnmSV7xaL4VCUT18qM/wTUpFIykUCoVC4SAlyCB3aeEhCoVCoVAoFMp3DpW8FAqFQqFQKJTvnDIMhUKhUP7zpAszX7z7HB2baFbBqEm96jraWgyFQqF8R5RiyRsYGIh/K1euzFAopQ169aoS+m1/k5DwmFXbj0dEx5MfLSuWXzzd2cq8AiM738e3XfJPkZae4R8SSVbLiFPBuBy+XkYl0L8Ff6Cfgg9Ql5dCoVD+0wSGRrlucEtJE+K4UkWTsKi48Kj41duOb1oyUassHSPk4fTVRwfP3s4tpNB2XfvKv85yKVtGk6FQKCqEdmcUCoXy38U3KGLppoOp6RkGejpLZg61t7N67xPkutENwvfFu88tGtZiKDKSmJzqduZ2ESe88wm89+xdp5aOOA4IiTp8/g4s4cJOtrY0neDcrYwmXXhDoZQUKnkplK/w8Px854knDjAgNaxTlSmdwF7acjEMBz/2qkQrEFLShZkv3/tGxSSUM9SrU8PWwiwvsP4pIOzXTYeEGZn6utrL5wyvamOBJ+vY2xoZ6kO3hUXGMhTZiYlLIgdTR/SEa17gtxv/PhuflBIeFUd+PHn54fO3n4p4t/efgho6VG3WoCbDJ5JT030Dw+MTUxKSU/GjsaF+eSODapUtDfV1GQpFRlLThOigMP3DFZXIXlHogkyMDapYm+Pi19fTYRQElbxS+JKdExufhK8eX3QFY0M9XZ7u00HbqXAOn7974tIDcvzwxYfh/doP6tGKKW1A7y47Fnj7fSKOk9K/LBlkq17VC70VEhFT2aoiFyXH9RAeHY8rAT2aBtu4nNzcgJBIeF2WZuUrlC9HTsvIzAoKizY3NS5nwN/tIfFtJySlxCUk49jE2BBt5dscA7p2/e7TUbGJ5EdNDY2RAzv269zMxz906ebD0Ls62lrLZ+fpXYb1fcmoYykh1yjFgUtnqGZjARVY4Ldly4ryGXLykx4muXSrXsVSKMws7N1sKpnyR+/iajlz7cnzNz7+IZFST8BV5NTAvn+X5tpaZZnSSUx8EpfULk5GRpaFqbGVpSmjDsKj4uH0m1UwYr4vvD4HHzhzy9s3pIhzHGrYjhrQoWZVa6bEUMn7L7jKz914+uFTEEZZ8ecxea1dzaZ1UweexPhoO5XBly/ZWw+43332Dsd6Oto5uTnCjKxD5+6ERMTOGNW7FEUVMZKuPx9C9C648TZBT1tjXh9rdemws9efuJ2+hQG+cd3qrjOc8UyaMGOK67Yv7LYWM0f36diiPg6u3X+16/BlHOCr3r9+LpkX/b7z5OsPfjhoVLfarNF9jQx5IXxzcnJhvL3w/PThc3BsQnJCYkqOWM4mFLyxkQGmduimm9Sr7lDdVkNDnRIYI8qyLYczs77g2NqiQlhUXHZOzr6TN2DRPXvjg0kFpMny2cM4ZRYYGrVs8yGGXcHWpF4NhqJkMJ2DQGRKAw9feO05dg0WNfcMrDjT8oaMSCYmk2mSX3AEHridJw3txjdnujhg1j150dYciSzsvCcEzN//m2maPydXGbA/56zcjYPtK6apbO2jskGgAJcTGXOLBiril7X7EXcdN7hLCS0zRUpeDAMI0PgFhWNI4J5Ed1/V1rJp/RqIpjF8BQ2Gt4cHBgPJ38KefPTSCw+Mu9NG9MJgxqgJ2k4lkZImXL3tuJdvMI4b1qk6Z1x/HGz65+yr9353n3pGxyUunDrYQHGxFeWBfnnd+RB3jzhyjA4aauv88zjckfP7qkH1Xr77Yv+pm3lt48wvsdGESLGCSLTz5Tvf3Uev/DRxIKNWPgeGX7z9/Onrj+kSnhz5dPi+MVjC8cUD3uq56090dbQwtevRvkl1CbdPBWDa9r8dJ/AlYyKBbw8SJDImYdW2Y8HhMfefv8cJZcpo/srm75LzubxeBGQWTh1C165RCJga/bnfHYEv8iOmr22d6jg52otXssNUFu7vnafvME3F9Y/5avvm9dDDfwer9ERlN9gtanEQGRWveskbGhlDDjCd+D4kb3Rs4rI/DodF5qX34EJq3aR2M8eaFhVN8iZRcSKv/fErb0y0cPnhmZuP3ogCU7NcKpTg+1dYj4YOdOOes1J/5fkxEF3/3PH92zStw/CP1DQhbs53PoHkR+Ny+hicqlephJhUREz858CIzwFhoWxOG8bdeav+hvpRiMFO26nednJExCTA1oIUwHGX1g2mDO9JQu2uM4buPHTp+oPXmGL+tHrPstnDEdVi+M0m91Cid21NtQNjMshBUEwGniyrIZjTx4pRIRB/B06L9G4Dh6p9OjrVrZlX1wbT9J0rp3OJDeTJrm0a4jpB2NTSrDxcdvLkL1MGvfcJOnfjyVvvAHR8P47NVtfwieF863539L/cM+XLGbRu4mBSXpSogwYfuXAXT7r0aYczYRRhmvT4hTf8MIjjW4/f4tGiUS0Y1TraKo32+odEwErBeO2Ynwxqbmr829yRSzcfgpsLf3fRtCEO+WYENPqyzYfRfnycFXOG26gpgFsEuDwQAP0qf9TYsFZVK1pCWKlAnazcdowE63AZD+/XXmr1Olw27ZrVwyMkPObguTuYGd554ukXFIHYjplJqQnH43betXqGeGJDeFT8PyevC4WZpN6crh5/E/OA+61n73yCBvdoVU0dc+xigs5n6ebDJCwAhTCoR6surRsWmGBbW5ri0aR+jcnDelx/8OrEpYc4H3P1+f/bu2z2MNtKZoxcKEbyohuCJcawgbMWjWpriLlJGPYevfCCxMEJFSsYqVfcSJKUkvbL2n24pnFsqK87cWg3qboc0e0/9p3HkJCYnLZ4vdui6UMa1anGqBDaTiWBsO+q7cch03HNjh3UpU8nJ+5XuIxhUdhYmu09eR2CGKpX3A/jIUHRGWefieYStaz0pnS1mL1XlBIwr4/V9mvhH0PTzzyLHdLKzMpEdeIAwwZcRhwM6Nqifq0q4r/CNL3ATB3ftqQPCk2GUADsFUhehp33q8XhQBDAdYMbumkmz41w6Niifu3qNuLnnL3+BL0efsU9M2FItw+fg+AFYOCHS/H4pTe+kJXzRnKCXgXUrmZjYmQYl5j8wvPzPydujBvcmREFo/XW/jIWfol5BWMuNVA8r3fFnH/zennCay//6/dfPXvr8+VLdoFfwah2qm+PKZNjbTumFMJlsRd2gnh2u+rBFGjplkOkSx/Wt93gnq2/+RIoFUxWj1+8f+TCPQhlzKPWLRqnysu+hMDE5Xxc/+DIvazexX2RLsxAX5Sbk8vwmJOXH0Ea4h6fylfJCyNg2ZY8vdukXvV5EwYWbQRACvfq0LRDi/rr/jqNAILo5ZsPb1g8vryRASM7CpC8UAOQs5j/wPZYt3C8ZOsHdmsxbckONBQ2MAyGirzJv0a8b/mWI+RmhjKb5NK9sOA1pPyaBWMv3Hx66NwdvGrtrlO//zyminVFRiXQdioJyJEte89n5+Tgppo/cSAmlJLnQARjyFm3+zRk8eL1B+ZOGMDbsk02ptot7A01NASLf7AJickbQfW0NTeNqbr8RFBZTYEq9S6oWMG4HuvsOpQsqalerSrkfdS1dGPP8WtE77Zzqjt2cGcjQ/3ivAqTqDo1bPFw7t1m74kbuNhEw+eJG9NH9mJUBWIU0LsMm0ty/sZTfV1tNIZhR5G69v8Wk4dnsfwP6N0sPC+e18sH8M3/fewaFzViWMlOFElMfBLmzBDBJFEK06oJzt14aE4XDZfFXhji2e0qJicnd83Ok6RLn+DcFcqj+K8d0qsN2rzn+PWwqLh1u04tmeWiUdpqx+CGXbzhAAI10OsTXbpv2XuO4T1ESqayNbZ5CIb7lX8eS0gSNbJf52ZjBnUu5gvxJ/h1pgu64ou3n0NMwqj63/zRcgT95JG8L9/7wriF0iU+P+yBLHbmPd65q1S1junRuCFdNvx9Jio28ccVu6rZivpTzJZMTcrVqVG5U0tHdd0IJy498AuOwEH75vV+HNO36JPRyL6dm1lbmP629Sg8m/W7T29dPoVRCbSdyuDohXvHLt5n2BUYy34cVoTghhSGQCcTU6hzlz5t0Zsz/ANf6ZqRUowuAx3NdSPVYIBhqF4xZwRTYhT1PvKBPho2LXo6BNBrVKn04PmHQk7LEjCCi7eeS/1tTTsrWLw+AWE3H71G6EMFObIYp/edugE5hWN0uUmpaQhPH3W/l56ROeaHTuJnQumu2nYM/2L8wI3AqzgGbMLj7E3KsEn2mHI0rW8vLv5gjj59IwqgIw6AB8YXlz7FciKVilF+gZGtbu76ugWn/dFxoqWlcIiY4qOmIfLSneckwPJD95Yy6V1C745OcQkpZ649hkl/9d7LHu0aM6UHBCGXbj6ULvJ3y66YOyI3l9fmbmkBXRARCbiXC+hd9LRnrj6GNRAeHYe5VmWrioj9uvRtx+laDHCYdyGSjHN8A8OhNxB2YGRE5p73wJlbaJbk82MHdW7VuHZhr0K8DxL54Nnb6FjffwrinkdX9eq97/xJaliVkpyafuHmM4btejAIFfNVCLMipnnr8dvQyFh87yrITi66nfjz33v2Hk6SnY05n9tZGKpvJ8MWTcN8/YGHSLvgvloyc+g3l9BBECOSAtUbEh6DYTg4PObHsf1ocfj/ArASGVZyRMUkXrzjUdSpublFnICoKHsKE5eQzJXFVQaIWuDa3n/yJllZX83WYuW8EVC6rhvcwiLjzl1/AsEtPlQgqk72XcNMtUC2hhpBuB8RmHvsau5yBnqYQqOvkDwN8rdD8/p4PH7lvePgJfRCh8/fxVgzbWQvNXqKCEfYVjJDTD8gJErqCWgaV82Ay2Iv2QwU7gAAEABJREFU7N3Es9tVCQbrE5ce4gD2+Yj+HRi5GDmg40f/0A+fgo6538efScW57HLD1qs+iG8ADV4+ezhuos+B4QylZKDrc2dFAu6OWV+bYuiClm46RNQwAeEdPLx8g5fOchFP1scL/YIiIBjQlfVo31i2qaOsktc3KILoXdwDNatakz6loqkofPnN/HRMEyF8EZ+KZstDokfz+hyMHxGQQlitVjVV5/jeevSGLAOcO6E/16EgHPC/HScQkJ01pg95BjoMfeiMUb25ihMTh3Z/9cEvPjHl8p0XKpBoUtvJgcAlJhIwn9b+MrbAr3jVziJQcTsxlYRyxbWHY0wif578QzHrR0IWr/tl7Nq/Tr167wdJgQb/OstF7UvaoxIyKxrLk64QmZhpbqT0PIdYVi+WPBNRUe8jBxam5WEzZGV9MdDXmTaip6am9FDajkOXoWenjugp9bdfsrO3H7iYkJiira1VUTmLINOEGU9efXz2xuettz8pKIGLc2D3lgO6tsABxoyV80YuWncAZjPcEVhWw/u1Jy9E34sRHX07OhN7O+vOrRwZdQOPZ+2uk0/f+OAYrrPrDOcCGxwgYCj4OtGlRcNaDtVtf/vzCD7IzUdvUtOF8yf9oEbVu2bBmDde/gGhUZKpn7iMG9S24xovNYudD9x56glLBQfF17voXZ+88sbF5uRoT57BX2DUgI6/rN2HENkDj/edWzVgeA+nd9k8n+F8Xr9RTEhNbrXnlF6595JkBAzt07ZA6cY9x69xetfG0hTzdlLMAeLw5OWH4lcgnCa8fMPfZ3CxXb//StaIq2wD9ruPAeQ/uWTG0DKyZ1GYmxqbi3X3UEgj5qyH5ebjH6p6yUv6U7h34jltm/45BxMaj+H92pHxFc4lBok1O09uWjKReIGY9nVr0wj+POYf6BGUXSRfajs50LPj32S2YyoAr9pZBCpu53a3i0TvIso2YWg3mQZFdOWuM4buPnIFty4ukl2HL88c3YdRH6efxm65GNqzYfkFA2Qz51afDrn6Ou7HXlYDm1VglAapv4uDEmYiKup95ANds3PvNgfP3oG1D99xkksPqXvyaYiuJAEmn5K/ggz954RoBSTGf1FfrwQdBiv6p9V7EpP/7QcQNxz9QyfxFR6wQ37/efTi9W4wSDCKQPVyA8ni6c6L1u2PiEnYc/xq0/o11F7/eOsBd9KfwFtZOHUIZw1iIIS1c+XuS5IbgCG8Z4emvTs21dQQxVvQ7JXzRq348whucKh/3KeTh/Vg1AT6Cvi4pbEwLcfjl174t1plS6lXtTj4u3h+DLz/7B28dky3fv95jPhva1a1wrgAwxv2Fv8lr3heO9/yfOTDPzhy7qq/cfDnsinWFkrs8L8JKYxoZKjfvMFX62EQjyI7nuI7X/3TKLKWANbSqm3HcGlBgw3v10G812zV2AGDL4zhhy+8ZJW8skVmSZcKLViE3iVFef7YdwGP20/ekiXbUoG7ZsKKSFJxRsV89BPt9iG+MBkG9ot3n3HQt3Mzzk8aO7gzvmvEy37fcYKrMstlEagg2CHZzuJD21kAzAvJXYc/8SSX7nKID7wE4yhJa4MLIr2srEo49SRms3soAuVZOTK/FvNMvBAvx5swSgPTdPyH8OD2VlXv+8jNgG4tIVVxsUAUQlFNdd2GDvfp649FBKPxK5yA0yDWETiC3sWVA2O1X2elbDpw9d5LdM4wIzBCQwIu/XHY7HH9JFc0Y7BZPX9UJXPRnmqnrjzal18yGWfOnTCAbXbWS7YPVCMQtRg4cIDJM2aY4np3we973c7cJnqXYb2rfSdvLFy7n+uZcTJkConIYV7qfusZQ5EL6Il3H0VLBovIV2TYbv+vI1fG/bxl+ZbDGPcx+huX05eUidAo+PfNB//0wjeZ4wPQ5cTfhcLBPFA8z6dSRRPcwnhYmpeyLQnDovK2DSdrcNUF/uuk9Fubpg4FBl6YBbo6opAjQr7c2lk4C43rVWfYUTsuIemr8wVMazYgHBQWHSbjoKDgsCwmebgByHI8gJ7L7fTtKcN7cGEOngAJS/bLsMuXaLh13c7eYtjUt1EDO3Jn1q5mAy8E/SzUGP4lKz+q2ua9iuwyqsp2Mmz+LqZEpKiNyLDMZVJShUcv3GNYq6lpfXtOQfKnnQVQVzth1JFtddqWLIOiXbO6F28/x0cOi4zlKsuqkjMifzcMB43s9Of3lTlCsqC/TVxy1kv/VLwJOvEBzdQ59ec/+Iqce7VxrGW3cc9Z6C0IX8gpPBi2qKSopJFJOfisglxmza6TMXFJOOb6QIKFqfHscf1hdzHKoV7NKjBuMSuATTJ2UJci9ntDFAU+iusGt5AIUTKcaPPhASKvF04eTFOIyFgl34NFgzGMCHELs/ILpgwSX5F94uID3yBR6LN1EwcyoMDKhXGIMPTpK4+4JWv4Bn6ZOvjn3/8Jj4rfe+IGvpnKVqouBcOwSdsvPD/5BkdITWxo3rBmgVQNvoHQK+kqC0s2wy/nrtotmazcrlk9SScBHeahc3fwhvhjFahXyCs8PD8h8o6bwnW6c4F2iqqJr5oOKVyKqq3xio/+oeSgpcQkCr3oll8nwRcoECLG8+QgTZhZYIjCTOzK3RcM68pXkmVfdEVK3oCQyLW7TjHsLd2YXWrwwvMzOtA1O09udJ2gln6nMOIT87ZMtMvXK3uOX8c9jKFi0bQhJEzGMbBbS4+3nxF2xwjRp5NTBWNDDHL6utqp6RnKlmiS7WTy83f/PUnApKQLj+Wva/bw/Mzl9fKrnV+jlnYWveoW6tw/JFL8HIFAgE8kGUwX5Hfq2dmyW6wlAwPo7uvhh+6LysLXsNR1aWP+IURKWktQtJB8Du/Q9LRMKY3EC5PTw3zChRsvhEYlZk3sYqHwbXGrV7acO74/7qkSFr1S1PvIjUjOsjmvW5dPuffsHezbN17+JCkN0hYPzIfJZQMRJv5CKDbH2naIbrd1qgspRt5HGRnJGJ4hytEJvPMJXPf36XnjBxSxthJe78p5UL0HoHpPXXkYn5A8c0xffCiS8Cd3jfeSg6/wj33nGfZ7c53hXKDEofttUTUM8cJGUGO7j169dMcD80/xKg144aJpznN++wtzgD8PuK9fOI5ROSu3HX35zrew37qduYUxntjw/KzLS7pifT2dwnYae/8pUOriPKk1HM1MjGDAwz2N/dqu4xs92jXOycmpX8tOaqZlKdpQg4fExucN7nbWUkLB+G4LfL3onW49esOwBlklCWedUxpkmUfxUaTk3ckWF4QdvXDqEDI7hyO9atuxt94Bu45cgbXA8AZu1RG3XThCGJiAwre7/vC189fZIb6B4Z8DRY4arBpSkhM9aRq7/rpsGeWuXpJsJ8Ou5+AmQxEx8XCVcBoXSxKPxfCnnZKopZ1FcPrqIwh0SUkMcTvmh859Ozdj+AEU6vnnokAVmuoTlj7/gF9hZxJZvuFCSBHvRs45dD8q40vOrJ7fyNiTA0WtR1Tjxo3QIpMXbcWB28Z5mPx0btUAD/Rsrz/4QemmpgkRq03PyCARW4TnYALp6Gjp6+pAqTesU43zKQu8D6NonHu3hXJ6+sbnyUtvlzdrO7VyhNpuULsqiRgWgKS9ilRveMztp56hUXEk6GluatzAoSqjJu4+9SQ+Lu64Avt7wVwnpUYb1/uqeHaTejUgeROT09DziK/dtraoMGpgp39OXEfvrbJSMByIAxC9W8W6omSRMoTmEBZ74PGBbHzDz7q8xOwvYls7kiFWAKlZDQS8FSRvnFpjCN8EEp+fBSi/Azht+s2qHbg7ENK5fNfjC+sodWzhWMCFZNi0WHIQoy7JC8ny0U9kXPfq0JTr5aGEerZvAsnr7RuMQYI/O7ZzxeT9gyPJhkkjB3RETwTVe/TCvSpWFbllB+hMV207DkcHf6clM12IdxIg8gJFvzU2KlZRegW2kxEFMSvXy9+7ldSMMzEy+G2ulMKl/Gln0aisnYWRmJzqdka6IY2G7T15o2NLx8K21VAfuQos1/lF5XZ1aSE1XUgivLefvOVKk6IrQ2xdpnytm4/ekPdJF2YoQ75g9jJ3woCRczegp0V/deXuy6v3XmoIBDXsKjnWrtrAwa6mnbV4wgNU7y9TB81YuosRpXWF4uXo3H4c21eNvTTXPMl+gEu7z8mRfqFK5g/ItzmTQkhJTScHM0b2lgxNTF68FYa6uDvwbVReeYL8ByX3uiPgQn7Gri8sAO6IwtZHkMy3HH5vXUZRHlz4lJR+K+y00MjYxevdEvPXd1W1sShQR5zwJTvvypQ10Kqw3i0xKS+6WiDTxUBPlLEkqkaZmGKhnOo8cgDnA43BXJyri4HuHqG0uav2YC6yee+5vWtnkwnuriOXSd80b8JAzl33C8p7lbK35ZRsp0zQdhYTOOXkYOqInuKJQaERsSR2ER2byBPJO7ePlYG2xuEH0QKoGUvdSV0stcpIGWSCooXrz4umoD/1tbY1kyKwMr/k/nVNlNiA4xFtK07sopRvnlhBJffYFPU+coBwG8RTfGLKmWtPGtetLl9JXfTjZ66JyjuaSuy0rECgVtcsGLtg7b5MtlxgLqsDIWfxOH7xPjo0TEFh4sL6JV3Z45f5aRiCXG0trUXThtSups66vG2d6l668+KjX8jOQ5fr1awinu2KLw0/wv558e6z+MbCHp6fGHbKbfJ1aW2cuePgJYbNUVZjiKA48LMurzE7YYDywNgtqWLffwoskK1OKCLxl9Q7K2/0jQroKgYD1odPwR1b1FfL5nYlxMc/9PTVx3XtbXt3dGJ4j1F+Ym5CUkoRvShCZ0TvYk7ev2uLvp2aSV2ZEJefS2ms1Lq8RWBuZowOFwYDWiwes/YJEI27cKH5o3cJjepVv3Tb4623f3B4DNmjspyBnuv0IQvW7MNMlN1zRSR5SYrVqAEdm7CLBxl24TAsd4Z1EVSQoCzZzmJC21l8OOehmo2FuCvDxfUK85ZUD27/Kd0szY21NrmHfgpPP3I/cs3IqtplC3YKumXz8o1rWenWtCq4UCYjK3eBm9+nCCHOmdPbSknL10IiYjfuOcuIqhRZl6QkpKLeR26mDu+5evtxTIZnLN3Zr0vzvp2dirnnMMOKhnM3np679oRYvIUV7lUUiKQvm+XiusEN/7mKJkbd2zb28gvx9A7IyMyCqHr+9hMeDFveC9f5c9aoa+dUr3NrR/MKxmY82Ap+5ujes5btgmBdufUYIlfiljMs9qPu9y7cfIbpR/OGNRGXePba5xFbSKt3x682BsOHxcsReMRgOXtcP4bf8LMur3H+FR4UFiXZLd9/JiWrAddenRrSw3r+IXmmhok6rPfCcui5MlgZmZk/dG/FlDYQyXn6+qO3b0ipkLxc3lFgaFQRkreZoz0mTg41bDu1dCxiP2FEhsmBrFeUwiQv7ttmDWqyGzfcqWprQcrEeH4MPOYuWlbVslFthmdgMIBEg7rdtOfs+kXjyUyiirX5Rh+uVsgAABAASURBVNcJmpqaXFBsweRB/sERDvn7UICTlx+StH3VbJ8otZ0cDR2qffIPk7pnEq/aWQQqbuf3AUQqBNRm99CX/qnrzoe4/iCbObfmbDBeiIPZStO7DFuoixwkp6SVRKoq6n3kpmn9GrNG9/lj/wWMjqevPsLDzsbcsZado0NVtAfKUrxoI2LBUXGJkdEJb7z933j5cUt80EP+OLZvozrVGCWD3mDckC5/H7sWHZf06oPf8tnDGdFy6ZC3XgFoEjxU3KSRsQlRbJ1gaMpJLt34Y3FZmVcY79x199GrcLB+33mCWxbCsJsZPX7ljfHygccHsnUiAXpxQLcW3I9ZX7KhY3zY5eGTXXrItJqbwsGp8Dde/gUkryir4e2/WQ2wilo1rt2uWV3MSAt7N7wJOahhp/gFA0VTWA79W++A1TuO446GH9dc2pI7/kP2fkpUR41XOeDqMuFiKKJeNWymueP7M9+C7IPNiOpGyxafVGTa1vghXRBmgj8Kj8HaogJm4aQMG26JsYM7MzwDTiSEOEwC/5DIM9cecZO8AssmELIX17sh4TFkz3dEmvp0UsWSpsLaSSgsX5Zv7SwM1bfzu4HsIrHlYmgZ2bc9LqspwJRklpK3ooDaILnvJaxkqaj3KQkdWtS3s7E4ePY2Kd3tHxyJx9nrT8hvEXMX7amWmxsViwEoXfLljetWHzWwo8qKIcAQRbDl6r2X73wCdx+7Otmle+1qNng4926DYfK9T9CBs7cCWS0OCc63kG7P9k3w3d54+Bom3NLNh36d6UIy/zCv+P3nMdsPXhRfOIWQ9ORhPbjVLcKMrOV/HIbvheM+HZ26tmnIUOTCxNgQ8zr8IaAtCizeJVkNGBlbNKzVxqkuBiDJ1UUFeMtK3hpVKqm+NFtYVBwJsIRHxXERPK/PwSu3Hv3CLtFZMWdEgUGfogwcqttilEfsBRNvpsSQSRQuJ3s72Wp0KlLyIti3dsHYNbtOIhCJB3mykrnJomlD+FmDcMLQrt5+IXEJyUfd76Px39wYBvc/umCSgD9nXD+VbRcuRztF5dmzcxkBbef3DARrq1qGcuwbvHCgzbhO5srecFhUyXLldEYiv19d7yM30FgCgaB1EwfXGc6RMQnwGhFPhF3K1feAzJVUugIBU6uqjVMD+1aNapOcAe59GOUzaWh3GMxo5JW7L+DSdW/biDwPTws3KdG7To72C6YMYvjH1OE94eg/feMDabJkoxu+dpJJgi4CDtAkl+5RsQkCRmBuaiyu16HDoGNIzQeosTH881mkUkT9coKVRQW1pCM71bdHF/36g19ETIJ4XmJKqnD6yN5tneoUc6VjaGQs8eQQLWF4AC6qZVsOIxoA1Y4YyDf3lqMoBPSHTR3t7z71DIuMw/VQWHnmzKwvOw+J1s9MGd6jsAsMk2FipxaxXLIwFLw419rSdN3C8bhJRF2SQEBK3vCnUEMBypczcJ3u/MvaffiWt7ldhEM5c3Sf8tKyoRH+OHX54fGLD8g2P2XLljE1UV2hRFnbecz9QXZuDi4FDQ1BWRV++aWlnd8TcstWZetdgqKWaqm+LilHbEJyfiaxlZmJEfq0/l2a4wGlAk0pKlKWkZku/LdIma6Otq62lr6eDkwy8ak+DGDyPnVq2KqgmADuqYVTB8/5bXd8UsruI1fgY5Fw0P5TN4k5jTD0PHbHNR6Cxv88ZRDZ2ftzYPjUJdsH92jdu5MTSXIw0NMx0Psqmgn5cu76k1NXHpLNPru0bjB1eC8l7OtcXLi63b7BEekS69KyskSLzbkaFEXXLyeoJYu9W9tGZ649Rme+4+BFkh5DaN5Qtl2U/9zvDp8VAetu+fMuNeLjHwq9iw+FW3XVvFHcdkgUFdC3sxMkLw4Qq/lz2RSpqboITJHNFzu2rC+1yCmrLtzJcb8uMm9mqXidgYm4rLeEGsEVv3LeyFXbjiUmp2HqMHnR1nq1qmB226xBTeNy+qT65rM3Ph6en0nGDLoprbJlERxcsvEgXqiy5VYyt5Mt507S2pbm78BJ20mhyAo3Y79022O0WLmccgZ6Mu0jxW1+q7JEAiNDPdeZzgvW7EMA9387jq9fOP7QuTtkvVcV64pLZ7nw1oxg8nf2tjArv+/UTUwnDpy5BUXr5FizZePaDjVsiN8PZ/T9p6CHL7w83vqk5ruk44d0UftqHrN8Q4RUjZAKt4Kn6PrlDFtNSC2rvjAxG9q7Lb55eHLHL96Xr2DtkQv3yF70Ln3a4pZh1Ar0LuK0343ebdm4FsbThl8vD2jZqPZbb/8Wjf7NTq5TozKZL6l9eK1qY9GxRf1bj98iVvbn/gtSc3ZxDmmttYX0RfAwDkjR6K6tG8q0UJ4gW5dH9n+LiYPBISx5zSYYJGRDDvWmPSCuscl14o5Dl56/FW02+PKdLx47D1+2rWQWFhkrXq8Uf4mpI3qZli+3eP0BfAWuG9xUqXplbaeRgd7iDQcwWqz448iKOSOUt9lpKW3n9we+2kWHRBlOSwZ/1bWlCLNXHA+Cc7ZqWBVN2RN/5QbWDoZ8zBJLaLkp6n3kA70TrsmPfqGX7nggxNypZQNZ25Cby1y7L6qSi2OHGrZcEXUVgPFjxqjem/85h/tr+tId5Mna1W0QjZG6RQXfgItTx77y3pM3PnwKgqiF/UMcINFakS/ZCLiLn4wRfdyQLuoqdCgOu6LL4eGLD0WcwC3pLn79ctWD7/+1lx8kL5SrpqbmD91byvTy01cfkaUaDWrb9e6k5nlIiKji5CXEARCB+W3OiO/A38U1hkeBJ+dPGljgGci2XatmMPxgvHNXn4CwkPCY+8/fo2FjB3Up0J0W0Vp0pHuOX3v6WlRaEXJi7OAujOzIJnnr1azCsFFpxMvaNatXpgTjJ6QPLG6SJ1CYnFcZmM4umjbELzjiws1nj196k4WQQWHR3AmYhXdq6di2WV0SjVr106iFa/dB96tY9crcznmjiJpctuXQyrkjVbZfK2/bySWDxieVaJUrt4eQDp90Q3hC5mMfUcPm7vOf0jVv4E/LyMaP3qGiophRSVmWxqrTWwjl33j4mhElVtZuWEf+Pb0wZSLvM7BbS/nK4paQ6SN7//z7XmFG5ja3i2hJz/ZNGtetrl+MOX9yavqr974Xb3uQAgK4/KaP6MWolnZOdQNDo85cfUx+bN3EYdaYvkVU/+Eb1Stbrpo3Em7W7SeesKjJ5gjcWhGGXdYG+diheT017hsnybwJA3q2b5xTyA7nGDJKRSFYzJMXTBk0f/U/YVFxB8/e9g+OnD22b5liXDwwO7bsPU9Ev2XF8gumDNZQY6IJq5ZguxC9u/qnUWrcVfs/DvrAZbNcfvrfPwlJqZAHoZGx8ycOLGKTP440Yca6v06jH2DYZWO/znKRb/GPICAgAP9XuXJxZ5kHTt8iNdUVBazpzUsmFb+mFUdgYCAjS8uLCaIe3r4h0XGJ8YkpuD0qGBvWsKskmZCKUQR6l7jdqlS9srYTndSSTQdhqONSg1JHQJNRLbxqJ0ag4bPXoeODSzS0TztEfsnzuPFIyvzU4T0qia3elfo8PsjRC/cwBqAPP7Ft4TdXK0tF8ur1jcxgSszGC6Fnn4nUQOWKOgFRom0m7PIPBjarMLu3Ahz0aubFHarFv+1h/dpDJnKR9Nj4pPDoeIz6VazNyViIkwNCIhGqtjQrz2XuYrLk4fn58Lk7Cv+2ZQVt+23rMfHtUiHF6teyK2egq6ero6erZaivl5ubm5KWnpaemZYuTExJgzfmGxjOnW9mYuQ6w1ldY+1tNphoU8lU0hZSOErqmRm2XJ23X2h8QrJoHwSBqHZseWPDWlWtijNkyoryPoUqUciniI5NXLX9ONmSupK5yZRhPYu2pd94+e86cjk8SrTACD354unOpiXLxS/Jp/gcGP7T6j0CRvQ/OBQThnQ1F1uKJ97bKJuSfIqHL7zW7z6Ng6G929axL2l+QkmKcCvkisK1tHTzYZLciI5x8rDuGB2KOP/ZG5+/jl4lWxZD766YM1yOjpS0XGbJC16+9330wgsdaG6u/JsHCgQCU5NyCIK3b15PvoXYau+S8GdbvMENKg1/g+2/TVXXcvJvgtEa6hxhQdpOcPnui7+OXGEUQavGtX+aOJCRCyVJXtyRGy6EnH8ex+Rv8EhW0vRtajKvj7VCfJbiS17m628bnRo0H8PO10fP20hSXGaO7tOxRX0cXLn3che70R1iR/vXzyUe2PIth1/nV/RU7LctB5i8XbrjAWdCXPgWB0zzENWFN8zn3FkFQsUif1DUp8DkEyEOrjxc/VpVHGrY1qxqbW9XKS+vWpjhw+7z9/5ToOfHQHJah+b1p47oWfKQQkk+hefHgCUbD4m6PrZDFHzdCWK+XaBer/IomeT9sH73GUZxbF8xDe47IzuKuqJi4pOWbj4UFhlHfqxTwxax31rVrE1NjNgJUm5MXFJ0XJI3u5OOl28wOc3KvMKy2cPkm0GRlsvTBTeqU00FBdX5D5xdROGX/3FYdBfJL/6VDow0+KaYVIms9P98O3u0a1zOQA9BOlLlRG4a1a02cWg3hmfgSoS0zcll3D3iuM69dxOF6V1ZEf+2/22A2B8XOlLKy/LPFOQHfyCX1f5tQ7CSWg3wjZ6+/ohHcHhMEefDh3ByFC3c5OHeWhSKTGhricrDNWtgv/voNZhziGBwewEghoMQDaddCIjjTXTp1oIHWzxYmJYnclcgoXfzUGfCRXGxsfzeMjEgW9cuGHv80oNLt5/D/nj/KQiPIs7HxKlH+ybOvdqUcH4ij8vLE3gyC0coVkNDg/81ZWk7CwBTOSVNyP2IUKmmpobkSkrJ58uWKYP5cQlXHyvJ5SXAzXA9EnDfSxQGautg9NvQygrUuzK5vBxBYdEWZuWLTmzwC4pA5LpAYgNeiECkwr9thZCTk5uaLkwXShQp09HS19WRI1nr+4D6o/xB4Z8Cd+i5G08x3/MPjpR6QlUbC6cG9pgZKnCZZgk/RXB4dFRskraWFIPPyqKC1DKayqCEnyIiJiEmLpFRBOhRzUzUmdggTnxiysXbHh6en0jmjCRVrCs2rW/fq0OT4m/zLhX5Ext4wvfRJVH+myhV8jKs6l17LgRicn5fBfu78kle9UL7ClVCxSJ/UN6nSE5N9w0Mj0tMSeTyqo0MqlW2VEb9Jfq34A/K+xSJyWnevsEBIVH+IZGaGhq2VmZVrCrWqmbDLbkpIfInNlAoFJ4Dmbugv2w7MVIoFEoxgbTlVYkMSmkH0rZZg5p4MMpEhVU6KRQKhUKhUCgUdUAlL4VCoVAoFArlO6cU5/JSKN8Tis3lVR6lMZeXQqFQKP9lSC4vdXkpFAqFQqFQKN85VPJSKBQKhUKhUL5zqOSlUCgUCoVCoXznaJD9SLKzsxkKhaI+NEvD9LNUNJJCoVAoFA4icSF38yRvVlYWQ6FQ1IdWmVIgJ0tFIykUCoVC4cjMFO2OKZK8AEfJyckMhUKpr/llAAABAElEQVRRH0Z6mgzvKRWNpFAoFAqFg0hckd4tU0a0AVtaWppQKGQoFIqa0NfW0NXitYeKFuLBUCgUCoVSSkhngcULuStKbNDUFDk3MTExVPVSKGrE1JC/HqpAgObR/ckpFAqFUmqArI2NjcUBhC7kriAqKio7OzszMzM3NxfPGhgY6Orq6ujokIQHCoWiYpKFOanCbGFWTnYOo3Y0NRidshr6OpqGOrRDoFAoFEopALI2IyMD5m5KSgrDpjRoaWnhX0F0dHROTk52PgyFQqFQKBQKhVL60cxHlMpLlrAx7Fo2/Av5S04ipi+FQqFQKBQKhVJaIIKWYf1dUQovC578PwAAAP//ShTAWQAAAAZJREFUAwDZ71SBC+ghBgAAAABJRU5ErkJggg==" alt="Single-map toolbar"></p>
253
+ <p><img src="data:image/png;base64,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" alt="Single-map toolbar"></p>
254
254
  <h3 id="single-map">Single map</h3>
255
255
  <table>
256
256
  <thead>
@@ -333,7 +333,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
333
333
  <tr>
334
334
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAzaDZ2NiIvPjxwYXRoIGQ9Ik05IDIxSDN2LTYiLz48cGF0aCBkPSJNMjEgM2wtNyA3Ii8+PHBhdGggZD0iTTMgMjFsNy03Ii8+PC9zdmc+Cg==" width="20" height="20"></td>
335
335
  <td>Expand</td>
336
- <td>Opens the map in an enlarged modal overlay. A maximise button in the modal grows it to fill the window (or press <strong>F</strong>). Press <strong>Esc</strong> or click outside to close. Useful for detailed inspection without changing the main view. Hidden while the Insights tab is open \u2014 each chart inside Insights has its own expand button instead.</td>
336
+ <td>Opens the map in an enlarged modal overlay. A maximise button in the modal grows it to fill the window (or press <strong>F</strong>). Press <strong>Esc</strong> or click outside to close. Useful for detailed inspection without changing the main view. Works in the Insights view too, where it opens the whole chart suite in a wide modal \u2014 useful because those charts interact and are best read side by side. Individual charts also have their own expand button.</td>
337
337
  </tr>
338
338
  <tr>
339
339
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMS44IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMy45OTcgNGEyIDIgMCAwIDEgMS43NiAxLjA1bC40ODYuOUEyIDIgMCAwIDAgMTguMDAzIDdIMjBhMiAyIDAgMCAxIDIgMnY5YTIgMiAwIDAgMS0yIDJINGEyIDIgMCAwIDEtMi0yVjlhMiAyIDAgMCAxIDItMmgxLjk5N2EyIDIgMCAwIDAgMS43NTktMS4wNDhsLjQ4OS0uOTA0QTIgMiAwIDAgMSAxMC4wMDQgNHoiLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjEzIiByPSIzIi8+PC9zdmc+Cg==" width="20" height="20"></td>
@@ -343,7 +343,7 @@ shows die tooltips \u2014 a separate thing from the toolbar, which is always pre
343
343
  <tr>
344
344
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNOCAydjQiLz48cGF0aCBkPSJNMTIgMnY0Ii8+PHBhdGggZD0iTTE2IDJ2NCIvPjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxOCIgeD0iNCIgeT0iNCIgcng9IjIiLz48cGF0aCBkPSJNOCAxMGg2Ii8+PHBhdGggZD0iTTggMTRoOCIvPjxwYXRoIGQ9Ik04IDE4aDUiLz48L3N2Zz4K" width="20" height="20"></td>
345
345
  <td>Findings</td>
346
- <td>Opens or closes the Findings sidebar (see <a href="#6-findings-sidebar" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'6-findings-sidebar\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 6</a>).</td>
346
+ <td>Opens or closes the Summary panel (see <a href="#6-summary-panel" onclick="(function(e){e.preventDefault();var g=e.target.closest('.wmap-guide');var el=g&&g.querySelector('[id=\\'6-summary-panel\\']');if(el)el.scrollIntoView({behavior:'smooth'});})(event)">Section 6</a>).</td>
347
347
  </tr>
348
348
  <tr>
349
349
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTAuMjkgMy44NiAxLjgyIDE4YTIgMiAwIDAgMCAxLjcxIDNoMTYuOTRhMiAyIDAgMCAwIDEuNzEtM0wxMy43MSAzLjg2YTIgMiAwIDAgMC0zLjQyIDB6Ii8+PHBhdGggZD0iTTEyIDl2NCIvPjxwYXRoIGQ9Ik0xMiAxN2guMDEiLz48L3N2Zz4K" width="20" height="20"></td>
@@ -485,7 +485,7 @@ failure category across the wafer.</p>
485
485
  <tr>
486
486
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNOCAydjQiLz48cGF0aCBkPSJNMTIgMnY0Ii8+PHBhdGggZD0iTTE2IDJ2NCIvPjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxOCIgeD0iNCIgeT0iNCIgcng9IjIiLz48cGF0aCBkPSJNOCAxMGg2Ii8+PHBhdGggZD0iTTggMTRoOCIvPjxwYXRoIGQ9Ik04IDE4aDUiLz48L3N2Zz4K" width="20" height="20"></td>
487
487
  <td>Findings</td>
488
- <td>Opens or closes the lot-level Findings sidebar.</td>
488
+ <td>Opens or closes the lot-level Summary panel.</td>
489
489
  </tr>
490
490
  <tr>
491
491
  <td><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzNzQxNTEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMyAzdjE4aDE4Ii8+PHBhdGggZD0iTTE4IDE3VjkiLz48cGF0aCBkPSJNMTMgMTdWNSIvPjxwYXRoIGQ9Ik04IDE3di0zIi8+PC9zdmc+Cg==" width="20" height="20"></td>
@@ -600,26 +600,52 @@ findings (ring, quadrant, sector), the panel groups them together \u2014 the pat
600
600
  classification is the primary finding and the regional findings provide
601
601
  supporting detail.</p>
602
602
  <hr>
603
- <h2 id="6-findings-sidebar">6. Findings sidebar</h2>
604
- <p>The Findings sidebar shows detected anomalies for the current wafer or lot,
605
- always docked next to the map so a clicked finding can highlight the affected
606
- dies right there. Open it from the toolbar. Severity, kind, and region filter
607
- controls at the top of the sidebar narrow the list down.</p>
608
- <div data-wmap-demo="summary-panel" class="wmap-demo"></div><p><em>The Findings sidebar open alongside a single wafer. Click any finding to
609
- highlight the affected dies; use the filter controls to narrow by severity,
610
- kind, or region.</em></p>
611
- <p>Yield, bin breakdown, ring/quadrant regional yield, and per-test statistics \u2014
612
- previously shown in this same panel \u2014 now live in the
613
- <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)">Insights tab</a>&#39;s Overview sub-tab instead, since that content
614
- doesn&#39;t need the map on screen the way a finding&#39;s highlight does.</p>
603
+ <h2 id="6-summary-panel">6. Summary panel</h2>
604
+ <p>The Summary panel docks next to the map and gives you the wafer or lot at a
605
+ glance, with findings sitting directly under the headline numbers so a clicked
606
+ finding can highlight the affected dies right there. Open it from the toolbar.</p>
607
+ <div data-wmap-demo="summary-panel" class="wmap-demo"></div><p><em>The Summary panel open alongside a single wafer. Click any finding to
608
+ highlight the affected dies.</em></p>
609
+ <p>Sections, top to bottom:</p>
610
+ <ul>
611
+ <li><strong>Summary / Lot Summary</strong> \u2014 die and wafer counts, yield, and the population
612
+ the rest of the panel is computed over. On a lot, &quot;Mean wafer yield&quot; is an
613
+ <em>unweighted</em> mean of each wafer&#39;s own yield; it is deliberately not the same
614
+ statistic as the bin breakdown&#39;s pass-bin share, which weights every die
615
+ equally. The two agree only when die counts are even across the lot.</li>
616
+ <li><strong>Findings</strong> \u2014 detected anomalies, most severe first. Severity chips narrow
617
+ the list; Kind and Region dropdowns appear once there are enough findings to
618
+ be worth narrowing.</li>
619
+ <li><strong>Bin breakdown</strong> \u2014 bars as a share of dies, pass bins first and then failing
620
+ bins by descending count, so the dominant failure mode is at the top. It
621
+ follows the map&#39;s plot mode: a soft-bin map gets a soft-bin breakdown. When a
622
+ wafer carries both bin types, a <strong>Hard / Soft</strong> selector in the section header
623
+ overrides that.</li>
624
+ <li><strong>Region yield</strong> \u2014 ring yield by default, with a <strong>Ring / Quadrant</strong> selector.
625
+ Ring is the default because edge roll-off is the pattern that dominates real
626
+ wafer maps; a genuinely asymmetric quadrant is reported as a finding above,
627
+ with a significance test behind it.</li>
628
+ <li><strong>Wafer yield</strong> (lot only) \u2014 one bar per wafer with the lot median marked.
629
+ Wafers are in slot order by default, since that is what makes a
630
+ slot-correlated pattern visible; a <strong>Slot / Yield</strong> selector re-sorts. Wafers
631
+ far below the rest of the lot are labelled &quot;low outlier&quot;.</li>
632
+ <li><strong>Test values</strong> \u2014 per test: mean, <strong>Ppk</strong>, and spec yield. Ppk (not Cpk)
633
+ because it measures against the <em>overall</em> spread, including wafer-to-wafer
634
+ variation, which is what the dies actually ship against. The full descriptive
635
+ statistics \u2014 min, quartiles, median, max, \u03C3, both spec limits, and all four
636
+ capability indices \u2014 are in the summary report and the CSV export.</li>
637
+ <li><strong>Functional tests</strong> \u2014 pass/fail counts and pass rate per functional test.</li>
638
+ </ul>
639
+ <p>Every section header can be collapsed, and stays collapsed as the panel
640
+ re-renders.</p>
615
641
  <p>A <strong>Summary report</strong> button (when present) opens a printable full-detail
616
- report in a new window or tab, still covering everything findings-adjacent \u2014
617
- yield, bin breakdown, ring and quadrant statistics, per-test statistics, and
618
- the findings list \u2014 and can be saved as a PDF from your browser&#39;s print
619
- dialog.</p>
642
+ report \u2014 yield, bin breakdown, ring and quadrant statistics, the full per-test
643
+ table with Cp/Cpk/Pp/Ppk, and the findings list \u2014 and can be saved as a PDF
644
+ from your browser&#39;s print dialog.</p>
620
645
  <h3 id="why-some-findings-name-two-bins">Why some findings name two bins</h3>
621
- <p>A finding may read <strong>&quot;hard bin 3 and soft bin 3 (same dies)&quot;</strong>. That is one
646
+ <p>A finding may read <strong>&quot;hard bin and soft bin 3 (Fail) (same dies)&quot;</strong>. That is one
622
647
  group of dies counted in two bin spaces, not two separate groups added together.
648
+ (When the two bins carry different names, both are spelled out in full.)
623
649
  Hard and soft bins are independent numbering systems, so this wording appears
624
650
  only when the two happen to cover exactly the same dies \u2014 reporting it twice
625
651
  would look like two independent problems.</p>
@@ -629,9 +655,11 @@ itself: &quot;pass-bin occurrence is 22 points lower&quot; and &quot;yield is 22
629
655
  are the same sentence.</p>
630
656
  <p>Nothing is discarded \u2014 an application reading the findings programmatically
631
657
  still receives every one of them.</p>
632
- <p>For <strong>lot-level views</strong> (gallery), the sidebar shows lot-level findings by
633
- default, with a <strong>Wafers</strong> tab listing every wafer that has its own per-wafer
634
- findings \u2014 click a row to open that wafer.</p>
658
+ <p>For <strong>lot-level views</strong> (gallery), the panel shows lot-level findings, and the
659
+ <strong>Wafer Yield</strong> section badges each wafer with its own findings count \u2014 click a
660
+ row to open that wafer. There is no separate wafers tab: every wafer appears in
661
+ that one list, including the ones with no findings, which is what lets you see a
662
+ low-yielding wafer that nothing flagged.</p>
635
663
  <hr>
636
664
  <h2 id="7-lot-stack-maps">7. Lot-stack maps</h2>
637
665
  <p>A lot-stack map combines multiple individual wafer results into a single
@@ -715,16 +743,51 @@ hides the other&#39;s toolbar button, since Findings has nothing to highlight
715
743
  against once the map is replaced.</p>
716
744
  <p>Insights is organized into three sub-tabs:</p>
717
745
  <ul>
718
- <li><strong>Overview</strong> \u2014 a yield bar (labelled with the pass bins actually in use), a
719
- hard/soft bin pareto, and ring/quadrant regional yield and per-test
720
- min/mean/max/spec-yield statistics.</li>
721
- <li><strong>Distributions</strong> \u2014 process capability (Cp/Cpk/Pp/Ppk for tests with both a
746
+ <li><p><strong>Overview</strong> \u2014 a <strong>test pass rate</strong> chart showing which test fails most (and,
747
+ with &quot;Group by&quot; active, whether it fails more in one split than another). It
748
+ offers up to three ways of judging, whichever the data supports: <strong>Spec
749
+ limits</strong> (the value against its limits), <strong>Tester flag</strong> (the pass/fail the
750
+ tester itself recorded, which a parametric test carries whether or not limits
751
+ were exported), and <strong>Functional</strong> for pass/fail-only tests. The two
752
+ parametric views can disagree \u2014 guard bands and dynamic limits routinely cause
753
+ it \u2014 so the card reports how many dies the two judged differently rather than
754
+ picking one for you. Alongside it, headline tiles stating the population (wafers, dies analysed,
755
+ and for a lot the mean wafer yield \u2014 an <em>unweighted</em> mean of each wafer&#39;s own
756
+ yield, not the die-weighted figure), a yield bar labelled with the pass bins
757
+ actually in use and marked with the median, a hard/soft bin pareto, and
758
+ ring/quadrant regional yield plus the full per-test statistics table.</p>
759
+ </li>
760
+ <li><p><strong>Distributions</strong> \u2014 process capability (Cp/Cpk/Pp/Ppk for tests with both a
722
761
  lower and upper spec limit; tests missing a spec still appear, normalized
723
- onto their own range and sorted by variability), a test-value box plot, and
724
- a value histogram. Clicking a capability box drives the box plot and
725
- histogram onto that same test automatically.</li>
726
- <li><strong>Correlation</strong> \u2014 a test-to-test correlation matrix and a die-level scatter
727
- plot. Clicking a matrix cell drives the scatter plot onto that pair.</li>
762
+ onto their own range and sorted by variability), a test-value box plot, a
763
+ value histogram, and a <strong>wafer-to-wafer trend</strong> \u2014 one point per wafer at its
764
+ mean, \xB11\u03C3 whiskers, the lot mean dashed across, and spec limits where the
765
+ test has them. The trend is always in slot order, deliberately: drift across
766
+ a cassette only reads in the physical sequence, so there is no sort control
767
+ to destroy it. Clicking a capability box drives the box plot, histogram and
768
+ trend onto that same test automatically.</p>
769
+ <p>The box plot, histogram and trend share one row of axis controls, kept in
770
+ sync across all three so switching between them never re-reads the same
771
+ data on a different scale: <strong>Axis includes limits</strong> widens the axis to cover
772
+ LSL/USL even where the data sits well clear of them (on by default only when
773
+ doing so still leaves the data at least a third of the axis \u2014 otherwise a
774
+ generous spec window would squash a perfectly capable distribution into a
775
+ sliver), and <strong>Clip outliers</strong> narrows the axis to a robust range (the
776
+ Tukey fence, 1.5\xD7 IQR beyond Q1/Q3) so a handful of extreme values don&#39;t
777
+ stretch the axis until the rest of the distribution reads as a flat line.
778
+ Clipping affects the AXIS only \u2014 every reported statistic (mean, \u03C3, Cpk,
779
+ the box&#39;s own five-number summary) is computed from every value; nothing is
780
+ excluded from the numbers, and the panel states how many points sit outside
781
+ the visible range when some do. The box plot additionally has its own
782
+ <strong>Log scale</strong> toggle, independent of the shared pair above (available once
783
+ every plotted value is positive) \u2014 the histogram and trend charts do not
784
+ offer one.</p>
785
+ </li>
786
+ <li><p><strong>Correlation</strong> \u2014 a test-to-test correlation matrix and a die-level scatter
787
+ plot, both stating the sample size the coefficients are computed over.
788
+ Clicking a matrix cell drives the scatter plot onto that pair, where <code>r</code> and
789
+ <code>n</code> for that pair are printed and update as you filter the legend.</p>
790
+ </li>
728
791
  </ul>
729
792
  <p>In a gallery with more than one wafer, a <strong>Group by</strong> control appears whenever
730
793
  wafer metadata (lot, product, test program, temperature, split, or a custom
@@ -1,3 +1,3 @@
1
- export declare const WMAP_VERSION = "0.26.1";
2
- export declare const WMAP_BUILD_TIME = "2026-08-28T14:51:07.720Z";
1
+ export declare const WMAP_VERSION = "0.27.0";
2
+ export declare const WMAP_BUILD_TIME = "2026-09-09T07:40:19.349Z";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- export const WMAP_VERSION="0.26.1",WMAP_BUILD_TIME="2026-08-28T14:51:07.720Z";
1
+ export const WMAP_VERSION="0.27.0",WMAP_BUILD_TIME="2026-09-09T07:40:19.349Z";
@@ -42,10 +42,6 @@ export declare function collectWarnings(sources: {
42
42
  statsSummary?: StatsSummary | null;
43
43
  lotStatsSummary?: LotStatsSummary | null;
44
44
  }): WaferWarning[];
45
- /**
46
- * The Summary panel banner. One block per severity present, so an "dies may be
47
- * mis-positioned" error is never visually flattened into an advisory.
48
- */
49
45
  export declare function buildWarningsBanner(warnings: WaferWarning[], ownerDocument?: Document): HTMLDivElement;
50
46
  /**
51
47
  * The popup opened by the toolbar's warning indicator.
@@ -1 +1 @@
1
- import{CLR as r,Z_BASE as E}from"./toolbar.js";const b={error:0,warning:1,info:2};export function severityOf(e){return e.severity??"warning"}export function collectWarnings(e){const s=[],i=new Set,n=t=>{if(!t?.message)return;const o=`${t.code}\0${t.message}`;i.has(o)||(i.add(o),s.push(t))};for(const t of e.result?.warnings??[])n(t);for(const t of e.statsSummary?.stats.warnings??[])n(t);for(const t of e.lotStatsSummary?.perWafer??[])for(const o of t.summary.stats.warnings??[])n(o);return s.sort((t,o)=>b[severityOf(t)]-b[severityOf(o)])}function h(e){return e==="error"?{bg:r.errBg,border:r.errBorder,text:r.errText}:{bg:r.warnBg,border:r.warnBorder,text:r.warnText}}function w(e){return e==="error"?"\u26D4":e==="info"?"\u2139":"\u26A0"}export function buildWarningsBanner(e,s=document){const i=s.createElement("div");Object.assign(i.style,{marginBottom:"10px"});for(const n of e){const t=severityOf(n),o=h(t),a=s.createElement("div");Object.assign(a.style,{background:o.bg,border:`1px solid ${o.border}`,borderRadius:"4px",padding:"7px 9px",marginBottom:"6px",fontSize:"10px",color:o.text,lineHeight:"1.5"}),a.setAttribute("role","status"),a.textContent=`${w(t)} ${n.message}`,i.appendChild(a)}return i}export function buildWarningsMenuEl(e,s,i=window){const n=i.document,t=n.createElement("div"),o=280,y=e.left+o<=(i.innerWidth??1/0)?e.left:Math.max(4,e.right-o);Object.assign(t.style,{position:"fixed",top:`${e.bottom+4}px`,left:`${y}px`,background:r.menuBg,border:`1px solid ${r.menuBorder}`,borderRadius:"4px",boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:E,width:`${o}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),t.setAttribute("role","dialog"),t.setAttribute("aria-label","Data warnings");const p=n.createElement("div");Object.assign(p.style,{padding:"6px 12px 4px",fontSize:"10px",fontWeight:"700",letterSpacing:"0.06em",textTransform:"uppercase",color:r.label}),p.textContent=s.length===1?"1 warning":`${s.length} warnings`,t.appendChild(p);for(const l of s){const f=severityOf(l),m=h(f),d=n.createElement("div");Object.assign(d.style,{display:"flex",gap:"8px",padding:"8px 12px",fontSize:"11px",lineHeight:"1.5",color:r.text,borderTop:`1px solid ${r.menuBorder}`,alignItems:"flex-start"});const c=n.createElement("span");Object.assign(c.style,{color:m.text,flexShrink:"0"}),c.textContent=w(f),d.appendChild(c);const g=n.createElement("div"),x=n.createElement("div");Object.assign(x.style,{fontWeight:"700",color:m.text,marginBottom:"2px"}),x.textContent=l.code,g.appendChild(x);const u=n.createElement("div");u.textContent=l.message,g.appendChild(u),d.appendChild(g),t.appendChild(d)}return t}
1
+ import{SHADOW as O,TRACKING as w,SPACE as s,RADIUS as E,FONT as y,CLR as l,Z_BASE as B}from"./toolbar.js";const $={error:0,warning:1,info:2};export function severityOf(e){return e.severity??"warning"}export function collectWarnings(e){const n=[],i=new Set,o=t=>{if(!t?.message)return;const r=`${t.code}\0${t.message}`;i.has(r)||(i.add(r),n.push(t))};for(const t of e.result?.warnings??[])o(t);for(const t of e.statsSummary?.stats.warnings??[])o(t);for(const t of e.lotStatsSummary?.perWafer??[])for(const r of t.summary.stats.warnings??[])o(r);return n.sort((t,r)=>$[severityOf(t)]-$[severityOf(r)])}function v(e){return e==="error"?{bg:l.errBg,border:l.errBorder,text:l.errText}:{bg:l.warnBg,border:l.warnBorder,text:l.warnText}}function C(e){return e==="error"?"\u26D4":e==="info"?"\u2139":"\u26A0"}const k={"inferred-pitch":"Die pitch inferred","partial-coverage":"Partial wafer coverage","geometry-conflict":"Geometry conflict","edge-exclusion-exceeds-radius":"Edge exclusion exceeds radius","test-count-capped":"Test analysis skipped"};function S(e){const n=e.code?k[e.code]:void 0;if(n)return n;const i=e.message.split(/(?<=\.)\s/)[0]??e.message;return i.length>60?`${i.slice(0,57)}\u2026`:i}export function buildWarningsBanner(e,n=document){const i=n.createElement("div");Object.assign(i.style,{marginBottom:s.lg});for(const o of e){const t=severityOf(o),r=v(t),m=n.createElement("div");Object.assign(m.style,{background:r.bg,border:`1px solid ${r.border}`,borderRadius:E.control,marginBottom:s.sm,fontSize:y.body,color:r.text,lineHeight:"1.5"}),m.setAttribute("role","status");const a=n.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=n.createElement("span");Object.assign(g.style,{flex:"1",minWidth:"0"}),g.textContent=`${C(t)} ${S(o)}`;const d=n.createElement("span");Object.assign(d.style,{flexShrink:"0"}),a.append(g,d);const p=n.createElement("div");Object.assign(p.style,{padding:`0 ${s.lg} ${s.sm}`}),p.textContent=o.message;let c=!1;const f=()=>{d.textContent=c?"\u25B4":"\u25BE",p.style.display=c?"block":"none",a.setAttribute("aria-expanded",String(c)),a.setAttribute("aria-label",`${S(o)}. ${c?"Click to collapse.":"Click to expand for detail."}`)};f(),a.addEventListener("click",()=>{c=!c,f()}),a.addEventListener("mouseenter",()=>{d.style.opacity="0.65"}),a.addEventListener("mouseleave",()=>{d.style.opacity="1"}),m.append(a,p),i.appendChild(m)}return i}export function buildWarningsMenuEl(e,n,i=window){const o=i.document,t=o.createElement("div"),r=280,a=e.left+r<=(i.innerWidth??1/0)?e.left:Math.max(4,e.right-r);Object.assign(t.style,{position:"fixed",top:`${e.bottom+4}px`,left:`${a}px`,background:l.menuBg,border:`1px solid ${l.menuBorder}`,borderRadius:E.control,boxShadow:O.menu,zIndex:B,width:`${r}px`,maxHeight:"320px",overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),t.setAttribute("role","dialog"),t.setAttribute("aria-label","Data warnings");const g=o.createElement("div");Object.assign(g.style,{padding:`${s.sm} ${s.xl} ${s.xs}`,fontSize:y.body,fontWeight:"700",letterSpacing:w,textTransform:"uppercase",color:l.label}),g.textContent=n.length===1?"1 warning":`${n.length} warnings`,t.appendChild(g);for(const d of n){const p=severityOf(d),c=v(p),f=o.createElement("div");Object.assign(f.style,{display:"flex",gap:s.md,padding:`${s.md} ${s.xl}`,fontSize:y.body,lineHeight:"1.5",color:l.text,borderTop:`1px solid ${l.menuBorder}`,alignItems:"flex-start"});const u=o.createElement("span");Object.assign(u.style,{color:c.text,flexShrink:"0"}),u.textContent=C(p),f.appendChild(u);const x=o.createElement("div"),b=o.createElement("div");Object.assign(b.style,{fontWeight:"700",color:c.text,marginBottom:s.xxs}),b.textContent=d.code,x.appendChild(b);const h=o.createElement("div");h.textContent=d.message,x.appendChild(h),f.appendChild(x),t.appendChild(f)}return t}
@@ -57,4 +57,15 @@ export declare function percentile98(values: number[]): number;
57
57
  * For pitch estimation, values should be pre-rounded to absorb noise.
58
58
  */
59
59
  export declare function modeOf(values: number[]): number | null;
60
+ /**
61
+ * CSV field escaper — quoting plus the formula-injection guard above.
62
+ *
63
+ * Lives here rather than in `canvas-adapter/summaryPanel.ts`, its original home:
64
+ * it is a pure string function with no DOM, and it now has consumers in the
65
+ * summary panel, the die list AND the correlation chart. Importing summaryPanel
66
+ * from a chart to reach it would have inverted the dependency (summaryPanel
67
+ * already imports charts/chartShell), so the third consumer made the wrong home
68
+ * obvious. Every CSV this library writes must go through it.
69
+ */
70
+ export declare function csvField(value: string): string;
60
71
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- export function compareNatural(t,n){return t.localeCompare(n,"en",{numeric:!0})}export function prettyKey(t){return t.replace(/([A-Z])/g," $1").replace(/_/g," ").trim().replace(/^./,n=>n.toUpperCase())}export function clamp01(t){return Math.max(0,Math.min(1,t))}export function mean(t){if(t.length===0)return NaN;let n=0;for(const e of t)n+=e;return n/t.length}export function medianOfSorted(t){const n=t.length;if(n===0)return 0;const e=Math.floor(n/2);return n%2===1?t[e]:(t[e-1]+t[e])/2}export function median(t){return medianOfSorted([...t].sort((n,e)=>n-e))}export function percentile98(t){if(t.length===0)return 0;const n=[...t].sort((o,r)=>o-r),e=Math.floor(n.length*.98);return n[Math.min(e,n.length-1)]}export function modeOf(t){if(!t.length)return null;const n=new Map;for(const r of t)n.set(r,(n.get(r)??0)+1);let e=0,o=t[0];for(const[r,c]of n)c>e&&(e=c,o=r);return o}
1
+ export function compareNatural(t,n){return t.localeCompare(n,"en",{numeric:!0})}export function prettyKey(t){return t.replace(/([A-Z])/g," $1").replace(/_/g," ").trim().replace(/^./,n=>n.toUpperCase())}export function clamp01(t){return Math.max(0,Math.min(1,t))}export function mean(t){if(t.length===0)return NaN;let n=0;for(const r of t)n+=r;return n/t.length}export function medianOfSorted(t){const n=t.length;if(n===0)return 0;const r=Math.floor(n/2);return n%2===1?t[r]:(t[r-1]+t[r])/2}export function median(t){return medianOfSorted([...t].sort((n,r)=>n-r))}export function percentile98(t){if(t.length===0)return 0;const n=[...t].sort((o,e)=>o-e),r=Math.floor(n.length*.98);return n[Math.min(r,n.length-1)]}export function modeOf(t){if(!t.length)return null;const n=new Map;for(const e of t)n.set(e,(n.get(e)??0)+1);let r=0,o=t[0];for(const[e,c]of n)c>r&&(r=c,o=e);return o}const i=/^[=+\-@]/;export function csvField(t){const n=i.test(t)&&Number.isNaN(Number(t))?`'${t}`:t;return/[",\n\r]/.test(n)?`"${n.replace(/"/g,'""')}"`:n}
@@ -75,14 +75,46 @@ export interface ViewOverlay {
75
75
  lineWidth: number;
76
76
  fill?: string;
77
77
  }
78
+ /**
79
+ * A renderer-agnostic description of one drawn wafer map: everything `toCanvas`
80
+ * needs, in physical (mm) coordinates, with no canvas or DOM dependency.
81
+ *
82
+ * This is the contract between `buildView` and a renderer — see the `pipeline`
83
+ * example for the low-level path. Hosts using `buildWaferMap` + `renderWaferMap`
84
+ * never need it: that pipeline builds and re-builds the view internally, and the
85
+ * copy hung on `WaferMapResult.view` is marked `@internal` precisely because
86
+ * reading it there means reaching past the API that owns it.
87
+ *
88
+ * Treat the geometry arrays as read-only. `dies` and `hoverPoints` are
89
+ * **index-parallel** — `hoverPoints[i]` is the transformed centre of `dies[i]` —
90
+ * and code that resolves a die from a screen position depends on that staying
91
+ * true.
92
+ */
78
93
  export interface View {
94
+ /** Die rectangles to fill, in draw order, already carrying their resolved colour. */
79
95
  rectangles: ViewRect[];
96
+ /**
97
+ * Transformed centre of each die, index-parallel with {@link dies}. Separate
98
+ * from `rectangles` because a die always has a centre for hit-testing and
99
+ * viewport fitting even when it is not drawn (a partial die with
100
+ * `showPartialDies: false`).
101
+ */
80
102
  hoverPoints: ViewHoverPoint[];
103
+ /** Text to draw — die labels and any axis/indicator strings the view generated. */
81
104
  texts: ViewText[];
105
+ /** Non-die geometry: wafer outline, notch, ring/quadrant boundaries, reticle grid, XY indicator. */
82
106
  overlays: ViewOverlay[];
107
+ /** The plot mode this view was built for. Colours in `rectangles` follow it. */
83
108
  plotMode: PlotMode;
109
+ /** Name of the colour scheme used, as registered with `registerColorScheme`. */
84
110
  colorScheme: string;
111
+ /** Wafer-level metadata carried through from the wafer, or `null`. */
85
112
  metadata: WaferMetadata | null;
113
+ /**
114
+ * The positioned dies this view was built from, index-parallel with
115
+ * {@link hoverPoints}. Original grid coordinates are preserved on each die —
116
+ * never derive display coordinates from these.
117
+ */
86
118
  dies: Die[];
87
119
  /** Actual [min, max] of the value data used for color normalization. */
88
120
  valueRange: [number, number];
@@ -370,7 +402,6 @@ export interface HoverTextOptions {
370
402
  aggrMethod?: string;
371
403
  /** Number of wafers in the stack, used to annotate a count with its share of the lot. */
372
404
  lotSize?: number;
373
- waferMeta?: WaferMetadata | null;
374
405
  /** Active test number (value mode only) — leads the tooltip and gets an out-of-spec note. */
375
406
  activeTest?: number;
376
407
  /** Reticle geometry, when configured — appended as a "Reticle (col, row)" line below Die (x, y). */
@@ -425,6 +456,8 @@ export declare function generateTextOverlay(dies: PositionedDie[], txCoords: Flo
425
456
  /** Active `die.metadata` key + its resolved value→color map, `'metadata'` mode only. */
426
457
  activeMetadataKey?: string;
427
458
  metadataColorMap?: Map<string, string> | null;
459
+ /** Mirrors `View.allIntegerValues` — see the colorbar's own `tickFmt` override in toCanvas.ts. */
460
+ allIntegerValues?: boolean;
428
461
  }): ViewText[];
429
462
  /**
430
463
  * Build a renderer-agnostic scene from a wafer, dies, and display options.
@@ -1 +1 @@
1
- import{hasPosition as Zt}from"../core/dies.js";import{getReticleCell as te}from"../core/reticle.js";import{metadataCategoricalValue as ee,metadataDisplayValue as ne}from"../core/metadata.js";import{affineRotation as dt,affineMirror as Rt,affineCompose as it,affinePoint as Y,affineVector as ht,affineSwapsAxes as ae}from"../core/transforms.js";import{contrastTextColor as G,SPEC_PASS_FILL as pt,SPEC_FAIL_LOW as ie,SPEC_FAIL_HIGH as mt}from"./colorMap.js";import{getColorScheme as se}from"./colorSchemes.js";import{getDieTestValue as R,getTestPassStatus as Q,isParametricTest as yt}from"./buildWaferMap.js";import{fmt as gt,fmtColorbarAxis as bt,fmtAggregationMethod as Ct}from"./fmt.js";import{metadataValueColor as oe}from"./colorMap.js";import{compareNatural as re,clamp01 as Nt}from"../core/utils.js";import{prettyKey as Ht}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,a)=>e-a)}const le="#d3d6db",Ot="#e8e9ea",ce="#aab0ba",j="#d6d9dd";function st(t,e){return e?ee(t.metadata?.[e]):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function ue(t,e,a){const{x:i,y:s}=t.center,n=dt(t.orientation,i,s),o=it(Rt(e?.x??!1,e?.y??!1,i,s),dt(t.orientation,i,s)),r=it(Rt(a?.flipX??!1,a?.flipY??!1,i,s),dt(a?.rotation??0,i,s));return{bakedToScreen:r,physicalToScreen:it(r,n),gridToScreen:it(r,o)}}function W(t,e=!1){return{points:[t],closed:e}}function fe(t,e){const{center:a,radius:i,notch:s}=t;let n=a.x+i*Math.cos(e),o=a.y+i*Math.sin(e);if(!s)return{x:n,y:o};const r=Math.sqrt(i**2-(s.length/2)**2),u=s.length/2,b=n-a.x,y=o-a.y;return s.type==="bottom"&&y<-r?(o=a.y-r,n=a.x+Math.max(-u,Math.min(u,b))):s.type==="top"&&y>r?(o=a.y+r,n=a.x+Math.max(-u,Math.min(u,b))):s.type==="left"&&b<-r?(n=a.x-r,o=a.y+Math.max(-u,Math.min(u,y))):s.type==="right"&&b>r&&(n=a.x+r,o=a.y+Math.max(-u,Math.min(u,y))),{x:n,y:o}}function At(t,e){return t.map(e).join(" / ")}function de(t,e=256){const a=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>a[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function he(t,e){const a=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,i*1.8))))}function pe(t,e,a){if(e?.length){const i=e.flatMap(s=>{const n=s.testNumber;if(!yt(s)){const u=Q(t,n,s);return u===void 0?[]:[{key:n,label:s.name,value:u?"Pass":"Fail"}]}const o=R(t,n);if(o===void 0)return[];const r=t.testPass?.[n]===!1?" <i>(recorded fail)</i>":"";return[{key:n,label:s.name,value:`${gt(o,s.unit,a)}${r}`}]});if(i.length)return i}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:gt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:i,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:r,lotSize:u,waferMeta:b,activeTest:y,reticleConfig:B,metadataFields:V}=a,v=s?new Map(s.map(c=>[c.bin,c])):null,T=n?new Map(n.map(c=>[c.bin,c])):null,m=[`Die (${t.x}, ${t.y})`];if(B&&Zt(t)){const c=te(t,B);m.push(`Reticle (${c.column}, ${c.row})`)}if(e==="stackedValues"){const c=R(t,0);if(c!==void 0){const f=i?.[0],h=f?.testNumber,g=f?.name??(h!=null?`Test ${h}`:"Value"),M=r?` (${r})`:"";m.push(`${g}${M}: ${gt(c,f?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const c=R(t,0),f=e==="stackedSoftBins"?t.sbin:t.hbin;if(c!==void 0){const h=e==="stackedSoftBins"?T:v,g=f!==void 0?h?.get(f)?.name?`${f} \xB7 ${h.get(f).name}`:`Bin ${f}`:"Bin";let M;if(r==="percent")M=`${c.toFixed(0)}%`;else{const H=u?` (${(c/u*100).toFixed(0)}% of lot)`:"";M=`${c}${H}`}const p=r?` [${Ct(r)}]`:"";m.push(`${g}: ${M}${p}`)}}else{const c=pe(t,i,o);if(e==="value"&&c.length){const{testNumber:f}=resolveTestNumber(y??0,i),h=findTestDef(i,f),g=R(t,f);let M=c.findIndex(E=>E.key===f);M<0&&(M=0);const p=c[M];let H=`<b>${p.label}: ${p.value}</b>`;if(p.key===f&&h){const E=classifySpec(g,h);(E==="failLow"||E==="failHigh")&&(H+=" <i>(out of spec)</i>")}m.push(H);const A=c.length-1;A>0&&m.push(`<i>+${A} more test${A===1?"":"s"}</i>`)}else if(c.length){const f=c.length;m.push(`<i>${f} test result${f===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const f=[];if(t.hbin!==void 0){const h=v?.get(t.hbin)?.name,g=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);f.push(`HBin: ${g}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,g=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);f.push(`SBin: ${g}`)}m.push(f.join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&m.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&m.push(`Site: ${t.siteNum}`),t.partId!==void 0&&m.push(`Part ID: ${t.partId}`),t.partial&&m.push("<i>partial die</i>"),t.probeIndex!==void 0&&m.push(`Probe: #${t.probeIndex}`);const S={...b??{},...t.metadata??{}};for(const[c,f]of Object.entries(S)){const h=ne(f);if(h===void 0)continue;const g=V?.find(M=>M.key===c)?.label??Ht(c);m.push(`${g}: ${h}`)}return m.join("<br>")}export function buildMapTitle(t,e="engineering",a){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${n} Bin`:o.name?`${n} Bin ${o.bin} \xB7 ${o.name}`:`${n} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const n=t.metadataFields?.find(r=>r.key===t.activeMetadataKey),o=t.activeMetadataKey?Ht(t.activeMetadataKey):"Metadata";return{primary:n?.label??o,secondary:""}}case"stackedValues":{const n=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=bt(o,n?.name,n?.unit,e),u=r||"Value",b=t.aggrMethod?` \xB7 ${Ct(t.aggrMethod)}`:"";return{primary:`${u}${b}`,secondary:i}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:r}=bt(o,n?.name,n?.unit,e),u=r||n?.name;if(t.passFailDisplay!=="off"){const b=n?.testNumber??t.activeTest,y=u?`${u} \xB7 #${b}`:`Test ${b}`,B=t.passFailDisplay==="spec"?"Spec pass/fail":n!==void 0&&!yt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:y,secondary:B}}return{primary:u??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:i,colorFns:s,normalize:n,activeTest:o,valueRange:r,testDefs:u,fallbackFormat:b,passFailDisplay:y="off",activeMetadataKey:B,metadataColorMap:V}=a,v=findTestDef(u,o),{tickFmt:T}=bt(r[1],v?.name,v?.unit,b),{testNumber:m}=resolveTestNumber(o,u);return t.flatMap((S,c)=>{let f="",h="#111111";if(i==="value"&&y==="test"){const p=Q(S,m,v);if(p===void 0)return[];f=p?"P":"F",h=G(p?pt:mt)}else if(i==="value"){const p=R(S,m);if(p===void 0)return[];f=At([p],T),h=G(s.forValue(n(p)))}else if(i==="hardBin"||i==="softBin"){const p=i==="softBin"?S.sbin:S.hbin;if(p===void 0)return[];f=String(p),h=G(s.forBin(p))}else if(i==="metadata"){const p=st(S,B);if(p===void 0)return[];f=p,h=G(V?.get(f)??j)}else{const p=R(S,0);if(p===void 0)return[];f=At([p],T),h=G(s.forValue(n(p)))}const g=e?e[c*2]:S.physX,M=e?e[c*2+1]:S.physY;return[{x:g,y:M,text:f,fontSize:he(S,f),color:h,align:"center"}]})}function Et(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function me(t,e,a=720){const{center:i,radius:s,notch:n}=t,o=m=>Y(e,m.x,m.y);if(!(n!==void 0&&t.diameter>150)){const m=[];for(let S=0;S<=a;S++){const c=2*Math.PI*S/a;m.push(o(fe(t,c)))}return[{kind:"wafer-boundary",...W(m,!0),lineColor:"#888888",lineWidth:1}]}const u=1.25,b=n.length,y=Et(n.type),B=Math.atan2(b,s),V=s-u,v=[];let T=!1;for(let m=0;m<=a;m++){const S=2*Math.PI*m/a;let c=S-y;for(;c>Math.PI;)c-=2*Math.PI;for(;c<-Math.PI;)c+=2*Math.PI;if(Math.abs(c)<=B){if(!T){T=!0;const f={x:i.x+s*Math.cos(y-B),y:i.y+s*Math.sin(y-B)},h={x:i.x+V*Math.cos(y),y:i.y+V*Math.sin(y)},g={x:i.x+s*Math.cos(y+B),y:i.y+s*Math.sin(y+B)};v.push(o(f),o(h),o(g))}continue}v.push(o({x:i.x+s*Math.cos(S),y:i.y+s*Math.sin(S)}))}return[{kind:"wafer-boundary",...W(v,!0),lineColor:"#888888",lineWidth:1}]}function ye(t,e,a,i=360){const s=[],n=Math.max(1,Math.floor(a));for(let o=1;o<n;o++){const r=t.radius*o/n,u=[];for(let b=0;b<=i;b++){const y=2*Math.PI*b/i,B={x:t.center.x+r*Math.cos(y),y:t.center.y+r*Math.sin(y)};u.push(Y(e,B.x,B.y))}s.push({kind:"ring-boundary",...W(u,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function ge(t,e,a,i){const{x:s,y:n}=t.center,o=t.radius,r=(S,c)=>Y(e,S,c),u=a-s,b=Math.sqrt(Math.max(0,o*o-u*u)),y=r(a,n-b),B=r(a,n+b),V=i-n,v=Math.sqrt(Math.max(0,o*o-V*V)),T=r(s-v,i),m=r(s+v,i);return[{kind:"quadrant-boundary",...W([y,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...W([T,m]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function be(t,e){return t.map(a=>{const i=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-i,a.y-s],[a.x+i,a.y-s],[a.x+i,a.y+s],[a.x-i,a.y+s]].map(([o,r])=>Y(e,o,r))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function xe(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,i)=>(a.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",...W(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function Me(t,e,a,i,s,n,o,r,u,b,y,B,V,v,T="off",m,S,c){const f=e.width-o,h=e.height-o,g=n?h:f,M=n?f:h,p=$=>s==="softBin"?$.sbin:$.hbin,H=$=>st($,m);if(e.partial){t.push({x:a,y:i,width:g,height:M,fill:le,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:i,width:g,height:M,fill:ce,type:"stacked",metadata:e.metadata});return}if(u!==void 0&&(s==="hardBin"||s==="softBin")&&p(e)!==u){t.push({x:a,y:i,width:g,height:M,fill:Ot,type:"hardBin",metadata:e.metadata});return}if(c!==void 0&&s==="metadata"&&H(e)!==c){t.push({x:a,y:i,width:g,height:M,fill:Ot,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(T==="test"){const J=Q(e,y,v),ot=J===void 0?j:J?pt:mt;t.push({x:a,y:i,width:g,height:M,fill:ot,type:"value",metadata:e.metadata});return}const $=R(e,y),D=classifySpec($,v);let _,U;$===void 0?_=j:T==="spec"?_=D==="failLow"?ie:D==="failHigh"?mt:pt:(_=r.forValue(b($)),(D==="failLow"||D==="failHigh")&&(U=D)),t.push({x:a,y:i,width:g,height:M,fill:_,type:"value",specMark:U,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const $=p(e),D=$!=null?r.forBin($):j,_=$!=null&&!V.has($)?!0:void 0;t.push({x:a,y:i,width:g,height:M,fill:D,type:s,binFail:_,metadata:e.metadata});return}if(s==="metadata"){const $=H(e),D=$!==void 0?S?.get($)??j:j;t.push({x:a,y:i,width:g,height:M,fill:D,type:"metadata",metadata:e.metadata});return}const A=R(e,0),E=A!==void 0?r.forValue(b(A)):j;t.push({x:a,y:i,width:g,height:M,fill:E,type:"value",metadata:e.metadata})}function Se(t,e,a){const i=t.radius*.15,s=ht(e,i,0),n=ht(e,0,i),o=s.x+n.x>=0?-1:1,r=s.y+n.y>=0?-1:1,u={x:t.center.x+o*t.radius*.9,y:t.center.y+r*t.radius*.9},b={x:u.x+s.x,y:u.y+s.y},y={x:u.x+n.x,y:u.y+n.y};return a.push({x:b.x+s.x*.35,y:b.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:y.x+n.x*.35,y:y.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...W([u,b]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...W([u,y]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},i){const s=a.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:r=!1,showProbePath:u=!1,ringCount:b=4,showRingBoundaries:y=!1,showQuadrantBoundaries:B=!1,showXYIndicator:V=!1,dieGap:v=1,colorScheme:T="default",passBins:m=[1],highlightBin:S,highlightMetadataValue:c,interactiveTransform:f,valueRange:h,testDefs:g,activeTest:M=0,activeMetadataKey:p,fallbackFormat:H="engineering",aggregationMethod:A,lotSize:E,dataAxisFlip:$,isLotStack:D=!1,logScale:_,colorbarRangeMode:U="spec",passFailDisplay:J,showPartialDies:ot=!0}=a,xt=J??"off",Mt=i?.hbinDefs,St=i?.sbinDefs,$t=i?.metadataFields,_t=$t?.find(l=>l.key===p),wt={x:($?.x??!1)!==(f?.flipX??!1),y:($?.y??!1)!==(f?.flipY??!1)},Wt=Mt?new Map(Mt.map(l=>[l.bin,l])):null,jt=St?new Map(St.map(l=>[l.bin,l])):null,rt=n==="softBin"?jt:Wt,lt=se(T),zt=new Set(m),kt={forValue:de(lt.forValue),forBin:T==="custom"&&rt?l=>rt.get(l)?.color??lt.forBin(l):lt.forBin};let{testNumber:O}=resolveTestNumber(M,g);if(!g?.length&&n==="value"&&!e.some(d=>d.testValues&&O in d.testValues)){const d=e.reduce((x,F)=>{if(!F.testValues)return x;const k=Object.keys(F.testValues).map(Number),q=k.length?Math.min(...k):void 0;return q!==void 0&&(x===void 0||q<x)?q:x},void 0);d!==void 0&&(O=d)}let Z;if(Array.isArray(h))Z=h;else if(h){const{testNumber:l}=resolveTestNumber(h.test,g);Z=l===O?h.range:void 0}const I=findTestDef(g,O),Bt=n==="value"&&I!==void 0&&!yt(I),Kt=I!==void 0&&(I.limitLow!==void 0||I.limitHigh!==void 0);let C="off";n==="value"&&(Bt?C="test":xt==="spec"&&Kt?C="spec":xt==="test"&&e.some(l=>Q(l,O,I)!==void 0)&&(C="test"));const vt=C==="spec"?"spec":U;let P,N,Tt=!1;if(Z)[P,N]=Z;else{const l=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins",d=!l&&n==="value"&&vt==="spec"&&I&&(I.limitLow!==void 0||I.limitHigh!==void 0);let x=1/0,F=-1/0,k=!0;for(const q of e){const X=l?R(q,0):R(q,O);X!==void 0&&(X<x&&(x=X),X>F&&(F=X),Number.isInteger(X)||(k=!1))}isFinite(x)||(k=!1),Tt=k,d?(P=I.limitLow!==void 0?I.limitLow:isFinite(x)?x:0,N=I.limitHigh!==void 0?I.limitHigh:isFinite(F)?F:1):(P=isFinite(x)?x:0,N=isFinite(F)?F:1)}P===N&&(N=P+1);const It=Bt?!1:_??I?.logScale??!1,Ft=It&&P>0&&N>0,qt=Ft;let tt;if(Ft){const l=Math.log10(P),d=Math.log10(N)-l;tt=x=>x<=0?0:Nt((Math.log10(x)-l)/d)}else tt=l=>Nt((l-P)/(N-P));const L=ue(t,$,f);let Lt=null;if(t.notch){const l=Et(t.notch.type),d=ht(L.physicalToScreen,Math.cos(l)*t.radius,Math.sin(l)*t.radius),x=Math.hypot(d.x,d.y);x>0&&(Lt={x:d.x/x,y:d.y/x})}let z=1/0;for(let l=0;l<e.length;l++){const d=e[l];d.width<z&&(z=d.width),d.height<z&&(z=d.height)}const Xt=z!==1/0?Math.min(v,z*.12):v,Vt=[],ct=[],Yt=L.bakedToScreen.a===1&&L.bakedToScreen.b===0&&L.bakedToScreen.c===0&&L.bakedToScreen.d===1&&L.bakedToScreen.e===0&&L.bakedToScreen.f===0;let w=null;if(!Yt){w=new Float64Array(e.length*2);for(let l=0;l<e.length;l++){const d=Y(L.bakedToScreen,e[l].physX,e[l].physY);w[l*2]=d.x,w[l*2+1]=d.y}}const Gt=ae(L.gridToScreen),Qt=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",Ut=n==="softBin"||n==="stackedSoftBins";let et=Qt?new Map:void 0;const ut=n==="value"&&C==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,ft=n==="value"&&C==="test"?{pass:0,fail:0}:void 0,Dt=n==="metadata";let nt=Dt?new Map:void 0,at=null;if(Dt&&p){const l=new Set;for(const x of e){if(x.partial||x.edgeExcluded)continue;const F=st(x,p);F!==void 0&&l.add(F)}const d=[...l].sort(re);at=new Map(d.map((x,F)=>[x,_t?.values?.find(k=>k.value===x)?.color??oe(F)]))}for(let l=0;l<e.length;l++){const d=e[l],x=w?w[l*2]:d.physX,F=w?w[l*2+1]:d.physY;if(ct.push({x,y:F}),et&&!d.partial&&!d.edgeExcluded){const k=Ut?d.sbin:d.hbin;k!=null&&et.set(k,(et.get(k)??0)+1)}if(ut&&!d.partial&&!d.edgeExcluded){const k=classifySpec(R(d,O),I);k&&ut[k]++}if(ft&&!d.partial&&!d.edgeExcluded){const k=Q(d,O,I);k!==void 0&&ft[k?"pass":"fail"]++}if(nt&&!d.partial&&!d.edgeExcluded){const k=st(d,p);k!==void 0&&nt.set(k,(nt.get(k)??0)+1)}d.partial&&!ot||Me(Vt,d,x,F,n,Gt,Xt,kt,S,tt,O,rt,zt,I,C,p,at,c)}const Pt=o?generateTextOverlay(e,w,{plotMode:n,colorFns:kt,normalize:tt,activeTest:M,valueRange:[P,N],testDefs:g,fallbackFormat:H,passFailDisplay:C,activeMetadataKey:p,metadataColorMap:at}):[],K=me(t,L.physicalToScreen);y&&K.push(...ye(t,L.physicalToScreen,b)),B&&K.push(...ge(t,L.physicalToScreen,t.center.x,t.center.y)),r&&K.push(...be(s,L.gridToScreen)),u&&K.push(...xe(e)),V&&K.push(...Se(t,L.gridToScreen,Pt));const Jt=ct.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Vt,hoverPoints:ct,texts:Pt,overlays:K,plotMode:n,colorScheme:T,metadata:t.metadata??null,dies:e,valueRange:[P,N],allIntegerValues:Tt,testDefs:g,activeTest:M,logScale:qt,logScaleRequested:It,aggrMethod:A,lotSize:E,axisFlip:wt,dataAxisFlip:{x:$?.x??!1,y:$?.y??!1},gridToScreen:L.gridToScreen,rotation:((t.orientation+(f?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:D,passFailDisplay:C,colorBySpec:C==="spec",colorbarRangeMode:vt,waferCenter:t.center,waferRadius:t.radius,notchDir:Lt,binCounts:et,metadataCounts:nt,metadataColorMap:at,activeMetadataKey:p,metadataFields:$t,specCounts:ut,passFailCounts:ft,dieBounds:Jt}}export{getDieKey}from"../core/dies.js";
1
+ import{hasPosition as Zt}from"../core/dies.js";import{getReticleCell as te}from"../core/reticle.js";import{metadataCategoricalValue as ee,metadataDisplayValue as ne}from"../core/metadata.js";import{affineRotation as ht,affineMirror as Rt,affineCompose as at,affinePoint as q,affineVector as mt,affineSwapsAxes as ae}from"../core/transforms.js";import{contrastTextColor as X,SPEC_PASS_FILL as pt,SPEC_FAIL_LOW as ie,SPEC_FAIL_HIGH as yt}from"./colorMap.js";import{getColorScheme as se}from"./colorSchemes.js";import{getDieTestValue as P,getTestPassStatus as Y,isParametricTest as gt}from"./buildWaferMap.js";import{fmt as bt,fmtColorbarAxis as xt,fmtAggregationMethod as Ct}from"./fmt.js";import{metadataValueColor as oe}from"./colorMap.js";import{compareNatural as re,clamp01 as Nt}from"../core/utils.js";import{prettyKey as Ht}from"../core/utils.js";export function resolveTestNumber(t,e){return e?.length?{testNumber:(findTestDef(e,t)??e[0]).testNumber}:{testNumber:t}}export function findTestDef(t,e){return t?.find(a=>a.testNumber===e)}export function getUniqueTestNumbers(t){return[...new Set(t.flatMap(e=>e.testValues?Object.keys(e.testValues).map(Number):[]))].sort((e,a)=>e-a)}const le="#d3d6db",Ot="#e8e9ea",ce="#aab0ba",j="#d6d9dd";function it(t,e){return e?ee(t.metadata?.[e]):void 0}export function classifySpec(t,e){return t===void 0?null:e?.limitLow!==void 0&&t<e.limitLow?"failLow":e?.limitHigh!==void 0&&t>e.limitHigh?"failHigh":"pass"}function ue(t,e,a){const{x:i,y:s}=t.center,n=ht(t.orientation,i,s),o=at(Rt(e?.x??!1,e?.y??!1,i,s),ht(t.orientation,i,s)),c=at(Rt(a?.flipX??!1,a?.flipY??!1,i,s),ht(a?.rotation??0,i,s));return{bakedToScreen:c,physicalToScreen:at(c,n),gridToScreen:at(c,o)}}function W(t,e=!1){return{points:[t],closed:e}}function fe(t,e){const{center:a,radius:i,notch:s}=t;let n=a.x+i*Math.cos(e),o=a.y+i*Math.sin(e);if(!s)return{x:n,y:o};const c=Math.sqrt(i**2-(s.length/2)**2),u=s.length/2,y=n-a.x,p=o-a.y;return s.type==="bottom"&&p<-c?(o=a.y-c,n=a.x+Math.max(-u,Math.min(u,y))):s.type==="top"&&p>c?(o=a.y+c,n=a.x+Math.max(-u,Math.min(u,y))):s.type==="left"&&y<-c?(n=a.x-c,o=a.y+Math.max(-u,Math.min(u,p))):s.type==="right"&&y>c&&(n=a.x+c,o=a.y+Math.max(-u,Math.min(u,p))),{x:n,y:o}}function At(t,e){return t.map(e).join(" / ")}function de(t,e=256){const a=Array.from({length:e},(i,s)=>t(s/(e-1)));return i=>a[Math.min(e-1,Math.max(0,Math.round(i*(e-1))))]}function he(t,e){const a=Math.max(1,Math.min(t.width,t.height)),i=t.width/Math.max(e.length,1);return Math.max(8,Math.min(16,Math.round(Math.min(a*.55,i*1.8))))}function me(t,e,a){if(e?.length){const i=e.flatMap(s=>{const n=s.testNumber;if(!gt(s)){const u=Y(t,n,s);return u===void 0?[]:[{key:n,label:s.name,value:u?"Pass":"Fail"}]}const o=P(t,n);if(o===void 0)return[];const c=t.testPass?.[n]===!1?" <i>(recorded fail)</i>":"";return[{key:n,label:s.name,value:`${bt(o,s.unit,a)}${c}`}]});if(i.length)return i}return t.testValues&&Object.keys(t.testValues).length>0?Object.entries(t.testValues).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:bt(s,void 0,a)})):t.testPass&&Object.keys(t.testPass).length>0?Object.entries(t.testPass).map(([i,s])=>({key:Number(i),label:`Test ${i}`,value:s?"Pass":"Fail"})):[]}export function buildHoverText(t,e,a={}){const{testDefs:i,hbinDefs:s,sbinDefs:n,fallbackFormat:o,aggrMethod:c,lotSize:u,activeTest:y,reticleConfig:p,metadataFields:B}=a,D=s?new Map(s.map(f=>[f.bin,f])):null,T=n?new Map(n.map(f=>[f.bin,f])):null,b=[`Die (${t.x}, ${t.y})`];if(p&&Zt(t)){const f=te(t,p);b.push(`Reticle (${f.column}, ${f.row})`)}if(e==="stackedValues"){const f=P(t,0);if(f!==void 0){const d=i?.[0],h=d?.testNumber,S=d?.name??(h!=null?`Test ${h}`:"Value"),m=c?` (${c})`:"";b.push(`${S}${m}: ${bt(f,d?.unit,o)}`)}}else if(e==="stackedBins"||e==="stackedSoftBins"){const f=P(t,0),d=e==="stackedSoftBins"?t.sbin:t.hbin;if(f!==void 0){const h=e==="stackedSoftBins"?T:D,S=d!==void 0?h?.get(d)?.name?`${d} \xB7 ${h.get(d).name}`:`Bin ${d}`:"Bin";let m;if(c==="percent")m=`${f.toFixed(0)}%`;else{const I=u?` (${(f/u*100).toFixed(0)}% of lot)`:"";m=`${f}${I}`}const M=c?` [${Ct(c)}]`:"";b.push(`${S}: ${m}${M}`)}}else{const f=me(t,i,o);if(e==="value"&&f.length){const{testNumber:d}=resolveTestNumber(y??0,i),h=findTestDef(i,d),S=P(t,d);let m=f.findIndex(x=>x.key===d);m<0&&(m=0);const M=f[m];let I=`<b>${M.label}: ${M.value}</b>`;if(M.key===d&&h){const x=classifySpec(S,h);(x==="failLow"||x==="failHigh")&&(I+=" <i>(out of spec)</i>")}b.push(I);const N=f.length-1;N>0&&b.push(`<i>+${N} more test${N===1?"":"s"}</i>`)}else if(f.length){const d=f.length;b.push(`<i>${d} test result${d===1?"":"s"} recorded</i>`)}if(t.hbin!==void 0||t.sbin!==void 0){const d=[];if(t.hbin!==void 0){const h=D?.get(t.hbin)?.name,S=h?`${t.hbin} \xB7 ${h}`:String(t.hbin);d.push(`HBin: ${S}`)}if(t.sbin!==void 0){const h=T?.get(t.sbin)?.name,S=h?`${t.sbin} \xB7 ${h}`:String(t.sbin);d.push(`SBin: ${S}`)}b.push(d.join(" &nbsp;\xB7&nbsp; "))}}t.retestCount!==void 0&&b.push(`Retests: ${t.retestCount}`),t.siteNum!==void 0&&b.push(`Site: ${t.siteNum}`),t.partId!==void 0&&b.push(`Part ID: ${t.partId}`),t.partial&&b.push("<i>partial die</i>"),t.probeIndex!==void 0&&b.push(`Probe: #${t.probeIndex}`);const v={...t.metadata??{}};for(const[f,d]of Object.entries(v)){const h=ne(d);if(h===void 0)continue;const S=B?.find(m=>m.key===f)?.label??Ht(f);b.push(`${S}: ${h}`)}return b.join("<br>")}export function buildMapTitle(t,e="engineering",a){const i=t.isLotStack&&t.lotSize!==void 0?`stacked (${t.lotSize} ${t.lotSize===1?"wafer":"wafers"})`:"stacked",s=n=>{const o=a?.length===1?a[0]:void 0;return o===void 0?`${n} Bin`:o.name?`${n} Bin ${o.bin} \xB7 ${o.name}`:`${n} Bin ${o.bin}`};switch(t.plotMode){case"hardBin":return{primary:"Hard Bin",secondary:""};case"softBin":return{primary:"Soft Bin",secondary:""};case"stackedBins":return{primary:s("Hard"),secondary:i};case"stackedSoftBins":return{primary:s("Soft"),secondary:i};case"metadata":{const n=t.metadataFields?.find(c=>c.key===t.activeMetadataKey),o=t.activeMetadataKey?Ht(t.activeMetadataKey):"Metadata";return{primary:n?.label??o,secondary:""}}case"stackedValues":{const n=t.testDefs?.[0],o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:c}=xt(o,n?.name,n?.unit,e),u=c||"Value",y=t.aggrMethod?` \xB7 ${Ct(t.aggrMethod)}`:"";return{primary:`${u}${y}`,secondary:i}}default:{const n=findTestDef(t.testDefs,t.activeTest),o=t.valueRange[1]||t.valueRange[0]||0,{axisLabel:c}=xt(o,n?.name,n?.unit,e),u=c||n?.name;if(t.passFailDisplay!=="off"){const y=n?.testNumber??t.activeTest,p=u?`${u} \xB7 #${y}`:`Test ${y}`,B=t.passFailDisplay==="spec"?"Spec pass/fail":n!==void 0&&!gt(n)?"Functional pass/fail":"Tester pass/fail";return{primary:p,secondary:B}}return{primary:u??`Test ${t.activeTest}`,secondary:""}}}}export function generateTextOverlay(t,e,a){const{plotMode:i,colorFns:s,normalize:n,activeTest:o,valueRange:c,testDefs:u,fallbackFormat:y,passFailDisplay:p="off",activeMetadataKey:B,metadataColorMap:D,allIntegerValues:T}=a,b=findTestDef(u,o),{tickFmt:v}=xt(c[1],b?.name,b?.unit,y),f=T?h=>String(Math.round(h)):v,{testNumber:d}=resolveTestNumber(o,u);return t.flatMap((h,S)=>{let m="",M="#111111";if(i==="value"&&p==="test"){const x=Y(h,d,b);if(x===void 0)return[];m=x?"P":"F",M=X(x?pt:yt)}else if(i==="value"){const x=P(h,d);if(x===void 0)return[];m=At([x],f),M=X(s.forValue(n(x)))}else if(i==="hardBin"||i==="softBin"){const x=i==="softBin"?h.sbin:h.hbin;if(x===void 0)return[];m=String(x),M=X(s.forBin(x))}else if(i==="metadata"){const x=it(h,B);if(x===void 0)return[];m=x,M=X(D?.get(m)??j)}else{const x=P(h,0);if(x===void 0)return[];m=At([x],f),M=X(s.forValue(n(x)))}const I=e?e[S*2]:h.physX,N=e?e[S*2+1]:h.physY;return[{x:I,y:N,text:m,fontSize:he(h,m),color:M,align:"center"}]})}function Et(t){return t==="top"?Math.PI/2:t==="bottom"?-Math.PI/2:t==="left"?Math.PI:0}function pe(t,e,a=720){const{center:i,radius:s,notch:n}=t,o=v=>q(e,v.x,v.y);if(!(n!==void 0&&t.diameter>150)){const v=[];for(let f=0;f<=a;f++){const d=2*Math.PI*f/a;v.push(o(fe(t,d)))}return[{kind:"wafer-boundary",...W(v,!0),lineColor:"#888888",lineWidth:1}]}const u=1.25,y=n.length,p=Et(n.type),B=Math.atan2(y,s),D=s-u,T=[];let b=!1;for(let v=0;v<=a;v++){const f=2*Math.PI*v/a;let d=f-p;for(;d>Math.PI;)d-=2*Math.PI;for(;d<-Math.PI;)d+=2*Math.PI;if(Math.abs(d)<=B){if(!b){b=!0;const h={x:i.x+s*Math.cos(p-B),y:i.y+s*Math.sin(p-B)},S={x:i.x+D*Math.cos(p),y:i.y+D*Math.sin(p)},m={x:i.x+s*Math.cos(p+B),y:i.y+s*Math.sin(p+B)};T.push(o(h),o(S),o(m))}continue}T.push(o({x:i.x+s*Math.cos(f),y:i.y+s*Math.sin(f)}))}return[{kind:"wafer-boundary",...W(T,!0),lineColor:"#888888",lineWidth:1}]}function ye(t,e,a,i=360){const s=[],n=Math.max(1,Math.floor(a));for(let o=1;o<n;o++){const c=t.radius*o/n,u=[];for(let y=0;y<=i;y++){const p=2*Math.PI*y/i,B={x:t.center.x+c*Math.cos(p),y:t.center.y+c*Math.sin(p)};u.push(q(e,B.x,B.y))}s.push({kind:"ring-boundary",...W(u,!0),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5})}return s}function ge(t,e,a,i){const{x:s,y:n}=t.center,o=t.radius,c=(f,d)=>q(e,f,d),u=a-s,y=Math.sqrt(Math.max(0,o*o-u*u)),p=c(a,n-y),B=c(a,n+y),D=i-n,T=Math.sqrt(Math.max(0,o*o-D*D)),b=c(s-T,i),v=c(s+T,i);return[{kind:"quadrant-boundary",...W([p,B]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5},{kind:"quadrant-boundary",...W([b,v]),lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5}]}function be(t,e){return t.map(a=>{const i=a.width/2,s=a.height/2;return{kind:"reticle",points:[[[a.x-i,a.y-s],[a.x+i,a.y-s],[a.x+i,a.y+s],[a.x-i,a.y+s]].map(([o,c])=>q(e,o,c))],closed:!0,lineColor:"rgba(255,255,255,0.7)",lineWidth:1.5,fill:"rgba(0,0,0,0)"}})}function xe(t){const e=t.filter(a=>a.probeIndex!==void 0).sort((a,i)=>(a.probeIndex??0)-(i.probeIndex??0));return e.length?[{kind:"probe-path",...W(e.map(a=>({x:a.physX,y:a.physY}))),lineColor:"rgba(220,80,0,0.55)",lineWidth:1}]:[]}function Me(t,e,a,i,s,n,o,c,u,y,p,B,D,T,b="off",v,f,d){const h=e.width-o,S=e.height-o,m=n?S:h,M=n?h:S,I=k=>s==="softBin"?k.sbin:k.hbin,N=k=>it(k,v);if(e.partial){t.push({x:a,y:i,width:m,height:M,fill:le,type:"stacked",metadata:e.metadata});return}if(e.edgeExcluded){t.push({x:a,y:i,width:m,height:M,fill:ce,type:"stacked",metadata:e.metadata});return}if(u!==void 0&&(s==="hardBin"||s==="softBin")&&I(e)!==u){t.push({x:a,y:i,width:m,height:M,fill:Ot,type:"hardBin",metadata:e.metadata});return}if(d!==void 0&&s==="metadata"&&N(e)!==d){t.push({x:a,y:i,width:m,height:M,fill:Ot,type:"metadata",metadata:e.metadata});return}if(s==="value"){if(b==="test"){const Q=Y(e,p,T),ot=Q===void 0?j:Q?pt:yt;t.push({x:a,y:i,width:m,height:M,fill:ot,type:"value",metadata:e.metadata});return}const k=P(e,p),R=classifySpec(k,T);let _,G;k===void 0?_=j:b==="spec"?_=R==="failLow"?ie:R==="failHigh"?yt:pt:(_=c.forValue(y(k)),(R==="failLow"||R==="failHigh")&&(G=R)),t.push({x:a,y:i,width:m,height:M,fill:_,type:"value",specMark:G,metadata:e.metadata});return}if(s==="hardBin"||s==="softBin"){const k=I(e),R=k!=null?c.forBin(k):j,_=k!=null&&!D.has(k)?!0:void 0;t.push({x:a,y:i,width:m,height:M,fill:R,type:s,binFail:_,metadata:e.metadata});return}if(s==="metadata"){const k=N(e),R=k!==void 0?f?.get(k)??j:j;t.push({x:a,y:i,width:m,height:M,fill:R,type:"metadata",metadata:e.metadata});return}const x=P(e,0),st=x!==void 0?c.forValue(y(x)):j;t.push({x:a,y:i,width:m,height:M,fill:st,type:"value",metadata:e.metadata})}function Se(t,e,a){const i=t.radius*.15,s=mt(e,i,0),n=mt(e,0,i),o=s.x+n.x>=0?-1:1,c=s.y+n.y>=0?-1:1,u={x:t.center.x+o*t.radius*.9,y:t.center.y+c*t.radius*.9},y={x:u.x+s.x,y:u.y+s.y},p={x:u.x+n.x,y:u.y+n.y};return a.push({x:y.x+s.x*.35,y:y.y+s.y*.35,text:"+X",fontSize:10,color:"#cc3300",align:"center",role:"indicator"},{x:p.x+n.x*.35,y:p.y+n.y*.35,text:"+Y",fontSize:10,color:"#0044cc",align:"center",role:"indicator"}),[{kind:"xy-indicator",...W([u,y]),lineColor:"#cc3300",lineWidth:2},{kind:"xy-indicator",...W([u,p]),lineColor:"#0044cc",lineWidth:2}]}export function buildView(t,e,a={},i){const s=a.reticles??[],{plotMode:n="value",showDieLabels:o=!1,showReticle:c=!1,showProbePath:u=!1,ringCount:y=4,showRingBoundaries:p=!1,showQuadrantBoundaries:B=!1,showXYIndicator:D=!1,dieGap:T=1,colorScheme:b="default",passBins:v=[1],highlightBin:f,highlightMetadataValue:d,interactiveTransform:h,valueRange:S,testDefs:m,activeTest:M=0,activeMetadataKey:I,fallbackFormat:N="engineering",aggregationMethod:x,lotSize:st,dataAxisFlip:k,isLotStack:R=!1,logScale:_,colorbarRangeMode:G="spec",passFailDisplay:Q,showPartialDies:ot=!0}=a,Mt=Q??"off",St=i?.hbinDefs,kt=i?.sbinDefs,$t=i?.metadataFields,_t=$t?.find(l=>l.key===I),wt={x:(k?.x??!1)!==(h?.flipX??!1),y:(k?.y??!1)!==(h?.flipY??!1)},Wt=St?new Map(St.map(l=>[l.bin,l])):null,jt=kt?new Map(kt.map(l=>[l.bin,l])):null,rt=n==="softBin"?jt:Wt,lt=se(b),zt=new Set(v),Bt={forValue:de(lt.forValue),forBin:b==="custom"&&rt?l=>rt.get(l)?.color??lt.forBin(l):lt.forBin};let{testNumber:H}=resolveTestNumber(M,m);if(!m?.length&&n==="value"&&!e.some(r=>r.testValues&&H in r.testValues)){const r=e.reduce((g,V)=>{if(!V.testValues)return g;const $=Object.keys(V.testValues).map(Number),E=$.length?Math.min(...$):void 0;return E!==void 0&&(g===void 0||E<g)?E:g},void 0);r!==void 0&&(H=r)}let U;if(Array.isArray(S))U=S;else if(S){const{testNumber:l}=resolveTestNumber(S.test,m);U=l===H?S.range:void 0}const F=findTestDef(m,H),vt=n==="value"&&F!==void 0&&!gt(F),Kt=F!==void 0&&(F.limitLow!==void 0||F.limitHigh!==void 0);let O="off";n==="value"&&(vt?O="test":Mt==="spec"&&Kt?O="spec":Mt==="test"&&e.some(l=>Y(l,H,F)!==void 0)&&(O="test"));const Tt=O==="spec"?"spec":G,ct=n==="stackedValues"||n==="stackedBins"||n==="stackedSoftBins";let C,A,J=!1;if(U){[C,A]=U;let l=!0,r=!1;for(const g of e){const V=ct?P(g,0):P(g,H);if(V!==void 0&&(r=!0,!Number.isInteger(V))){l=!1;break}}J=r&&l}else{const l=!ct&&n==="value"&&Tt==="spec"&&F&&(F.limitLow!==void 0||F.limitHigh!==void 0);let r=1/0,g=-1/0,V=!0;for(const $ of e){const E=ct?P($,0):P($,H);E!==void 0&&(E<r&&(r=E),E>g&&(g=E),Number.isInteger(E)||(V=!1))}isFinite(r)||(V=!1),J=V,l?(C=F.limitLow!==void 0?F.limitLow:isFinite(r)?r:0,A=F.limitHigh!==void 0?F.limitHigh:isFinite(g)?g:1):(C=isFinite(r)?r:0,A=isFinite(g)?g:1)}C===A&&(A=C+1);const It=vt?!1:_??F?.logScale??!1,Ft=It&&C>0&&A>0,qt=Ft;let Z;if(Ft){const l=Math.log10(C),r=Math.log10(A)-l;Z=g=>g<=0?0:Nt((Math.log10(g)-l)/r)}else Z=l=>Nt((l-C)/(A-C));const L=ue(t,k,h);let Vt=null;if(t.notch){const l=Et(t.notch.type),r=mt(L.physicalToScreen,Math.cos(l)*t.radius,Math.sin(l)*t.radius),g=Math.hypot(r.x,r.y);g>0&&(Vt={x:r.x/g,y:r.y/g})}let z=1/0;for(let l=0;l<e.length;l++){const r=e[l];r.width<z&&(z=r.width),r.height<z&&(z=r.height)}const Xt=z!==1/0?Math.min(T,z*.12):T,Lt=[],ut=[],Yt=L.bakedToScreen.a===1&&L.bakedToScreen.b===0&&L.bakedToScreen.c===0&&L.bakedToScreen.d===1&&L.bakedToScreen.e===0&&L.bakedToScreen.f===0;let w=null;if(!Yt){w=new Float64Array(e.length*2);for(let l=0;l<e.length;l++){const r=q(L.bakedToScreen,e[l].physX,e[l].physY);w[l*2]=r.x,w[l*2+1]=r.y}}const Gt=ae(L.gridToScreen),Qt=n==="hardBin"||n==="softBin"||n==="stackedBins"||n==="stackedSoftBins",Ut=n==="softBin"||n==="stackedSoftBins";let tt=Qt?new Map:void 0;const ft=n==="value"&&O==="spec"?{pass:0,failHigh:0,failLow:0}:void 0,dt=n==="value"&&O==="test"?{pass:0,fail:0}:void 0,Dt=n==="metadata";let et=Dt?new Map:void 0,nt=null;if(Dt&&I){const l=new Set;for(const g of e){if(g.partial||g.edgeExcluded)continue;const V=it(g,I);V!==void 0&&l.add(V)}const r=[...l].sort(re);nt=new Map(r.map((g,V)=>[g,_t?.values?.find($=>$.value===g)?.color??oe(V)]))}for(let l=0;l<e.length;l++){const r=e[l],g=w?w[l*2]:r.physX,V=w?w[l*2+1]:r.physY;if(ut.push({x:g,y:V}),tt&&!r.partial&&!r.edgeExcluded){const $=Ut?r.sbin:r.hbin;$!=null&&tt.set($,(tt.get($)??0)+1)}if(ft&&!r.partial&&!r.edgeExcluded){const $=classifySpec(P(r,H),F);$&&ft[$]++}if(dt&&!r.partial&&!r.edgeExcluded){const $=Y(r,H,F);$!==void 0&&dt[$?"pass":"fail"]++}if(et&&!r.partial&&!r.edgeExcluded){const $=it(r,I);$!==void 0&&et.set($,(et.get($)??0)+1)}r.partial&&!ot||Me(Lt,r,g,V,n,Gt,Xt,Bt,f,Z,H,rt,zt,F,O,I,nt,d)}const Pt=o?generateTextOverlay(e,w,{plotMode:n,colorFns:Bt,normalize:Z,activeTest:M,valueRange:[C,A],testDefs:m,fallbackFormat:N,passFailDisplay:O,activeMetadataKey:I,metadataColorMap:nt,allIntegerValues:J}):[],K=pe(t,L.physicalToScreen);p&&K.push(...ye(t,L.physicalToScreen,y)),B&&K.push(...ge(t,L.physicalToScreen,t.center.x,t.center.y)),c&&K.push(...be(s,L.gridToScreen)),u&&K.push(...xe(e)),D&&K.push(...Se(t,L.gridToScreen,Pt));const Jt=ut.length>0?{minX:t.center.x-t.radius,maxX:t.center.x+t.radius,minY:t.center.y-t.radius,maxY:t.center.y+t.radius}:null;return{rectangles:Lt,hoverPoints:ut,texts:Pt,overlays:K,plotMode:n,colorScheme:b,metadata:t.metadata??null,dies:e,valueRange:[C,A],allIntegerValues:J,testDefs:m,activeTest:M,logScale:qt,logScaleRequested:It,aggrMethod:x,lotSize:st,axisFlip:wt,dataAxisFlip:{x:k?.x??!1,y:k?.y??!1},gridToScreen:L.gridToScreen,rotation:((t.orientation+(h?.rotation??0))%360+360)%360,hasReticle:s.length>0,isLotStack:R,passFailDisplay:O,colorBySpec:O==="spec",colorbarRangeMode:Tt,waferCenter:t.center,waferRadius:t.radius,notchDir:Vt,binCounts:tt,metadataCounts:et,metadataColorMap:nt,activeMetadataKey:I,metadataFields:$t,specCounts:ft,passFailCounts:dt,dieBounds:Jt}}export{getDieKey}from"../core/dies.js";