@genome-spy/core 0.72.0 → 0.74.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 (396) hide show
  1. package/LICENSE +1 -1
  2. package/dist/bundle/AbortablePromiseCache-3gHJdF3E.js +96 -0
  3. package/dist/bundle/browser-BTgw5ieH.js +126 -0
  4. package/dist/bundle/chunk-DmhlhrBa.js +11 -0
  5. package/dist/bundle/esm-BDFRLEuD.js +1248 -0
  6. package/dist/bundle/esm-BygJiwh0.js +573 -0
  7. package/dist/bundle/esm-CGX-qz1d.js +155 -0
  8. package/dist/bundle/esm-CgfVIRJ-.js +121 -0
  9. package/dist/bundle/esm-CuMSzCHy.js +298 -0
  10. package/dist/bundle/esm-DMXpJXM4.js +369 -0
  11. package/dist/bundle/esm-DQiq2Zhd.js +1426 -0
  12. package/dist/bundle/esm-DtE8VqAv.js +1015 -0
  13. package/dist/bundle/esm-sIoQYZ21.js +461 -0
  14. package/dist/bundle/index.es.js +21078 -24556
  15. package/dist/bundle/index.js +379 -383
  16. package/dist/bundle/parquetRead-DG_-F5j5.js +1609 -0
  17. package/dist/schema.json +13349 -7082
  18. package/dist/src/config/axisConfig.d.ts +16 -0
  19. package/dist/src/config/axisConfig.d.ts.map +1 -0
  20. package/dist/src/config/axisConfig.js +84 -0
  21. package/dist/src/config/defaultConfig.d.ts +3 -0
  22. package/dist/src/config/defaultConfig.d.ts.map +1 -0
  23. package/dist/src/config/defaultConfig.js +38 -0
  24. package/dist/src/config/defaults/axisDefaults.d.ts +5 -0
  25. package/dist/src/config/defaults/axisDefaults.d.ts.map +1 -0
  26. package/dist/src/config/defaults/axisDefaults.js +72 -0
  27. package/dist/src/config/defaults/markDefaults.d.ts +15 -0
  28. package/dist/src/config/defaults/markDefaults.d.ts.map +1 -0
  29. package/dist/src/config/defaults/markDefaults.js +121 -0
  30. package/dist/src/config/defaults/scaleDefaults.d.ts +5 -0
  31. package/dist/src/config/defaults/scaleDefaults.d.ts.map +1 -0
  32. package/dist/src/config/defaults/scaleDefaults.js +18 -0
  33. package/dist/src/config/defaults/titleDefaults.d.ts +5 -0
  34. package/dist/src/config/defaults/titleDefaults.d.ts.map +1 -0
  35. package/dist/src/config/defaults/titleDefaults.js +47 -0
  36. package/dist/src/config/defaults/viewDefaults.d.ts +3 -0
  37. package/dist/src/config/defaults/viewDefaults.d.ts.map +1 -0
  38. package/dist/src/config/defaults/viewDefaults.js +2 -0
  39. package/dist/src/config/markConfig.d.ts +8 -0
  40. package/dist/src/config/markConfig.d.ts.map +1 -0
  41. package/dist/src/config/markConfig.js +27 -0
  42. package/dist/src/config/mergeConfig.d.ts +8 -0
  43. package/dist/src/config/mergeConfig.d.ts.map +1 -0
  44. package/dist/src/config/mergeConfig.js +81 -0
  45. package/dist/src/config/resolveConfig.d.ts +22 -0
  46. package/dist/src/config/resolveConfig.d.ts.map +1 -0
  47. package/dist/src/config/resolveConfig.js +32 -0
  48. package/dist/src/config/scaleConfig.d.ts +40 -0
  49. package/dist/src/config/scaleConfig.d.ts.map +1 -0
  50. package/dist/src/config/scaleConfig.js +220 -0
  51. package/dist/src/config/styleUtils.d.ts +6 -0
  52. package/dist/src/config/styleUtils.d.ts.map +1 -0
  53. package/dist/src/config/styleUtils.js +10 -0
  54. package/dist/src/config/themes.d.ts +15 -0
  55. package/dist/src/config/themes.d.ts.map +1 -0
  56. package/dist/src/config/themes.js +293 -0
  57. package/dist/src/config/titleConfig.d.ts +12 -0
  58. package/dist/src/config/titleConfig.d.ts.map +1 -0
  59. package/dist/src/config/titleConfig.js +42 -0
  60. package/dist/src/config/viewConfig.d.ts +7 -0
  61. package/dist/src/config/viewConfig.d.ts.map +1 -0
  62. package/dist/src/config/viewConfig.js +29 -0
  63. package/dist/src/data/flowNode.d.ts +22 -1
  64. package/dist/src/data/flowNode.d.ts.map +1 -1
  65. package/dist/src/data/flowNode.js +37 -1
  66. package/dist/src/data/formats/bed.d.ts +8 -0
  67. package/dist/src/data/formats/bed.d.ts.map +1 -0
  68. package/dist/src/data/formats/bed.js +58 -0
  69. package/dist/src/data/formats/bedpe.d.ts +8 -0
  70. package/dist/src/data/formats/bedpe.d.ts.map +1 -0
  71. package/dist/src/data/formats/bedpe.js +164 -0
  72. package/dist/src/data/formats/fasta.d.ts.map +1 -1
  73. package/dist/src/data/formats/fasta.js +4 -0
  74. package/dist/src/data/formats/parquet.d.ts.map +1 -1
  75. package/dist/src/data/formats/parquet.js +4 -0
  76. package/dist/src/data/sources/dataSourceFactory.d.ts +2 -13
  77. package/dist/src/data/sources/dataSourceFactory.d.ts.map +1 -1
  78. package/dist/src/data/sources/dataSourceFactory.js +5 -141
  79. package/dist/src/data/sources/dataUtils.d.ts +16 -0
  80. package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
  81. package/dist/src/data/sources/dataUtils.js +53 -3
  82. package/dist/src/data/sources/lazy/axisGenomeSource.d.ts.map +1 -1
  83. package/dist/src/data/sources/lazy/axisGenomeSource.js +11 -0
  84. package/dist/src/data/sources/lazy/axisTickSource.d.ts +1 -1
  85. package/dist/src/data/sources/lazy/axisTickSource.d.ts.map +1 -1
  86. package/dist/src/data/sources/lazy/axisTickSource.js +19 -8
  87. package/dist/src/data/sources/lazy/bamSource.d.ts.map +1 -1
  88. package/dist/src/data/sources/lazy/bamSource.js +11 -0
  89. package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
  90. package/dist/src/data/sources/lazy/bigBedSource.js +12 -1
  91. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  92. package/dist/src/data/sources/lazy/bigWigSource.js +11 -0
  93. package/dist/src/data/sources/lazy/gff3Source.d.ts.map +1 -1
  94. package/dist/src/data/sources/lazy/gff3Source.js +12 -1
  95. package/dist/src/data/sources/lazy/indexedFastaSource.d.ts.map +1 -1
  96. package/dist/src/data/sources/lazy/indexedFastaSource.js +11 -0
  97. package/dist/src/data/sources/lazy/lazyDataSourceRegistry.d.ts +27 -0
  98. package/dist/src/data/sources/lazy/lazyDataSourceRegistry.d.ts.map +1 -0
  99. package/dist/src/data/sources/lazy/lazyDataSourceRegistry.js +65 -0
  100. package/dist/src/data/sources/lazy/registerBuiltInLazySources.d.ts +2 -0
  101. package/dist/src/data/sources/lazy/registerBuiltInLazySources.d.ts.map +1 -0
  102. package/dist/src/data/sources/lazy/registerBuiltInLazySources.js +8 -0
  103. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  104. package/dist/src/data/sources/lazy/singleAxisLazySource.js +11 -2
  105. package/dist/src/data/sources/lazy/vcfSource.d.ts.map +1 -1
  106. package/dist/src/data/sources/lazy/vcfSource.js +11 -0
  107. package/dist/src/data/sources/urlSource.d.ts +4 -0
  108. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  109. package/dist/src/data/sources/urlSource.js +138 -16
  110. package/dist/src/data/transforms/aggregate.d.ts +1 -0
  111. package/dist/src/data/transforms/aggregate.d.ts.map +1 -1
  112. package/dist/src/data/transforms/aggregate.js +30 -8
  113. package/dist/src/data/transforms/aggregateOps.d.ts.map +1 -1
  114. package/dist/src/data/transforms/aggregateOps.js +12 -1
  115. package/dist/src/data/transforms/coverage.js +2 -2
  116. package/dist/src/data/transforms/filter.js +1 -1
  117. package/dist/src/data/transforms/filterScoredLabels.d.ts +6 -0
  118. package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
  119. package/dist/src/data/transforms/filterScoredLabels.js +9 -0
  120. package/dist/src/data/transforms/measureText.d.ts +1 -0
  121. package/dist/src/data/transforms/measureText.d.ts.map +1 -1
  122. package/dist/src/data/transforms/measureText.js +14 -5
  123. package/dist/src/data/transforms/pileup.d.ts.map +1 -1
  124. package/dist/src/data/transforms/pileup.js +1 -2
  125. package/dist/src/data/transforms/regexFold.d.ts.map +1 -1
  126. package/dist/src/data/transforms/regexFold.js +0 -1
  127. package/dist/src/embedFactory.d.ts +13 -0
  128. package/dist/src/embedFactory.d.ts.map +1 -0
  129. package/dist/src/embedFactory.js +127 -0
  130. package/dist/src/encoder/accessor.d.ts +3 -12
  131. package/dist/src/encoder/accessor.d.ts.map +1 -1
  132. package/dist/src/encoder/accessor.js +10 -65
  133. package/dist/src/encoder/encoder.d.ts +51 -8
  134. package/dist/src/encoder/encoder.d.ts.map +1 -1
  135. package/dist/src/encoder/encoder.js +179 -55
  136. package/dist/src/fonts/bmFontManager.js +1 -1
  137. package/dist/src/full.d.ts +2 -0
  138. package/dist/src/full.d.ts.map +1 -0
  139. package/dist/src/full.js +2 -0
  140. package/dist/src/genome/assemblyPreflight.d.ts +31 -0
  141. package/dist/src/genome/assemblyPreflight.d.ts.map +1 -0
  142. package/dist/src/genome/assemblyPreflight.js +99 -0
  143. package/dist/src/genome/genome.d.ts +10 -2
  144. package/dist/src/genome/genome.d.ts.map +1 -1
  145. package/dist/src/genome/genome.js +20 -2
  146. package/dist/src/genome/genomeStore.d.ts +34 -3
  147. package/dist/src/genome/genomeStore.d.ts.map +1 -1
  148. package/dist/src/genome/genomeStore.js +409 -18
  149. package/dist/src/genome/rootGenomeConfig.d.ts +26 -0
  150. package/dist/src/genome/rootGenomeConfig.d.ts.map +1 -0
  151. package/dist/src/genome/rootGenomeConfig.js +98 -0
  152. package/dist/src/genome/scaleLocus.d.ts.map +1 -1
  153. package/dist/src/genome/scaleLocus.js +26 -7
  154. package/dist/src/genomeSpy/cursorManager.d.ts +69 -0
  155. package/dist/src/genomeSpy/cursorManager.d.ts.map +1 -0
  156. package/dist/src/genomeSpy/cursorManager.js +131 -0
  157. package/dist/src/genomeSpy/headlessBootstrap.d.ts +113 -0
  158. package/dist/src/genomeSpy/headlessBootstrap.d.ts.map +1 -0
  159. package/dist/src/genomeSpy/headlessBootstrap.js +246 -0
  160. package/dist/src/genomeSpy/interactionController.d.ts +10 -1
  161. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  162. package/dist/src/genomeSpy/interactionController.js +448 -40
  163. package/dist/src/genomeSpy/interactionDispatcher.d.ts +50 -0
  164. package/dist/src/genomeSpy/interactionDispatcher.d.ts.map +1 -0
  165. package/dist/src/genomeSpy/interactionDispatcher.js +203 -0
  166. package/dist/src/genomeSpy/renderCoordinator.js +1 -1
  167. package/dist/src/genomeSpy/viewContextFactory.d.ts +4 -2
  168. package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -1
  169. package/dist/src/genomeSpy/viewContextFactory.js +12 -4
  170. package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -1
  171. package/dist/src/genomeSpy/viewDataInit.js +7 -3
  172. package/dist/src/genomeSpy.d.ts +1 -114
  173. package/dist/src/genomeSpy.d.ts.map +1 -1
  174. package/dist/src/genomeSpy.js +7 -614
  175. package/dist/src/genomeSpyBase.d.ts +133 -0
  176. package/dist/src/genomeSpyBase.d.ts.map +1 -0
  177. package/dist/src/genomeSpyBase.js +719 -0
  178. package/dist/src/gl/arrayBuilder.d.ts.map +1 -1
  179. package/dist/src/gl/arrayBuilder.js +0 -3
  180. package/dist/src/gl/canvasSizeHelper.d.ts +74 -0
  181. package/dist/src/gl/canvasSizeHelper.d.ts.map +1 -0
  182. package/dist/src/gl/canvasSizeHelper.js +203 -0
  183. package/dist/src/gl/colorUtils.d.ts.map +1 -1
  184. package/dist/src/gl/colorUtils.js +3 -0
  185. package/dist/src/gl/dataToVertices.d.ts.map +1 -1
  186. package/dist/src/gl/dataToVertices.js +13 -8
  187. package/dist/src/gl/glslScaleGenerator.d.ts +2 -2
  188. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
  189. package/dist/src/gl/glslScaleGenerator.js +5 -7
  190. package/dist/src/gl/webGLHelper.d.ts +25 -11
  191. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  192. package/dist/src/gl/webGLHelper.js +59 -33
  193. package/dist/src/index.d.ts +3 -9
  194. package/dist/src/index.d.ts.map +1 -1
  195. package/dist/src/index.js +5 -111
  196. package/dist/src/marks/__snapshots__/shaderSnapshot.test.js.snap +4462 -0
  197. package/dist/src/marks/link.d.ts.map +1 -1
  198. package/dist/src/marks/link.js +5 -26
  199. package/dist/src/marks/mark.d.ts +8 -1
  200. package/dist/src/marks/mark.d.ts.map +1 -1
  201. package/dist/src/marks/mark.js +67 -21
  202. package/dist/src/marks/markUtils.d.ts +18 -1
  203. package/dist/src/marks/markUtils.d.ts.map +1 -1
  204. package/dist/src/marks/markUtils.js +52 -4
  205. package/dist/src/marks/point.d.ts.map +1 -1
  206. package/dist/src/marks/point.js +6 -26
  207. package/dist/src/marks/rect.d.ts.map +1 -1
  208. package/dist/src/marks/rect.js +13 -21
  209. package/dist/src/marks/rule.d.ts +7 -2
  210. package/dist/src/marks/rule.d.ts.map +1 -1
  211. package/dist/src/marks/rule.js +125 -16
  212. package/dist/src/marks/text.d.ts.map +1 -1
  213. package/dist/src/marks/text.js +5 -47
  214. package/dist/src/minimal.d.ts +8 -0
  215. package/dist/src/minimal.d.ts.map +1 -0
  216. package/dist/src/minimal.js +21 -0
  217. package/dist/src/paramRuntime/viewParamRuntime.d.ts +19 -0
  218. package/dist/src/paramRuntime/viewParamRuntime.d.ts.map +1 -1
  219. package/dist/src/paramRuntime/viewParamRuntime.js +35 -0
  220. package/dist/src/scale/scale.d.ts.map +1 -1
  221. package/dist/src/scale/scale.js +13 -7
  222. package/dist/src/scales/axisResolution.d.ts.map +1 -1
  223. package/dist/src/scales/axisResolution.js +9 -5
  224. package/dist/src/scales/domainPlanner.d.ts +58 -7
  225. package/dist/src/scales/domainPlanner.d.ts.map +1 -1
  226. package/dist/src/scales/domainPlanner.js +395 -40
  227. package/dist/src/scales/indexLikeDomainUtils.d.ts +29 -0
  228. package/dist/src/scales/indexLikeDomainUtils.d.ts.map +1 -0
  229. package/dist/src/scales/indexLikeDomainUtils.js +67 -0
  230. package/dist/src/scales/resolutionMemberOrder.d.ts +15 -0
  231. package/dist/src/scales/resolutionMemberOrder.d.ts.map +1 -0
  232. package/dist/src/scales/resolutionMemberOrder.js +22 -0
  233. package/dist/src/scales/scaleInstanceManager.d.ts +2 -1
  234. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  235. package/dist/src/scales/scaleInstanceManager.js +10 -11
  236. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
  237. package/dist/src/scales/scaleInteractionController.js +59 -18
  238. package/dist/src/scales/scalePropsResolver.d.ts +3 -1
  239. package/dist/src/scales/scalePropsResolver.d.ts.map +1 -1
  240. package/dist/src/scales/scalePropsResolver.js +83 -6
  241. package/dist/src/scales/scaleResolution.d.ts +39 -0
  242. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  243. package/dist/src/scales/scaleResolution.js +504 -82
  244. package/dist/src/scales/scaleResolutionTestUtils.d.ts +21 -0
  245. package/dist/src/scales/scaleResolutionTestUtils.d.ts.map +1 -0
  246. package/dist/src/scales/scaleResolutionTestUtils.js +37 -0
  247. package/dist/src/scales/scaleRules.d.ts.map +1 -1
  248. package/dist/src/scales/scaleRules.js +16 -2
  249. package/dist/src/scales/selectionDomainUtils.d.ts +52 -0
  250. package/dist/src/scales/selectionDomainUtils.d.ts.map +1 -0
  251. package/dist/src/scales/selectionDomainUtils.js +194 -0
  252. package/dist/src/scales/zoomDomainUtils.d.ts +18 -0
  253. package/dist/src/scales/zoomDomainUtils.d.ts.map +1 -0
  254. package/dist/src/scales/zoomDomainUtils.js +69 -0
  255. package/dist/src/screenshotHarness.d.ts +16 -0
  256. package/dist/src/screenshotHarness.d.ts.map +1 -0
  257. package/dist/src/screenshotHarness.js +241 -0
  258. package/dist/src/singlePageApp.js +1 -1
  259. package/dist/src/spec/axis.d.ts +41 -30
  260. package/dist/src/spec/channel.d.ts +15 -9
  261. package/dist/src/spec/config.d.ts +264 -0
  262. package/dist/src/spec/data.d.ts +30 -3
  263. package/dist/src/spec/decoration.d.ts +51 -0
  264. package/dist/src/spec/exampleFiles.d.ts +12 -0
  265. package/dist/src/spec/exampleFiles.d.ts.map +1 -0
  266. package/dist/src/spec/exampleFiles.js +52 -0
  267. package/dist/src/spec/font.d.ts +1 -1
  268. package/dist/src/spec/genome.d.ts +22 -2
  269. package/dist/src/spec/mark.d.ts +97 -13
  270. package/dist/src/spec/parameter.d.ts +62 -5
  271. package/dist/src/spec/root.d.ts +34 -1
  272. package/dist/src/spec/scale.d.ts +46 -5
  273. package/dist/src/spec/title.d.ts +13 -2
  274. package/dist/src/spec/tooltip.d.ts +1 -1
  275. package/dist/src/spec/transform.d.ts +39 -4
  276. package/dist/src/spec/view.d.ts +67 -19
  277. package/dist/src/styles/genome-spy.css +63 -55
  278. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  279. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  280. package/dist/src/styles/genome-spy.css.js +31 -22
  281. package/dist/src/testSetup.d.ts +2 -0
  282. package/dist/src/testSetup.d.ts.map +1 -0
  283. package/dist/src/testSetup.js +5 -0
  284. package/dist/src/tooltip/dataTooltipHandler.js +66 -11
  285. package/dist/src/tooltip/tooltipContext.d.ts.map +1 -1
  286. package/dist/src/tooltip/tooltipContext.js +3 -2
  287. package/dist/src/types/embedApi.d.ts +26 -0
  288. package/dist/src/types/encoder.d.ts +17 -15
  289. package/dist/src/types/scaleResolutionApi.d.ts +20 -0
  290. package/dist/src/types/viewContext.d.ts +23 -1
  291. package/dist/src/utils/expression.d.ts +2 -2
  292. package/dist/src/utils/expression.d.ts.map +1 -1
  293. package/dist/src/utils/expression.js +63 -8
  294. package/dist/src/utils/field.d.ts.map +1 -1
  295. package/dist/src/utils/field.js +0 -1
  296. package/dist/src/utils/inertia.d.ts.map +1 -1
  297. package/dist/src/utils/inertia.js +0 -1
  298. package/dist/src/utils/inferSpecBaseUrl.d.ts +14 -0
  299. package/dist/src/utils/inferSpecBaseUrl.d.ts.map +1 -0
  300. package/dist/src/utils/inferSpecBaseUrl.js +73 -0
  301. package/dist/src/utils/inputBinding.d.ts +1 -1
  302. package/dist/src/utils/interaction.d.ts +109 -0
  303. package/dist/src/utils/interaction.d.ts.map +1 -0
  304. package/dist/src/utils/interaction.js +200 -0
  305. package/dist/src/utils/interactionEvent.d.ts +62 -33
  306. package/dist/src/utils/interactionEvent.d.ts.map +1 -1
  307. package/dist/src/utils/interactionEvent.js +86 -48
  308. package/dist/src/utils/kWayMerge.js +1 -1
  309. package/dist/src/utils/mergeObjects.d.ts.map +1 -1
  310. package/dist/src/utils/mergeObjects.js +0 -2
  311. package/dist/src/utils/radixSort.d.ts.map +1 -1
  312. package/dist/src/utils/radixSort.js +0 -2
  313. package/dist/src/utils/throttle.d.ts.map +1 -1
  314. package/dist/src/utils/throttle.js +0 -2
  315. package/dist/src/utils/ui/tooltip.d.ts +1 -0
  316. package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
  317. package/dist/src/utils/ui/tooltip.js +1 -0
  318. package/dist/src/utils/url.js +1 -1
  319. package/dist/src/view/axisGridView.d.ts +1 -1
  320. package/dist/src/view/axisGridView.d.ts.map +1 -1
  321. package/dist/src/view/axisGridView.js +2 -47
  322. package/dist/src/view/axisView.d.ts +2 -3
  323. package/dist/src/view/axisView.d.ts.map +1 -1
  324. package/dist/src/view/axisView.js +251 -106
  325. package/dist/src/view/concatView.d.ts +2 -1
  326. package/dist/src/view/concatView.d.ts.map +1 -1
  327. package/dist/src/view/concatView.js +4 -2
  328. package/dist/src/view/containerMutationHelper.d.ts +3 -0
  329. package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
  330. package/dist/src/view/containerMutationHelper.js +12 -1
  331. package/dist/src/view/dataReadiness.d.ts +2 -2
  332. package/dist/src/view/dataReadiness.d.ts.map +1 -1
  333. package/dist/src/view/dataReadiness.js +63 -58
  334. package/dist/src/view/facetView.d.ts +1 -1
  335. package/dist/src/view/facetView.js +4 -4
  336. package/dist/src/view/flowBuilder.js +2 -2
  337. package/dist/src/view/gridView/gridChild.d.ts +13 -0
  338. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  339. package/dist/src/view/gridView/gridChild.js +247 -49
  340. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  341. package/dist/src/view/gridView/gridView.js +296 -99
  342. package/dist/src/view/gridView/keyboardZoomController.d.ts +2 -2
  343. package/dist/src/view/gridView/keyboardZoomController.d.ts.map +1 -1
  344. package/dist/src/view/gridView/keyboardZoomController.js +1 -1
  345. package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
  346. package/dist/src/view/gridView/scrollbar.js +4 -2
  347. package/dist/src/view/gridView/selectionRect.d.ts +4 -0
  348. package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
  349. package/dist/src/view/gridView/selectionRect.js +20 -1
  350. package/dist/src/view/gridView/separatorView.d.ts +1 -0
  351. package/dist/src/view/gridView/separatorView.d.ts.map +1 -1
  352. package/dist/src/view/gridView/separatorView.js +9 -0
  353. package/dist/src/view/interactionRouting.d.ts +20 -0
  354. package/dist/src/view/interactionRouting.d.ts.map +1 -0
  355. package/dist/src/view/interactionRouting.js +53 -0
  356. package/dist/src/view/layerView.d.ts.map +1 -1
  357. package/dist/src/view/layerView.js +12 -9
  358. package/dist/src/view/layout/grid.js +1 -1
  359. package/dist/src/view/renderingContext/bufferedViewRenderingContext.d.ts.map +1 -1
  360. package/dist/src/view/renderingContext/bufferedViewRenderingContext.js +0 -2
  361. package/dist/src/view/testUtils.d.ts +17 -3
  362. package/dist/src/view/testUtils.d.ts.map +1 -1
  363. package/dist/src/view/testUtils.js +62 -69
  364. package/dist/src/view/title.d.ts +8 -1
  365. package/dist/src/view/title.d.ts.map +1 -1
  366. package/dist/src/view/title.js +66 -76
  367. package/dist/src/view/unitView.d.ts +1 -1
  368. package/dist/src/view/unitView.d.ts.map +1 -1
  369. package/dist/src/view/unitView.js +67 -17
  370. package/dist/src/view/view.d.ts +76 -30
  371. package/dist/src/view/view.d.ts.map +1 -1
  372. package/dist/src/view/view.js +136 -47
  373. package/dist/src/view/viewFactory.d.ts +11 -3
  374. package/dist/src/view/viewFactory.d.ts.map +1 -1
  375. package/dist/src/view/viewFactory.js +37 -11
  376. package/dist/src/view/viewUtils.d.ts.map +1 -1
  377. package/dist/src/view/viewUtils.js +41 -5
  378. package/dist/src/view/zoom.d.ts +15 -3
  379. package/dist/src/view/zoom.d.ts.map +1 -1
  380. package/dist/src/view/zoom.js +375 -80
  381. package/package.json +20 -10
  382. package/dist/bundle/AbortablePromiseCache-Dj0vzLnp.js +0 -149
  383. package/dist/bundle/browser-0iNU5Wit.js +0 -138
  384. package/dist/bundle/index-BYsZN7b0.js +0 -1597
  385. package/dist/bundle/index-C3kClAEN.js +0 -1771
  386. package/dist/bundle/index-C7wOh6y1.js +0 -657
  387. package/dist/bundle/index-CRaQAuki.js +0 -326
  388. package/dist/bundle/index-D9v1PCj9.js +0 -507
  389. package/dist/bundle/index-GDOuv_D5.js +0 -266
  390. package/dist/bundle/index-Gt44EOIH.js +0 -628
  391. package/dist/bundle/inflate-GtwLkvSP.js +0 -1048
  392. package/dist/bundle/parquetRead-BnAGCa4_.js +0 -1663
  393. package/dist/bundle/unzip-Bac01w6X.js +0 -1492
  394. package/dist/src/config/scaleDefaults.d.ts +0 -8
  395. package/dist/src/config/scaleDefaults.d.ts.map +0 -1
  396. package/dist/src/config/scaleDefaults.js +0 -45
