@genome-spy/core 0.67.0 → 0.69.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 (290) hide show
  1. package/dist/bundle/index.es.js +15884 -13130
  2. package/dist/bundle/index.js +130 -149
  3. package/dist/schema.json +6498 -6191
  4. package/dist/src/data/collector.d.ts +20 -0
  5. package/dist/src/data/collector.d.ts.map +1 -1
  6. package/dist/src/data/collector.js +148 -0
  7. package/dist/src/data/dataFlow.d.ts +6 -0
  8. package/dist/src/data/dataFlow.d.ts.map +1 -1
  9. package/dist/src/data/dataFlow.js +20 -0
  10. package/dist/src/data/flowInit.d.ts.map +1 -1
  11. package/dist/src/data/flowInit.js +2 -3
  12. package/dist/src/data/flowNode.d.ts +33 -10
  13. package/dist/src/data/flowNode.d.ts.map +1 -1
  14. package/dist/src/data/flowNode.js +84 -13
  15. package/dist/src/data/flowTestUtils.d.ts +2 -2
  16. package/dist/src/data/flowTestUtils.d.ts.map +1 -1
  17. package/dist/src/data/flowTestUtils.js +5 -4
  18. package/dist/src/data/keyIndex.d.ts +18 -0
  19. package/dist/src/data/keyIndex.d.ts.map +1 -0
  20. package/dist/src/data/keyIndex.js +241 -0
  21. package/dist/src/data/keyIndex.test.d.ts +2 -0
  22. package/dist/src/data/keyIndex.test.d.ts.map +1 -0
  23. package/dist/src/data/sources/dataSource.d.ts.map +1 -1
  24. package/dist/src/data/sources/dataSource.js +7 -3
  25. package/dist/src/data/sources/dataSourceFactory.d.ts +14 -12
  26. package/dist/src/data/sources/dataSourceFactory.d.ts.map +1 -1
  27. package/dist/src/data/sources/dataSourceFactory.js +52 -16
  28. package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
  29. package/dist/src/data/sources/lazy/bigBedSource.js +11 -10
  30. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  31. package/dist/src/data/sources/lazy/bigWigSource.js +11 -10
  32. package/dist/src/data/sources/lazy/mockLazySource.d.ts +29 -0
  33. package/dist/src/data/sources/lazy/mockLazySource.d.ts.map +1 -0
  34. package/dist/src/data/sources/lazy/mockLazySource.js +44 -0
  35. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +22 -1
  36. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  37. package/dist/src/data/sources/lazy/singleAxisLazySource.js +34 -2
  38. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  39. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +16 -1
  40. package/dist/src/data/sources/lazy/tabixSource.d.ts +0 -1
  41. package/dist/src/data/sources/lazy/tabixSource.d.ts.map +1 -1
  42. package/dist/src/data/sources/lazy/tabixSource.js +56 -16
  43. package/dist/src/data/sources/sequenceSource.d.ts.map +1 -1
  44. package/dist/src/data/sources/sequenceSource.js +5 -3
  45. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  46. package/dist/src/data/sources/urlSource.js +7 -3
  47. package/dist/src/data/transforms/filter.d.ts +4 -4
  48. package/dist/src/data/transforms/filter.d.ts.map +1 -1
  49. package/dist/src/data/transforms/filter.js +13 -7
  50. package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
  51. package/dist/src/data/transforms/filterScoredLabels.js +11 -6
  52. package/dist/src/data/transforms/filterScoredLabels.test.d.ts +2 -0
  53. package/dist/src/data/transforms/filterScoredLabels.test.d.ts.map +1 -0
  54. package/dist/src/data/transforms/formula.d.ts +4 -4
  55. package/dist/src/data/transforms/formula.d.ts.map +1 -1
  56. package/dist/src/data/transforms/formula.js +12 -6
  57. package/dist/src/data/transforms/measureText.d.ts +2 -2
  58. package/dist/src/data/transforms/measureText.d.ts.map +1 -1
  59. package/dist/src/data/transforms/measureText.js +16 -12
  60. package/dist/src/data/transforms/stack.d.ts.map +1 -1
  61. package/dist/src/data/transforms/stack.js +1 -0
  62. package/dist/src/data/transforms/transform.d.ts +2 -2
  63. package/dist/src/data/transforms/transform.d.ts.map +1 -1
  64. package/dist/src/data/transforms/transform.js +3 -3
  65. package/dist/src/encoder/accessor.d.ts +51 -4
  66. package/dist/src/encoder/accessor.d.ts.map +1 -1
  67. package/dist/src/encoder/accessor.js +174 -10
  68. package/dist/src/encoder/encoder.d.ts +11 -2
  69. package/dist/src/encoder/encoder.d.ts.map +1 -1
  70. package/dist/src/encoder/encoder.js +29 -9
  71. package/dist/src/encoder/metadataChannels.d.ts +15 -0
  72. package/dist/src/encoder/metadataChannels.d.ts.map +1 -0
  73. package/dist/src/encoder/metadataChannels.js +65 -0
  74. package/dist/src/encoder/metadataChannels.test.d.ts +2 -0
  75. package/dist/src/encoder/metadataChannels.test.d.ts.map +1 -0
  76. package/dist/src/genome/genome.d.ts +8 -0
  77. package/dist/src/genome/genome.d.ts.map +1 -1
  78. package/dist/src/genome/genome.js +16 -1
  79. package/dist/src/genome/scaleLocus.d.ts.map +1 -1
  80. package/dist/src/genome/scaleLocus.js +14 -1
  81. package/dist/src/genomeSpy/containerUi.d.ts +0 -1
  82. package/dist/src/genomeSpy/containerUi.d.ts.map +1 -1
  83. package/dist/src/genomeSpy/containerUi.js +0 -14
  84. package/dist/src/genomeSpy/inputBindingManager.js +1 -1
  85. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  86. package/dist/src/genomeSpy/interactionController.js +7 -1
  87. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts +3 -7
  88. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts.map +1 -1
  89. package/dist/src/genomeSpy/loadingIndicatorManager.js +68 -20
  90. package/dist/src/genomeSpy/loadingStatusRegistry.d.ts +52 -0
  91. package/dist/src/genomeSpy/loadingStatusRegistry.d.ts.map +1 -0
  92. package/dist/src/genomeSpy/loadingStatusRegistry.js +86 -0
  93. package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -1
  94. package/dist/src/genomeSpy/viewContextFactory.js +0 -1
  95. package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -1
  96. package/dist/src/genomeSpy/viewDataInit.js +56 -11
  97. package/dist/src/genomeSpy.d.ts +0 -2
  98. package/dist/src/genomeSpy.d.ts.map +1 -1
  99. package/dist/src/genomeSpy.js +47 -27
  100. package/dist/src/gl/glslScaleGenerator.js +1 -1
  101. package/dist/src/marks/mark.d.ts.map +1 -1
  102. package/dist/src/marks/mark.js +40 -41
  103. package/dist/src/marks/markUtils.js +1 -1
  104. package/dist/src/marks/point.d.ts.map +1 -1
  105. package/dist/src/marks/point.js +4 -6
  106. package/dist/src/paramRuntime/expressionCompiler.d.ts +7 -0
  107. package/dist/src/paramRuntime/expressionCompiler.d.ts.map +1 -0
  108. package/dist/src/paramRuntime/expressionCompiler.js +10 -0
  109. package/dist/src/paramRuntime/expressionRef.d.ts +20 -0
  110. package/dist/src/paramRuntime/expressionRef.d.ts.map +1 -0
  111. package/dist/src/paramRuntime/expressionRef.js +95 -0
  112. package/dist/src/paramRuntime/expressionRef.test.d.ts +2 -0
  113. package/dist/src/paramRuntime/expressionRef.test.d.ts.map +1 -0
  114. package/dist/src/paramRuntime/graphRuntime.d.ts +176 -0
  115. package/dist/src/paramRuntime/graphRuntime.d.ts.map +1 -0
  116. package/dist/src/paramRuntime/graphRuntime.js +628 -0
  117. package/dist/src/paramRuntime/graphRuntime.test.d.ts +2 -0
  118. package/dist/src/paramRuntime/graphRuntime.test.d.ts.map +1 -0
  119. package/dist/src/paramRuntime/index.d.ts +9 -0
  120. package/dist/src/paramRuntime/index.d.ts.map +1 -0
  121. package/dist/src/paramRuntime/index.js +8 -0
  122. package/dist/src/paramRuntime/lifecycleRegistry.d.ts +27 -0
  123. package/dist/src/paramRuntime/lifecycleRegistry.d.ts.map +1 -0
  124. package/dist/src/paramRuntime/lifecycleRegistry.js +54 -0
  125. package/dist/src/paramRuntime/paramRuntime.d.ts +165 -0
  126. package/dist/src/paramRuntime/paramRuntime.d.ts.map +1 -0
  127. package/dist/src/paramRuntime/paramRuntime.js +222 -0
  128. package/dist/src/paramRuntime/paramRuntime.test.d.ts +2 -0
  129. package/dist/src/paramRuntime/paramRuntime.test.d.ts.map +1 -0
  130. package/dist/src/paramRuntime/paramStore.d.ts +68 -0
  131. package/dist/src/paramRuntime/paramStore.d.ts.map +1 -0
  132. package/dist/src/paramRuntime/paramStore.js +148 -0
  133. package/dist/src/paramRuntime/paramStore.test.d.ts +2 -0
  134. package/dist/src/paramRuntime/paramStore.test.d.ts.map +1 -0
  135. package/dist/src/paramRuntime/paramUtils.d.ts +86 -0
  136. package/dist/src/paramRuntime/paramUtils.d.ts.map +1 -0
  137. package/dist/src/paramRuntime/paramUtils.js +272 -0
  138. package/dist/src/paramRuntime/selectionStore.d.ts +6 -0
  139. package/dist/src/paramRuntime/selectionStore.d.ts.map +1 -0
  140. package/dist/src/paramRuntime/selectionStore.js +13 -0
  141. package/dist/src/paramRuntime/types.d.ts +16 -0
  142. package/dist/src/paramRuntime/types.d.ts.map +1 -0
  143. package/dist/src/paramRuntime/types.js +25 -0
  144. package/dist/src/paramRuntime/viewParamRuntime.d.ts +164 -0
  145. package/dist/src/paramRuntime/viewParamRuntime.d.ts.map +1 -0
  146. package/dist/src/paramRuntime/viewParamRuntime.js +443 -0
  147. package/dist/src/scale/scale.d.ts +6 -1
  148. package/dist/src/scale/scale.d.ts.map +1 -1
  149. package/dist/src/scale/scale.js +83 -23
  150. package/dist/src/scales/axisResolution.d.ts.map +1 -1
  151. package/dist/src/scales/axisResolution.js +10 -0
  152. package/dist/src/scales/{scaleDomainAggregator.d.ts → domainPlanner.d.ts} +6 -3
  153. package/dist/src/scales/domainPlanner.d.ts.map +1 -0
  154. package/dist/src/scales/{scaleDomainAggregator.js → domainPlanner.js} +128 -10
  155. package/dist/src/scales/domainPlanner.test.d.ts +2 -0
  156. package/dist/src/scales/domainPlanner.test.d.ts.map +1 -0
  157. package/dist/src/scales/scaleInstanceManager.d.ts +6 -3
  158. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  159. package/dist/src/scales/scaleInstanceManager.js +17 -11
  160. package/dist/src/scales/scaleInteractionController.d.ts +6 -0
  161. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
  162. package/dist/src/scales/scaleInteractionController.js +41 -3
  163. package/dist/src/scales/scaleResolution.d.ts +20 -17
  164. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  165. package/dist/src/scales/scaleResolution.js +188 -71
  166. package/dist/src/scales/scaleResolution.test.d.ts.map +1 -1
  167. package/dist/src/selection/selection.d.ts +21 -0
  168. package/dist/src/selection/selection.d.ts.map +1 -1
  169. package/dist/src/selection/selection.js +83 -1
  170. package/dist/src/spec/channel.d.ts +52 -15
  171. package/dist/src/spec/coreSchemaRoot.d.ts +53 -0
  172. package/dist/src/spec/data.d.ts +4 -0
  173. package/dist/src/spec/parameter.d.ts +16 -11
  174. package/dist/src/spec/root.d.ts +1 -1
  175. package/dist/src/spec/testing.d.ts +12 -0
  176. package/dist/src/spec/testing.d.ts.map +1 -0
  177. package/dist/src/spec/testing.js +20 -0
  178. package/dist/src/spec/view.d.ts +157 -41
  179. package/dist/src/styles/genome-spy.css +3 -31
  180. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  181. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  182. package/dist/src/styles/genome-spy.css.js +0 -29
  183. package/dist/src/tooltip/dataTooltipHandler.d.ts +1 -1
  184. package/dist/src/tooltip/dataTooltipHandler.js +23 -32
  185. package/dist/src/tooltip/dataTooltipHandler.test.d.ts +2 -0
  186. package/dist/src/tooltip/dataTooltipHandler.test.d.ts.map +1 -0
  187. package/dist/src/tooltip/flattenDatumRows.d.ts +13 -0
  188. package/dist/src/tooltip/flattenDatumRows.d.ts.map +1 -0
  189. package/dist/src/tooltip/flattenDatumRows.js +47 -0
  190. package/dist/src/tooltip/flattenDatumRows.test.d.ts +2 -0
  191. package/dist/src/tooltip/flattenDatumRows.test.d.ts.map +1 -0
  192. package/dist/src/tooltip/refseqGeneTooltipHandler.d.ts +1 -1
  193. package/dist/src/tooltip/refseqGeneTooltipHandler.js +7 -1
  194. package/dist/src/tooltip/tooltipContext.d.ts +13 -0
  195. package/dist/src/tooltip/tooltipContext.d.ts.map +1 -0
  196. package/dist/src/tooltip/tooltipContext.js +543 -0
  197. package/dist/src/tooltip/tooltipContext.test.d.ts +2 -0
  198. package/dist/src/tooltip/tooltipContext.test.d.ts.map +1 -0
  199. package/dist/src/tooltip/tooltipHandler.d.ts +40 -1
  200. package/dist/src/tooltip/tooltipHandler.d.ts.map +1 -1
  201. package/dist/src/tooltip/tooltipHandler.ts +62 -1
  202. package/dist/src/types/encoder.d.ts +38 -3
  203. package/dist/src/types/rendering.d.ts +4 -3
  204. package/dist/src/types/viewContext.d.ts +0 -14
  205. package/dist/src/utils/inputBinding.d.ts +10 -2
  206. package/dist/src/utils/inputBinding.d.ts.map +1 -1
  207. package/dist/src/utils/inputBinding.js +12 -3
  208. package/dist/src/utils/throttle.d.ts +4 -1
  209. package/dist/src/utils/throttle.d.ts.map +1 -1
  210. package/dist/src/utils/throttle.js +54 -23
  211. package/dist/src/utils/throttle.test.d.ts +2 -0
  212. package/dist/src/utils/throttle.test.d.ts.map +1 -0
  213. package/dist/src/utils/transition.d.ts +21 -0
  214. package/dist/src/utils/transition.d.ts.map +1 -1
  215. package/dist/src/utils/transition.js +28 -0
  216. package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
  217. package/dist/src/utils/ui/tooltip.js +7 -1
  218. package/dist/src/utils/ui/tooltip.test.d.ts +2 -0
  219. package/dist/src/utils/ui/tooltip.test.d.ts.map +1 -0
  220. package/dist/src/view/axisGridView.d.ts.map +1 -1
  221. package/dist/src/view/axisGridView.js +22 -5
  222. package/dist/src/view/axisView.d.ts.map +1 -1
  223. package/dist/src/view/axisView.js +20 -5
  224. package/dist/src/view/concatView.js +3 -3
  225. package/dist/src/view/containerMutationHelper.js +1 -1
  226. package/dist/src/view/containerView.d.ts +9 -5
  227. package/dist/src/view/containerView.d.ts.map +1 -1
  228. package/dist/src/view/containerView.js +34 -9
  229. package/dist/src/view/dataReadiness.d.ts +46 -0
  230. package/dist/src/view/dataReadiness.d.ts.map +1 -0
  231. package/dist/src/view/dataReadiness.js +267 -0
  232. package/dist/src/view/dataReadiness.test.d.ts +2 -0
  233. package/dist/src/view/dataReadiness.test.d.ts.map +1 -0
  234. package/dist/src/view/facetView.d.ts.map +1 -1
  235. package/dist/src/view/facetView.js +7 -5
  236. package/dist/src/view/flowBuilder.d.ts.map +1 -1
  237. package/dist/src/view/flowBuilder.js +17 -4
  238. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  239. package/dist/src/view/gridView/gridChild.js +16 -3
  240. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  241. package/dist/src/view/gridView/gridView.js +119 -2
  242. package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
  243. package/dist/src/view/gridView/scrollbar.js +3 -0
  244. package/dist/src/view/gridView/selectionRect.d.ts +6 -10
  245. package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
  246. package/dist/src/view/gridView/selectionRect.js +22 -24
  247. package/dist/src/view/gridView/separatorView.d.ts +51 -0
  248. package/dist/src/view/gridView/separatorView.d.ts.map +1 -0
  249. package/dist/src/view/gridView/separatorView.js +275 -0
  250. package/dist/src/view/layerView.d.ts.map +1 -1
  251. package/dist/src/view/layerView.js +7 -5
  252. package/dist/src/view/layout/flexLayout.d.ts +0 -30
  253. package/dist/src/view/layout/flexLayout.d.ts.map +1 -1
  254. package/dist/src/view/layout/flexLayout.js +0 -86
  255. package/dist/src/view/multiscale.d.ts +35 -0
  256. package/dist/src/view/multiscale.d.ts.map +1 -0
  257. package/dist/src/view/multiscale.js +233 -0
  258. package/dist/src/view/multiscale.test.d.ts +2 -0
  259. package/dist/src/view/multiscale.test.d.ts.map +1 -0
  260. package/dist/src/view/testUtils.d.ts.map +1 -1
  261. package/dist/src/view/testUtils.js +6 -1
  262. package/dist/src/view/unitView.d.ts +8 -13
  263. package/dist/src/view/unitView.d.ts.map +1 -1
  264. package/dist/src/view/unitView.js +120 -45
  265. package/dist/src/view/view.d.ts +27 -18
  266. package/dist/src/view/view.d.ts.map +1 -1
  267. package/dist/src/view/view.js +298 -37
  268. package/dist/src/view/viewFactory.d.ts +0 -12
  269. package/dist/src/view/viewFactory.d.ts.map +1 -1
  270. package/dist/src/view/viewFactory.js +55 -25
  271. package/dist/src/view/viewParamRuntime.test.d.ts +2 -0
  272. package/dist/src/view/viewParamRuntime.test.d.ts.map +1 -0
  273. package/dist/src/view/viewSelectors.d.ts +148 -0
  274. package/dist/src/view/viewSelectors.d.ts.map +1 -0
  275. package/dist/src/view/viewSelectors.js +776 -0
  276. package/dist/src/view/viewSelectors.test.d.ts +2 -0
  277. package/dist/src/view/viewSelectors.test.d.ts.map +1 -0
  278. package/dist/src/view/viewUtils.d.ts +0 -8
  279. package/dist/src/view/viewUtils.d.ts.map +1 -1
  280. package/dist/src/view/viewUtils.js +1 -21
  281. package/package.json +4 -4
  282. package/dist/src/scales/scaleDomainAggregator.d.ts.map +0 -1
  283. package/dist/src/scales/scaleDomainAggregator.test.d.ts +0 -2
  284. package/dist/src/scales/scaleDomainAggregator.test.d.ts.map +0 -1
  285. package/dist/src/spec/sampleView.d.ts +0 -197
  286. package/dist/src/view/paramMediator.d.ts +0 -149
  287. package/dist/src/view/paramMediator.d.ts.map +0 -1
  288. package/dist/src/view/paramMediator.js +0 -478
  289. package/dist/src/view/paramMediator.test.d.ts +0 -2
  290. package/dist/src/view/paramMediator.test.d.ts.map +0 -1
