@genome-spy/core 0.65.0 → 0.67.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 (249) hide show
  1. package/dist/bundle/browser-BRemItdO.js +138 -0
  2. package/dist/bundle/{index-CD7FLu9x.js → index-BatuyGAI.js} +23 -21
  3. package/dist/bundle/{index-C0llXMqm.js → index-ByuE8dvu.js} +140 -88
  4. package/dist/bundle/index-Cq3QFUxX.js +1781 -0
  5. package/dist/bundle/index-D28m8tSW.js +1607 -0
  6. package/dist/bundle/index-DbJ0oeYM.js +631 -0
  7. package/dist/bundle/index.es.js +15821 -14601
  8. package/dist/bundle/index.js +214 -212
  9. package/dist/bundle/{inflate-DRgHi_KK.js → inflate-GtwLkvSP.js} +222 -224
  10. package/dist/bundle/unzip-NywezaRR.js +1492 -0
  11. package/dist/schema.json +13 -3
  12. package/dist/src/config/scaleDefaults.d.ts +8 -0
  13. package/dist/src/config/scaleDefaults.d.ts.map +1 -0
  14. package/dist/src/config/scaleDefaults.js +45 -0
  15. package/dist/src/data/flowHandle.d.ts +2 -0
  16. package/dist/src/data/flowHandle.d.ts.map +1 -1
  17. package/dist/src/data/flowHandle.js +1 -0
  18. package/dist/src/data/flowInit.d.ts +12 -4
  19. package/dist/src/data/flowInit.d.ts.map +1 -1
  20. package/dist/src/data/flowInit.js +115 -16
  21. package/dist/src/data/sources/lazy/axisTickSource.js +1 -1
  22. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +1 -1
  23. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  24. package/dist/src/data/sources/lazy/singleAxisLazySource.js +10 -3
  25. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  26. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +5 -1
  27. package/dist/src/data/transforms/filterScoredLabels.d.ts +1 -1
  28. package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
  29. package/dist/src/data/transforms/filterScoredLabels.js +1 -1
  30. package/dist/src/data/transforms/linearizeGenomicCoordinate.d.ts.map +1 -1
  31. package/dist/src/data/transforms/linearizeGenomicCoordinate.js +2 -1
  32. package/dist/src/encoder/encoder.d.ts +1 -1
  33. package/dist/src/encoder/encoder.d.ts.map +1 -1
  34. package/dist/src/encoder/encoder.js +1 -1
  35. package/dist/src/genome/scaleLocus.d.ts +39 -0
  36. package/dist/src/genome/scaleLocus.d.ts.map +1 -1
  37. package/dist/src/genome/scaleLocus.js +76 -0
  38. package/dist/src/genomeSpy/canvasExport.d.ts +19 -0
  39. package/dist/src/genomeSpy/canvasExport.d.ts.map +1 -0
  40. package/dist/src/genomeSpy/canvasExport.js +66 -0
  41. package/dist/src/genomeSpy/containerUi.d.ts +17 -0
  42. package/dist/src/genomeSpy/containerUi.d.ts.map +1 -0
  43. package/dist/src/genomeSpy/containerUi.js +78 -0
  44. package/dist/src/genomeSpy/eventListenerRegistry.d.ts +19 -0
  45. package/dist/src/genomeSpy/eventListenerRegistry.d.ts.map +1 -0
  46. package/dist/src/genomeSpy/eventListenerRegistry.js +38 -0
  47. package/dist/src/genomeSpy/inputBindingManager.d.ts +14 -0
  48. package/dist/src/genomeSpy/inputBindingManager.d.ts.map +1 -0
  49. package/dist/src/genomeSpy/inputBindingManager.js +63 -0
  50. package/dist/src/genomeSpy/interactionController.d.ts +40 -0
  51. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -0
  52. package/dist/src/genomeSpy/interactionController.js +371 -0
  53. package/dist/src/genomeSpy/keyboardListenerManager.d.ts +10 -0
  54. package/dist/src/genomeSpy/keyboardListenerManager.d.ts.map +1 -0
  55. package/dist/src/genomeSpy/keyboardListenerManager.js +31 -0
  56. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts +15 -0
  57. package/dist/src/genomeSpy/loadingIndicatorManager.d.ts.map +1 -0
  58. package/dist/src/genomeSpy/loadingIndicatorManager.js +92 -0
  59. package/dist/src/genomeSpy/renderCoordinator.d.ts +22 -0
  60. package/dist/src/genomeSpy/renderCoordinator.d.ts.map +1 -0
  61. package/dist/src/genomeSpy/renderCoordinator.js +118 -0
  62. package/dist/src/genomeSpy/viewContextFactory.d.ts +18 -0
  63. package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -0
  64. package/dist/src/genomeSpy/viewContextFactory.js +79 -0
  65. package/dist/src/genomeSpy/viewDataInit.d.ts +22 -0
  66. package/dist/src/genomeSpy/viewDataInit.d.ts.map +1 -0
  67. package/dist/src/genomeSpy/viewDataInit.js +160 -0
  68. package/dist/src/genomeSpy/viewDataInit.test.d.ts +2 -0
  69. package/dist/src/genomeSpy/viewDataInit.test.d.ts.map +1 -0
  70. package/dist/src/genomeSpy/viewHierarchyConfig.d.ts +14 -0
  71. package/dist/src/genomeSpy/viewHierarchyConfig.d.ts.map +1 -0
  72. package/dist/src/genomeSpy/viewHierarchyConfig.js +24 -0
  73. package/dist/src/genomeSpy/viewHighlight.d.ts +5 -0
  74. package/dist/src/genomeSpy/viewHighlight.d.ts.map +1 -0
  75. package/dist/src/genomeSpy/viewHighlight.js +30 -0
  76. package/dist/src/genomeSpy.d.ts +17 -71
  77. package/dist/src/genomeSpy.d.ts.map +1 -1
  78. package/dist/src/genomeSpy.js +197 -741
  79. package/dist/src/gl/dataToVertices.d.ts.map +1 -1
  80. package/dist/src/gl/dataToVertices.js +16 -4
  81. package/dist/src/gl/glslScaleGenerator.d.ts +1 -1
  82. package/dist/src/gl/webGLHelper.d.ts +2 -2
  83. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  84. package/dist/src/gl/webGLHelper.js +4 -4
  85. package/dist/src/index.d.ts.map +1 -1
  86. package/dist/src/index.js +2 -12
  87. package/dist/src/marks/mark.d.ts.map +1 -1
  88. package/dist/src/marks/mark.js +4 -2
  89. package/dist/src/{view → scales}/axisResolution.d.ts +9 -16
  90. package/dist/src/scales/axisResolution.d.ts.map +1 -0
  91. package/dist/src/{view → scales}/axisResolution.js +29 -18
  92. package/dist/src/scales/axisResolution.test.d.ts.map +1 -0
  93. package/dist/src/scales/scaleDomainAggregator.d.ts +57 -0
  94. package/dist/src/scales/scaleDomainAggregator.d.ts.map +1 -0
  95. package/dist/src/scales/scaleDomainAggregator.js +167 -0
  96. package/dist/src/scales/scaleDomainAggregator.test.d.ts +2 -0
  97. package/dist/src/scales/scaleDomainAggregator.test.d.ts.map +1 -0
  98. package/dist/src/scales/scaleInstanceManager.d.ts +40 -0
  99. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -0
  100. package/dist/src/scales/scaleInstanceManager.js +317 -0
  101. package/dist/src/scales/scaleInstanceManager.test.d.ts +2 -0
  102. package/dist/src/scales/scaleInstanceManager.test.d.ts.map +1 -0
  103. package/dist/src/scales/scaleInteractionController.d.ts +73 -0
  104. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -0
  105. package/dist/src/scales/scaleInteractionController.js +336 -0
  106. package/dist/src/scales/scaleInteractionController.test.d.ts +2 -0
  107. package/dist/src/scales/scaleInteractionController.test.d.ts.map +1 -0
  108. package/dist/src/scales/scalePropsResolver.d.ts +23 -0
  109. package/dist/src/scales/scalePropsResolver.d.ts.map +1 -0
  110. package/dist/src/scales/scalePropsResolver.js +74 -0
  111. package/dist/src/{view → scales}/scaleResolution.d.ts +53 -35
  112. package/dist/src/scales/scaleResolution.d.ts.map +1 -0
  113. package/dist/src/scales/scaleResolution.js +732 -0
  114. package/dist/src/scales/scaleResolution.test.d.ts.map +1 -0
  115. package/dist/src/scales/scaleResolutionConstants.d.ts +6 -0
  116. package/dist/src/scales/scaleResolutionConstants.d.ts.map +1 -0
  117. package/dist/src/scales/scaleResolutionConstants.js +5 -0
  118. package/dist/src/scales/scaleRules.d.ts +16 -0
  119. package/dist/src/scales/scaleRules.d.ts.map +1 -0
  120. package/dist/src/scales/scaleRules.js +103 -0
  121. package/dist/src/scales/scaleRules.test.d.ts +2 -0
  122. package/dist/src/scales/scaleRules.test.d.ts.map +1 -0
  123. package/dist/src/spec/channel.d.ts +13 -18
  124. package/dist/src/spec/scale.d.ts +6 -0
  125. package/dist/src/types/embedApi.d.ts +5 -0
  126. package/dist/src/types/scaleResolutionApi.d.ts +1 -1
  127. package/dist/src/utils/domainArray.d.ts.map +1 -1
  128. package/dist/src/utils/domainArray.js +3 -0
  129. package/dist/src/utils/indexer.d.ts +3 -0
  130. package/dist/src/utils/indexer.d.ts.map +1 -1
  131. package/dist/src/utils/indexer.js +3 -0
  132. package/dist/src/view/concatView.d.ts +18 -0
  133. package/dist/src/view/concatView.d.ts.map +1 -1
  134. package/dist/src/view/concatView.js +73 -0
  135. package/dist/src/view/concatView.test.d.ts +2 -0
  136. package/dist/src/view/concatView.test.d.ts.map +1 -0
  137. package/dist/src/view/containerMutationHelper.d.ts +74 -0
  138. package/dist/src/view/containerMutationHelper.d.ts.map +1 -0
  139. package/dist/src/view/containerMutationHelper.js +118 -0
  140. package/dist/src/view/containerView.d.ts +0 -7
  141. package/dist/src/view/containerView.d.ts.map +1 -1
  142. package/dist/src/view/containerView.js +0 -10
  143. package/dist/src/view/facetView.d.ts.map +1 -1
  144. package/dist/src/view/facetView.js +0 -15
  145. package/dist/src/view/flowBuilder.d.ts +5 -3
  146. package/dist/src/view/flowBuilder.d.ts.map +1 -1
  147. package/dist/src/view/flowBuilder.js +69 -6
  148. package/dist/src/view/gridView/gridChild.d.ts +11 -0
  149. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  150. package/dist/src/view/gridView/gridChild.js +32 -6
  151. package/dist/src/view/gridView/gridView.d.ts +39 -1
  152. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  153. package/dist/src/view/gridView/gridView.js +106 -48
  154. package/dist/src/view/gridView/gridView.test.d.ts +2 -0
  155. package/dist/src/view/gridView/gridView.test.d.ts.map +1 -0
  156. package/dist/src/view/gridView/scrollbar.d.ts +39 -8
  157. package/dist/src/view/gridView/scrollbar.d.ts.map +1 -1
  158. package/dist/src/view/gridView/scrollbar.js +184 -69
  159. package/dist/src/view/layerView.d.ts +14 -0
  160. package/dist/src/view/layerView.d.ts.map +1 -1
  161. package/dist/src/view/layerView.js +66 -0
  162. package/dist/src/view/layerView.test.d.ts +2 -0
  163. package/dist/src/view/layerView.test.d.ts.map +1 -0
  164. package/dist/src/view/testUtils.d.ts.map +1 -1
  165. package/dist/src/view/testUtils.js +7 -1
  166. package/dist/src/view/unitView.d.ts.map +1 -1
  167. package/dist/src/view/unitView.js +41 -36
  168. package/dist/src/view/view.d.ts +18 -6
  169. package/dist/src/view/view.d.ts.map +1 -1
  170. package/dist/src/view/view.js +30 -4
  171. package/package.json +2 -2
  172. package/dist/bundle/browser-txUcLy2H.js +0 -123
  173. package/dist/bundle/index-BQpbYrv4.js +0 -1712
  174. package/dist/bundle/index-BhtHKLUo.js +0 -73
  175. package/dist/bundle/index-CCe8rnZz.js +0 -716
  176. package/dist/bundle/index-DhcU-Gk-.js +0 -1487
  177. package/dist/src/data/collector.test.js +0 -138
  178. package/dist/src/data/dataFlow.test.js +0 -38
  179. package/dist/src/data/flow.test.js +0 -81
  180. package/dist/src/data/flowInit.test.js +0 -413
  181. package/dist/src/data/flowNode.test.js +0 -50
  182. package/dist/src/data/flowOptimizer.test.js +0 -209
  183. package/dist/src/data/formats/fasta.test.js +0 -27
  184. package/dist/src/data/sources/inlineSource.test.js +0 -63
  185. package/dist/src/data/sources/sequenceSource.test.js +0 -81
  186. package/dist/src/data/transforms/aggregate.test.js +0 -134
  187. package/dist/src/data/transforms/clone.test.js +0 -11
  188. package/dist/src/data/transforms/coverage.test.js +0 -238
  189. package/dist/src/data/transforms/filter.test.js +0 -20
  190. package/dist/src/data/transforms/flatten.test.js +0 -96
  191. package/dist/src/data/transforms/flattenDelimited.test.js +0 -90
  192. package/dist/src/data/transforms/flattenSequence.test.js +0 -34
  193. package/dist/src/data/transforms/formula.test.js +0 -25
  194. package/dist/src/data/transforms/identifier.test.js +0 -92
  195. package/dist/src/data/transforms/pileup.test.js +0 -70
  196. package/dist/src/data/transforms/project.test.js +0 -32
  197. package/dist/src/data/transforms/regexExtract.test.js +0 -70
  198. package/dist/src/data/transforms/regexFold.test.js +0 -201
  199. package/dist/src/data/transforms/sample.test.js +0 -38
  200. package/dist/src/data/transforms/stack.test.js +0 -91
  201. package/dist/src/encoder/accessor.test.js +0 -162
  202. package/dist/src/encoder/encoder.test.js +0 -105
  203. package/dist/src/genome/genome.test.js +0 -268
  204. package/dist/src/genome/genomes.test.js +0 -8
  205. package/dist/src/genome/scaleIndex.test.js +0 -78
  206. package/dist/src/genome/scaleLocus.test.js +0 -4
  207. package/dist/src/scale/scale.test.js +0 -326
  208. package/dist/src/scale/ticks.test.js +0 -46
  209. package/dist/src/selection/selection.test.js +0 -14
  210. package/dist/src/utils/addBaseUrl.test.js +0 -30
  211. package/dist/src/utils/binnedIndex.test.js +0 -201
  212. package/dist/src/utils/cloner.test.js +0 -35
  213. package/dist/src/utils/coalesce.test.js +0 -16
  214. package/dist/src/utils/concatIterables.test.js +0 -8
  215. package/dist/src/utils/domainArray.test.js +0 -130
  216. package/dist/src/utils/indexer.test.js +0 -49
  217. package/dist/src/utils/interactionEvent.test.js +0 -35
  218. package/dist/src/utils/iterateNestedMaps.test.js +0 -33
  219. package/dist/src/utils/kWayMerge.test.js +0 -30
  220. package/dist/src/utils/mergeObjects.test.js +0 -42
  221. package/dist/src/utils/numberExtractor.test.js +0 -6
  222. package/dist/src/utils/propertyCacher.test.js +0 -89
  223. package/dist/src/utils/propertyCoalescer.test.js +0 -25
  224. package/dist/src/utils/radixSort.test.js +0 -51
  225. package/dist/src/utils/reservationMap.test.js +0 -20
  226. package/dist/src/utils/ringBuffer.test.js +0 -39
  227. package/dist/src/utils/topK.test.js +0 -54
  228. package/dist/src/utils/trees.test.js +0 -135
  229. package/dist/src/utils/url.test.js +0 -28
  230. package/dist/src/utils/variableTools.test.js +0 -13
  231. package/dist/src/view/axisResolution.d.ts.map +0 -1
  232. package/dist/src/view/axisResolution.test.d.ts.map +0 -1
  233. package/dist/src/view/axisResolution.test.js +0 -206
  234. package/dist/src/view/flowBuilder.test.js +0 -125
  235. package/dist/src/view/gridView/selectionRect.test.js +0 -87
  236. package/dist/src/view/layout/flexLayout.test.js +0 -323
  237. package/dist/src/view/layout/grid.test.js +0 -71
  238. package/dist/src/view/layout/rectangle.test.js +0 -192
  239. package/dist/src/view/paramMediator.test.js +0 -282
  240. package/dist/src/view/scaleResolution.d.ts.map +0 -1
  241. package/dist/src/view/scaleResolution.js +0 -1059
  242. package/dist/src/view/scaleResolution.test.d.ts.map +0 -1
  243. package/dist/src/view/scaleResolution.test.js +0 -645
  244. package/dist/src/view/view.test.js +0 -245
  245. package/dist/src/view/viewDispose.test.js +0 -110
  246. package/dist/src/view/viewFactory.test.js +0 -25
  247. package/dist/src/view/viewUtils.test.js +0 -87
  248. /package/dist/src/{view → scales}/axisResolution.test.d.ts +0 -0
  249. /package/dist/src/{view → scales}/scaleResolution.test.d.ts +0 -0