@@ -24,10 +24,21 @@ import {
24
24
  } from "./scaleResolutionConstants.js";
25
25
 
26
26
  import { getAccessorDomainKey } from "../encoder/accessor.js";
27
- import { isSecondaryChannel } from "../encoder/encoder.js";
27
+ import {
28
+ isPrimaryPositionalChannel,
29
+ isSecondaryChannel,
30
+ } from "../encoder/encoder.js";
28
31
  import { NominalDomain } from "../utils/domainArray.js";
29
32
  import { shallowArrayEquals } from "../utils/arrayUtils.js";
30
33
  import createIndexer from "../utils/indexer.js";
34
+ import { getCachedOrCall, invalidate } from "../utils/propertyCacher.js";
35
+ import { resolveUrl } from "../utils/url.js";
36
+ import {
37
+ findIntervalSelectionBindingOwners,
38
+ normalizeIntervalForSelection,
39
+ requireIntervalSelection,
40
+ } from "./selectionDomainUtils.js";
41
+ import { toExternalIndexLikeInterval } from "./indexLikeDomainUtils.js";
31
42
 
32
43
  // Register scaleLocus to Vega-Scale.
33
44
  // Loci are discrete but the scale's domain can be adjusted in a continuous manner.
@@ -111,6 +122,16 @@ export default class ScaleResolution {
111
122
 
112
123
  #categoricalIndexerExplicit = false;
113
124
 
125
+ /** @type {(() => void)[]} */
126
+ #selectionDomainParamUnsubscribers = [];
127
+
128
+ #selectionReverseSyncSuppressionDepth = 0;
129
+
130
+ #ignoreSelectionInitial = false;
131
+
132
+ /** @type {[number, number] | null | undefined} */
133
+ #lastLinkedSelectionInterval = undefined;
134
+
114
135
  /**
115
136
  * @param {Channel} channel
116
137
  */
@@ -124,10 +145,11 @@ export default class ScaleResolution {
124
145
 
125
146
  this.#domainAggregator = new DomainPlanner({
126
147
  getMembers: () => this.#getActiveMembers(),
148
+ getAllMembers: () => this.#members,
127
149
  getDataMembers: () =>
128
150
  this.#getActiveMembers(this.#dataDomainMembers),
129
151
  getType: () => this.type,
130
- getLocusExtent: () => this.#getLocusExtent(),
152
+ getLocusExtent: (assembly) => this.#getLocusExtent(assembly),
131
153
  fromComplexInterval: this.fromComplexInterval.bind(this),
132
154
  });