@@ -13,7 +13,7 @@ import { configureDomain } from "../scale/scale.js";
13
13
 
14
14
  import ScaleInstanceManager from "./scaleInstanceManager.js";
15
15
  import { resolveScalePropsBase } from "./scalePropsResolver.js";
16
- import ScaleDomainAggregator from "./scaleDomainAggregator.js";
16
+ import DomainPlanner from "./domainPlanner.js";
17
17
  import ScaleInteractionController from "./scaleInteractionController.js";
18
18
  import {
19
19
  INDEX,
@@ -23,10 +23,10 @@ import {
23
23
  QUANTITATIVE,
24
24
  } from "./scaleResolutionConstants.js";
25
25
 
26
+ import { getAccessorDomainKey } from "../encoder/accessor.js";
26
27
  import { isSecondaryChannel } from "../encoder/encoder.js";
27
28
  import { NominalDomain } from "../utils/domainArray.js";
28
- import { asArray, shallowArrayEquals } from "../utils/arrayUtils.js";
29
- import { VISIT_SKIP } from "../view/view.js";
29
+ import { shallowArrayEquals } from "../utils/arrayUtils.js";
30
30
  import createIndexer from "../utils/indexer.js";
31
31
 
32
32
  // Register scaleLocus to Vega-Scale.
@@ -44,7 +44,7 @@ export { INDEX, LOCUS, NOMINAL, ORDINAL, QUANTITATIVE };
44
44
  * @prop {import("../view/unitView.js").default} view TODO: Get rid of the view reference
45
45
  * @prop {T} channel
46
46
  * @prop {import("../spec/channel.js").ChannelDefWithScale} channelDef
47
- * @prop {(channel: ChannelWithScale, type: import("../spec/channel.js").Type) => DomainArray} dataDomainSource
47
+ * @prop {boolean} contributesToDomain
48
48
  */
49
49
  /**
50
50
  * Resolves a shared scale for a channel by merging scale properties and domains
@@ -53,6 +53,16 @@ export { INDEX, LOCUS, NOMINAL, ORDINAL, QUANTITATIVE };
53
53
  * notifications, while delegating domain aggregation, scale instance setup, and
54
54
  * interaction logic to focused helpers.
55
55
  *
56
+ * Documentation overview of current concerns this class (and its helpers) deal with:
57
+ * - Resolution membership and rules (shared/independent/forced/excluded, visibility, registration).
58
+ * - Scale property aggregation (merge props, channel overrides, unique scale names).
59
+ * - Domain computation and caching (configured/data unions, defaults, indexer stability, subscriptions).
60
+ * - Scale instance lifecycle (create, reconfigure props, apply domains, notify changes).
61
+ * - Interaction and zoom (zoom/pan/reset coordination, snapshots, zoom extents).
62
+ * - Rendering integration (range textures, axis sizing/positioning).
63
+ * - Locus-specific conversions (complex intervals, genome extent bindings).
64
+ * - Diagnostics and edge cases (ordinal unknown, nice/zero/padding, log warnings).
65
+ *
56
66
  * @implements {ScaleResolutionApi}
57
67
  */
58
68
  export default class ScaleResolution {
@@ -76,6 +86,9 @@ export default class ScaleResolution {
76
86
  /** @type {Set<ScaleResolutionMember>} The involved views */
77
87
  #members = new Set();
78
88
 
89
+ /** @type {Set<ScaleResolutionMember>} */
90
+ #dataDomainMembers = new Set();
91
+
79
92
  /**
80
93
  * @type {Record<ScaleResolutionEventType, Set<ScaleResolutionListener>>}
81
94
  */
@@ -87,7 +100,7 @@ export default class ScaleResolution {
87
100
  /** @type {ScaleInstanceManager} */
88
101
  #scaleManager;
89
102
 
90
- /** @type {ScaleDomainAggregator} */
103
+ /** @type {DomainPlanner} */
91
104
  #domainAggregator;
92
105
 
93
106
  /** @type {ScaleInteractionController} */
@@ -109,15 +122,17 @@ export default class ScaleResolution {
109
122
  /** @type {string} An optional unique identifier for the scale */
110
123
  this.name = undefined;
111
124
 
112
- this.#domainAggregator = new ScaleDomainAggregator({
125
+ this.#domainAggregator = new DomainPlanner({
113
126
  getMembers: () => this.#getActiveMembers(),
127
+ getDataMembers: () =>
128
+ this.#getActiveMembers(this.#dataDomainMembers),
114
129
  getType: () => this.type,
115
130
  getLocusExtent: () => this.#getLocusExtent(),
116
131
  fromComplexInterval: this.fromComplexInterval.bind(this),
117
132
  });
118
133
 
119
134
  this.#scaleManager = new ScaleInstanceManager({
120
- getParamMediator: () => this.#firstMemberView.paramMediator,
135
+ getParamRuntime: () => this.#firstMemberView.paramRuntime,
121
136
  onRangeChange: () => this.#notifyListeners("range"),
122
137
  onDomainChange: () => this.#notifyListeners("domain"),
123
138
  getGenomeStore: () => this.#viewContext.genomeStore,
@@ -146,10 +161,13 @@ export default class ScaleResolution {
146
161
  return first.view;
147
162
  }
148
163
 
149
- #getActiveMembers() {
164
+ /**
165
+ * @param {Set<ScaleResolutionMember>} [members]
166
+ */
167
+ #getActiveMembers(members = this.#members) {
150
168
  /** @type {Set<ScaleResolutionMember>} */
151
169
  const active = new Set();
152
- for (const member of this.#members) {
170
+ for (const member of members) {
153
171
  const view = member.view;
154
172
  if (!view.isConfiguredVisible()) {
155
173
  continue;
@@ -289,6 +307,10 @@ export default class ScaleResolution {
289
307
  }
290
308
 
291
309
  this.#members.add(newMember);
310
+ if (newMember.contributesToDomain) {
311
+ this.#dataDomainMembers.add(newMember);
312
+ }
313
+ this.#domainAggregator.invalidateConfiguredDomain();
292
314
  }
293
315
 
294
316
  /**
@@ -299,10 +321,68 @@ export default class ScaleResolution {
299
321
  this.#addMember(member);
300
322
  return () => {
301
323
  const removed = this.#members.delete(member);
324
+ if (removed) {
325
+ this.#dataDomainMembers.delete(member);
326
+ this.#domainAggregator.invalidateConfiguredDomain();
327
+ }
302
328
  return removed && this.#members.size === 0;
303
329
  };
304
330
  }
305
331
 
332
+ dispose() {
333
+ this.#listeners.domain.clear();
334
+ this.#listeners.range.clear();
335
+ this.#scaleManager.dispose();
336
+ }
337
+
338
+ #hasRenderedMember() {
339
+ for (const member of this.#members) {
340
+ if (member.view.hasRendered()) {
341
+ return true;
342
+ }
343
+ }
344
+ return false;
345
+ }
346
+
347
+ /**
348
+ * @param {import("../data/collector.js").default} collector
349
+ * @param {Iterable<import("../types/encoder.js").ScaleAccessor>} accessors
350
+ * @returns {() => void}
351
+ */
352
+ registerCollectorSubscriptions(collector, accessors) {
353
+ /** @type {Set<string>} */
354
+ const domainKeys = new Set();
355
+
356
+ for (const accessor of accessors) {
357
+ if (accessor.channelDef.domainInert) {
358
+ continue;
359
+ }
360
+ domainKeys.add(getAccessorDomainKey(accessor, this.type));
361
+ }
362
+
363
+ if (domainKeys.size === 0) {
364
+ return () => undefined;
365
+ }
366
+
367
+ const listener = () => {
368
+ this.reconfigureDomain();
369
+ };
370
+
371
+ /** @type {(() => void)[]} */
372
+ const unregisters = [];
373
+ for (const domainKey of domainKeys) {
374
+ unregisters.push(
375
+ collector.subscribeDomainChanges(domainKey, listener)
376
+ );
377
+ }
378
+
379
+ return () => {
380
+ for (const unregister of unregisters) {
381
+ unregister();
382
+ }
383
+ };
384
+ }
385
+
306
386
  /**
307
387
  * Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
308
388
  */
@@ -432,36 +512,108 @@ export default class ScaleResolution {
432
512
  * or when scale properties are otherwise re-resolved from the view hierarchy.
433
513
  */
434
514
  reconfigure() {
435
- const props = this.#getScaleProps(true);
436
- this.#reconfigureWith(() => this.#scaleManager.reconfigureScale(props));
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);
437
524
  }
438
525
 
439
526
  /**
440
527
  * Reconfigures only the effective domain (configured + data-derived).
441
528
  *
442
529
  * Use this when data changes but the scale membership and properties are stable.
530
+ *
443
531
  */
444
532
  reconfigureDomain() {
445
- const props = this.#getScaleProps(true);
446
- this.#reconfigureWith(() => {
447
- configureDomain(this.#scaleManager.scale, props);
448
- });
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);
449
555
  }
450
556
 
451
557
  /**
452
- * @param {() => void} apply
558
+ * @param {boolean} extractDataDomain
559
+ * @param {boolean} [includeDomainConfig]
560
+ * @returns {{
561
+ * scale: ScaleWithProps,
562
+ * props: import("../spec/scale.js").Scale,
563
+ * previousDomain: any[],
564
+ * domainWasInitialized: boolean,
565
+ * domainConfig?: ReturnType<typeof configureDomain>,
566
+ * targetDomain?: any[] | null,
567
+ * } | undefined}
453
568
  */
454
- #reconfigureWith(apply) {
569
+ #computeScaleState(extractDataDomain, includeDomainConfig = false) {
455
570
  const scale = this.#scaleManager.scale;
456
571
 
457
572
  if (!scale || scale.type == "null") {
458
573
  return;
459
574
  }
460
575
 
461
- const domainWasInitialized = this.#isDomainInitialized();
462
- const previousDomain = scale.domain();
576
+ const state = {
577
+ scale,
578
+ props: this.#getScaleProps(extractDataDomain),
579
+ previousDomain: scale.domain(),
580
+ domainWasInitialized: this.#isDomainInitialized(),
581
+ };
582
+
583
+ if (includeDomainConfig) {
584
+ const domainConfig = configureDomain(scale, state.props);
585
+ return {
586
+ ...state,
587
+ domainConfig,
588
+ targetDomain: domainConfig.domain,
589
+ };
590
+ }
591
+
592
+ return state;
593
+ }
594
+
595
+ /**
596
+ * @param {{
597
+ * scale: ScaleWithProps,
598
+ * props: import("../spec/scale.js").Scale,
599
+ * }} inputs
600
+ * @param {(scale: ScaleWithProps, props: import("../spec/scale.js").Scale) => void} apply
601
+ */
602
+ #applyReconfigure(inputs, apply) {
603
+ this.#scaleManager.withDomainNotificationsSuppressed(() => {
604
+ apply(inputs.scale, inputs.props);
605
+ });
606
+ }
463
607
 
