@genome-spy/core 0.14.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 (226) hide show
  1. package/dist/index.js +224 -0
  2. package/dist/style.css +1 -0
  3. package/package.json +54 -0
  4. package/src/data/collector.js +178 -0
  5. package/src/data/collector.test.js +82 -0
  6. package/src/data/dataFlow.js +109 -0
  7. package/src/data/dataFlow.test.js +3 -0
  8. package/src/data/facetNode.js +17 -0
  9. package/src/data/flow.test.js +71 -0
  10. package/src/data/flowBatch.d.ts +40 -0
  11. package/src/data/flowNode.js +283 -0
  12. package/src/data/flowNode.test.js +49 -0
  13. package/src/data/flowOptimizer.js +117 -0
  14. package/src/data/flowOptimizer.test.js +192 -0
  15. package/src/data/flowTestUtils.js +63 -0
  16. package/src/data/formats/fasta.js +32 -0
  17. package/src/data/formats/fasta.test.js +26 -0
  18. package/src/data/sources/dataSource.js +22 -0
  19. package/src/data/sources/dataSourceFactory.js +24 -0
  20. package/src/data/sources/dataUtils.js +31 -0
  21. package/src/data/sources/dynamicCallbackSource.js +56 -0
  22. package/src/data/sources/dynamicSource.js +36 -0
  23. package/src/data/sources/inlineSource.js +69 -0
  24. package/src/data/sources/inlineSource.test.js +55 -0
  25. package/src/data/sources/namedSource.js +74 -0
  26. package/src/data/sources/sequenceSource.js +46 -0
  27. package/src/data/sources/sequenceSource.test.js +45 -0
  28. package/src/data/sources/urlSource.js +74 -0
  29. package/src/data/transforms/aggregate.js +69 -0
  30. package/src/data/transforms/clone.js +40 -0
  31. package/src/data/transforms/clone.test.js +10 -0
  32. package/src/data/transforms/coverage.js +187 -0
  33. package/src/data/transforms/coverage.test.js +122 -0
  34. package/src/data/transforms/filter.js +37 -0
  35. package/src/data/transforms/filter.test.js +17 -0
  36. package/src/data/transforms/filterScoredLabels.js +134 -0
  37. package/src/data/transforms/flattenCompressedExons.js +57 -0
  38. package/src/data/transforms/flattenDelimited.js +68 -0
  39. package/src/data/transforms/flattenDelimited.test.js +86 -0
  40. package/src/data/transforms/flattenSequence.js +39 -0
  41. package/src/data/transforms/flattenSequence.test.js +33 -0
  42. package/src/data/transforms/formula.js +39 -0
  43. package/src/data/transforms/formula.test.js +18 -0
  44. package/src/data/transforms/identifier.js +108 -0
  45. package/src/data/transforms/identifier.test.js +82 -0
  46. package/src/data/transforms/linearizeGenomicCoordinate.js +101 -0
  47. package/src/data/transforms/measureText.js +44 -0
  48. package/src/data/transforms/pileup.js +128 -0
  49. package/src/data/transforms/pileup.test.js +69 -0
  50. package/src/data/transforms/project.js +41 -0
  51. package/src/data/transforms/project.test.js +31 -0
  52. package/src/data/transforms/regexExtract.js +61 -0
  53. package/src/data/transforms/regexExtract.test.js +66 -0
  54. package/src/data/transforms/regexFold.js +141 -0
  55. package/src/data/transforms/regexFold.test.js +159 -0
  56. package/src/data/transforms/sample.js +101 -0
  57. package/src/data/transforms/sample.test.js +37 -0
  58. package/src/data/transforms/stack.js +137 -0
  59. package/src/data/transforms/stack.test.js +90 -0
  60. package/src/data/transforms/transformFactory.js +60 -0
  61. package/src/encoder/accessor.js +82 -0
  62. package/src/encoder/accessor.test.js +46 -0
  63. package/src/encoder/encoder.js +369 -0
  64. package/src/encoder/encoder.test.js +97 -0
  65. package/src/fonts/Lato-Regular.json +1267 -0
  66. package/src/fonts/Lato-Regular.png +0 -0
  67. package/src/fonts/OFL.txt +93 -0
  68. package/src/fonts/README.md +3 -0
  69. package/src/fonts/bmFont.d.ts +58 -0
  70. package/src/fonts/bmFontManager.js +357 -0
  71. package/src/fonts/bmFontMetrics.js +108 -0
  72. package/src/genome/genome.js +305 -0
  73. package/src/genome/genome.test.js +152 -0
  74. package/src/genome/genomeStore.js +54 -0
  75. package/src/genome/locusFormat.js +31 -0
  76. package/src/genome/scaleIndex.js +199 -0
  77. package/src/genome/scaleIndex.test.js +61 -0
  78. package/src/genome/scaleLocus.js +112 -0
  79. package/src/genome/scaleLocus.test.js +3 -0
  80. package/src/genomeSpy.js +753 -0
  81. package/src/gl/arrayBuilder.js +199 -0
  82. package/src/gl/dataToVertices.js +621 -0
  83. package/src/gl/includes/common.glsl +63 -0
  84. package/src/gl/includes/fp64-arithmetic.glsl +187 -0
  85. package/src/gl/includes/fp64-utils.js +132 -0
  86. package/src/gl/includes/picking.fragment.glsl +3 -0
  87. package/src/gl/includes/picking.vertex.glsl +29 -0
  88. package/src/gl/includes/sampleFacet.glsl +107 -0
  89. package/src/gl/includes/scales.glsl +79 -0
  90. package/src/gl/includes/scales_fp64.glsl +30 -0
  91. package/src/gl/link.fragment.glsl +18 -0
  92. package/src/gl/link.vertex.glsl +111 -0
  93. package/src/gl/point.fragment.glsl +123 -0
  94. package/src/gl/point.vertex.glsl +128 -0
  95. package/src/gl/rect.fragment.glsl +51 -0
  96. package/src/gl/rect.vertex.glsl +114 -0
  97. package/src/gl/rule.fragment.glsl +52 -0
  98. package/src/gl/rule.vertex.glsl +89 -0
  99. package/src/gl/text.fragment.glsl +31 -0
  100. package/src/gl/text.vertex.glsl +246 -0
  101. package/src/gl/webGLHelper.js +490 -0
  102. package/src/img/bowtie.svg +1 -0
  103. package/src/img/genomespy-favicon.svg +34 -0
  104. package/src/index.html +11 -0
  105. package/src/index.js +151 -0
  106. package/src/marks/link.js +189 -0
  107. package/src/marks/mark.js +867 -0
  108. package/src/marks/markUtils.js +109 -0
  109. package/src/marks/pointMark.js +279 -0
  110. package/src/marks/rectMark.js +236 -0
  111. package/src/marks/rule.js +231 -0
  112. package/src/marks/text.js +274 -0
  113. package/src/options.d.ts +9 -0
  114. package/src/scale/colorUtils.js +184 -0
  115. package/src/scale/glslScaleGenerator.js +462 -0
  116. package/src/scale/scale.js +441 -0
  117. package/src/scale/scale.test.js +323 -0
  118. package/src/scale/ticks.js +198 -0
  119. package/src/scale/ticks.test.js +39 -0
  120. package/src/singlePageApp.js +13 -0
  121. package/src/spec/axis.d.ts +296 -0
  122. package/src/spec/channel.d.ts +127 -0
  123. package/src/spec/data.d.ts +185 -0
  124. package/src/spec/font.d.ts +15 -0
  125. package/src/spec/genome.d.ts +35 -0
  126. package/src/spec/mark.d.ts +432 -0
  127. package/src/spec/root.d.ts +22 -0
  128. package/src/spec/scale.d.ts +265 -0
  129. package/src/spec/tooltip.d.ts +9 -0
  130. package/src/spec/transform.d.ts +479 -0
  131. package/src/spec/view.d.ts +215 -0
  132. package/src/styles/genome-spy.scss +153 -0
  133. package/src/tooltip/dataTooltipHandler.js +59 -0
  134. package/src/tooltip/refseqGeneTooltipHandler.js +77 -0
  135. package/src/tooltip/tooltipHandler.ts +12 -0
  136. package/src/types/filetypes.d.ts +4 -0
  137. package/src/types/flatqueue.d.ts +53 -0
  138. package/src/types/glsl.d.ts +4 -0
  139. package/src/types/object.d.ts +21 -0
  140. package/src/types/vega-scale.d.ts +60 -0
  141. package/src/utils/animator.js +83 -0
  142. package/src/utils/arrayUtils.js +55 -0
  143. package/src/utils/binnedRangeIndex.js +83 -0
  144. package/src/utils/clamp.js +8 -0
  145. package/src/utils/cloner.js +32 -0
  146. package/src/utils/cloner.test.js +23 -0
  147. package/src/utils/coalesce.js +11 -0
  148. package/src/utils/coalesce.test.js +15 -0
  149. package/src/utils/concatIterables.js +26 -0
  150. package/src/utils/concatIterables.test.js +7 -0
  151. package/src/utils/debounce.js +37 -0
  152. package/src/utils/domainArray.js +224 -0
  153. package/src/utils/domainArray.test.js +129 -0
  154. package/src/utils/eerp.js +13 -0
  155. package/src/utils/expression.js +32 -0
  156. package/src/utils/field.js +28 -0
  157. package/src/utils/fisheye.js +60 -0
  158. package/src/utils/formatObject.js +31 -0
  159. package/src/utils/html.js +23 -0
  160. package/src/utils/html.test.js +13 -0
  161. package/src/utils/indexer.js +43 -0
  162. package/src/utils/indexer.test.js +46 -0
  163. package/src/utils/inertia.js +124 -0
  164. package/src/utils/interactionEvent.js +33 -0
  165. package/src/utils/iterateNestedMaps.js +21 -0
  166. package/src/utils/iterateNestedMaps.test.js +32 -0
  167. package/src/utils/kWayMerge.js +42 -0
  168. package/src/utils/kWayMerge.test.js +25 -0
  169. package/src/utils/layout/flexLayout.js +336 -0
  170. package/src/utils/layout/flexLayout.test.js +296 -0
  171. package/src/utils/layout/padding.js +107 -0
  172. package/src/utils/layout/point.js +23 -0
  173. package/src/utils/layout/rectangle.js +282 -0
  174. package/src/utils/layout/rectangle.test.js +171 -0
  175. package/src/utils/mergeObjects.js +99 -0
  176. package/src/utils/mergeObjects.test.js +41 -0
  177. package/src/utils/numberExtractor.js +24 -0
  178. package/src/utils/numberExtractor.test.js +5 -0
  179. package/src/utils/point.js +14 -0
  180. package/src/utils/propertyCacher.js +70 -0
  181. package/src/utils/propertyCacher.test.js +84 -0
  182. package/src/utils/propertyCoalescer.js +37 -0
  183. package/src/utils/propertyCoalescer.test.js +21 -0
  184. package/src/utils/reservationMap.js +103 -0
  185. package/src/utils/reservationMap.test.js +19 -0
  186. package/src/utils/scaleNull.js +19 -0
  187. package/src/utils/setOperations.js +75 -0
  188. package/src/utils/smoothstep.js +10 -0
  189. package/src/utils/throttle.js +34 -0
  190. package/src/utils/topK.js +76 -0
  191. package/src/utils/topK.test.js +63 -0
  192. package/src/utils/transition.js +74 -0
  193. package/src/utils/ui/tooltip.js +189 -0
  194. package/src/utils/url.js +22 -0
  195. package/src/utils/variableTools.js +24 -0
  196. package/src/utils/variableTools.test.js +12 -0
  197. package/src/view/axisResolution.js +135 -0
  198. package/src/view/axisResolution.test.js +200 -0
  199. package/src/view/axisView.js +746 -0
  200. package/src/view/channel.js +5 -0
  201. package/src/view/concatView.js +296 -0
  202. package/src/view/containerView.js +141 -0
  203. package/src/view/decoratorView.js +510 -0
  204. package/src/view/facetView.js +488 -0
  205. package/src/view/flowBuilder.js +362 -0
  206. package/src/view/flowBuilder.test.js +124 -0
  207. package/src/view/importView.js +19 -0
  208. package/src/view/layerView.js +60 -0
  209. package/src/view/rendering.d.ts +44 -0
  210. package/src/view/renderingContext/compositeViewRenderingContext.js +51 -0
  211. package/src/view/renderingContext/deferredViewRenderingContext.js +174 -0
  212. package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +128 -0
  213. package/src/view/renderingContext/simpleViewRenderingContext.js +62 -0
  214. package/src/view/renderingContext/svgViewRenderingContext.js +121 -0
  215. package/src/view/renderingContext/viewRenderingContext.js +41 -0
  216. package/src/view/scaleResolution.js +756 -0
  217. package/src/view/scaleResolution.test.js +571 -0
  218. package/src/view/scaleResolutionApi.d.ts +40 -0
  219. package/src/view/testUtils.js +48 -0
  220. package/src/view/unitView.js +368 -0
  221. package/src/view/view.js +589 -0
  222. package/src/view/view.test.js +213 -0
  223. package/src/view/viewContext.d.ts +57 -0
  224. package/src/view/viewFactory.js +179 -0
  225. package/src/view/viewFactory.test.js +16 -0
  226. package/src/view/viewUtils.js +420 -0