133
155
 
@@ -143,8 +165,7 @@ export default class ScaleResolution {
143
165
  getAnimator: () => this.#viewContext.animator,
144
166
  getInitialDomainSnapshot: () =>
145
167
  this.#domainAggregator.initialDomainSnapshot,
146
- getResetDomain: () =>
147
- this.#domainAggregator.getConfiguredOrDefaultDomain(),
168
+ getResetDomain: () => this.#getConfiguredOrDefaultDomain(),
148
169
  fromComplexInterval: this.fromComplexInterval.bind(this),
149
170
  getGenomeExtent: () => this.#getLocusExtent(),
150
171
  });
@@ -200,21 +221,24 @@ export default class ScaleResolution {
200
221
  }
201
222
 
202
223
  /**
224
+ * @param {import("../spec/scale.js").Scale["assembly"]} [assembly]
203
225
  * @returns {number[]}
204
226
  */
205
- #getLocusExtent() {
206
- return getGenomeExtent(this.#getGenomeSource());
227
+ #getLocusExtent(assembly) {
228
+ return getGenomeExtent(this.#getGenomeSource(assembly));
207
229
  }
208
230
 
209
231
  /**
232
+ * @param {import("../spec/scale.js").Scale["assembly"]} [assembly]
210
233
  * @returns {import("../genome/scaleLocus.js").GenomeSource}
211
234
  */
212
- #getGenomeSource() {
235
+ #getGenomeSource(assembly) {
213
236
  if (this.type !== LOCUS) {
214
237
  return undefined;
215
238
  }
216
239
  return /** @type {import("../genome/scaleLocus.js").GenomeSource} */ (
217
- this.#scaleManager.scale ?? this.#scaleManager.getLocusGenome()
240
+ this.#scaleManager.scale ??
241
+ this.#scaleManager.getLocusGenome(assembly)
218
242
  );
219
243
  }