464
- this.#scaleManager.withDomainNotificationsSuppressed(apply);
608
+ /**
609
+ * @param {{
610
+ * scale: ScaleWithProps,
611
+ * previousDomain: any[],
612
+ * domainWasInitialized: boolean,
613
+ * }} inputs
614
+ */
615
+ #finalizeReconfigure(inputs) {
616
+ const { scale, previousDomain, domainWasInitialized } = inputs;
465
617
 
466
618
  if (
467
619
  this.#domainAggregator.captureInitialDomain(
@@ -475,13 +627,18 @@ export default class ScaleResolution {
475
627
  }
476
628
 
477
629
  const newDomain = scale.domain();
478
- if (!shallowArrayEquals(newDomain, previousDomain)) {
479
- if (this.isZoomable()) {
480
- // Don't mess with zoomed views, restore the previous domain
481
- this.#scaleManager.withDomainNotificationsSuppressed(() => {
482
- scale.domain(previousDomain);
483
- });
484
- } else if (this.#interactionController.isZoomingSupported()) {
630
+ const action = this.#interactionController.getDomainChangeAction(
631
+ previousDomain,
632
+ newDomain
633
+ );
634
+
635
+ if (action === "restore") {
636
+ // Don't mess with zoomed views, restore the previous domain
637
+ this.#scaleManager.withDomainNotificationsSuppressed(() => {
638
+ scale.domain(previousDomain);
639
+ });
640
+ } else if (action === "animate") {
641
+ if (this.#hasRenderedMember()) {
485
642
  // It can be zoomed, so lets make a smooth transition.
486
643
  // Restore the previous domain and zoom smoothly to the new domain.
487
644
  this.#scaleManager.withDomainNotificationsSuppressed(() => {
@@ -489,10 +646,12 @@ export default class ScaleResolution {
489
646
  });
490
647
  this.zoomTo(newDomain, 500); // TODO: Configurable duration
491
648
  } else {
492
- // Update immediately if the previous domain was the initial domain [0, 0]
493
- // Notifications were suppressed during reconfigure; notify explicitly.
494
649
  this.#notifyListeners("domain");
495
650
  }