@@ -0,0 +1,571 @@
1
+ import { createAndInitialize } from "./testUtils";
2
+ import createDomain, { toRegularArray as r } from "../utils/domainArray";
3
+ import LayerView from "./layerView";
4
+ import UnitView from "./unitView";
5
+ import { primaryPositionalChannels } from "../encoder/encoder";
6
+
7
+ /**
8
+ * @typedef {import("../spec/channel").Channel} Channel
9
+ */
10
+
11
+ // NOTE: The most of these tests don't actually test scaleResolution but the resolution algorithm.
12
+
13
+ describe("Scale resolution", () => {
14
+ test("Channels with just values (no fields or scales) do not resolve", async () => {
15
+ /** @type {import("../spec/view").LayerSpec} */
16
+ const spec = {
17
+ data: { values: [] },
18
+
19
+ resolve: {
20
+ scale: { x: "shared" },
21
+ },
22
+
23
+ layer: [
24
+ {
25
+ mark: "point",
26
+ encoding: {
27
+ color: { value: "red" },
28
+ },
29
+ },
30
+ {
31
+ mark: "point",
32
+ encoding: {
33
+ color: { value: "green" },
34
+ },
35
+ },
36
+ ],
37
+ };
38
+ const view = await createAndInitialize(spec, LayerView);
39
+ expect(view.children[0].getScaleResolution("color")).toBeUndefined();
40
+ expect(view.children[1].getScaleResolution("color")).toBeUndefined();
41
+ });
42
+
43
+ test("Deeply shared scales are shared", async () => {
44
+ /** @type {import("../spec/view").LayerSpec} */
45
+ const spec = {
46
+ data: { values: [] },
47
+ encoding: {
48
+ x: { field: "data", type: "quantitative" },
49
+ },
50
+
51
+ resolve: { scale: { x: "shared" } },
52
+
53
+ layer: [
54
+ {
55
+ resolve: { scale: { x: "shared" } },
56
+ layer: [{ mark: "point" }, { mark: "point" }],
57
+ },
58
+ {
59
+ resolve: { scale: { x: "shared" } },
60
+ layer: [{ mark: "point" }, { mark: "point" }],
61
+ },
62
+ ],
63
+ };
64
+
65
+ const view = await createAndInitialize(spec, LayerView);
66
+
67
+ expect(view.children[0].children[0].getScaleResolution("x")).toBe(
68
+ view.children[1].children[1].getScaleResolution("x")
69
+ );
70
+ });
71
+
72
+ test("Shared branches under an independent branch works as expected", async () => {
73
+ /** @type {import("../spec/view").LayerSpec} */
74
+ const spec = {
75
+ data: { values: [] },
76
+ encoding: {
77
+ x: { field: "data", type: "quantitative" },
78
+ },
79
+
80
+ resolve: {
81
+ scale: { x: "independent" },
82
+ // TODO: Axis should be set independent implicitly
83
+ axis: { x: "independent" },
84
+ },
85
+
86
+ layer: [
87
+ {
88
+ resolve: { scale: { x: "shared" } },
89
+ layer: [{ mark: "point" }, { mark: "point" }],
90
+ },
91
+ {
92
+ resolve: { scale: { x: "shared" } },
93
+ layer: [{ mark: "point" }, { mark: "point" }],
94
+ },
95
+ ],
96
+ };
97
+
98
+ const view = await createAndInitialize(spec, LayerView);
99
+
100
+ expect(view.children[0].children[0].getScaleResolution("x")).not.toBe(
101
+ view.children[1].children[1].getScaleResolution("x")
102
+ );
103
+ expect(view.children[0].children[0].getScaleResolution("x")).toBe(
104
+ view.children[0].children[1].getScaleResolution("x")
105
+ );
106
+ expect(view.children[1].children[0].getScaleResolution("x")).toBe(
107
+ view.children[1].children[1].getScaleResolution("x")
108
+ );
109
+ });
110
+
111
+ test("Independent branches under a shared branch works as expected", async () => {
112
+ /** @type {import("../spec/view").LayerSpec} */
113
+ const spec = {
114
+ data: { values: [] },
115
+ encoding: {
116
+ x: { field: "data", type: "quantitative" },
117
+ },
118
+
119
+ resolve: {
120
+ scale: { x: "shared" },
121
+ },
122
+
123
+ layer: [
124
+ {
125
+ resolve: {
126
+ scale: { x: "independent" },
127
+ // TODO: Axis should be set independent implicitly
128
+ axis: { x: "independent" },
129
+ },
130
+ layer: [{ mark: "point" }, { mark: "point" }],
131
+ },
132
+ {
133
+ resolve: {
134
+ scale: { x: "independent" },
135
+ // TODO: Axis should be set independent implicitly
136
+ axis: { x: "independent" },
137
+ },
138
+ layer: [{ mark: "point" }, { mark: "point" }],
139
+ },
140
+ ],
141
+ };
142
+
143
+ const view = await createAndInitialize(spec, LayerView);
144
+
145
+ expect(view.children[0].children[0].getScaleResolution("x")).not.toBe(
146
+ view.children[1].children[0].getScaleResolution("x")
147
+ );
148
+ expect(view.children[0].children[1].getScaleResolution("x")).not.toBe(
149
+ view.children[1].children[1].getScaleResolution("x")
150
+ );
151
+ expect(view.children[0].children[0].getScaleResolution("x")).not.toBe(
152
+ view.children[0].children[1].getScaleResolution("x")
153
+ );
154
+ expect(view.children[1].children[0].getScaleResolution("x")).not.toBe(
155
+ view.children[1].children[1].getScaleResolution("x")
156
+ );
157
+ });
158
+
159
+ test("Excluded resolution is not pushed towards the root but collects from children.", async () => {
160
+ /** @type {import("../spec/view").LayerSpec} */
161
+ const spec = {
162
+ data: { values: [] },
163
+ encoding: {
164
+ x: { field: "data", type: "quantitative" },
165
+ },
166
+
167
+ resolve: {
168
+ scale: { x: "shared" },
169
+ },
170
+
171
+ layer: [
172
+ { mark: "point" },
173
+ { mark: "point" },
174
+ {
175
+ resolve: {
176
+ scale: { x: "excluded" },
177
+ // TODO: Implicit
178
+ axis: { x: "excluded" },
179
+ },
180
+ layer: [{ mark: "point" }, { mark: "point" }],
181
+ },
182
+ ],
183
+ };
184
+
185
+ const view = await createAndInitialize(spec, LayerView);
186
+
187
+ expect(view.children[0].getScaleResolution("x")).toBe(
188
+ view.children[1].getScaleResolution("x")
189
+ );
190
+
191
+ expect(view.children[2].children[0].getScaleResolution("x")).toBe(
192
+ view.children[2].children[1].getScaleResolution("x")
193
+ );
194
+
195
+ expect(view.children[0].getScaleResolution("x")).not.toBe(
196
+ view.children[2].children[0].getScaleResolution("x")
197
+ );
198
+ });
199
+
200
+ test("Default resolution is configurable", async () => {
201
+ /** @type {import("../spec/view").LayerSpec} */
202
+ const spec = {
203
+ data: { values: [] },
204
+ encoding: {
205
+ x: { field: "data", type: "quantitative" },
206
+ y: { field: "data", type: "quantitative" },
207
+ },
208
+
209
+ resolve: {
210
+ scale: {
211
+ // The hard default in LayerView is "shared".
212
+ default: "independent",
213
+ x: "shared",
214
+ },
215
+ axis: {
216
+ // TODO: Implicit
217
+ default: "independent",
218
+ },
219
+ },
220
+
221
+ layer: [{ mark: "point" }, { mark: "point" }],
222
+ };
223
+
224
+ const view = await createAndInitialize(spec, LayerView);
225
+
226
+ expect(view.children[0].getScaleResolution("x")).toBe(
227
+ view.children[1].getScaleResolution("x")
228
+ );
229
+
230
+ expect(view.children[0].getScaleResolution("y")).not.toBe(
231
+ view.children[1].getScaleResolution("y")
232
+ );
233
+ });
234
+ });
235
+
236
+ describe("Domain handling", () => {
237
+ test("Scales are shared and explicit domains merged properly", async () => {
238
+ const view = await createAndInitialize(
239
+ {
240
+ data: { values: [] },
241
+ resolve: { scale: { default: "independent", y: "shared" } },
242
+ layer: [
243
+ {
244
+ mark: "point",
245
+ encoding: {
246
+ y: {
247
+ field: "a",
248
+ type: "quantitative",
249
+ scale: { domain: [1, 2] },
250
+ },
251
+ },
252
+ },
253
+ {
254
+ mark: "point",
255
+ encoding: {
256
+ y: {
257
+ field: "b",
258
+ type: "quantitative",
259
+ scale: { domain: [4, 5] },
260
+ },
261
+ },
262
+ },
263
+ ],
264
+ },
265
+ LayerView
266
+ );
267
+
268
+ /** @param {import("./view").default} view */
269
+ const d = (view) => view.getScaleResolution("y").getScale().domain();
270
+
271
+ expect(r(d(view))).toEqual([1, 5]);
272
+ expect(r(d(view.children[0]))).toEqual([1, 5]);
273
+ expect(r(d(view.children[1]))).toEqual([1, 5]);
274
+ });
275
+
276
+ test("Scales are shared and extracted domains merged properly", async () => {
277
+ const view = await createAndInitialize(
278
+ {
279
+ resolve: { scale: { default: "independent", y: "shared" } },
280
+ layer: [
281
+ {
282
+ data: { values: [1, 2] },
283
+ mark: "point",
284
+ encoding: {
285
+ y: {
286
+ field: "data",
287
+ type: "quantitative",
288
+ scale: { zero: false },
289
+ },
290
+ },
291
+ },
292
+ {
293
+ data: { values: [4, 5] },
294
+ mark: "point",
295
+ encoding: {
296
+ y: { field: "data", type: "quantitative" },
297
+ },
298
+ },
299
+ ],
300
+ },
301
+ LayerView
302
+ );
303
+
304
+ /** @param {import("./view").default} view */
305
+ const d = (view) => view.getScaleResolution("y").getScale().domain();
306
+
307
+ expect(r(d(view))).toEqual([1, 5]);
308
+ expect(r(d(view.children[0]))).toEqual([1, 5]);
309
+ expect(r(d(view.children[1]))).toEqual([1, 5]);
310
+ });
311
+
312
+ test("Scales of primary and secondary channels are shared and extracted domains merged properly", async () => {
313
+ const view = await createAndInitialize(
314
+ {
315
+ data: {
316
+ values: [
317
+ { a: 1, b: 4 },
318
+ { a: 2, b: 5 },
319
+ ],
320
+ },
321
+ mark: "point",
322
+ encoding: {
323
+ y: {
324
+ field: "a",
325
+ type: "quantitative",
326
+ scale: { zero: false },
327
+ },
328
+ y2: {
329
+ field: "b",
330
+ },
331
+ },
332
+ },
333
+ UnitView
334
+ );
335
+
336
+ /** @param {import("./view").default} view */
337
+ const d = (view) => view.getScaleResolution("y").getScale().domain();
338
+
339
+ // FAILS!!!!!!! TODO: FIX!!
340
+ // expect(r(d(view))).toEqual([1, 5]);
341
+ });
342
+
343
+ test("resolutionChannel property is respected", async () => {
344
+ const view = await createAndInitialize(
345
+ {
346
+ data: { values: [] },
347
+ resolve: { scale: { default: "independent", y: "shared" } },
348
+ layer: [
349
+ {
350
+ mark: "point",
351
+ encoding: {
352
+ y: {
353
+ field: "a",
354
+ type: "quantitative",
355
+ scale: { domain: [1, 2] },
356
+ },
357
+ },
358
+ },
359
+ {
360
+ mark: "point",
361
+ encoding: {
362
+ x: {
363
+ field: "b",
364
+ type: "quantitative",
365
+ scale: { domain: [4, 5] },
366
+ resolutionChannel: "y",
367
+ },
368
+ },
369
+ },
370
+ ],
371
+ },
372
+ LayerView
373
+ );
374
+
375
+ /** @param {import("./view").default} view */
376
+ const d = (view) => view.getScaleResolution("y").getScale().domain();
377
+
378
+ expect(r(d(view))).toEqual([1, 5]);
379
+ expect(r(d(view.children[0]))).toEqual([1, 5]);
380
+ expect(r(d(view.children[1]))).toEqual([1, 5]);
381
+ });
382
+
383
+ test("Channels with quantitative fields include zero in their scale domain by default", async () => {
384
+ const view = await createAndInitialize(
385
+ {
386
+ data: { values: [2, 3] },
387
+ mark: "point",
388
+ encoding: {
389
+ x: { field: "data", type: "quantitative" },
390
+ y: { field: "data", type: "quantitative" },
391
+ },
392
+ },
393
+ UnitView
394
+ );
395
+
396
+ for (const channel of primaryPositionalChannels) {
397
+ // Extract domain from data
398
+ view.getScaleResolution(channel).reconfigure();
399
+ }
400
+
401
+ const d = /** @param {import("../spec/channel").Channel} channel*/ (
402
+ channel
403
+ ) => view.getScaleResolution(channel).getScale().domain();
404
+
405
+ expect(d("x")).toEqual([0, 3]);
406
+ expect(d("y")).toEqual([0, 3]);
407
+ });
408
+
409
+ test("Channels with quantitative fields do not include zero in their scale domain if the domain has been defined explicitly", async () => {
410
+ const view = await createAndInitialize(
411
+ {
412
+ data: { values: [2, 3] },
413
+ mark: "point",
414
+ encoding: {
415
+ x: {
416
+ field: "data",
417
+ type: "quantitative",
418
+ scale: { domain: [1, 4] },
419
+ },
420
+ y: {
421
+ field: "data",
422
+ type: "quantitative",
423
+ scale: { domain: [1, 4] },
424
+ },
425
+ },
426
+ },
427
+ UnitView
428
+ );
429
+
430
+ const d = /** @param {Channel} channel*/ (channel) =>
431
+ view.getScaleResolution(channel).getScale().domain();
432
+
433
+ expect(d("x")).toEqual([1, 4]);
434
+ expect(d("x")).toEqual([1, 4]);
435
+ });
436
+
437
+ test("Channels with quantitative fields do not include zero in their scale domain if zero is explicitly false", async () => {
438
+ const view = await createAndInitialize(
439
+ {
440
+ data: { values: [2, 3] },
441
+ mark: "point",
442
+ encoding: {
443
+ x: {
444
+ field: "data",
445
+ type: "quantitative",
446
+ scale: { zero: false },
447
+ },
448
+ y: {
449
+ field: "data",
450
+ type: "quantitative",
451
+ scale: { zero: false },
452
+ },
453
+ },
454
+ },
455
+ UnitView
456
+ );
457
+
458
+ for (const channel of primaryPositionalChannels) {
459
+ // Extract domain from data
460
+ view.getScaleResolution(channel).reconfigure();
461
+ }
462
+
463
+ const d = /** @param {Channel} channel*/ (channel) =>
464
+ view.getScaleResolution(channel).getScale().domain();
465
+
466
+ expect(d("x")).toEqual([2, 3]);
467
+ expect(d("y")).toEqual([2, 3]);
468
+ });
469
+ });
470
+
471
+ describe("Named scales", () => {
472
+ test("Resolution of shared scales with conflicting names fails with an exception", async () => {
473
+ return expect(
474
+ createAndInitialize(
475
+ {
476
+ data: { values: [1, 2] },
477
+ layer: [
478
+ {
479
+ mark: "point",
480
+ encoding: {
481
+ x: {
482
+ field: "data",
483
+ type: "quantitative",
484
+ scale: { name: "scale_1" },
485
+ },
486
+ },
487
+ },
488
+ {
489
+ mark: "point",
490
+ encoding: {
491
+ x: {
492
+ field: "data",
493
+ type: "quantitative",
494
+ scale: { name: "scale_2" },
495
+ },
496
+ },
497
+ },
498
+ ],
499
+ },
500
+ LayerView
501
+ )
502
+ ).rejects.toThrow(/conflicting/);
503
+ });
504
+
505
+ test("A name is properly registered to the ScaleResolution object", async () => {
506
+ expect(
507
+ await createAndInitialize(
508
+ {
509
+ data: { values: [1, 2] },
510
+ layer: [
511
+ {
512
+ mark: "point",
513
+ encoding: {
514
+ x: {
515
+ field: "data",
516
+ type: "quantitative",
517
+ scale: { name: "scale_1" },
518
+ },
519
+ },
520
+ },
521
+ {
522
+ mark: "point",
523
+ encoding: {
524
+ x: {
525
+ field: "data",
526
+ type: "quantitative",
527
+ scale: { name: "scale_1" },
528
+ },
529
+ },
530
+ },
531
+ ],
532
+ },
533
+ LayerView
534
+ ).then((view) => view.getScaleResolution("x"))
535
+ ).toHaveProperty("name", "scale_1");
536
+ });
537
+
538
+ test("The scale name must be unique among the scale resolutions", async () => {
539
+ return expect(
540
+ createAndInitialize(
541
+ {
542
+ resolve: { scale: { x: "independent" } },
543
+ data: { values: [1, 2] },
544
+ layer: [
545
+ {
546
+ mark: "point",
547
+ encoding: {
548
+ x: {
549
+ field: "data",
550
+ type: "quantitative",
551
+ scale: { name: "scale_1" },
552
+ },
553
+ },
554
+ },
555
+ {
556
+ mark: "point",
557
+ encoding: {
558
+ x: {
559
+ field: "data",
560
+ type: "quantitative",
561
+ scale: { name: "scale_1" },
562
+ },
563
+ },
564
+ },
565
+ ],
566
+ },
567
+ LayerView
568
+ )
569
+ ).rejects.toThrow(/multiple/);
570
+ });
571
+ });
@@ -0,0 +1,40 @@
1
+ import { ComplexDomain, NumericDomain } from "../spec/scale";
2
+ import ScaleResolution from "./scaleResolution";
3
+
4
+ export interface ScaleResolutionEvent {
5
+ type: "domain";
6
+
7
+ scaleResolution: ScaleResolution;
8
+ }
9
+
10
+ export type ScaleResolutionListener = (event: ScaleResolutionEvent) => void;
11
+
12
+ /**
13
+ * A public API for ScaleResolution
14
+ */
15
+ export interface ScaleResolutionApi {
16
+ addEventListener(type: "domain", listener: ScaleResolutionListener): void;
17
+
18
+ removeEventListener(
19
+ type: "domain",
20
+ listener: ScaleResolutionListener
21
+ ): void;
22
+
23
+ /**
24
+ * Returns the current, possible zoomed domain.
25
+ */
26
+ getDomain(): any[];
27
+
28
+ /**
29
+ * Returns the current, possible zoomed domain converted into complex objects
30
+ * such as genomic coordinates.
31
+ */
32
+ getComplexDomain(): NumericDomain | ComplexDomain;
33
+
34
+ isZoomable(): boolean;
35
+
36
+ zoomTo(
37
+ domain: number[] | ComplexDomain,
38
+ duration?: boolean | number
39
+ ): Promise<void>;
40
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Utils for Jest tests
3
+ * TODO: Find a better place and convention
4
+ *
5
+ * @typedef {import("./view").default} View
6
+ * @typedef {import("../spec/view").ViewSpec} ViewSpec
7
+ * @typedef {import("./viewContext").default} ViewContext
8
+ */
9
+
10
+ import { resolveScalesAndAxes, initializeData } from "./viewUtils";
11
+ import AccessorFactory from "../encoder/accessor";
12
+ import DataFlow from "../data/dataFlow";
13
+ import { ViewFactory } from "./viewFactory";
14
+
15
+ /** @type {<V extends View>(spec: ViewSpec, viewClass: { new(...args: any[]): V }, context?: ViewContext) => V} */
16
+ export function create(spec, viewClass, context = undefined) {
17
+ const viewTypeRegistry = new ViewFactory();
18
+ /** @type {ViewContext} */
19
+ const c = {
20
+ ...(context || {}),
21
+ accessorFactory: new AccessorFactory(),
22
+
23
+ createView: function (spec, parent, defaultName) {
24
+ return viewTypeRegistry.createView(spec, c, parent, defaultName);
25
+ },
26
+ };
27
+
28
+ const view = c.createView(spec, null, "root");
29
+
30
+ if (!(view instanceof viewClass)) {
31
+ throw new Error("ViewClass and the spec do not match!");
32
+ }
33
+
34
+ return view;
35
+ }
36
+
37
+ /** @type {<V extends View>(spec: ViewSpec, viewClass: { new(...args: any[]): V }, context?: ViewContext) => Promise<V>} */
38
+ export async function createAndInitialize(
39
+ spec,
40
+ viewClass,
41
+ context = undefined
42
+ ) {
43
+ context = { ...(context || {}), dataFlow: new DataFlow() };
44
+ const view = create(spec, viewClass, context);
45
+ resolveScalesAndAxes(view);
46
+ await initializeData(view, context.dataFlow);
47
+ return view;
48
+ }