220
244
 
@@ -242,6 +266,13 @@ export default class ScaleResolution {
242
266
  * @param {ScaleResolutionEventType} type
243
267
  */
244
268
  #notifyListeners(type) {
269
+ if (
270
+ type === "domain" &&
271
+ this.#selectionReverseSyncSuppressionDepth === 0
272
+ ) {
273
+ this.syncLinkedSelectionFromDomain();
274
+ }
275
+
245
276
  for (const listener of this.#listeners[type].values()) {
246
277
  listener({
247
278
  type,
@@ -250,14 +281,139 @@ export default class ScaleResolution {
250
281
  }
251
282
  }
252
283
 
284
+ /**
285
+ * @param {() => void} callback
286
+ */
287
+ #withSelectionReverseSyncSuppressed(callback) {
288
+ this.#selectionReverseSyncSuppressionDepth += 1;
289
+ try {
290
+ callback();
291
+ } finally {
292
+ this.#selectionReverseSyncSuppressionDepth -= 1;
293
+ }
294
+ }
295
+
296
+ syncLinkedSelectionFromDomain() {
297
+ const linkInfo =
298
+ this.#domainAggregator.getSelectionConfiguredDomainBindingInfo();
299
+ if (!linkInfo || !this.isZoomable()) {
300
+ return;
301
+ }
302
+
303
+ const selection = requireIntervalSelection(
304
+ linkInfo.runtime.getValue(linkInfo.param),
305
+ linkInfo.param
306
+ );
307
+
308
+ const interval = this.#normalizeDomainIntervalForLinkedSelection(
309
+ this.getScale().domain()
310
+ );
311
+ if (!interval) {
312
+ return;
313
+ }
314
+
315
+ const fallbackInterval =
316
+ this.#normalizeDomainIntervalForLinkedSelection(
317
+ this.#domainAggregator.getDefaultDomain(true)
318
+ );
319
+
320
+ const syncedInterval =
321
+ fallbackInterval && shallowArrayEquals(interval, fallbackInterval)
322
+ ? null
323
+ : interval;
324
+
325
+ const previousInterval = selection.intervals[linkInfo.encoding] ?? null;
326
+ if (intervalsEqual(previousInterval, syncedInterval)) {
327
+ return;
328
+ }
329
+
330
+ linkInfo.runtime.setValue(linkInfo.param, {
331
+ ...selection,
332
+ type: "interval",
333
+ intervals: {
334
+ ...selection.intervals,
335
+ [linkInfo.encoding]: syncedInterval,
336
+ },
337
+ });
338
+ }
339
+
340
+ /**
341
+ * @param {any[]} domain
342
+ * @returns {[number, number] | undefined}
343
+ */
344
+ #normalizeDomainIntervalForLinkedSelection(domain) {
345
+ return normalizeIntervalForSelection(domain, this.zoomExtent);
346
+ }
347
+
348
+ #getLinkedSelectionInfo() {
349
+ return this.#domainAggregator.getSelectionConfiguredDomainBindingInfo();
350
+ }
351
+
352
+ #shouldIncludeSelectionInitial() {
353
+ return !this.#ignoreSelectionInitial;
354
+ }
355
+
356
+ /**
357
+ * @param {boolean} [extractDataDomain]
358
+ * @param {import("../spec/scale.js").Scale["assembly"]} [locusAssembly]
359
+ * @returns {any[]}
360
+ */
361
+ #getConfiguredOrDefaultDomain(extractDataDomain = false, locusAssembly) {
362
+ return this.#domainAggregator.getConfiguredOrDefaultDomain(
363
+ extractDataDomain,
364
+ locusAssembly,
365
+ {
366
+ includeSelectionInitial: this.#shouldIncludeSelectionInitial(),
367
+ }
368
+ );
369
+ }
370
+
371
+ /**
372
+ * @returns {boolean}
373
+ */
374
+ #hasConfiguredDomain() {
375
+ return this.#domainAggregator.hasConfiguredDomain({
376
+ includeSelectionInitial: this.#shouldIncludeSelectionInitial(),
377
+ });
378
+ }
379
+
380
+ /**
381
+ * @param {import("./domainPlanner.js").SelectionDomainLinkInfo} linkInfo
382
+ * @returns {[number, number] | null}
383
+ */
384
+ #getCurrentLinkedSelectionInterval(linkInfo) {
385
+ const selection = requireIntervalSelection(
386
+ linkInfo.runtime.getValue(linkInfo.param),
387
+ linkInfo.param
388
+ );
389
+ const interval = selection.intervals[linkInfo.encoding];
390
+ return interval && interval.length === 2
391
+ ? /** @type {[number, number]} */ (interval)
392
+ : null;
393
+ }
394
+
395
+ /**
396
+ * @param {[number, number] | null} previousInterval
397
+ * @param {[number, number] | null} nextInterval
398
+ */
399
+ #updateSelectionInitialBypass(previousInterval, nextInterval) {
400
+ if (nextInterval) {
401
+ this.#ignoreSelectionInitial = false;
402
+ } else if (previousInterval) {
403
+ this.#ignoreSelectionInitial = true;
404
+ }
405
+ }
406
+
253
407
  /**
254
408
  * Add a view to this resolution.
255
409
  * N.B. This is expected to be called in depth-first order
256
410
  *
257
411
  * @param {ScaleResolutionMember} newMember
412
+ * @returns {ScaleResolutionMember}
258
413
  */