@@ -1,1059 +0,0 @@
1
- import scaleLocus, { isScaleLocus } from "../genome/scaleLocus.js";
2
- import scaleIndex from "../genome/scaleIndex.js";
3
- import scaleNull from "../utils/scaleNull.js";
4
-
5
- import {
6
- panLinear,
7
- zoomLinear,
8
- clampRange,
9
- span,
10
- panLog,
11
- zoomLog,
12
- panPow,
13
- zoomPow,
14
- isArray,
15
- isObject,
16
- isBoolean,
17
- } from "vega-util";
18
- import { scale as vegaScale, isDiscrete, isContinuous } from "vega-scale";
19
-
20
- import mergeObjects from "../utils/mergeObjects.js";
21
- import createScale, { configureScale } from "../scale/scale.js";
22
-
23
- import {
24
- isColorChannel,
25
- isDiscreteChannel,
26
- isPositionalChannel,
27
- isPrimaryPositionalChannel,
28
- isSecondaryChannel,
29
- } from "../encoder/encoder.js";
30
- import {
31
- isChromosomalLocus,
32
- isChromosomalLocusInterval,
33
- } from "../genome/genome.js";
34
- import createDomain, { NominalDomain } from "../utils/domainArray.js";
35
- import { easeCubicInOut } from "d3-ease";
36
- import { asArray, shallowArrayEquals } from "../utils/arrayUtils.js";
37
- import eerp from "../utils/eerp.js";
38
- import { isExprRef } from "./paramMediator.js";
39
-
40
- // Register scaleLocus to Vega-Scale.
41
- // Loci are discrete but the scale's domain can be adjusted in a continuous manner.
42
- vegaScale("index", scaleIndex, ["continuous"]);
43
- vegaScale("locus", scaleLocus, ["continuous"]);
44
- vegaScale("null", scaleNull, []);
45
-
46
- export const QUANTITATIVE = "quantitative";
47
- export const ORDINAL = "ordinal";
48
- export const NOMINAL = "nominal";
49
- export const LOCUS = "locus";
50
- export const INDEX = "index";
51
-
52
- /**
53
- * @template {ChannelWithScale}[T=ChannelWithScale]
54
- *
55
- * @typedef {object} ScaleResolutionMember
56
- * @prop {import("./unitView.js").default} view TODO: Get rid of the view reference
57
- * @prop {T} channel
58
- * @prop {import("../spec/channel.js").ChannelDefWithScale} channelDef
59
- * @prop {(channel: ChannelWithScale, type: import("../spec/channel.js").Type) => DomainArray} dataDomainSource
60
- */
61
- /**
62
- * Resolution takes care of merging domains and scales from multiple views.
63
- * This class also provides some utility methods for zooming the scales etc..
64
- *
65
- * TODO: This has grown a bit too fat. Consider splitting.
66
- *
67
- * @implements {ScaleResolutionApi}
68
- */
69
- export default class ScaleResolution {
70
- /**
71
- * @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionApi} ScaleResolutionApi
72
- * @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionEventType} ScaleResolutionEventType
73
- * @typedef {import("../spec/channel.js").Channel} Channel
74
- * @typedef {import("../spec/channel.js").ChannelWithScale} ChannelWithScale
75
- * @typedef {import("../spec/scale.js").NumericDomain} NumericDomain
76
- * @typedef {import("../spec/scale.js").ScalarDomain} ScalarDomain
77
- * @typedef {import("../spec/scale.js").ComplexDomain} ComplexDomain
78
- * @typedef {import("../spec/scale.js").ZoomParams} ZoomParams
79
- * @typedef {import("./unitView.js").default} UnitView
80
- * @typedef {import("../types/encoder.js").VegaScale} VegaScale
81
- * @typedef {import("../utils/domainArray.js").DomainArray} DomainArray
82
- * @typedef {import("../genome/genome.js").ChromosomalLocus} ChromosomalLocus
83
- * @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionListener} ScaleResolutionListener
84
- *
85
- * @typedef {VegaScale & { props: import("../spec/scale.js").Scale }} ScaleWithProps
86
- */
87
-
88
- /**
89
- * @type {Record<ScaleResolutionEventType, Set<ScaleResolutionListener>>}
90
- */
91
- #listeners = {
92
- domain: new Set(),
93
- range: new Set(),
94
- };
95
-
96
- /** @type {ScaleWithProps} */
97
- #scale;
98
-
99
- /**
100
- * The initial domain before any zooming.
101
- * @type {any[]}
102
- */
103
- #initialDomain;
104
-
105
- /**
106
- * Keeps track of the expression references in the range. If range is modified,
107
- * new expressions are created and the old ones must be invalidated.
108
- *
109
- * @type {Set<import("./paramMediator.js").ExprRefFunction>}
110
- */
111
- #rangeExprRefListeners = new Set();
112
-
113
- /**
114
- * @param {Channel} channel
115
- */
116
- constructor(channel) {
117
- this.channel = channel;
118
- /** @type {ScaleResolutionMember[]} The involved views */
119
- this.members = [];
120
- /** @type {import("../spec/channel.js").Type} Data type (quantitative, nominal, etc...) */
121
- this.type = null;
122
-
123
- /** @type {string} An optional unique identifier for the scale */
124
- this.name = undefined;
125
- }
126
-
127
- get #firstMemberView() {
128
- return this.members[0].view;
129
- }
130
-
131
- get #viewContext() {
132
- return this.#firstMemberView.context;
133
- }
134
-
135
- get zoomExtent() {
136
- return (
137
- (this.#scale &&
138
- isContinuous(this.#scale.type) &&
139
- this.#getZoomExtent()) ?? [-Infinity, Infinity]
140
- );
141
- }
142
-
143
- /**
144
- * Adds a listener that is called when the scale domain is changed,
145
- * e.g., zoomed. The call is synchronous and happens before the views
146
- * are rendered.
147
- *
148
- * @param {ScaleResolutionEventType} type
149
- * @param {ScaleResolutionListener} listener function
150
- */
151
- addEventListener(type, listener) {
152
- this.#listeners[type].add(listener);
153
- }
154
-
155
- /**
156
- * @param {ScaleResolutionEventType} type
157
- * @param {ScaleResolutionListener} listener function
158
- */
159
- removeEventListener(type, listener) {
160
- this.#listeners[type].delete(listener);
161
- }
162
-
163
- /**
164
- * @param {ScaleResolutionEventType} type
165
- */
166
- #notifyListeners(type) {
167
- for (const listener of this.#listeners[type].values()) {
168
- listener({
169
- type,
170
- scaleResolution: this,
171
- });
172
- }
173
- }
174
-
175
- /**
176
- * Add a view to this resolution.
177
- * N.B. This is expected to be called in depth-first order
178
- *
179
- * @param {ScaleResolutionMember} newMember
180
- */
181
- addMember(newMember) {
182
- const { channel, channelDef } = newMember;
183
-
184
- // A convenience hack for cases where the new member should adapt
185
- // the scale type to the existing one. For example: SelectionRect
186
- // TODO: Add test
187
- const adapt = channelDef.type == null && this.type;
188
-
189
- if (
190
- // @ts-expect-error "sample" is not really a channel with scale
191
- channel != "sample" &&
192
- !channelDef.type &&
193
- !isSecondaryChannel(channel) &&
194
- !adapt
195
- ) {
196
- throw new Error(
197
- `The "type" property must be defined in channel definition: "${channel}": ${JSON.stringify(
198
- channelDef
199
- )}. Must be one of: "quantitative", "ordinal", "nominal", "locus", "index"`
200
- );
201
- }
202
-
203
- // A hack for sample channel, which really doesn't have a scale but the
204
- // domain is needed when samples are not specified explicitly.
205
- // @ts-expect-error "sample" is not really a channel with scale
206
- const type = channel == "sample" ? "nominal" : channelDef.type;
207
- const name = channelDef?.scale?.name;
208
-
209
- if (name) {
210
- if (this.name !== undefined && name != this.name) {
211
- throw new Error(
212
- `Shared scales have conflicting names: "${name}" vs. "${this.name}"!`
213
- );
214
- }
215
- this.name = name;
216
- }
217
-
218
- if (!adapt) {
219
- if (!this.type) {
220
- this.type = type;
221
- } else if (type !== this.type && !isSecondaryChannel(channel)) {
222
- // TODO: Include a reference to the layer
223
- throw new Error(
224
- `Can not use shared scale for different data types: ${this.type} vs. ${type}. Use "resolve: independent" for channel ${this.channel}`
225
- );
226
- // Actually, point scale could be changed into band scale
227
- // TODO: Use the same merging logic as in: https://github.com/vega/vega-lite/blob/master/src/scale.ts
228
- }
229
- }
230
-
231
- this.members.push(newMember);
232
- }
233
-
234
- /**
235
- * @param {UnitView} view
236
- * @returns {boolean}
237
- */
238
- removeMembersByView(view) {
239
- const before = this.members.length;
240
- this.members = this.members.filter((member) => member.view !== view);
241
- return this.members.length !== before;
242
- }
243
-
244
- /**
245
- * Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
246
- */
247
- #isExplicitDomain() {
248
- return !!this.#getConfiguredDomain();
249
- }
250
-
251
- #isDomainInitialized() {
252
- const s = this.#scale;
253
- if (!s) {
254
- return false;
255
- }
256
-
257
- const domain = s.domain();
258
-
259
- // We could alternatively have a flag that is set when the domain is initialized.
260
- if (isContinuous(s.type)) {
261
- return (
262
- domain.length > 2 ||
263
- (domain.length == 2 && (domain[0] !== 0 || domain[1] !== 0))
264
- );
265
- } else {
266
- return domain.length > 0;
267
- }
268
- }
269
-
270
- /**
271
- * Collects and merges scale properties from the participating views.
272
- * Does not include inferred default values such as schemes etc.
273
- *
274
- * @returns {import("../spec/scale.js").Scale}
275
- */
276
- #getMergedScaleProps() {
277
- const propArray = this.members
278
- .map((member) => member.channelDef.scale)
279
- .filter((props) => props !== undefined);
280
-
281
- // TODO: Disabled scale: https://vega.github.io/vega-lite/docs/scale.html#disable
282
- return mergeObjects(propArray, "scale", ["domain"]);
283
- }
284
-
285
- /**
286
- * Returns the merged scale properties supplemented with inferred properties
287
- * and domain.
288
- *
289
- * @param {boolean} [extractDataDomain]
290
- * @returns {import("../spec/scale.js").Scale}
291
- */
292
- #getScaleProps(extractDataDomain = false) {
293
- const mergedProps = this.#getMergedScaleProps();
294
- if (mergedProps === null || mergedProps.type == "null") {
295
- // No scale (pass-thru)
296
- // TODO: Check that the channel is compatible
297
- return { type: "null" };
298
- }
299
-
300
- const props = {
301
- ...this.#getDefaultScaleProperties(this.type),
302
- ...mergedProps,
303
- };
304
-
305
- if (!props.type) {
306
- props.type = getDefaultScaleType(this.channel, this.type);
307
- }
308
-
309
- const domain = this.#getInitialDomain(extractDataDomain);
310
-
311
- if (domain && domain.length > 0) {
312
- props.domain = domain;
313
- } else if (isDiscrete(props.type)) {
314
- props.domain = new NominalDomain();
315
- }
316
-
317
- if (!props.domain && props.domainMid !== undefined) {
318
- // Initialize with a bogus domain so that scale.js can inject the domainMid.
319
- // The number of domain elements must be know before the glsl scale is generated.
320
- props.domain = [props.domainMin ?? 0, props.domainMax ?? 1];
321
- }
322
-
323
- // Reverse discrete y axis
324
- if (
325
- this.channel == "y" &&
326
- isDiscrete(props.type) &&
327
- props.reverse == undefined
328
- ) {
329
- props.reverse = true;
330
- }
331
-
332
- if (props.range && props.scheme) {
333
- delete props.scheme;
334
- // TODO: Props should be set more intelligently
335
- /*
336
- throw new Error(
337
- `Scale has both "range" and "scheme" defined! Views: ${this._getViewPaths()}`
338
- );
339
- */
340
- }
341
-
342
- // By default, index and locus scales are zoomable, others are not
343
- if (!("zoom" in props) && ["index", "locus"].includes(props.type)) {
344
- props.zoom = true;
345
- }
346
-
347
- applyLockedProperties(props, this.channel);
348
-
349
- return props;
350
- }
351
-
352
- /**
353
- * Configures range. If range is an array of expressions, they are evaluated
354
- * and the scale is updated when the expressions change.
355
- */
356
- #configureRange() {
357
- const props = this.#scale.props;
358
- const range = props.range;
359
- this.#rangeExprRefListeners.forEach((fn) => fn.invalidate());
360
-
361
- if (!range || !isArray(range)) {
362
- // Named ranges?
363
- return;
364
- }
365
-
366
- /**
367
- * @param {T} array
368
- * @param {boolean} reverse
369
- * @returns {T}
370
- * @template T
371
- */
372
- const flip = (array, reverse) =>
373
- // @ts-ignore TODO: Fix the type (should be a generic union array type)
374
- reverse ? array.slice().reverse() : array;
375
-
376
- if (range.some(isExprRef)) {
377
- /** @type {(() => void)[]} */
378
- let expressions;
379
-
380
- const evaluateAndSet = () => {
381
- this.#scale.range(
382
- flip(
383
- expressions.map((expr) => expr()),
384
- props.reverse
385
- )
386
- );
387
- };
388
-
389
- expressions = range.map((elem) => {
390
- if (isExprRef(elem)) {
391
- const fn =
392
- this.#firstMemberView.paramMediator.createExpression(
393
- elem.expr
394
- );
395
- fn.addListener(evaluateAndSet);
396
- this.#rangeExprRefListeners.add(fn);
397
- return () => fn(null);
398
- } else {
399
- return () => elem;
400
- }
401
- });
402
-
403
- evaluateAndSet();
404
- } else {
405
- this.#scale.range(flip(range, props.reverse));
406
- }
407
- }
408
-
409
- /**
410
- *
411
- * @param {boolean} extractDataDomain
412
- */
413
- #getInitialDomain(extractDataDomain = false) {
414
- // TODO: intersect the domain with zoom extent (if it's defined)
415
- return (
416
- this.#getConfiguredDomain() ??
417
- (this.type == LOCUS
418
- ? this.getGenome().getExtent()
419
- : extractDataDomain
420
- ? this.getDataDomain()
421
- : [])
422
- );
423
- }
424
-
425
- /**
426
- * Unions the configured domains of all participating views.
427
- *
428
- * @return { DomainArray }
429
- */
430
- #getConfiguredDomain() {
431
- const domains = this.members
432
- .map((member) => member.channelDef)
433
- .filter((channelDef) => channelDef.scale?.domain)
434
- .map((channelDef) =>
435
- // TODO: Handle ExprRefs and Param in domain
436
- createDomain(
437
- channelDef.type,
438
- // Chrom/pos must be linearized first
439
- this.fromComplexInterval(channelDef.scale.domain)
440
- )
441
- );
442
-
443
- if (domains.length > 0) {
444
- return domains.reduce((acc, curr) => acc.extendAll(curr));
445
- }
446
- }
447
-
448
- /**
449
- * Extracts and unions the data domains of all participating views.
450
- *
451
- * @return { DomainArray }
452
- */
453
- getDataDomain() {
454
- return this.members
455
- .map((member) =>
456
- member.dataDomainSource?.(member.channel, this.type)
457
- )
458
- .filter((domain) => !!domain)
459
- .reduce((acc, curr) => acc.extendAll(curr));
460
- }
461
-
462
- /**
463
- * Reconfigures the scale: updates domain and other settings
464
- */
465
- reconfigure() {
466
- const scale = this.#scale;
467
-
468
- if (!scale || scale.type == "null") {
469
- return;
470
- }
471
-
472
- const domainWasInitialized = this.#isDomainInitialized();
473
- const previousDomain = scale.domain();
474
-
475
- const props = this.#getScaleProps(true);
476
- configureScale({ ...props, range: undefined }, scale);
477
-
478
- // Annotate the scale with the new props
479
- scale.props = props;
480
- this.#configureRange();
481
-
482
- if (!this.#initialDomain && isContinuous(scale.type)) {
483
- const domain = scale.domain();
484
- if (span(domain) > 0) {
485
- this.#initialDomain = domain;
486
- }
487
- }
488
-
489
- if (!domainWasInitialized) {
490
- this.#initialDomain = scale.domain();
491
- this.#notifyListeners("domain");
492
- return;
493
- }
494
-
495
- const newDomain = scale.domain();
496
- if (!shallowArrayEquals(newDomain, previousDomain)) {
497
- if (this.isZoomable()) {
498
- // Don't mess with zoomed views, restore the previous domain
499
- scale.domain(previousDomain);
500
- } else if (this.#isZoomingSupported()) {
501
- // It can be zoomed, so lets make a smooth transition.
502
- // Restore the previous domain and zoom smoothly to the new domain.
503
- scale.domain(previousDomain);
504
- this.zoomTo(newDomain, 500); // TODO: Configurable duration
505
- } else {
506
- // Update immediately if the previous domain was the initial domain [0, 0]
507
- this.#notifyListeners("domain");
508
- }
509
- }
510
- }
511
-
512
- /**
513
- * @returns {ScaleWithProps}
514
- */
515
- get scale() {
516
- if (this.#scale) {
517
- return this.#scale;
518
- }
519
-
520
- const props = this.#getScaleProps();
521
-
522
- const scale = createScale({ ...props, range: undefined });
523
- // Annotate the scale with props
524
- scale.props = props;
525
-
526
- if ("unknown" in scale) {
527
- // Never allow implicit domain construction
528
- scale.unknown(null);
529
- }
530
-
531
- this.#scale = scale;
532
- this.#configureRange();
533
-
534
- if (isScaleLocus(scale)) {
535
- scale.genome(this.getGenome());
536
- }
537
-
538
- // Hijack the range method
539
- const range = scale.range;
540
- if (range) {
541
- const notify = () => this.#notifyListeners("range");
542
- scale.range = function (/** @type {any} */ _) {
543
- if (arguments.length) {
544
- range(_);
545
- notify();
546
- } else {
547
- return range();
548
- }
549
- };
550
- // The initial setting
551
- notify();
552
- }
553
-
554
- return scale;
555
- }
556
-
557
- getDomain() {
558
- return this.scale.domain();
559
- }
560
-
561
- /**
562
- * @returns {NumericDomain | ComplexDomain}
563
- */
564
- getComplexDomain() {
565
- return (
566
- this.getGenome()?.toChromosomalInterval(this.getDomain()) ??
567
- this.getDomain()
568
- );
569
- }
570
-
571
- /**
572
- * Return true if the scale is zoomable and the current domain differs from the initial domain.
573
- *
574
- * @returns true if zoomed
575
- */
576
- isZoomed() {
577
- return (
578
- this.#isZoomingSupported() &&
579
- shallowArrayEquals(this.#getInitialDomain(), this.getDomain())
580
- );
581
- }
582
-
583
- /**
584
- * Returns true if zooming is supported and allowed in view spec.
585
- */
586
- isZoomable() {
587
- // Check explicit configuration
588
- return this.#isZoomingSupported() && !!this.scale.props.zoom;
589
- }
590
-
591
- /**
592
- * Returns true if zooming is supported but not necessarily allowed in view spec.
593
- */
594
- #isZoomingSupported() {
595
- const type = this.scale.type;
596
- return isContinuous(type);
597
- }
598
-
599
- /**
600
- * Pans (translates) and zooms using a specified scale factor.
601
- *
602
- * @param {number} scaleFactor
603
- * @param {number} scaleAnchor
604
- * @param {number} pan
605
- * @returns {boolean} true if the scale was zoomed
606
- */
607
- zoom(scaleFactor, scaleAnchor, pan) {
608
- if (!this.#isZoomingSupported()) {
609
- return false;
610
- }
611
-
612
- const scale = this.scale;
613
- const oldDomain = scale.domain();
614
- let newDomain = [...oldDomain];
615
-
616
- /** @type {number} */
617
- // @ts-ignore
618
- let anchor = scale.invert(scaleAnchor);
619
-
620
- if (scale.props.reverse) {
621
- pan = -pan;
622
- }
623
-
624
- if ("align" in scale) {
625
- anchor += scale.align();
626
- }
627
-
628
- // TODO: symlog
629
- switch (scale.type) {
630
- case "linear":
631
- case "index":
632
- case "locus":
633
- newDomain = panLinear(newDomain, pan || 0);
634
- newDomain = zoomLinear(newDomain, anchor, scaleFactor);
635
- break;
636
- case "log":
637
- newDomain = panLog(newDomain, pan || 0);
638
- newDomain = zoomLog(newDomain, anchor, scaleFactor);
639
- break;
640
- case "pow":
641
- case "sqrt": {
642
- const powScale =
643
- /** @type {import("d3-scale").ScalePower<number, number>} */ (
644
- scale
645
- );
646
- newDomain = panPow(newDomain, pan || 0, powScale.exponent());
647
- newDomain = zoomPow(
648
- newDomain,
649
- anchor,
650
- scaleFactor,
651
- powScale.exponent()
652
- );
653
- break;
654
- }
655
- default:
656
- throw new Error(
657
- "Zooming is not implemented for: " + scale.type
658
- );
659
- }
660
-
661
- // TODO: Use the zoomTo method. Move clamping etc there.
662
- const zoomExtent = this.zoomExtent;
663
- newDomain = clampRange(newDomain, zoomExtent[0], zoomExtent[1]);
664
-
665
- if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
666
- scale.domain(newDomain);
667
- this.#notifyListeners("domain");
668
- return true;
669
- }
670
-
671
- return false;
672
- }
673
-
674
- /**
675
- * Immediately zooms to the given interval.
676
- *
677
- * @param {NumericDomain | ComplexDomain} domain
678
- * @param {boolean | number} [duration] an approximate duration for transition.
679
- * Zero duration zooms immediately. Boolean `true` indicates a default duration.
680
- */
681
- async zoomTo(domain, duration = false) {
682
- if (isBoolean(duration)) {
683
- duration = duration ? 700 : 0;
684
- }
685
-
686
- if (!this.#isZoomingSupported()) {
687
- throw new Error("Not a zoomable scale!");
688
- }
689
-
690
- const to = this.fromComplexInterval(domain);
691
-
692
- // TODO: Intersect the domain with zoom extent
693
-
694
- const animator = this.#viewContext.animator;
695
-
696
- const scale = this.scale;
697
- const from = /** @type {number[]} */ (scale.domain());
698
-
699
- if (duration > 0 && from.length == 2) {
700
- // Spans
701
- const fw = from[1] - from[0];
702
- const tw = to[1] - to[0];
703
-
704
- // Centers
705
- const fc = from[0] + fw / 2;
706
- const tc = to[0] + tw / 2;
707
-
708
- // Constant endpoints. Skip calculation to maintain precision.
709
- const ac = from[0] == to[0];
710
- const bc = from[1] == to[1];
711
-
712
- // TODO: Abort possible previous transition
713
- await animator.transition({
714
- duration,
715
- easingFunction: easeCubicInOut,
716
- onUpdate: (t) => {
717
- const w = eerp(fw, tw, t);
718
- const wt = fw == tw ? t : (fw - w) / (fw - tw);
719
- const c = wt * tc + (1 - wt) * fc;
720
- const domain = [
721
- ac ? from[0] : c - w / 2,
722
- bc ? from[1] : c + w / 2,
723
- ];
724
- scale.domain(domain);
725
- this.#notifyListeners("domain");
726
- },
727
- });
728
-
729
- scale.domain(to);
730
- this.#notifyListeners("domain");
731
- } else {
732
- scale.domain(to);
733
- animator?.requestRender();
734
- this.#notifyListeners("domain");
735
- }
736
- }
737
-
738
- /**
739
- * Resets the current domain to the initial one
740
- *
741
- * @returns true if the domain was changed
742
- */
743
- resetZoom() {
744
- if (!this.#isZoomingSupported()) {
745
- throw new Error("Not a zoomable scale!");
746
- }
747
-
748
- const oldDomain = this.getDomain();
749
- const newDomain = this.#getInitialDomain();
750
-
751
- if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
752
- this.#scale.domain(newDomain);
753
- this.#notifyListeners("domain");
754
- return true;
755
- }
756
- return false;
757
- }
758
-
759
- /**
760
- * Returns the zoom level with respect to the reference domain span (the original domain).
761
- *
762
- * In principle, this is highly specific to positional channels. However, zooming can
763
- * be generalized to other quantitative channels such as color, opacity, size, etc.
764
- */
765
- getZoomLevel() {
766
- // Zoom level makes sense only for user-zoomable scales where zoom extent is defined
767
- if (this.isZoomable()) {
768
- return span(this.zoomExtent) / span(this.scale.domain());
769
- }
770
-
771
- return 1.0;
772
- }
773
-
774
- /**
775
- * Returns the length of the axis in pixels. Chooses the smallest of the views.
776
- * They should all be the same, but some exotic configuration might break that assumption.
777
- *
778
- * This method is needed because positional channels have unit ranges and the
779
- * length of the axis is not directly available from the scale. Ideally, ranges would
780
- * be configured as pixels, but that is yet to be materialized.
781
- */
782
- getAxisLength() {
783
- if (this.channel !== "x" && this.channel !== "y") {
784
- throw new Error(
785
- "Axis length is only defined for x and y channels!"
786
- );
787
- }
788
-
789
- // Here's a problem: if the view has been hidden, it may have stale coords.
790
- // TODO: They should be cleared when the layout is invalidated.
791
- // Alternatively, scale ranges could be set in pixels.
792
- const lengths = this.members
793
- .map(
794
- (m) =>
795
- m.view.coords?.[this.channel === "x" ? "width" : "height"]
796
- )
797
- .filter((len) => len > 0);
798
-
799
- return lengths.length
800
- ? lengths.reduce((a, b) => Math.min(a, b), 10000)
801
- : 0;
802
- }
803
-
804
- /**
805
- * @returns {number[]}
806
- */
807
- #getZoomExtent() {
808
- const props = this.scale.props;
809
- const zoom = props.zoom;
810
-
811
- if (isZoomParams(zoom)) {
812
- if (isArray(zoom.extent)) {
813
- return this.fromComplexInterval(zoom.extent);
814
- }
815
- }
816
-
817
- if (zoom) {
818
- if (props.type == "locus") {
819
- return this.getGenome().getExtent();
820
- }
821
- }
822
-
823
- // TODO: Perhaps this should be "domain" for index scale and nothing for quantitative.
824
- // Would behave similarly to Vega-Lite, which doesn't have constraints.
825
- return this.#initialDomain;
826
- }
827
-
828
- /**
829
- * TODO: These actually depend on the mark, so this is clearly a wrong place.
830
- * And besides, these should be configurable (themeable)
831
- *
832
- * @param {string} dataType
833
- */
834
- #getDefaultScaleProperties(dataType) {
835
- const channel = this.channel;
836
- const props = {};
837
-
838
- if (this.#isExplicitDomain()) {
839
- props.zero = false;
840
- }
841
-
842
- if (isPositionalChannel(channel)) {
843
- props.nice = !this.#isExplicitDomain();
844
- } else if (isColorChannel(channel)) {
845
- // TODO: Named ranges
846
- props.scheme =
847
- dataType == NOMINAL
848
- ? "tableau10"
849
- : dataType == ORDINAL
850
- ? "blues"
851
- : "viridis";
852
- } else if (isDiscreteChannel(channel)) {
853
- // Shapes of point mark, for example
854
- props.range =
855
- channel == "shape"
856
- ? ["circle", "square", "triangle-up", "cross", "diamond"]
857
- : [];
858
- } else if (channel == "size") {
859
- props.range = [0, 400]; // TODO: Configurable default. This is currently optimized for points.
860
- } else if (channel == "angle") {
861
- props.range = [0, 360];
862
- }
863
-
864
- return props;
865
- }
866
-
867
- /**
868
- *
869
- * @returns {import("../genome/genome.js").default}
870
- */
871
- getGenome() {
872
- if (this.type !== "locus") {
873
- return undefined;
874
- }
875
-
876
- // TODO: Support multiple assemblies
877
- const genome = this.#viewContext.genomeStore?.getGenome();
878
- if (!genome) {
879
- throw new Error("No genome has been defined!");
880
- }
881
- return genome;
882
- }
883
-
884
- // TODO: Move the "complex" stuff into scaleLocus.
885
-
886
- /**
887
- * Inverts a value in range to a value on domain. Returns an object in
888
- * case of locus scale.
889
- *
890
- * @param {number} value
891
- */
892
- invertToComplex(value) {
893
- const scale = this.scale;
894
- if ("invert" in scale) {
895
- const inverted = /** @type {number} */ (scale.invert(value));
896
- return this.toComplex(inverted);
897
- } else {
898
- throw new Error("The scale does not support inverting!");
899
- }
900
- }
901
-
902
- /**
903
- * @param {number} value
904
- */
905
- toComplex(value) {
906
- const genome = this.getGenome();
907
- return genome ? genome.toChromosomal(value) : value;
908
- }
909
-
910
- /**
911
- * @param {number | ChromosomalLocus} complex
912
- * @returns {number}
913
- */
914
- fromComplex(complex) {
915
- if (isChromosomalLocus(complex)) {
916
- const genome = this.getGenome();
917
- return genome.toContinuous(complex.chrom, complex.pos);
918
- }
919
- return complex;
920
- }
921
-
922
- /**
923
- * @param {ScalarDomain | ComplexDomain} interval
924
- * @returns {number[]}
925
- */
926
- fromComplexInterval(interval) {
927
- if (this.type === "locus" && isChromosomalLocusInterval(interval)) {
928
- return this.getGenome().toContinuousInterval(interval);
929
- }
930
- return /** @type {number[]} */ (interval);
931
- }
932
- }
933
-
934
- /**
935
- *
936
- * @param {Channel} channel
937
- * @param {string} dataType
938
- * @returns {import("../spec/scale.js").ScaleType}
939
- */
940
- function getDefaultScaleType(channel, dataType) {
941
- // TODO: Band scale, Bin-Quantitative
942
-
943
- if (dataType == INDEX || dataType == LOCUS) {
944
- if (isPrimaryPositionalChannel(channel)) {
945
- return dataType;
946
- } else {
947
- // TODO: Also explicitly set scales should be validated
948
- throw new Error(
949
- `${channel} does not support ${dataType} data type. Only positional channels do.`
950
- );
951
- }
952
- }
953
-
954
- /**
955
- * @type {Partial<Record<Channel, (import("../spec/scale.js").ScaleType | undefined)[]>>}
956
- * Default types: nominal, ordinal, quantitative.
957
- * undefined = incompatible, "null" = disabled (pass-thru)
958
- */
959
- const defaults = {
960
- x: ["band", "band", "linear"],
961
- y: ["band", "band", "linear"],
962
- size: [undefined, "point", "linear"],
963
- opacity: [undefined, "point", "linear"],
964
- fillOpacity: [undefined, "point", "linear"],
965
- strokeOpacity: [undefined, "point", "linear"],
966
- color: ["ordinal", "ordinal", "linear"],
967
- fill: ["ordinal", "ordinal", "linear"],
968
- stroke: ["ordinal", "ordinal", "linear"],
969
- strokeWidth: [undefined, undefined, "linear"],
970
- shape: ["ordinal", "ordinal", undefined],
971
- dx: [undefined, undefined, "null"],
972
- dy: [undefined, undefined, "null"],
973
- angle: [undefined, undefined, "linear"],
974
- sample: ["null", undefined, undefined],
975
- };
976
-
977
- /** @type {Channel[]} */
978
- const typelessChannels = ["sample"];
979
-
980
- const type = typelessChannels.includes(channel)
981
- ? "null"
982
- : defaults[channel]
983
- ? defaults[channel][
984
- [NOMINAL, ORDINAL, QUANTITATIVE].indexOf(dataType)
985
- ]
986
- : dataType == QUANTITATIVE
987
- ? "linear"
988
- : "ordinal";
989
-
990
- if (type === undefined) {
991
- throw new Error(
992
- `Channel "${channel}" is not compatible with "${dataType}" data type. Use of a proper scale may be needed.`
993
- );
994
- }
995
-
996
- return type;
997
- }
998
-
999
- /**
1000
- * @param {import("../spec/scale.js").Scale} props
1001
- * @param {import("../spec/channel.js").Channel} channel
1002
- */
1003
- function applyLockedProperties(props, channel) {
1004
- if (isPositionalChannel(channel) && props.type !== "ordinal") {
1005
- props.range = [0, 1];
1006
- }
1007
-
1008
- if (channel == "opacity" && isContinuous(props.type)) {
1009
- props.clamp = true;
1010
- }
1011
- }
1012
-
1013
- /**
1014
- *
1015
- * @param {boolean | ZoomParams} zoom
1016
- * @returns {zoom is ZoomParams}
1017
- */
1018
- function isZoomParams(zoom) {
1019
- return isObject(zoom);
1020
- }
1021
-
1022
- /**
1023
- * Reconfigures scales, starting from the given view.
1024
- *
1025
- * TODO: This should be made unnecessary. Collectors should trigger the reconfiguration
1026
- * for those views that get their data from the collector.
1027
- *
1028
- * TODO: This may reconfigure channels that are not affected by the change.
1029
- * Causes performance issues with domains that are extracted from data.
1030
- *
1031
- * @param {import("./view.js").default | import("./view.js").default[]} fromViews
1032
- */
1033
- export function reconfigureScales(fromViews) {
1034
- /** @type {Set<ScaleResolution>} */
1035
- const uniqueResolutions = new Set();
1036
-
1037
- /** @param {import("./view.js").default} view */
1038
- function collectResolutions(view) {
1039
- for (const resolution of Object.values(view.resolutions.scale)) {
1040
- uniqueResolutions.add(resolution);
1041
- }
1042
- }
1043
-
1044
- for (const fromView of asArray(fromViews)) {
1045
- // Descendants
1046
- fromView.visit(collectResolutions);
1047
-
1048
- // Ancestors
1049
- for (const view of fromView.getDataAncestors()) {
1050
- // Skip axis views etc. They should not mess with the domains.
1051
- if (!view.options.contributesToScaleDomain) {
1052
- break;
1053
- }
1054
- collectResolutions(view);
1055
- }
1056
- }
1057
-
1058
- uniqueResolutions.forEach((resolution) => resolution.reconfigure());
1059
- }