651
+ } else if (action === "notify") {
652
+ // Update immediately if the previous domain was the initial domain [0, 0]
653
+ // Notifications were suppressed during reconfigure; notify explicitly.
654
+ this.#notifyListeners("domain");
496
655
  }
497
656
  }
498
657
 
@@ -688,45 +847,3 @@ export default class ScaleResolution {
688
847
  return /** @type {number[]} */ (interval);
689
848
  }
690
849
  }
691
-
692
- /**
693
- * Reconfigures scale domains for resolutions used by the given view(s).
694
- *
695
- * Use this for data-driven updates where only domains need refreshing.
696
- *
697
- * TODO: This should be made unnecessary. Collectors should trigger the reconfiguration
698
- * for those views that get their data from the collector.
699
- *
700
- * TODO: This may reconfigure channels that are not affected by the change.
701
- * Causes performance issues with domains that are extracted from data.
702
- *
703
- * @param {import("../view/view.js").default | import("../view/view.js").default[]} fromViews
704
- * @param {(view: import("../view/view.js").default) => boolean} [viewFilter]
705
- */
706
- export function reconfigureScaleDomains(fromViews, viewFilter) {
707
- /** @type {Set<ScaleResolution>} */
708
- const uniqueResolutions = new Set();
709
-
710
- /** @param {import("../view/view.js").default} view */
711
- function collectResolutions(view) {
712
- for (const resolution of Object.values(view.resolutions.scale)) {
713
- uniqueResolutions.add(resolution);
714
- }
715
- }
716
-
717
- /** @type {import("../view/view.js").VisitorCallback} */
718
- function collectVisibleResolutions(view) {
719
- if (viewFilter && !viewFilter(view)) {
720
- return VISIT_SKIP;
721
- }
722
- if (view.options.contributesToScaleDomain) {
723
- collectResolutions(view);
724
- }
725
- }
726
-
727
- for (const fromView of asArray(fromViews)) {
728
- fromView.visit(collectVisibleResolutions);
729
- }
730
-
731
- uniqueResolutions.forEach((resolution) => resolution.reconfigureDomain());
732
- }
@@ -1 +1 @@
1
- {"version":3,"file":"scaleResolution.test.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolution.test.js"],"names":[],"mappings":"sBAYa,OAAO,oBAAoB,EAAE,OAAO"}
1
+ {"version":3,"file":"scaleResolution.test.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolution.test.js"],"names":[],"mappings":"sBAWa,OAAO,oBAAoB,EAAE,OAAO"}
@@ -8,6 +8,27 @@ export function createSinglePointSelection(datum: import("../data/flowNode.js").
8
8
  * @returns {import("../types/selectionTypes.js").MultiPointSelection}
9
9
  */
10
10
  export function createMultiPointSelection(data?: import("../data/flowNode.js").Datum[]): import("../types/selectionTypes.js").MultiPointSelection;
11
+ /**
12
+ * Returns key tuples for a point selection.
13
+ *
14
+ * @param {import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection} selection
15
+ * @param {string[]} keyFields
16
+ * @returns {import("../spec/channel.js").Scalar[][] | undefined}
17
+ */
18
+ export function getPointSelectionKeyTuples(selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection, keyFields: string[]): import("../spec/channel.js").Scalar[][] | undefined;
19
+ /**
20
+ * Resolves key tuples to a point selection value object.
21
+ *
22
+ * @param {"single" | "multi"} type
23
+ * @param {string[]} keyFields
24
+ * @param {import("../spec/channel.js").Scalar[][]} keyTuples
25
+ * @param {(keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]) => import("../data/flowNode.js").Datum | undefined} resolveDatum
26
+ * @returns {{ selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection, unresolved: import("../spec/channel.js").Scalar[][] } | undefined}
27
+ */
28
+ export function resolvePointSelectionFromKeyTuples(type: "single" | "multi", keyFields: string[], keyTuples: import("../spec/channel.js").Scalar[][], resolveDatum: (keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]) => import("../data/flowNode.js").Datum | undefined): {
29
+ selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection;
30
+ unresolved: import("../spec/channel.js").Scalar[][];
31
+ } | undefined;
11
32
  /**
12
33
  *
13
34
  * @param {import("../spec/channel.js").ChannelWithScale[]} channels
@@ -1 +1 @@
1
- {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/selection/selection.js"],"names":[],"mappings":"AAOA;;;GAGG;AACH,kDAHW,OAAO,qBAAqB,EAAE,KAAK,GACjC,OAAO,4BAA4B,EAAE,oBAAoB,CAQrE;AAED;;;GAGG;AACH,iDAHW,OAAO,qBAAqB,EAAE,KAAK,EAAE,GACnC,OAAO,4BAA4B,EAAE,mBAAmB,CAQpE;AAED;;;;GAIG;AACH,kDAHW,OAAO,oBAAoB,EAAE,gBAAgB,EAAE,GAC7C,OAAO,4BAA4B,EAAE,iBAAiB,CASlE;AAED;;;;;;;GAOG;AACH,qDAJW,OAAO,4BAA4B,EAAE,mBAAmB,2BACxD,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,QAAQ,GAAG,QAAQ,EAAE,QAAQ,CAAC,OAAO,qBAAqB,EAAE,KAAK,CAAC,CAAC,CAAC,GACzF,OAAO,4BAA4B,EAAE,mBAAmB,CA2BpE;AAED;;;;;GAKG;AACH,oDAHW,OAAO,sBAAsB,EAAE,qBAAqB,aACpD,OAAO,4BAA4B,EAAE,SAAS,UAgExD;AAED;;;GAGG;AACH,+CAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,iBAAiB,CAI/E;AAED;;;GAGG;AACH,kDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,oBAAoB,CAIlF;AAED;;;GAGG;AACH,iDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,mBAAmB,CAIjF;AAED;;;GAGG;AACH,gDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,kBAAkB,CAIhF;AAED;;;GAGG;AACH,gDAHW,OAAO,sBAAsB,EAAE,qBAAqB,GAClD,OAAO,sBAAsB,EAAE,eAAe,CA4B1D;AAED;;;GAGG;AACH,+CAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,oBAAoB,CAIzE;AAED;;;;GAIG;AACH,kDAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,uBAAuB,CAI5E;AAED;;GAEG;AACH,qDAFW,OAAO,4BAA4B,EAAE,iBAAiB,WAMhE;AAED;;;;;GAKG;AACH,kDAHW,iBAAiB,SACjB,aAAa,WAUvB;AAED;;;GAGG;AACH,yCAHW,OAAO,sBAAsB,EAAE,eAAe,CAAC,IAAI,CAAC,GAClD,OAAO,sBAAsB,EAAE,WAAW,CAsBtD;gCAvCY,OAAO,4BAA4B,EAAE,iBAAiB;4BACtD,OAAO,CAAC,MAAM,CAAC,MAAM,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/selection/selection.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,kDAHW,OAAO,qBAAqB,EAAE,KAAK,GACjC,OAAO,4BAA4B,EAAE,oBAAoB,CAQrE;AAED;;;GAGG;AACH,iDAHW,OAAO,qBAAqB,EAAE,KAAK,EAAE,GACnC,OAAO,4BAA4B,EAAE,mBAAmB,CAQpE;AAED;;;;;;GAMG;AACH,sDAJW,OAAO,4BAA4B,EAAE,oBAAoB,GAAG,OAAO,4BAA4B,EAAE,mBAAmB,aACpH,MAAM,EAAE,GACN,OAAO,oBAAoB,EAAE,MAAM,EAAE,EAAE,GAAG,SAAS,CA2B/D;AAED;;;;;;;;GAQG;AACH,yDANW,QAAQ,GAAG,OAAO,aAClB,MAAM,EAAE,aACR,OAAO,oBAAoB,EAAE,MAAM,EAAE,EAAE,gBACvC,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,oBAAoB,EAAE,MAAM,EAAE,KAAK,OAAO,qBAAqB,EAAE,KAAK,GAAG,SAAS,GACvH;IAAE,SAAS,EAAE,OAAO,4BAA4B,EAAE,oBAAoB,GAAG,OAAO,4BAA4B,EAAE,mBAAmB,CAAC;IAAC,UAAU,EAAE,OAAO,oBAAoB,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,GAAG,SAAS,CAsChN;AAED;;;;GAIG;AACH,kDAHW,OAAO,oBAAoB,EAAE,gBAAgB,EAAE,GAC7C,OAAO,4BAA4B,EAAE,iBAAiB,CASlE;AAED;;;;;;;GAOG;AACH,qDAJW,OAAO,4BAA4B,EAAE,mBAAmB,2BACxD,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,QAAQ,GAAG,QAAQ,EAAE,QAAQ,CAAC,OAAO,qBAAqB,EAAE,KAAK,CAAC,CAAC,CAAC,GACzF,OAAO,4BAA4B,EAAE,mBAAmB,CA2BpE;AAED;;;;;GAKG;AACH,oDAHW,OAAO,sBAAsB,EAAE,qBAAqB,aACpD,OAAO,4BAA4B,EAAE,SAAS,UAgExD;AAED;;;GAGG;AACH,+CAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,iBAAiB,CAI/E;AAED;;;GAGG;AACH,kDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,oBAAoB,CAIlF;AAED;;;GAGG;AACH,iDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,mBAAmB,CAIjF;AAED;;;GAGG;AACH,gDAHW,OAAO,4BAA4B,EAAE,SAAS,GAC5C,SAAS,IAAI,OAAO,4BAA4B,EAAE,kBAAkB,CAIhF;AAED;;;GAGG;AACH,gDAHW,OAAO,sBAAsB,EAAE,qBAAqB,GAClD,OAAO,sBAAsB,EAAE,eAAe,CA4B1D;AAED;;;GAGG;AACH,+CAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,oBAAoB,CAIzE;AAED;;;;GAIG;AACH,kDAHW,OAAO,sBAAsB,EAAE,eAAe,GAC5C,MAAM,IAAI,OAAO,sBAAsB,EAAE,uBAAuB,CAI5E;AAED;;GAEG;AACH,qDAFW,OAAO,4BAA4B,EAAE,iBAAiB,WAMhE;AAED;;;;;GAKG;AACH,kDAHW,iBAAiB,SACjB,aAAa,WAUvB;AAED;;;GAGG;AACH,yCAHW,OAAO,sBAAsB,EAAE,eAAe,CAAC,IAAI,CAAC,GAClD,OAAO,sBAAsB,EAAE,WAAW,CAsBtD;gCAvCY,OAAO,4BAA4B,EAAE,iBAAiB;4BACtD,OAAO,CAAC,MAAM,CAAC,MAAM,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC"}
@@ -3,7 +3,8 @@ import {
3
3
  getSecondaryChannel,
4
4
  isPrimaryPositionalChannel,
5
5
  } from "../encoder/encoder.js";
6
- import { validateParameterName } from "../view/paramMediator.js";
6
+ import { validateParameterName } from "../paramRuntime/paramUtils.js";
7
+ import { field } from "../utils/field.js";
7
8
 
8
9
  /**
9
10
  * @param {import("../data/flowNode.js").Datum} datum
@@ -29,6 +30,87 @@ export function createMultiPointSelection(data) {
29
30
  };
30
31
  }
31
32
 
33
+ /**
34
+ * Returns key tuples for a point selection.
35
+ *
36
+ * @param {import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection} selection
37
+ * @param {string[]} keyFields
38
+ * @returns {import("../spec/channel.js").Scalar[][] | undefined}
39
+ */
40
+ export function getPointSelectionKeyTuples(selection, keyFields) {
41
+ if (!keyFields || keyFields.length === 0) {
42
+ return;
43
+ }
44
+
45
+ const accessors = keyFields.map((fieldName) => field(fieldName));
46
+ const toTuple = (
47
+ /** @type {import("../data/flowNode.js").Datum} */ datum
48
+ ) => accessors.map((accessor) => accessor(datum));
49
+
50
+ if (isSinglePointSelection(selection)) {
51
+ if (!selection.datum) {
52
+ return [];
53
+ }
54
+
55
+ return [toTuple(selection.datum)];
56
+ }
57
+
58
+ if (isMultiPointSelection(selection)) {
59
+ return [...selection.data.values()].map(toTuple);
60
+ }
61
+
62
+ throw new Error(
63
+ `Expected a point selection, got: ${JSON.stringify(selection)}`
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Resolves key tuples to a point selection value object.
69
+ *
70
+ * @param {"single" | "multi"} type
71
+ * @param {string[]} keyFields
72
+ * @param {import("../spec/channel.js").Scalar[][]} keyTuples
73
+ * @param {(keyFields: string[], keyTuple: import("../spec/channel.js").Scalar[]) => import("../data/flowNode.js").Datum | undefined} resolveDatum
74
+ * @returns {{ selection: import("../types/selectionTypes.js").SinglePointSelection | import("../types/selectionTypes.js").MultiPointSelection, unresolved: import("../spec/channel.js").Scalar[][] } | undefined}
75
+ */
76
+ export function resolvePointSelectionFromKeyTuples(
77
+ type,
78
+ keyFields,
79
+ keyTuples,
80
+ resolveDatum
81
+ ) {
82
+ if (!keyFields || keyFields.length === 0) {
83
+ return;
84
+ }
85
+
86
+ if (type === "single" && keyTuples.length > 1) {
87
+ throw new Error(
88
+ "Single point selections expect at most one key tuple."
89
+ );
90
+ }
91
+
92
+ /** @type {import("../data/flowNode.js").Datum[]} */
93
+ const datums = [];
94
+ /** @type {import("../spec/channel.js").Scalar[][]} */
95
+ const unresolved = [];
96
+
97
+ for (const tuple of keyTuples) {
98
+ const datum = resolveDatum(keyFields, tuple);
99
+ if (datum) {
100
+ datums.push(datum);
101
+ } else {
102
+ unresolved.push(tuple);
103
+ }
104
+ }
105
+
106
+ const selection =
107
+ type === "single"
108
+ ? createSinglePointSelection(datums[0] ?? null)
109
+ : createMultiPointSelection(datums);
110
+
111
+ return { selection, unresolved };
112
+ }
113
+
32
114
  /**
33
115
  *
34
116
  * @param {import("../spec/channel.js").ChannelWithScale[]} channels
@@ -46,6 +46,7 @@ export type ChannelWithoutScale =
46
46
  | "uniqueId"
47
47
  | "search"
48
48
  | "text"
49
+ | "key"
49
50
  | "facetIndex"
50
51
  | "semanticScore"
51
52
  | "uniqueId"
@@ -121,9 +122,13 @@ export type TypedFieldDef<T extends Type = Type> = FieldDefBase &
121
122
  TitleMixins &
122
123
  TypeMixins<T>;
123
124
 
124
- export type ScaleFieldDef<T extends Type> = TypedFieldDef<T> & ScaleMixins;
125
+ export type ScaleFieldDef<T extends Type> = TypedFieldDef<T> &
126
+ ScaleMixins &
127
+ DomainContributionMixins;
125
128
 
126
129
  export type FieldDefWithoutScale = FieldDefBase & TitleMixins;
130
+ export type KeyDef = FieldDefWithoutScale | FieldDefWithoutScale[];
131
+ export type SearchDef = FieldDefWithoutScale | FieldDefWithoutScale[];
127
132
 
128
133
  export interface ScaleMixins {
129
134
  /**
@@ -145,15 +150,18 @@ export interface ScaleMixins {
145
150
  * @internal
146
151
  */
147
152
  resolutionChannel?: ChannelWithScale;
153
+ }
148
154
 
155
+ export interface DomainContributionMixins {
149
156
  /**
150
- * Whether the field or evaluated expr should be included in the scale's domain.
157
+ * Whether the field or evaluated expr should be excluded from the scale's domain.
158
+ * Prefer the view-level `domainInert` when an entire subtree should be excluded.
151
159
  *
152
- * **Default value:** `true`
160
+ * **Default value:** `false`
153
161
  *
154
162
  * @internal
155
163
  */
156
- contributesToScaleDomain?: boolean;
164
+ domainInert?: boolean;
157
165
  }
158
166
 
159
167
  export interface ValueDefBase<V extends Value = Scalar> {
@@ -190,6 +198,7 @@ export type MarkPropFieldDef<T extends Type = Type> = ScaleFieldDef<T> &
190
198
  export type MarkPropExprDef<T extends Type = Type> = ExprDef &
191
199
  TypeMixins<T> &
192
200
  ScaleMixins &
201
+ DomainContributionMixins &
193
202
  TitleMixins;
194
203
 
195
204
  export type MarkPropDatumDef<T extends Type> = LegendMixins &
@@ -279,16 +288,18 @@ export type MarkPropFieldOrDatumOrExprDef<T extends Type = Type> =
279
288
 
280
289
  export type MarkPropDef<V extends Value, T extends Type = Type> =
281
290
  | FieldOrDatumDefWithCondition<MarkPropFieldDef<T>, V>
282
- | FieldOrDatumDefWithCondition<DatumDef, V>
291
+ | FieldOrDatumDefWithCondition<ScaleDatumDef, V>
283
292
  | ValueDefWithCondition<V>;
284
293
 
285
294
  export type ColorDef = MarkPropDef<string | null>;
286
295
 
287
- export type SecondaryFieldDef = FieldDefBase & TitleMixins;
296
+ export type SecondaryFieldDef = FieldDefBase &
297
+ TitleMixins &
298
+ DomainContributionMixins;
288
299
 
289
300
  export type NumericValueDef = ValueDef<number>;
290
301
 
291
- export type ScaleDatumDef = ScaleMixins & DatumDef;
302
+ export type ScaleDatumDef = ScaleMixins & DatumDef & DomainContributionMixins;
292
303
 
293
304
  export type PositionDatumDefBase = ScaleDatumDef & TypeMixins<Type>;
294
305
 
@@ -299,7 +310,8 @@ export type PositionDatumDef = PositionDatumDefBase & PositionMixins;
299
310
  export type PositionExprDef = ExprDef &
300
311
  PositionMixins &
301
312
  BandMixins &
302
- TypeMixins<Type>;
313
+ TypeMixins<Type> &
314
+ DomainContributionMixins;
303
315
 
304
316
  export type PositionValueDef = NumericValueDef;
305
317
 
@@ -344,7 +356,8 @@ export interface ChromPosDefBase extends BandMixins {
344
356
 
345
357
  export type SecondaryChromPosDef = ChromPosDefBase &
346
358
  TitleMixins &
347
- PositionMixins;
359
+ PositionMixins &
360
+ DomainContributionMixins;
348
361
 
349
362
  export type ChromPosDef = SecondaryChromPosDef &
350
363
  TypeMixins<"locus"> &
@@ -360,8 +373,8 @@ export type PositionDef =
360
373
  export type Position2Def =
361
374
  | (SecondaryFieldDef & BandMixins)
362
375
  | SecondaryChromPosDef
363
- | (DatumDef & BandMixins)
364
- | (ExprDef & BandMixins)
376
+ | (ScaleDatumDef & BandMixins)
377
+ | (ExprDef & BandMixins & DomainContributionMixins)
365
378
  | PositionValueDef;
366
379
 
367
380
  export type NumericMarkPropDef = MarkPropDef<number>;
@@ -372,10 +385,15 @@ export interface StringFieldDef extends FieldDefWithoutScale, FormatMixins {}
372
385
 
373
386
  export type TextDef = StringFieldDef | StringDatumDef | ExprDef; // TODO: Conditions
374
387
 
375
- export type ChannelDef = Encoding[keyof Encoding];
388
+ export type ChannelDef = Exclude<
389
+ Encoding[keyof Encoding],
390
+ FieldDefWithoutScale[]
391
+ >;
376
392
 
377
393
  // TODO: Does this make sense?
378
- export type ChannelDefWithScale = ScaleMixins & TypeMixins<Type>;
394
+ export type ChannelDefWithScale = ScaleMixins &
395
+ TypeMixins<Type> &
396
+ DomainContributionMixins;
379
397
 
380
398
  export interface XIndexDef {
381
399
  /**
@@ -497,14 +515,33 @@ export interface Encoding {
497
515
  */
498
516
  sample?: FieldDefWithoutScale;
499
517
 
518
+ /**
519
+ * One or more data fields that uniquely identify data objects for stable
520
+ * point selections and bookmarking across sessions. Unlike `uniqueId` (an
521
+ * implicit surrogate key), key fields must be stable in the source data.
522
+ *
523
+ * Use a single field definition for simple keys, or an array of field
524
+ * definitions for composite keys. For composite keys, field order is
525
+ * significant.
526
+ */
527
+ key?: KeyDef;
528
+
500
529
  /**
501
530
  * For internal use
531
+ * @internal
502
532
  */
503
533
  // TODO: proper type
504
534
  uniqueId?: FieldDefWithoutScale;
505
535
 
506
- // TODO: proper type
507
- search?: FieldDefWithoutScale;
536
+ /**
537
+ * One or more fields used by the App's location/search input to match
538
+ * data objects in this view.
539
+ *
540
+ * Use a single field definition for simple search, or an array for
541
+ * matching against multiple fields. A datum matches when any configured
542
+ * search field matches the entered term.
543
+ */
544
+ search?: SearchDef;
508
545
 
509
546
  /**
510
547
  * For internal use