259
414
  #addMember(newMember) {
260
- const { channel, channelDef } = newMember;
415
+ const member = normalizeMember(newMember);
416
+ const { channel, channelDef } = member;
261
417
 
262
418
  // A convenience hack for cases where the new member should adapt
263
419
  // the scale type to the existing one. For example: SelectionRect
@@ -283,6 +439,20 @@ export default class ScaleResolution {
283
439
  // @ts-expect-error "sample" is not really a channel with scale
284
440
  const type = channel == "sample" ? "nominal" : channelDef.type;
285
441
  const name = channelDef?.scale?.name;
442
+ const explicitScaleType = channelDef.scale?.type;
443
+ const effectiveScaleType =
444
+ explicitScaleType ??
445
+ (type === INDEX || type === LOCUS ? type : undefined);
446
+
447
+ if (
448
+ effectiveScaleType &&
449
+ [INDEX, LOCUS].includes(effectiveScaleType) &&
450
+ !isPrimaryPositionalChannel(this.channel)
451
+ ) {
452
+ throw new Error(
453
+ `Index and locus scales are only supported on positional channels (x/y). Channel "${this.channel}" resolves to scale type "${effectiveScaleType}".`
454
+ );
455
+ }
286
456
 
287
457
  if (name) {
288
458
  if (this.name !== undefined && name != this.name) {
@@ -297,20 +467,26 @@ export default class ScaleResolution {
297
467
  if (!this.type) {
298
468
  this.type = type;
299
469
  } else if (type !== this.type && !isSecondaryChannel(channel)) {
470
+ // TODO: Revisit shared discrete positional scales when
471
+ // implementing Vega-Lite-like band-vs-point inference.
472
+ // Nominal/ordinal members should be able to share a scale even
473
+ // if one member wants "point" and another wants "band"; the
474
+ // merged result should resolve to "band". Explicit
475
+ // user-specified incompatible scale types should still error.
300
476
  // TODO: Include a reference to the layer
301
477
  throw new Error(
302
478
  `Can not use shared scale for different data types: ${this.type} vs. ${type}. Use "resolve: independent" for channel ${this.channel}`
303
479
  );
304
- // Actually, point scale could be changed into band scale
305
- // TODO: Use the same merging logic as in: https://github.com/vega/vega-lite/blob/master/src/scale.ts
306
480
  }
307
481
  }
308
482
 
309
- this.#members.add(newMember);
310
- if (newMember.contributesToDomain) {
311
- this.#dataDomainMembers.add(newMember);
483
+ this.#members.add(member);
484
+ if (member.contributesToDomain) {
485
+ this.#dataDomainMembers.add(member);
312
486
  }
313
- this.#domainAggregator.invalidateConfiguredDomain();
487
+ this.#invalidateConfiguredDomain();
488
+ this.#refreshSelectionDomainParamSubscriptions();
489
+ return member;
314
490
  }
315
491
 
316
492
  /**
@@ -318,23 +494,64 @@ export default class ScaleResolution {
318
494
  * @returns {() => boolean}
319
495
  */
320
496
  registerMember(member) {
321
- this.#addMember(member);
497
+ const registeredMember = this.#addMember(member);
322
498
  return () => {
323
- const removed = this.#members.delete(member);
499
+ const removed = this.#members.delete(registeredMember);
324
500
  if (removed) {
325
- this.#dataDomainMembers.delete(member);
326
- this.#domainAggregator.invalidateConfiguredDomain();
501
+ this.#dataDomainMembers.delete(registeredMember);
502
+ this.#invalidateConfiguredDomain();
503
+ this.#refreshSelectionDomainParamSubscriptions();
327
504
  }
328
505
  return removed && this.#members.size === 0;
329
506
  };
330
507
  }
331
508
 
332
509
  dispose() {
510
+ this.#clearSelectionDomainParamSubscriptions();
333
511
  this.#listeners.domain.clear();
334
512
  this.#listeners.range.clear();
335
513
  this.#scaleManager.dispose();
336
514
  }
337
515
 
516
+ #clearSelectionDomainParamSubscriptions() {
517
+ for (const unsubscribe of this.#selectionDomainParamUnsubscribers) {
518
+ unsubscribe();
519
+ }
520
+ this.#selectionDomainParamUnsubscribers = [];
521
+ this.#lastLinkedSelectionInterval = undefined;
522
+ }
523
+
524
+ #refreshSelectionDomainParamSubscriptions() {
525
+ this.#clearSelectionDomainParamSubscriptions();
526
+
527
+ if (this.#members.size === 0) {
528
+ return;
529
+ }
530
+
531
+ const linkInfo = this.#getLinkedSelectionInfo();
532
+ if (!linkInfo) {
533
+ return;
534
+ }
535
+
536
+ this.#lastLinkedSelectionInterval =
537
+ this.#getCurrentLinkedSelectionInterval(linkInfo);
538
+
539
+ this.#selectionDomainParamUnsubscribers.push(
540
+ linkInfo.runtime.subscribe(linkInfo.param, () => {
541
+ const previousInterval = this.#lastLinkedSelectionInterval;
542
+ const currentInterval =
543
+ this.#getCurrentLinkedSelectionInterval(linkInfo);
544
+ this.#updateSelectionInitialBypass(
545
+ previousInterval,
546
+ currentInterval
547
+ );
548
+ this.#lastLinkedSelectionInterval = currentInterval;
549
+ this.#invalidateConfiguredDomain();
550
+ this.reconfigureDomain();
551
+ })
552
+ );
553
+ }
554
+
338
555
  #hasRenderedMember() {
