@genome-spy/core 0.78.0 → 0.79.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 (289) hide show
  1. package/dist/bundle/{browser-KWU9rWZT.js → browser-CETrb2cm.js} +53 -33
  2. package/dist/bundle/esm-BdLYkz-m.js +248 -0
  3. package/dist/bundle/esm-BwiDsqSb.js +1367 -0
  4. package/dist/bundle/esm-CDFd1cjk.js +441 -0
  5. package/dist/bundle/{esm-DVOHLB1e.js → esm-CTUHLDbv.js} +30 -30
  6. package/dist/bundle/{esm-NIYEaYkc.js → esm-Cx-EbkOj.js} +13 -13
  7. package/dist/bundle/esm-DlYGqi79.js +128 -0
  8. package/dist/bundle/{esm-BygJiwh0.js → esm-k9p3oHkt.js} +133 -158
  9. package/dist/bundle/{esm-CT3ygiMq.js → esm-zAZJQO6D.js} +226 -212
  10. package/dist/bundle/index.es.js +14840 -11661
  11. package/dist/bundle/index.js +119 -108
  12. package/dist/bundle/{parquetRead-DG_-F5j5.js → parquetRead-Cad1SOVV.js} +473 -399
  13. package/dist/schema.json +18940 -6914
  14. package/dist/src/config/axisConfig.d.ts +2 -2
  15. package/dist/src/config/axisConfig.d.ts.map +1 -1
  16. package/dist/src/config/axisConfig.js +28 -44
  17. package/dist/src/config/configLayers.d.ts +45 -0
  18. package/dist/src/config/configLayers.d.ts.map +1 -0
  19. package/dist/src/config/configLayers.js +110 -0
  20. package/dist/src/config/defaultConfig.d.ts.map +1 -1
  21. package/dist/src/config/defaultConfig.js +8 -1
  22. package/dist/src/config/defaults/legendDefaults.d.ts +14 -0
  23. package/dist/src/config/defaults/legendDefaults.d.ts.map +1 -0
  24. package/dist/src/config/defaults/legendDefaults.js +46 -0
  25. package/dist/src/config/defaults/titleDefaults.d.ts.map +1 -1
  26. package/dist/src/config/defaults/titleDefaults.js +26 -18
  27. package/dist/src/config/legendConfig.d.ts +11 -0
  28. package/dist/src/config/legendConfig.d.ts.map +1 -0
  29. package/dist/src/config/legendConfig.js +63 -0
  30. package/dist/src/config/styleUtils.d.ts +8 -2
  31. package/dist/src/config/styleUtils.d.ts.map +1 -1
  32. package/dist/src/config/styleUtils.js +25 -1
  33. package/dist/src/config/themes.d.ts.map +1 -1
  34. package/dist/src/config/themes.js +21 -2
  35. package/dist/src/config/titleConfig.d.ts.map +1 -1
  36. package/dist/src/config/titleConfig.js +2 -18
  37. package/dist/src/data/collector.d.ts.map +1 -1
  38. package/dist/src/data/collector.js +40 -18
  39. package/dist/src/data/flowInit.d.ts +6 -0
  40. package/dist/src/data/flowInit.d.ts.map +1 -1
  41. package/dist/src/data/flowInit.js +1 -1
  42. package/dist/src/data/flowNode.d.ts +32 -0
  43. package/dist/src/data/flowNode.d.ts.map +1 -1
  44. package/dist/src/data/flowNode.js +59 -0
  45. package/dist/src/data/sources/lazy/bamSource.d.ts +0 -1
  46. package/dist/src/data/sources/lazy/bamSource.d.ts.map +1 -1
  47. package/dist/src/data/sources/lazy/bamSource.js +39 -30
  48. package/dist/src/data/sources/lazy/bigBedSource.d.ts +0 -10
  49. package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
  50. package/dist/src/data/sources/lazy/bigBedSource.js +127 -62
  51. package/dist/src/data/sources/lazy/bigWigSource.d.ts +2 -2
  52. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  53. package/dist/src/data/sources/lazy/bigWigSource.js +234 -81
  54. package/dist/src/data/sources/lazy/gff3Source.d.ts +7 -3
  55. package/dist/src/data/sources/lazy/gff3Source.d.ts.map +1 -1
  56. package/dist/src/data/sources/lazy/gff3Source.js +7 -8
  57. package/dist/src/data/sources/lazy/indexedFastaSource.d.ts +1 -1
  58. package/dist/src/data/sources/lazy/indexedFastaSource.d.ts.map +1 -1
  59. package/dist/src/data/sources/lazy/indexedFastaSource.js +28 -19
  60. package/dist/src/data/sources/lazy/legendEntriesSource.d.ts +24 -0
  61. package/dist/src/data/sources/lazy/legendEntriesSource.d.ts.map +1 -0
  62. package/dist/src/data/sources/lazy/legendEntriesSource.js +217 -0
  63. package/dist/src/data/sources/lazy/legendGradientSource.d.ts +30 -0
  64. package/dist/src/data/sources/lazy/legendGradientSource.d.ts.map +1 -0
  65. package/dist/src/data/sources/lazy/legendGradientSource.js +388 -0
  66. package/dist/src/data/sources/lazy/mockLazySource.d.ts +4 -1
  67. package/dist/src/data/sources/lazy/mockLazySource.d.ts.map +1 -1
  68. package/dist/src/data/sources/lazy/mockLazySource.js +49 -4
  69. package/dist/src/data/sources/lazy/registerCoreLazySources.js +2 -0
  70. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  71. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +3 -4
  72. package/dist/src/data/sources/lazy/tabixSource.d.ts +9 -4
  73. package/dist/src/data/sources/lazy/tabixSource.d.ts.map +1 -1
  74. package/dist/src/data/sources/lazy/tabixSource.js +201 -70
  75. package/dist/src/data/sources/lazy/tabixTsvSource.d.ts +2 -3
  76. package/dist/src/data/sources/lazy/tabixTsvSource.d.ts.map +1 -1
  77. package/dist/src/data/sources/lazy/tabixTsvSource.js +14 -12
  78. package/dist/src/data/sources/lazy/vcfSource.d.ts +7 -3
  79. package/dist/src/data/sources/lazy/vcfSource.d.ts.map +1 -1
  80. package/dist/src/data/sources/lazy/vcfSource.js +7 -8
  81. package/dist/src/data/sources/urlDescriptor.d.ts +165 -0
  82. package/dist/src/data/sources/urlDescriptor.d.ts.map +1 -0
  83. package/dist/src/data/sources/urlDescriptor.js +473 -0
  84. package/dist/src/data/sources/urlDescriptorController.d.ts +25 -0
  85. package/dist/src/data/sources/urlDescriptorController.d.ts.map +1 -0
  86. package/dist/src/data/sources/urlDescriptorController.js +72 -0
  87. package/dist/src/data/sources/urlDescriptorState.d.ts +47 -0
  88. package/dist/src/data/sources/urlDescriptorState.d.ts.map +1 -0
  89. package/dist/src/data/sources/urlDescriptorState.js +129 -0
  90. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  91. package/dist/src/data/sources/urlSource.js +101 -61
  92. package/dist/src/data/transforms/packLegendLabels.d.ts +21 -0
  93. package/dist/src/data/transforms/packLegendLabels.d.ts.map +1 -0
  94. package/dist/src/data/transforms/packLegendLabels.js +189 -0
  95. package/dist/src/data/transforms/transformFactory.d.ts.map +1 -1
  96. package/dist/src/data/transforms/transformFactory.js +4 -0
  97. package/dist/src/data/transforms/truncateText.d.ts +27 -0
  98. package/dist/src/data/transforms/truncateText.d.ts.map +1 -0
  99. package/dist/src/data/transforms/truncateText.js +94 -0
  100. package/dist/src/debug/dataflowDebugSnapshot.d.ts +58 -0
  101. package/dist/src/debug/dataflowDebugSnapshot.d.ts.map +1 -0
  102. package/dist/src/debug/dataflowDebugSnapshot.js +159 -0
  103. package/dist/src/debug/markDebugSnapshot.d.ts +54 -0
  104. package/dist/src/debug/markDebugSnapshot.d.ts.map +1 -0
  105. package/dist/src/debug/markDebugSnapshot.js +100 -0
  106. package/dist/src/debug/paramDebugSnapshot.d.ts +53 -0
  107. package/dist/src/debug/paramDebugSnapshot.d.ts.map +1 -0
  108. package/dist/src/debug/paramDebugSnapshot.js +86 -0
  109. package/dist/src/debug/resolutionDebugSnapshot.d.ts +155 -0
  110. package/dist/src/debug/resolutionDebugSnapshot.d.ts.map +1 -0
  111. package/dist/src/debug/resolutionDebugSnapshot.js +291 -0
  112. package/dist/src/debug/valuePreview.d.ts +9 -0
  113. package/dist/src/debug/valuePreview.d.ts.map +1 -0
  114. package/dist/src/debug/valuePreview.js +57 -0
  115. package/dist/src/debug/viewDebugSnapshot.d.ts +131 -0
  116. package/dist/src/debug/viewDebugSnapshot.d.ts.map +1 -0
  117. package/dist/src/debug/viewDebugSnapshot.js +390 -0
  118. package/dist/src/embedFactory.d.ts.map +1 -1
  119. package/dist/src/embedFactory.js +6 -1
  120. package/dist/src/encoder/encoder.d.ts +2 -2
  121. package/dist/src/encoder/encoder.d.ts.map +1 -1
  122. package/dist/src/encoder/encoder.js +5 -4
  123. package/dist/src/fonts/bmFontManager.d.ts +1 -1
  124. package/dist/src/fonts/bmFontManager.d.ts.map +1 -1
  125. package/dist/src/fonts/bmFontManager.js +45 -10
  126. package/dist/src/fonts/textMetrics.d.ts +69 -0
  127. package/dist/src/fonts/textMetrics.d.ts.map +1 -0
  128. package/dist/src/fonts/textMetrics.js +73 -0
  129. package/dist/src/genomeSpy/headlessBootstrap.d.ts.map +1 -1
  130. package/dist/src/genomeSpy/headlessBootstrap.js +6 -0
  131. package/dist/src/genomeSpy/renderCoordinator.d.ts.map +1 -1
  132. package/dist/src/genomeSpy/renderCoordinator.js +25 -3
  133. package/dist/src/genomeSpy/viewDataInit.d.ts +14 -0
  134. package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -1
  135. package/dist/src/genomeSpy/viewDataInit.js +45 -8
  136. package/dist/src/genomeSpyBase.d.ts +6 -0
  137. package/dist/src/genomeSpyBase.d.ts.map +1 -1
  138. package/dist/src/genomeSpyBase.js +20 -3
  139. package/dist/src/gl/glslScaleGenerator.d.ts +17 -0
  140. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
  141. package/dist/src/gl/glslScaleGenerator.js +39 -2
  142. package/dist/src/gl/includes/common.glsl.js +1 -1
  143. package/dist/src/gl/vertexRangeIndex.d.ts.map +1 -1
  144. package/dist/src/gl/vertexRangeIndex.js +4 -2
  145. package/dist/src/gl/webGLHelper.d.ts +1 -1
  146. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  147. package/dist/src/gl/webGLHelper.js +13 -8
  148. package/dist/src/marks/__snapshots__/shaderSnapshot.test.js.snap +140 -3
  149. package/dist/src/marks/mark.d.ts +47 -4
  150. package/dist/src/marks/mark.d.ts.map +1 -1
  151. package/dist/src/marks/mark.js +158 -54
  152. package/dist/src/marks/point.d.ts.map +1 -1
  153. package/dist/src/marks/point.js +4 -0
  154. package/dist/src/marks/point.vertex.glsl.js +1 -1
  155. package/dist/src/marks/text.d.ts +1 -1
  156. package/dist/src/marks/text.d.ts.map +1 -1
  157. package/dist/src/marks/text.js +2 -7
  158. package/dist/src/marks/text.vertex.glsl.js +1 -1
  159. package/dist/src/paramRuntime/paramUtils.d.ts +43 -9
  160. package/dist/src/paramRuntime/paramUtils.d.ts.map +1 -1
  161. package/dist/src/paramRuntime/paramUtils.js +61 -1
  162. package/dist/src/paramRuntime/viewParamRuntime.d.ts +32 -0
  163. package/dist/src/paramRuntime/viewParamRuntime.d.ts.map +1 -1
  164. package/dist/src/paramRuntime/viewParamRuntime.js +63 -0
  165. package/dist/src/scales/axisResolution.d.ts +35 -0
  166. package/dist/src/scales/axisResolution.d.ts.map +1 -1
  167. package/dist/src/scales/axisResolution.js +115 -7
  168. package/dist/src/scales/legendResolution.d.ts +83 -0
  169. package/dist/src/scales/legendResolution.d.ts.map +1 -0
  170. package/dist/src/scales/legendResolution.js +461 -0
  171. package/dist/src/scales/scaleResolution.d.ts +36 -0
  172. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  173. package/dist/src/scales/scaleResolution.js +59 -0
  174. package/dist/src/scales/viewLevelGuideConfig.d.ts +53 -0
  175. package/dist/src/scales/viewLevelGuideConfig.d.ts.map +1 -0
  176. package/dist/src/scales/viewLevelGuideConfig.js +224 -0
  177. package/dist/src/scales/viewLevelScaleConfig.d.ts.map +1 -1
  178. package/dist/src/scales/viewLevelScaleConfig.js +13 -2
  179. package/dist/src/spec/axis.d.ts +109 -3
  180. package/dist/src/spec/channel.d.ts +23 -4
  181. package/dist/src/spec/config.d.ts +59 -4
  182. package/dist/src/spec/data.d.ts +177 -17
  183. package/dist/src/spec/legend.d.ts +246 -0
  184. package/dist/src/spec/mark.d.ts +16 -4
  185. package/dist/src/spec/title.d.ts +58 -1
  186. package/dist/src/spec/transform.d.ts +149 -0
  187. package/dist/src/spec/view.d.ts +39 -6
  188. package/dist/src/types/embedApi.d.ts +262 -6
  189. package/dist/src/types/rendering.d.ts +19 -3
  190. package/dist/src/types/viewContext.d.ts +18 -2
  191. package/dist/src/utils/arrayUtils.d.ts +11 -0
  192. package/dist/src/utils/arrayUtils.d.ts.map +1 -1
  193. package/dist/src/utils/arrayUtils.js +23 -0
  194. package/dist/src/utils/suspension.d.ts +17 -0
  195. package/dist/src/utils/suspension.d.ts.map +1 -0
  196. package/dist/src/utils/suspension.js +41 -0
  197. package/dist/src/view/axisGridView.d.ts.map +1 -1
  198. package/dist/src/view/axisGridView.js +1 -4
  199. package/dist/src/view/axisView.d.ts +18 -2
  200. package/dist/src/view/axisView.d.ts.map +1 -1
  201. package/dist/src/view/axisView.js +180 -75
  202. package/dist/src/view/concatView.d.ts +10 -2
  203. package/dist/src/view/concatView.d.ts.map +1 -1
  204. package/dist/src/view/concatView.js +46 -9
  205. package/dist/src/view/containerMutationHelper.d.ts +20 -1
  206. package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
  207. package/dist/src/view/containerMutationHelper.js +196 -33
  208. package/dist/src/view/facetView.d.ts +1 -1
  209. package/dist/src/view/gridView/gridChild.d.ts +54 -4
  210. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  211. package/dist/src/view/gridView/gridChild.js +301 -120
  212. package/dist/src/view/gridView/gridChildLegends.d.ts +57 -0
  213. package/dist/src/view/gridView/gridChildLegends.d.ts.map +1 -0
  214. package/dist/src/view/gridView/gridChildLegends.js +503 -0
  215. package/dist/src/view/gridView/gridView.d.ts +25 -0
  216. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  217. package/dist/src/view/gridView/gridView.js +454 -78
  218. package/dist/src/view/gridView/legendLayout.d.ts +26 -0
  219. package/dist/src/view/gridView/legendLayout.d.ts.map +1 -0
  220. package/dist/src/view/gridView/legendLayout.js +111 -0
  221. package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
  222. package/dist/src/view/gridView/scrollbar.js +1 -4
  223. package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
  224. package/dist/src/view/gridView/selectionRect.js +1 -4
  225. package/dist/src/view/gridView/separatorView.d.ts.map +1 -1
  226. package/dist/src/view/gridView/separatorView.js +1 -4
  227. package/dist/src/view/layerView.d.ts +9 -2
  228. package/dist/src/view/layerView.d.ts.map +1 -1
  229. package/dist/src/view/layerView.js +18 -1
  230. package/dist/src/view/layout/flexLayout.d.ts +20 -4
  231. package/dist/src/view/layout/flexLayout.d.ts.map +1 -1
  232. package/dist/src/view/layout/flexLayout.js +331 -31
  233. package/dist/src/view/layout/rectangle.d.ts +14 -0
  234. package/dist/src/view/layout/rectangle.d.ts.map +1 -1
  235. package/dist/src/view/layout/rectangle.js +40 -0
  236. package/dist/src/view/legend/legendEntries.d.ts +20 -0
  237. package/dist/src/view/legend/legendEntries.d.ts.map +1 -0
  238. package/dist/src/view/legend/legendEntries.js +21 -0
  239. package/dist/src/view/legendView.d.ts +134 -0
  240. package/dist/src/view/legendView.d.ts.map +1 -0
  241. package/dist/src/view/legendView.js +1611 -0
  242. package/dist/src/view/renderingContext/bufferedViewRenderingContext.d.ts.map +1 -1
  243. package/dist/src/view/renderingContext/bufferedViewRenderingContext.js +26 -4
  244. package/dist/src/view/renderingContext/clipOptions.d.ts +44 -0
  245. package/dist/src/view/renderingContext/clipOptions.d.ts.map +1 -0
  246. package/dist/src/view/renderingContext/clipOptions.js +140 -0
  247. package/dist/src/view/renderingContext/simpleViewRenderingContext.d.ts.map +1 -1
  248. package/dist/src/view/renderingContext/simpleViewRenderingContext.js +12 -1
  249. package/dist/src/view/resolutionPlanner.d.ts +2 -1
  250. package/dist/src/view/resolutionPlanner.d.ts.map +1 -1
  251. package/dist/src/view/resolutionPlanner.js +89 -25
  252. package/dist/src/view/testUtils.d.ts +4 -2
  253. package/dist/src/view/testUtils.d.ts.map +1 -1
  254. package/dist/src/view/testUtils.js +29 -7
  255. package/dist/src/view/titleView.d.ts +37 -0
  256. package/dist/src/view/titleView.d.ts.map +1 -0
  257. package/dist/src/view/titleView.js +584 -0
  258. package/dist/src/view/unitView.d.ts +3 -3
  259. package/dist/src/view/unitView.d.ts.map +1 -1
  260. package/dist/src/view/unitView.js +3 -2
  261. package/dist/src/view/view.d.ts +25 -24
  262. package/dist/src/view/view.d.ts.map +1 -1
  263. package/dist/src/view/view.js +121 -16
  264. package/dist/src/view/viewFactory.d.ts +2 -5
  265. package/dist/src/view/viewFactory.d.ts.map +1 -1
  266. package/dist/src/view/viewFactory.js +1 -2
  267. package/dist/src/view/viewIdentityRegistry.d.ts +37 -0
  268. package/dist/src/view/viewIdentityRegistry.d.ts.map +1 -0
  269. package/dist/src/view/viewIdentityRegistry.js +71 -0
  270. package/dist/src/view/viewMutationAcidTestUtils.d.ts +112 -0
  271. package/dist/src/view/viewMutationAcidTestUtils.d.ts.map +1 -0
  272. package/dist/src/view/viewMutationAcidTestUtils.js +234 -0
  273. package/dist/src/view/viewMutationApi.d.ts +42 -0
  274. package/dist/src/view/viewMutationApi.d.ts.map +1 -0
  275. package/dist/src/view/viewMutationApi.js +811 -0
  276. package/dist/src/view/viewSelectors.d.ts +10 -0
  277. package/dist/src/view/viewSelectors.d.ts.map +1 -1
  278. package/dist/src/view/viewSelectors.js +23 -1
  279. package/package.json +4 -4
  280. package/dist/bundle/esm-CuMSzCHy.js +0 -298
  281. package/dist/bundle/esm-DAnOffpD.js +0 -1426
  282. package/dist/bundle/esm-DMXpJXM4.js +0 -369
  283. package/dist/bundle/esm-DNtC3H80.js +0 -121
  284. package/dist/src/view/title.d.ts +0 -13
  285. package/dist/src/view/title.d.ts.map +0 -1
  286. package/dist/src/view/title.js +0 -154
  287. /package/dist/bundle/{AbortablePromiseCache-3gHJdF3E.js → AbortablePromiseCache-BTmAcN-t.js} +0 -0
  288. /package/dist/bundle/{esm-CuVa5T98.js → esm-VvpZ9hsq.js} +0 -0
  289. /package/dist/bundle/{chunk-DmhlhrBa.js → rolldown-runtime-Dy4uBu1J.js} +0 -0