339
556
  for (const member of this.#members) {
340
557
  if (member.view.hasRendered()) {
@@ -386,11 +603,11 @@ export default class ScaleResolution {
386
603
  /**
387
604
  * Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
388
605
  */
389
- #isExplicitDomain() {
390
- return this.#domainAggregator.hasConfiguredDomain();
606
+ isDomainDefinedExplicitly() {
607
+ return this.#hasConfiguredDomain();
391
608
  }
392
609
 
393
- #isDomainInitialized() {
610
+ isDomainInitialized() {
394
611
  const s = this.#scaleManager.scale;
395
612
  if (!s) {
396
613
  return false;
@@ -416,14 +633,92 @@ export default class ScaleResolution {
416
633
  * @returns {import("../spec/scale.js").Scale}
417
634
  */
418
635
  #getMergedScaleProps() {
419
- return resolveScalePropsBase({
420
- channel: this.channel,
421
- dataType: this.type,
422
- members: this.#members,
423
- isExplicitDomain: this.#isExplicitDomain(),
636
+ return getCachedOrCall(this, "mergedScaleProps", () => {
637
+ const props = resolveScalePropsBase({
638
+ channel: this.channel,
639
+ dataType: this.type,
640
+ members: this.#members,
641
+ isExplicitDomain: this.isDomainDefinedExplicitly(),
642
+ configScopes: this.#firstMemberView.getConfigScopes(),
643
+ });
644
+ this.#validateLinkedSelectionConfiguration(props);
645
+ return props;
424
646
  });
425
647
  }
426
648
 
649
+ #invalidateMergedScaleProps() {
650
+ invalidate(this, "mergedScaleProps");
651
+ }
652
+
653
+ #invalidateConfiguredDomain() {
654
+ this.#domainAggregator.invalidateConfiguredDomain();
655
+ this.#invalidateMergedScaleProps();
656
+ }
657
+
658
+ /**
659
+ * @param {import("../spec/scale.js").Scale} props
660
+ */
661
+ #validateLinkedSelectionConfiguration(props) {
662
+ const linkInfo = this.#getLinkedSelectionInfo();
663
+ if (!linkInfo || props === null || props.type === "null") {
664
+ return;
665
+ }
666
+
667
+ const isZoomable =
668
+ isContinuous(props.type) && !isDiscrete(props.type) && !!props.zoom;
669
+
670
+ if (linkInfo.hasInitial && !isZoomable) {
671
+ throw new Error(
672
+ `Selection domain reference "${linkInfo.param}.${linkInfo.encoding}" cannot use "initial" with a non-zoomable ${this.channel} scale. ` +
673
+ `Enable zoom on the linked scale or remove "initial".`
674
+ );
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Returns locus assembly requirements without initializing the scale.
680
+ *
681
+ * This is intentionally side-effect free: it only inspects merged scale
682
+ * properties from registered members and does not touch default domains or
683
+ * instantiate scale instances.
684
+ *
685
+ * @returns {{
686
+ * assembly: import("../spec/scale.js").Scale["assembly"] | undefined,
687
+ * needsDefaultAssembly: boolean
688
+ * }}
689
+ */
690
+ getAssemblyRequirement() {
691
+ const props = this.#getMergedScaleProps();
692
+ if (props === null || props.type === "null" || props.type !== LOCUS) {
693
+ return {
694
+ assembly: undefined,
695
+ needsDefaultAssembly: false,
696
+ };
697
+ }
698
+
699
+ return {
700
+ assembly: props.assembly,
701
+ needsDefaultAssembly: props.assembly === undefined,
702
+ };
703
+ }
704
+
705
+ /**
706
+ * Returns the resolved scale type without instantiating the scale.
707
+ *
708
+ * Useful during view construction, before assembly preflight has loaded
709
+ * URL-backed locus genomes.
710
+ *
711
+ * @returns {import("../spec/scale.js").Scale["type"] | undefined}
712
+ */
713
+ getResolvedScaleType() {
714
+ const props = this.#getMergedScaleProps();
715
+ if (props === null || props.type === "null") {
716
+ return undefined;
717
+ }
718
+
719
+ return props.type;
720
+ }
721
+
427
722
  /**
428
723
  * Returns the merged scale properties supplemented with inferred properties
429
724
  * and domain.
@@ -439,13 +734,15 @@ export default class ScaleResolution {
439
734
  return { type: "null" };
440
735
  }
441
736
 
442
- const domain =
443
- this.#domainAggregator.getConfiguredOrDefaultDomain(
444
- extractDataDomain
445
- );
737
+ const resolvedProps = { ...props };
738
+
739
+ const domain = this.#getConfiguredOrDefaultDomain(
740
+ extractDataDomain,
741
+ resolvedProps.type === LOCUS ? resolvedProps.assembly : undefined
742
+ );
446
743
 
447
- if (isDiscrete(props.type)) {
448
- const isExplicit = this.#isExplicitDomain();
744
+ if (isDiscrete(resolvedProps.type)) {
745
+ const isExplicit = this.isDomainDefinedExplicitly();
449
746
  const indexer = this.#getCategoricalIndexer(isExplicit);
450
747
  if (domain != null) {
451
748
  if (
@@ -461,7 +758,7 @@ export default class ScaleResolution {
461
758
  const indexedDomain = indexer
462
759
  .domain()
463
760
  .filter((value) => active.has(value));
464
- props.domain =
761
+ resolvedProps.domain =
465
762
  indexedDomain.length > 0
466
763
  ? /** @type {import("../spec/scale.js").ScalarDomain} */ (
467
764
  indexedDomain
@@ -469,7 +766,7 @@ export default class ScaleResolution {
469
766
  : new NominalDomain();
470
767
  } else {
471
768
  const indexedDomain = indexer.domain();
472
- props.domain =
769
+ resolvedProps.domain =
473
770
  indexedDomain.length > 0
474
771
  ? /** @type {import("../spec/scale.js").ScalarDomain} */ (
475
772
  indexedDomain
@@ -477,18 +774,21 @@ export default class ScaleResolution {
477
774
  : new NominalDomain();
478
775
  }
479
776
  // Scale props are spec-shaped; keep the indexer off the public type.
480
- /** @type {any} */ (props).domainIndexer = indexer;
777
+ /** @type {any} */ (resolvedProps).domainIndexer = indexer;
481
778
  } else if (domain && domain.length > 0) {
482
- props.domain = domain;
779
+ resolvedProps.domain = domain;
483
780
  }
484
781
 
485
- if (!props.domain && props.domainMid !== undefined) {
782
+ if (!resolvedProps.domain && resolvedProps.domainMid !== undefined) {
486
783
  // Initialize with a bogus domain so that scale.js can inject the domainMid.
487
784
  // The number of domain elements must be know before the glsl scale is generated.
488
- props.domain = [props.domainMin ?? 0, props.domainMax ?? 1];
785
+ resolvedProps.domain = [
786
+ resolvedProps.domainMin ?? 0,
787
+ resolvedProps.domainMax ?? 1,
788
+ ];
489
789
  }
490
790
 
491
- return props;
791
+ return resolvedProps;
492
792
  }
493
793
 
494
794
  /**
@@ -512,15 +812,17 @@ export default class ScaleResolution {
512
812
  * or when scale properties are otherwise re-resolved from the view hierarchy.
513
813
  */
514
814
  reconfigure() {
515
- this.#domainAggregator.invalidateConfiguredDomain();
516
- const state = this.#computeScaleState(true);
517
- if (!state) {
518
- return;
519
- }
520
- this.#applyReconfigure(state, (scale, props) =>
521
- this.#scaleManager.reconfigureScale(props)
522
- );
523
- this.#finalizeReconfigure(state);
815
+ this.#withSelectionReverseSyncSuppressed(() => {
816
+ this.#invalidateConfiguredDomain();
817
+ const state = this.#computeScaleState(true);
818
+ if (!state) {
819
+ return;
820
+ }
821
+ this.#applyReconfigure(state, (scale, props) =>
822
+ this.#scaleManager.reconfigureScale(props)
823
+ );
824
+ this.#finalizeReconfigure(state);
825
+ });
524
826
  }
525
827
 
526
828
  /**
@@ -530,28 +832,32 @@ export default class ScaleResolution {
530
832
  *
531
833
  */
532
834
  reconfigureDomain() {
533
- const state = this.#computeScaleState(true, true);
534
- if (!state) {
535
- return;
536
- }
537
- const { domainConfig, targetDomain } = state;
538
- const domainMatches =
539
- targetDomain != null &&
540
- shallowArrayEquals(targetDomain, state.scale.domain());
541
-
542
- if (targetDomain != null && !domainMatches) {
543
- this.#applyReconfigure(state, (scale) => {
544
- scale.domain(targetDomain);
545
- if (domainConfig.applyOrdinalUnknown) {
546
- // Keep ordinal unknown handling close to the domain write so
547
- // domainImplicit semantics stay aligned with the applied domain.
548
- /** @type {any} */ (scale).unknown(
549
- domainConfig.ordinalUnknown
550
- );
551
- }
552
- });
553
- }
554
- this.#finalizeReconfigure(state);
835
+ this.#withSelectionReverseSyncSuppressed(() => {
836
+ this.#invalidateMergedScaleProps();
837
+ const state = this.#computeScaleState(true, true);
838
+ if (!state) {
839
+ return;
840
+ }
841
+ const { domainConfig, targetDomain } = state;
842
+ const domainMatches =
843
+ targetDomain != null &&
844
+ shallowArrayEquals(targetDomain, state.scale.domain());
845
+
846
+ if (targetDomain != null && !domainMatches) {
847
+ this.#applyReconfigure(state, (scale) => {
848
+ scale.domain(targetDomain);
849
+ if (domainConfig.applyOrdinalUnknown) {
850
+ // Keep ordinal unknown handling close to the domain write so
851
+ // domainImplicit semantics stay aligned with the applied domain.
852
+ /** @type {any} */ (scale).unknown(
853
+ domainConfig.ordinalUnknown
854
+ );
855
+ }
856
+ });
857
+ }
858
+ this.#finalizeReconfigure(state);
859
+ this.syncLinkedSelectionFromDomain();
860
+ });
555
861
  }
556
862
 
557
863
  /**
@@ -562,6 +868,7 @@ export default class ScaleResolution {
562
868
  * props: import("../spec/scale.js").Scale,
563
869
  * previousDomain: any[],
564
870
  * domainWasInitialized: boolean,
871
+ * hasSelectionConfiguredDomain: boolean,
565
872
  * domainConfig?: ReturnType<typeof configureDomain>,
566
873
  * targetDomain?: any[] | null,
567
874
  * } | undefined}
@@ -577,7 +884,9 @@ export default class ScaleResolution {
577
884
  scale,
578
885
  props: this.#getScaleProps(extractDataDomain),
579
886
  previousDomain: scale.domain(),
580
- domainWasInitialized: this.#isDomainInitialized(),
887
+ domainWasInitialized: this.isDomainInitialized(),
888
+ hasSelectionConfiguredDomain:
889
+ this.#domainAggregator.hasSelectionConfiguredDomain(),
581
890
  };
582
891
 
583
892
  if (includeDomainConfig) {
@@ -610,15 +919,26 @@ export default class ScaleResolution {
610
919
  * scale: ScaleWithProps,
611
920
  * previousDomain: any[],
612
921
  * domainWasInitialized: boolean,
922
+ * hasSelectionConfiguredDomain: boolean,
613
923
  * }} inputs
614
924
  */