@@ -2,6 +2,8 @@ import { primaryPositionalChannels } from "../../encoder/encoder.js";
2
2
  import {
3
3
  FlexDimensions,
4
4
  getLargestSize,
5
+ getSizeDefMaxPx,
6
+ getSizeDefMinPx,
5
7
  mapToPixelCoords,
6
8
  parseSizeDef,
7
9
  ZERO_SIZEDEF,
@@ -9,7 +11,11 @@ import {
9
11
  import Grid from "../layout/grid.js";
10
12
  import Padding from "../layout/padding.js";
11
13
  import Rectangle from "../layout/rectangle.js";
12
- import AxisView, { CHANNEL_ORIENTS, ORIENT_CHANNELS } from "../axisView.js";
14
+ import AxisView, {
15
+ CHANNEL_ORIENTS,
16
+ ORIENT_CHANNELS,
17
+ getExternalAxisOverhang,
18
+ } from "../axisView.js";
13
19
  import ContainerView from "../containerView.js";
14
20
  import {
15
21
  propagateInteraction,
@@ -20,9 +26,26 @@ import UnitView from "../unitView.js";
20
26
  import { interactionToZoom } from "../zoom.js";
21
27
  import GridChild from "./gridChild.js";
22
28
  import KeyboardZoomController from "./keyboardZoomController.js";
29
+ import { renderLocalLegends } from "./legendLayout.js";
30
+ import {
31
+ addLegendView,
32
+ createGridChildLegend,
33
+ disposeLegendViews,
34
+ getLegendOverhang,
35
+ getOrderedLegendEntries,
36
+ iterateLegendViews,
37
+ isActiveLegendRegion,
38
+ } from "./gridChildLegends.js";
23
39
  import SeparatorView, { resolveSeparatorProps } from "./separatorView.js";
24
40
  import { getZoomableResolutions } from "./zoomNavigationUtils.js";
41
+ import { moveArrayItem } from "../../utils/arrayUtils.js";
25
42
  import { isHConcatSpec, isVConcatSpec } from "../viewSpecGuards.js";
43
+ import {
44
+ clipCoords,
45
+ combineClipOptions,
46
+ createClipOptions,
47
+ normalizeClipOptions,
48
+ } from "../renderingContext/clipOptions.js";
26
49
 
27
50
  // Secondary ordering within a z-index bucket for GridView-owned decorations.
28
51
  // These are not z-indices themselves: actual layering is decided first by the
@@ -34,6 +57,7 @@ const DECORATION_ORDER = Object.freeze({
34
57
  grid: 20,
35
58
  backgroundStroke: 30,
36
59
  axis: 40,
60
+ legend: 50,
37
61
  selectionRect: 80,
38
62
  scrollbar: 90,
39
63
  title: 100,
@@ -44,6 +68,76 @@ const DECORATION_ORDER = Object.freeze({
44
68
  // letting an explicit user zindex override the default.
45
69
  const CLIPPED_DECORATION_ZINDEX = 10;
46
70
 
71
+ /**
72
+ * Legends are rendered as guide regions, not grid children. Their thickness is
73
+ * handled as overhang, but their parallel min/max constraints still affect the
74
+ * grid dimension that the parent concat sees.
75
+ *
76
+ * @param {import("./gridChildLegends.js").GridChildLegends} legends
77
+ * @returns {FlexDimensions}
78
+ */
79
+ function getLegendParallelSizeConstraints(legends) {
80
+ /** @type {import("../layout/flexLayout.js").SizeDef[]} */
81
+ const widths = [];
82
+ /** @type {import("../layout/flexLayout.js").SizeDef[]} */
83
+ const heights = [];
84
+
85
+ for (const [orient, region] of Object.entries(legends)) {
86
+ if (!isActiveLegendRegion(region)) {
87
+ continue;
88
+ }
89
+
90
+ const size = region.legendView.getSize();
91
+ if (orient == "top" || orient == "bottom") {
92
+ widths.push(size.width);
93
+ } else {
94
+ heights.push(size.height);
95
+ }
96
+ }
97
+
98
+ return new FlexDimensions(getLargestSize(widths), getLargestSize(heights));
99
+ }
100
+
101
+ /**
102
+ * Treat an explicit concat/grid size as preferred available space while still
103
+ * letting fixed children and guide chrome establish a larger minimum. This
104
+ * mirrors flexbox behavior more closely than treating the explicit size as a
105
+ * hard clipping bound.
106
+ *
107
+ * @param {import("../layout/flexLayout.js").SizeDef} preferred
108
+ * @param {import("../layout/flexLayout.js").SizeDef} content
109
+ * @returns {import("../layout/flexLayout.js").SizeDef}
110
+ */
111
+ function combinePreferredAndContentSize(preferred, content) {
112
+ const preferredGrow = preferred.grow ?? 0;
113
+ const preferredPx = preferred.px ?? 0;
114
+ const minPx = Math.max(
115
+ getSizeDefMinPx(preferred),
116
+ getSizeDefMinPx(content)
117
+ );
118
+
119
+ if (!preferredGrow) {
120
+ return { px: Math.max(preferredPx, minPx), grow: 0 };
121
+ }
122
+
123
+ /** @type {import("../layout/flexLayout.js").SizeDef} */
124
+ const size = {
125
+ px: Math.max(preferredPx, content.px ?? 0),
126
+ grow: preferredGrow,
127
+ };
128
+
129
+ if (minPx > (size.px ?? 0)) {
130
+ size.minPx = minPx;
131
+ }
132
+
133
+ const preferredMaxPx = getSizeDefMaxPx(preferred);
134
+ if (preferredMaxPx !== undefined && preferredMaxPx >= minPx) {
135
+ size.maxPx = preferredMaxPx;
136
+ }
137
+
138
+ return size;
139
+ }
140
+
47
141
  /**
48
142
  * Modeled after: https://vega.github.io/vega/docs/layout/
49
143
  *
@@ -97,6 +191,9 @@ export default class GridView extends ContainerView {
97
191
  */
98
192
  #sharedAxes = {};
99
193
 
194
+ /** @type {import("./gridChildLegends.js").GridChildLegends} */
195
+ #sharedLegends = {};
196
+
100
197
  #childSerial = 0;
101
198
 
102
199
  /** @type {Partial<Record<"horizontal" | "vertical", SeparatorView>>} */
@@ -223,6 +320,17 @@ export default class GridView extends ContainerView {
223
320
  this.invalidateSizeCache();
224
321
  }
225
322
 
323
+ /**
324
+ * Moves a child within the grid without disposing it.
325
+ *
326
+ * @param {number} fromIndex
327
+ * @param {number} index Destination index after temporarily removing the child.
328
+ */
329
+ moveChildAt(fromIndex, index) {
330
+ moveArrayItem(this.#children, fromIndex, index);
331
+ this.invalidateSizeCache();
332
+ }
333
+
226
334
  get #visibleChildren() {
227
335
  return this.#children.filter((gridChild) =>
228
336
  gridChild.view.isConfiguredVisible()
@@ -275,10 +383,25 @@ export default class GridView extends ContainerView {
275
383
  * @protected
276
384
  */
277
385
  async createAxes() {
386
+ await this.syncGuideViews();
387
+ }
388
+
389
+ /**
390
+ * Recreates guide and chrome views that depend on the child hierarchy.
391
+ * Shared guides always depend on the whole container. Grid-child guides can
392
+ * be limited to newly inserted children during mutations.
393
+ *
394
+ * @param {{ gridChildren?: GridChild[] }} [options]
395
+ */
396
+ async syncGuideViews(options = {}) {
397
+ const gridChildren = options.gridChildren ?? this.#children;
398
+
278
399
  await this.syncSharedAxes();
400
+ await this.syncSharedLegends();
279
401
  await Promise.all(
280
- this.#children.map((gridChild) => gridChild.createAxes())
402
+ gridChildren.map((gridChild) => gridChild.createAxes())
281
403
  );
404
+ this.invalidateSizeCache();
282
405
  }
283
406
 
284
407
  /**
@@ -325,6 +448,25 @@ export default class GridView extends ContainerView {
325
448
  await Promise.all(promises);
326
449
  }
327
450
 
451
+ /**
452
+ * Recreates shared legends based on current legend resolutions.
453
+ *
454
+ * Shared legends are GridView-owned for the same reason as shared axes:
455
+ * their placement is relative to the whole child grid, not any individual
456
+ * GridChild.
457
+ */
458
+ async syncSharedLegends() {
459
+ disposeLegendViews(this.#sharedLegends);
460
+ this.#sharedLegends = {};
461
+
462
+ for (const { definition, resolution } of getOrderedLegendEntries([
463
+ this,
464
+ ])) {
465
+ const legend = await createGridChildLegend(definition, this);
466
+ await addLegendView(this.#sharedLegends, legend, resolution);
467
+ }
468
+ }
469
+
328
470
  /**
329
471
  * @returns {IterableIterator<View>}
330
472
  */
@@ -340,6 +482,8 @@ export default class GridView extends ContainerView {
340
482
  for (const axisView of Object.values(this.#sharedAxes)) {
341
483
  yield axisView;
342
484
  }
485
+
486
+ yield* iterateLegendViews(this.#sharedLegends);
343
487
  }
344
488
 
345
489
  /**
@@ -367,17 +511,48 @@ export default class GridView extends ContainerView {
367
511
  })
368
512
  .reduce((a, b) => Math.max(a, b), 0);
369
513
 
514
+ /**
515
+ * @param {GridChild} child
516
+ * @returns {import("../layout/flexLayout.js").SizeDef}
517
+ */
518
+ const getPlotSize = (child) => {
519
+ // External overhang is represented by axis/padding slots. The
520
+ // growable view slot should contain only the child's plot area.
521
+ const size = child.view.getViewportSize()[dim];
522
+ const overhang = child.view.getOverhang();
523
+ const overhangSize =
524
+ direction == "column" ? overhang.width : overhang.height;
525
+
526
+ const plotSize = {
527
+ px: Math.max((size.px ?? 0) - overhangSize, 0),
528
+ grow: size.grow,
529
+ minPx:
530
+ size.minPx === undefined
531
+ ? undefined
532
+ : Math.max(size.minPx - overhangSize, 0),
533
+ maxPx:
534
+ size.maxPx === undefined
535
+ ? undefined
536
+ : Math.max(size.maxPx - overhangSize, 0),
537
+ };
538
+
539
+ const legendSize = getLegendParallelSizeConstraints(child.legends);
540
+ // Side legends constrain row height, while top/bottom legends
541
+ // constrain column width. Their perpendicular size is overhang.
542
+ return getLargestSize([
543
+ plotSize,
544
+ direction == "column" ? legendSize.width : legendSize.height,
545
+ ]);
546
+ };
547
+
370
548
  return this._cache(`size/directionSizes/${direction}`, () =>
371
549
  this.#grid[direction == "column" ? "colIndices" : "rowIndices"].map(
372
550
  (col) => ({
373
551
  axisBefore: getMaxAxisSize(col, 0),
374
552
  axisAfter: getMaxAxisSize(col, 1),
375
553
  view: getLargestSize(
376
- col.map(
377
- (rowIndex) =>
378
- this.#visibleChildren[
379
- rowIndex
380
- ].view.getViewportSize()[dim]
554
+ col.map((rowIndex) =>
555
+ getPlotSize(this.#visibleChildren[rowIndex])
381
556
  )
382
557
  ),
383
558
  })
@@ -448,12 +623,21 @@ export default class GridView extends ContainerView {
448
623
  #getFlexSize(direction) {
449
624
  let grow = 0;
450
625
  let px = 0;
626
+ let minPx = 0;
627
+ let maxPx = 0;
628
+ let hasMaxPx = true;
451
629
 
452
630
  const explicitSize =
453
631
  direction == "row" ? this.spec.height : this.spec.width;
454
- if (explicitSize || explicitSize === 0) {
455
- return parseSizeDef(explicitSize);
456
- }
632
+ const preferredSize =
633
+ explicitSize || explicitSize === 0
634
+ ? parseSizeDef(explicitSize)
635
+ : undefined;
636
+ const usePreferredAsViewSlot =
637
+ preferredSize &&
638
+ (direction == "column"
639
+ ? this.#grid.colIndices.length == 1
640
+ : this.#grid.rowIndices.length == 1);
457
641
 
458
642
  const sizes = this.#getSizes(direction);
459
643
 
@@ -461,6 +645,8 @@ export default class GridView extends ContainerView {
461
645
  if (i > 0) {
462
646
  // Spacing
463
647
  px += this.#spacing;
648
+ minPx += this.#spacing;
649
+ maxPx += this.#spacing;
464
650
  }
465
651
 
466
652
  if (i == 0 || this.wrappingFacet) {
@@ -470,13 +656,28 @@ export default class GridView extends ContainerView {
470
656
 
471
657
  // Axis/padding
472
658
  px += size.axisBefore;
659
+ minPx += size.axisBefore;
660
+ maxPx += size.axisBefore;
473
661
 
474
662
  // View
475
- px += size.view.px ?? 0;
476
- grow += size.view.grow ?? 0;
663
+ const viewSize = usePreferredAsViewSlot
664
+ ? combinePreferredAndContentSize(preferredSize, size.view)
665
+ : size.view;
666
+ px += viewSize.px ?? 0;
667
+ grow += viewSize.grow ?? 0;
668
+ minPx += getSizeDefMinPx(viewSize);
669
+
670
+ const viewMaxPx = getSizeDefMaxPx(viewSize);
671
+ if (viewMaxPx === undefined) {
672
+ hasMaxPx = false;
673
+ } else {
674
+ maxPx += viewMaxPx;
675
+ }
477
676
 
478
677
  // Axis/padding
479
678
  px += size.axisAfter;
679
+ minPx += size.axisAfter;
680
+ maxPx += size.axisAfter;
480
681
 
481
682
  if (i == sizes.length - 1 || this.wrappingFacet) {
482
683
  //Footer
@@ -484,7 +685,16 @@ export default class GridView extends ContainerView {
484
685
  }
485
686
  }
486
687
 
487
- return { px, grow };
688
+ const measuredSize = {
689
+ px,
690
+ grow,
691
+ minPx: minPx || undefined,
692
+ maxPx: sizes.length && hasMaxPx ? maxPx : undefined,
693
+ };
694
+
695
+ return preferredSize && !usePreferredAsViewSlot
696
+ ? combinePreferredAndContentSize(preferredSize, measuredSize)
697
+ : measuredSize;
488
698
  }
489
699
 
490
700
  /**
@@ -505,7 +715,7 @@ export default class GridView extends ContainerView {
505
715
  * @return {Padding}
506
716
  */
507
717
  getOverhang() {
508
- return this.#getGridOverhang().union(this.#getSharedAxisOverhang());
718
+ return this.#getGridOverhang().add(this.#getSharedGuideOverhang());
509
719
  }
510
720
 
511
721
  #getGridOverhang() {
@@ -535,11 +745,7 @@ export default class GridView extends ContainerView {
535
745
  return 0;
536
746
  }
537
747
 
538
- return Math.max(
539
- axisView.getPerpendicularSize() +
540
- (axisView.axisProps.offset ?? 0),
541
- 0
542
- );
748
+ return getExternalAxisOverhang(axisView);
543
749
  };
544
750
 
545
751
  return new Padding(
@@ -550,16 +756,58 @@ export default class GridView extends ContainerView {
550
756
  );
551
757
  }
552
758
 
759
+ #getSharedLegendOverhang() {
760
+ const getSharedLegendSize = (
761
+ /** @type {import("../../spec/legend.js").LegendOrient} */ orient
762
+ ) => getLegendOverhang(this.#sharedLegends, orient);
763
+
764
+ return new Padding(
765
+ getSharedLegendSize("top"),
766
+ getSharedLegendSize("right"),
767
+ getSharedLegendSize("bottom"),
768
+ getSharedLegendSize("left")
769
+ );
770
+ }
771
+
772
+ #getSharedGuideOverhang() {
773
+ return this.#getSharedAxisOverhang().add(
774
+ this.#getSharedLegendOverhang()
775
+ );
776
+ }
777
+
778
+ #getSharedAxesByOrient() {
779
+ /** @type {Partial<Record<import("../../spec/axis.js").AxisOrient, AxisView>>} */
780
+ const axes = {};
781
+ for (const axisView of Object.values(this.#sharedAxes)) {
782
+ axes[axisView.axisProps.orient] = axisView;
783
+ }
784
+
785
+ return axes;
786
+ }
787
+
553
788
  /**
554
789
  * @returns {FlexDimensions}
555
790
  */
556
791
  getSize() {
557
- return this._cache("size", () =>
558
- new FlexDimensions(
559
- this.#getFlexSize("column"),
560
- this.#getFlexSize("row")
561
- ).addPadding(this.#getSharedAxisOverhang())
562
- );
792
+ return this._cache("size", () => {
793
+ const parallelLegendSize = getLegendParallelSizeConstraints(
794
+ this.#sharedLegends
795
+ );
796
+
797
+ // Shared legends are placed around the whole grid, so combine their
798
+ // parallel constraints with the child-grid size before adding
799
+ // guide overhang.
800
+ return new FlexDimensions(
801
+ getLargestSize([
802
+ this.#getFlexSize("column"),
803
+ parallelLegendSize.width,
804
+ ]),
805
+ getLargestSize([
806
+ this.#getFlexSize("row"),
807
+ parallelLegendSize.height,
808
+ ])
809
+ ).addPadding(this.#getSharedGuideOverhang());
810
+ });
563
811
  }
564
812
 
565
813
  /**
@@ -578,7 +826,8 @@ export default class GridView extends ContainerView {
578
826
  // Usually padding is applied by the parent GridView, but if this is the root view, we need to apply it here
579
827
  coords = coords.shrink(this.getPadding());
580
828
  }
581
- coords = coords.shrink(this.#getSharedAxisOverhang());
829
+ const sharedGuideOverhang = this.#getSharedGuideOverhang();
830
+ coords = coords.shrink(sharedGuideOverhang);
582
831
 
583
832
  context.pushView(this, coords);
584
833
 
@@ -587,7 +836,7 @@ export default class GridView extends ContainerView {
587
836
  const flexOpts = {
588
837
  devicePixelRatio,
589
838
  };
590
- const columnFlexCoords = mapToPixelCoords(
839
+ let columnFlexCoords = mapToPixelCoords(
591
840
  this.#makeFlexItems("column"),
592
841
  coords.width,
593
842
  flexOpts
@@ -604,6 +853,33 @@ export default class GridView extends ContainerView {
604
853
  this.#columns ?? Infinity
605
854
  );
606
855
 
856
+ let columnLayoutDirty = false;
857
+ for (const [i, gridChild] of this.#visibleChildren.entries()) {
858
+ const [col, row] = grid.getCellCoords(i);
859
+ const colLocSize =
860
+ columnFlexCoords[this.#getViewSlot("column", col)];
861
+ const rowLocSize = rowFlexCoords[this.#getViewSlot("row", row)];
862
+
863
+ // Some child views have side overhang that depends on the final
864
+ // row height, such as SampleView's repeated y-axis threshold. Row
865
+ // slots are known here, but final columns may need one more pass if
866
+ // a child reports that its height-dependent overhang changed.
867
+ const layoutChanged =
868
+ /** @type {{ prepareLayoutSize?: (width: number, height: number) => boolean }} */ (
869
+ gridChild.view
870
+ ).prepareLayoutSize?.(colLocSize.size, rowLocSize.size);
871
+ columnLayoutDirty ||= layoutChanged === true;
872
+ }
873
+
874
+ if (columnLayoutDirty) {
875
+ this._invalidateCacheByPrefix("size/directionSizes/column");
876
+ columnFlexCoords = mapToPixelCoords(
877
+ this.#makeFlexItems("column"),
878
+ coords.width,
879
+ flexOpts
880
+ );
881
+ }
882
+
607
883
  /** @param {number} x */
608
884
  const round = (x) =>
609
885
  Math.round(x * devicePixelRatio) / devicePixelRatio;
@@ -641,16 +917,36 @@ export default class GridView extends ContainerView {
641
917
  /**
642
918
  * @param {FlexDimensions} size
643
919
  * @param {"width" | "height"} dimension
920
+ * @param {boolean} explicitViewport
644
921
  */
645
- const getLen = (size, dimension) =>
646
- (size[dimension].grow
647
- ? (dimension == "width" ? colLocSize : rowLocSize).size
648
- : size[dimension].px) + overhang[dimension];
649
-
650
- const viewportWidth = getLen(viewportSize, "width");
651
- const viewportHeight = getLen(viewportSize, "height");
652
- const viewWidth = getLen(viewSize, "width");
653
- const viewHeight = getLen(viewSize, "height");
922
+ const getLen = (size, dimension, explicitViewport = false) =>
923
+ explicitViewport
924
+ ? size[dimension].grow
925
+ ? (dimension == "width" ? colLocSize : rowLocSize).size
926
+ : size[dimension].px
927
+ : (dimension == "width" ? colLocSize : rowLocSize).size +
928
+ overhang[dimension];
929
+
930
+ const viewportWidth = getLen(
931
+ viewportSize,
932
+ "width",
933
+ view.spec.viewportWidth != null
934
+ );
935
+ const viewportHeight = getLen(
936
+ viewportSize,
937
+ "height",
938
+ view.spec.viewportHeight != null
939
+ );
940
+ const viewWidth = getLen(
941
+ viewSize,
942
+ "width",
943
+ view.spec.viewportWidth != null
944
+ );
945
+ const viewHeight = getLen(
946
+ viewSize,
947
+ "height",
948
+ view.spec.viewportHeight != null
949
+ );
654
950
 
655
951
  const hScrollbar = gridChild.scrollbars.horizontal;
656
952
  const vScrollbar = gridChild.scrollbars.vertical;
@@ -684,9 +980,8 @@ export default class GridView extends ContainerView {
684
980
 
685
981
  gridChild.coords = viewportCoords;
686
982
 
687
- const clippedChildCoords = options.clipRect
688
- ? viewportCoords.intersect(options.clipRect)
689
- : viewportCoords;
983
+ const parentClip = normalizeClipOptions(options);
984
+ const visibleChildCoords = clipCoords(viewportCoords, parentClip);
690
985
 
691
986
  renderItems.push({
692
987
  col,
@@ -700,7 +995,8 @@ export default class GridView extends ContainerView {
700
995
  selectionRect,
701
996
  viewportCoords,
702
997
  viewCoords,
703
- clippedChildCoords,
998
+ parentClip,
999
+ visibleChildCoords,
704
1000
  viewWidth,
705
1001
  viewHeight,
706
1002
  scrollable,
@@ -755,7 +1051,7 @@ export default class GridView extends ContainerView {
755
1051
  () =>
756
1052
  item.background?.render(
757
1053
  context,
758
- item.clippedChildCoords,
1054
+ item.visibleChildCoords,
759
1055
  {
760
1056
  ...options,
761
1057
  clipRect: undefined,
@@ -809,7 +1105,8 @@ export default class GridView extends ContainerView {
809
1105
  selectionRect,
810
1106
  viewportCoords,
811
1107
  viewCoords,
812
- clippedChildCoords,
1108
+ parentClip,
1109
+ visibleChildCoords,
813
1110
  viewWidth,
814
1111
  viewHeight,
815
1112
  scrollable,
@@ -818,7 +1115,9 @@ export default class GridView extends ContainerView {
818
1115
  row,
819
1116
  } = item;
820
1117
 
821
- const clipped = isClippedChildren(view) || scrollable;
1118
+ const clippedChildren = isClippedChildren(view);
1119
+ const clipped = clippedChildren || scrollable;
1120
+ const clippedDecorations = hasClippedChildren(view) || scrollable;
822
1121
 
823
1122
  for (const gridLineView of Object.values(gridLines)) {
824
1123
  queueDecoration(
@@ -828,6 +1127,19 @@ export default class GridView extends ContainerView {
828
1127
  );
829
1128
  }
830
1129
 
1130
+ const childClip = clipped
1131
+ ? combineClipOptions(
1132
+ parentClip,
1133
+ createClipOptions(
1134
+ visibleChildCoords,
1135
+ clippedChildren ||
1136
+ Boolean(gridChild.scrollbars.horizontal),
1137
+ clippedChildren ||
1138
+ Boolean(gridChild.scrollbars.vertical)
1139
+ )
1140
+ )
1141
+ : options.clip;
1142
+
831
1143
  const renderContent = () =>
832
1144
  view.render(
833
1145
  context,
@@ -835,7 +1147,8 @@ export default class GridView extends ContainerView {
835
1147
  clipped
836
1148
  ? {
837
1149
  ...options,
838
- clipRect: clippedChildCoords,
1150
+ clipRect: childClip?.rect,
1151
+ clip: childClip,
839
1152
  }
840
1153
  : options
841
1154
  );
@@ -846,11 +1159,11 @@ export default class GridView extends ContainerView {
846
1159
  queueDecoration(
847
1160
  defaultBackgroundStrokeZindex(
848
1161
  gridChild.backgroundStrokeZindex,
849
- clipped
1162
+ clippedDecorations
850
1163
  ),
851
1164
  DECORATION_ORDER.backgroundStroke,
852
1165
  () =>
853
- backgroundStroke?.render(context, clippedChildCoords, {
1166
+ backgroundStroke?.render(context, visibleChildCoords, {
854
1167
  ...options,
855
1168
  clipRect: undefined,
856
1169
  })
@@ -888,39 +1201,51 @@ export default class GridView extends ContainerView {
888
1201
  axisView
889
1202
  );
890
1203
 
891
- let clipRect = options.clipRect;
1204
+ let clip = normalizeClipOptions(options);
1205
+ let clipRect = clip?.rect;
892
1206
 
893
1207
  // Scrollable axes must be clipped along the scroll direction.
894
1208
  if (scrollable) {
895
- clipRect = translatedCoords.intersect(clipRect).intersect(
896
- scrollable
897
- ? viewportCoords.modify(
898
- // Ugly hack. Need to implement intersectX and intersectY.
899
- direction == "vertical"
900
- ? {
901
- x: -100000,
902
- width: 200000,
903
- }
904
- : {
905
- y: -100000,
906
- height: 200000,
907
- }
908
- )
909
- : undefined
1209
+ const axisClip = createClipOptions(
1210
+ viewportCoords,
1211
+ direction == "horizontal",
1212
+ direction == "vertical"
910
1213
  );
1214
+ clip = combineClipOptions(clip, axisClip);
1215
+ clipRect = clip?.rect;
1216
+ }
1217
+
1218
+ if (clip && axisView.labelClipPolicy === "anchor") {
1219
+ clip = createClipOptions(
1220
+ clip.rect,
1221
+ ORIENT_CHANNELS[orient] === "x",
1222
+ ORIENT_CHANNELS[orient] === "y"
1223
+ );
1224
+ clipRect = clip?.rect;
911
1225
  }
912
1226
 
913
1227
  queueDecoration(
914
- defaultAxisZindex(axisView.axisProps.zindex, clipped),
1228
+ defaultAxisZindex(axisView.axisProps, clippedDecorations),
915
1229
  DECORATION_ORDER.axis,
916
1230
  () =>
917
1231
  axisView.render(context, translatedCoords, {
918
1232
  ...options,
919
1233
  clipRect,
1234
+ clip,
920
1235
  })
921
1236
  );
922
1237
  }
923
1238
 
1239
+ renderLocalLegends(
1240
+ gridChild.legends,
1241
+ axes,
1242
+ viewportCoords,
1243
+ context,
1244
+ options,
1245
+ queueDecoration,
1246
+ DECORATION_ORDER.legend
1247
+ );
1248
+
924
1249
  // Axes shared between children
925
1250
  // TODO: What if some have scrollable viewports?
926
1251
  // Should throw an error because cannot have shared axes in such cases.
@@ -934,7 +1259,10 @@ export default class GridView extends ContainerView {
934
1259
  (orient == "bottom" && row == grid.nRows - 1)
935
1260
  ) {
936
1261
  queueDecoration(
937
- defaultAxisZindex(axisView.axisProps.zindex, clipped),
1262
+ defaultAxisZindex(
1263
+ axisView.axisProps,
1264
+ clippedDecorations
1265
+ ),
938
1266
  DECORATION_ORDER.axis,
939
1267
  () =>
940
1268
  axisView.render(
@@ -969,13 +1297,24 @@ export default class GridView extends ContainerView {
969
1297
 
970
1298
  if (title) {
971
1299
  queueDecoration(
972
- gridChild.titleZindex,
1300
+ gridChild.getTitleZindex(),
973
1301
  DECORATION_ORDER.title,
974
- () => title?.render(context, viewportCoords, options)
1302
+ () =>
1303
+ gridChild.renderTitle(context, viewportCoords, options)
975
1304
  );
976
1305
  }
977
1306
  }
978
1307
 
1308
+ renderLocalLegends(
1309
+ this.#sharedLegends,
1310
+ this.#getSharedAxesByOrient(),
1311
+ coords,
1312
+ context,
1313
+ options,
1314
+ queueDecoration,
1315
+ DECORATION_ORDER.legend
1316
+ );
1317
+
979
1318
  renderDecorations(underlays);
980
1319
 
981
1320
  for (const renderContent of contents) {
@@ -1302,6 +1641,22 @@ export function isClippedChildren(view) {
1302
1641
  return clipped;
1303
1642
  }
1304
1643
 
1644
+ /**
1645
+ * @param {View} view
1646
+ */
1647
+ function hasClippedChildren(view) {
1648
+ let clipped = false;
1649
+
1650
+ view.visit((v) => {
1651
+ if (v instanceof UnitView) {
1652
+ const clip = v.mark.properties.clip;
1653
+ clipped ||= clip === true || clip === "x" || clip === "y";
1654
+ }
1655
+ });
1656
+
1657
+ return clipped;
1658
+ }
1659
+
1305
1660
  /**
1306
1661
  * @param {View} view
1307
1662
  * @returns {boolean}
@@ -1337,15 +1692,24 @@ function getSeparatorDirections(spec) {
1337
1692
  }
1338
1693
 
1339
1694
  /**
1340
- * Default z-index for axes. Clipped or scrollable content gets a higher
1695
+ * Default z-index for axes. Inside axes default to overlays because they share
1696
+ * plot space with marks. Clipped or scrollable outside axes get a higher
1341
1697
  * default to keep guides above visible edge artifacts.
1342
1698
  *
1343
- * @param {number | undefined} zindex
1699
+ * @param {import("../../spec/axis.js").Axis} axisProps
1344
1700
  * @param {boolean} clipped
1345
1701
  * @returns {number}
1346
1702
  */
1347
- function defaultAxisZindex(zindex, clipped) {
1348
- return zindex ?? (clipped ? CLIPPED_DECORATION_ZINDEX : 0);
1703
+ function defaultAxisZindex(axisProps, clipped) {
1704
+ if (axisProps.zindex !== undefined) {
1705
+ return axisProps.zindex;
1706
+ } else if (axisProps.placement === "inside") {
1707
+ return 1;
1708
+ } else if (clipped) {
1709
+ return CLIPPED_DECORATION_ZINDEX;
1710
+ } else {
1711
+ return 0;
1712
+ }
1349
1713
  }
1350
1714
 
1351
1715
  /**
@@ -1369,18 +1733,30 @@ function defaultBackgroundStrokeZindex(zindex, clipped) {
1369
1733
  export function translateAxisCoords(coords, orient, axisView) {
1370
1734
  const props = axisView.axisProps;
1371
1735
  const ps = axisView.getPerpendicularSize();
1736
+ const inside = props.placement === "inside";
1737
+ const offset = props.offset ?? 0;
1372
1738
 
1373
1739
  if (orient == "bottom") {
1374
- return coords
1375
- .translate(0, coords.height + props.offset)
1376
- .modify({ height: ps });
1740
+ return inside
1741
+ ? coords.translate(0, coords.height - ps - offset).modify({
1742
+ height: ps,
1743
+ })
1744
+ : coords.translate(0, coords.height + offset).modify({
1745
+ height: ps,
1746
+ });
1377
1747
  } else if (orient == "top") {
1378
- return coords.translate(0, -ps - props.offset).modify({ height: ps });
1748
+ return inside
1749
+ ? coords.translate(0, offset).modify({ height: ps })
1750
+ : coords.translate(0, -ps - offset).modify({ height: ps });
1379
1751
  } else if (orient == "left") {
1380
- return coords.translate(-ps - props.offset, 0).modify({ width: ps });
1752
+ return inside
1753
+ ? coords.translate(offset, 0).modify({ width: ps })
1754
+ : coords.translate(-ps - offset, 0).modify({ width: ps });
1381
1755
  } else if (orient == "right") {
1382
- return coords
1383
- .translate(coords.width + props.offset, 0)
1384
- .modify({ width: ps });
1756
+ return inside
1757
+ ? coords.translate(coords.width - ps - offset, 0).modify({
1758
+ width: ps,
1759
+ })
1760
+ : coords.translate(coords.width + offset, 0).modify({ width: ps });
1385
1761
  }
1386
1762
  }