615
925
  #finalizeReconfigure(inputs) {
616
- const { scale, previousDomain, domainWasInitialized } = inputs;
926
+ const {
927
+ scale,
928
+ previousDomain,
929
+ domainWasInitialized,
930
+ hasSelectionConfiguredDomain,
931
+ } = inputs;
932
+
933
+ const initialDomainSnapshot = hasSelectionConfiguredDomain
934
+ ? this.#domainAggregator.getDefaultDomain(true)
935
+ : undefined;
617
936
 
618
937
  if (
619
938
  this.#domainAggregator.captureInitialDomain(
620
939
  scale,
621
- domainWasInitialized
940
+ domainWasInitialized,
941
+ initialDomainSnapshot
622
942
  )
623
943
  ) {
624
944
  // Domain changes were suppressed during reconfigure; notify explicitly.
@@ -633,12 +953,22 @@ export default class ScaleResolution {
633
953
  );
634
954
 
635
955
  if (action === "restore") {
636
- // Don't mess with zoomed views, restore the previous domain
637
- this.#scaleManager.withDomainNotificationsSuppressed(() => {
638
- scale.domain(previousDomain);
639
- });
956
+ if (hasSelectionConfiguredDomain) {
957
+ // Selection-linked domains are the source of truth and must not
958
+ // be overridden by previously zoomed domains.
959
+ this.#notifyListeners("domain");
960
+ } else {
961
+ // Don't mess with zoomed views, restore the previous domain
962
+ this.#scaleManager.withDomainNotificationsSuppressed(() => {
963
+ scale.domain(previousDomain);
964
+ });
965
+ }
640
966
  } else if (action === "animate") {
641
- if (this.#hasRenderedMember()) {
967
+ if (hasSelectionConfiguredDomain) {
968
+ // Linked domains can update continuously (e.g., brushing), so
969
+ // skip zoomTo transitions and apply domain updates directly.
970
+ this.#notifyListeners("domain");
971
+ } else if (this.#hasRenderedMember()) {
642
972
  // It can be zoomed, so lets make a smooth transition.
643
973
  // Restore the previous domain and zoom smoothly to the new domain.
644
974
  this.#scaleManager.withDomainNotificationsSuppressed(() => {
@@ -712,10 +1042,39 @@ export default class ScaleResolution {
712
1042
  */
713
1043
  getComplexDomain() {
714
1044
  return /** @type {NumericDomain | ComplexDomain} */ (
715
- toComplexInterval(this.#getGenomeSource(), this.getDomain())
1045
+ toComplexInterval(
1046
+ this.#getGenomeSource(),
1047
+ toExternalIndexLikeInterval(this.type, this.getDomain())
1048
+ )
716
1049
  );
717
1050
  }
718
1051
 
1052
+ /**
1053
+ * Returns metadata about a selection-linked domain, if present.
1054
+ */
1055
+ getLinkedSelectionDomainInfo() {
1056
+ const linkInfo = this.#getLinkedSelectionInfo();
1057
+ if (!linkInfo) {
1058
+ return;
1059
+ }
1060
+
1061
+ const root = this.#firstMemberView.getLayoutAncestors().at(-1);
1062
+ const persist = root
1063
+ ? findIntervalSelectionBindingOwners(
1064
+ root,
1065
+ linkInfo.runtime,
1066
+ linkInfo.param,
1067
+ linkInfo.encoding
1068
+ ).some((owner) => owner.param.persist !== false)
1069
+ : false;
1070
+
1071
+ return {
1072
+ param: linkInfo.param,
1073
+ encoding: linkInfo.encoding,
1074
+ persist,
1075
+ };
1076
+ }
1077
+
719
1078
  /**
720
1079
  * Return true if the scale is zoomable and the current domain differs from the initial domain.
721
1080
  *
@@ -729,8 +1088,14 @@ export default class ScaleResolution {
729
1088
  * Returns true if zooming is supported and allowed in view spec.
730
1089
  */
731
1090
  isZoomable() {
732
- // Check explicit configuration
733
- return this.#interactionController.isZoomable();
1091
+ const props = this.#getMergedScaleProps();
1092
+ if (props === null || props.type === "null") {
1093
+ return false;
1094
+ }
1095
+
1096
+ return (
1097
+ isContinuous(props.type) && !isDiscrete(props.type) && !!props.zoom
1098
+ );
734
1099
  }
735
1100
 
736
1101
  /**
@@ -847,3 +1212,60 @@ export default class ScaleResolution {
847
1212
  return /** @type {number[]} */ (interval);
848
1213
  }
849
1214
  }
1215
+
1216
+ /**
1217
+ * @param {number[] | null} a
1218
+ * @param {number[] | null} b
1219
+ * @returns {boolean}
1220
+ */
1221
+ function intervalsEqual(a, b) {
1222
+ if (a === b) {
1223
+ return true;
1224
+ }
1225
+
1226
+ if (!a || !b) {
1227
+ return false;
1228
+ }
1229
+
1230
+ return a.length === b.length && shallowArrayEquals(a, b);
1231
+ }
1232
+
1233
+ /**
1234
+ * Normalizes member-specific scale URLs so that inline `scale.assembly.url`
1235
+ * values resolve against the member view's base URL before scale props are
1236
+ * merged.
1237
+ *
1238
+ * @template {ChannelWithScale}[T=ChannelWithScale]
1239
+ * @param {ScaleResolutionMember<T>} member
1240
+ * @returns {ScaleResolutionMember<T>}
1241
+ */
1242
+ function normalizeMember(member) {
1243
+ const scale = member.channelDef.scale;
1244
+ const assembly = scale?.assembly;
1245
+ if (!scale || !assembly || typeof assembly !== "object") {
1246
+ return member;
1247
+ }
1248
+
1249
+ if (!("url" in assembly)) {
1250
+ return member;
1251
+ }
1252
+
1253
+ const resolvedUrl = resolveUrl(member.view.getBaseUrl(), assembly.url);
1254
+ if (resolvedUrl === assembly.url) {
1255
+ return member;
1256
+ }
1257
+
1258
+ return {
1259
+ ...member,
1260
+ channelDef: {
1261
+ ...member.channelDef,
1262
+ scale: {
1263
+ ...scale,
1264
+ assembly: {
1265
+ ...assembly,
1266
+ url: resolvedUrl,
1267
+ },
1268
+ },
1269
+ },
1270
+ };
1271
+ }