@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,362 @@
1
+ import Collector from "../data/collector";
2
+ import createTransform from "../data/transforms/transformFactory";
3
+ import createDataSource from "../data/sources/dataSourceFactory";
4
+ import UnitView from "./unitView";
5
+ import { BEHAVIOR_MODIFIES } from "../data/flowNode";
6
+ import CloneTransform from "../data/transforms/clone";
7
+ import { isDynamicCallbackData } from "../data/sources/dynamicCallbackSource";
8
+ import DataFlow from "../data/dataFlow";
9
+ import DataSource from "../data/sources/dataSource";
10
+ import {
11
+ isChannelDefWithScale,
12
+ isChromPosDef,
13
+ isDatumDef,
14
+ isFieldDef,
15
+ isPositionalChannel,
16
+ getPrimaryChannel,
17
+ } from "../encoder/encoder";
18
+ import LinearizeGenomicCoordinate from "../data/transforms/linearizeGenomicCoordinate";
19
+ import { isAggregateSamplesSpec } from "./viewFactory";
20
+ import { group } from "d3-array";
21
+ import IdentifierTransform from "../data/transforms/identifier";
22
+ import { invalidate } from "../utils/propertyCacher";
23
+ import NamedSource, { isNamedData } from "../data/sources/namedSource";
24
+
25
+ /**
26
+ * @typedef {import("./view").default} View
27
+ * @typedef {import("../data/flowNode").default} FlowNode
28
+ * @typedef {import("../spec/channel").Channel} Channel
29
+ * @typedef {import("../spec/channel").Encoding} Encoding
30
+ *
31
+ * @param {View} root
32
+ * @param {DataFlow<View>} [existingFlow] Add data flow
33
+ * graphs to an existing DataFlow object.
34
+ */
35
+ export function buildDataFlow(root, existingFlow) {
36
+ /** @type {FlowNode[]} "Current nodes" on the path from view root to the current view */
37
+ const nodeStack = [];
38
+
39
+ /** @type {FlowNode} */
40
+ let currentNode;
41
+
42
+ /** @type {DataFlow<View>} */
43
+ const dataFlow = existingFlow ?? new DataFlow();
44
+
45
+ /** @type {(function():void)[]} */
46
+ const postProcessOps = [];
47
+
48
+ /**
49
+ * @param {FlowNode} node
50
+ * @param {function():Error} [onMissingParent]
51
+ * @returns {FlowNode} The appended node
52
+ */
53
+ function appendNode(node, onMissingParent = () => undefined) {
54
+ if (!currentNode) {
55
+ throw (
56
+ onMissingParent() ||
57
+ new Error("Cannot append data flow node, no parent exist!")
58
+ );
59
+ }
60
+ currentNode.addChild(node);
61
+ currentNode = node;
62
+ return node;
63
+ }
64
+
65
+ /**
66
+ * @param {FlowNode} transform
67
+ * @param {any} [params]
68
+ * @returns {FlowNode} The appended node
69
+ */
70
+ function appendTransform(transform, params) {
71
+ return appendNode(
72
+ transform,
73
+ () =>
74
+ new Error(
75
+ `Cannot append a transform because no (inherited) data are available! ${
76
+ params ? JSON.stringify(params) : ""
77
+ }`
78
+ )
79
+ );
80
+ }
81
+
82
+ /**
83
+ *
84
+ * @param {import("../spec/transform").TransformParams[]} transforms
85
+ * @param {View} view
86
+ */
87
+ function createTransforms(transforms, view) {
88
+ for (const params of transforms) {
89
+ /** @type {FlowNode} */
90
+ let transform;
91
+ try {
92
+ transform = createTransform(params, view);
93
+ } catch (e) {
94
+ console.warn(e);
95
+ throw new Error(
96
+ `Cannot initialize "${params.type}" transform: ${e}`
97
+ );
98
+ }
99
+
100
+ if (transform.behavior & BEHAVIOR_MODIFIES) {
101
+ // Make defensive copies before every modifying transform to
102
+ // ensure that modifications don't inadvertently become visible
103
+ // in other branches of the flow.
104
+ // These can be later optimized away where possible.
105
+ appendTransform(new CloneTransform());
106
+ }
107
+ appendTransform(transform);
108
+ }
109
+ }
110
+
111
+ /** @param {View} view */
112
+ const processView = (view) => {
113
+ nodeStack.push(currentNode);
114
+
115
+ if (view.spec.data) {
116
+ const dataSource = isDynamicCallbackData(view.spec.data)
117
+ ? view.getDynamicDataSource()
118
+ : isNamedData(view.spec.data)
119
+ ? new NamedSource(view.spec.data, view.context.getNamedData)
120
+ : createDataSource(view.spec.data, view.getBaseUrl());
121
+
122
+ currentNode = dataSource;
123
+ dataFlow.addDataSource(dataSource, view);
124
+ }
125
+
126
+ if (view.spec.transform) {
127
+ createTransforms(view.spec.transform, view);
128
+ }
129
+
130
+ if (view instanceof UnitView) {
131
+ if (!currentNode) {
132
+ throw new Error("A unit view has no (inherited) data source");
133
+ }
134
+
135
+ // Support chrom/pos channelDefs
136
+ const linearize = linearizeLocusAccess(view);
137
+ if (linearize) {
138
+ postProcessOps.push(linearize.rewrite);
139
+ for (const transform of linearize.transforms) {
140
+ // TODO: Transforms should not be added if they already exist in the flow.
141
+ // Alternatively they should be optimized away.
142
+ // TODO: Add CloneTransform
143
+ appendTransform(transform);
144
+ }
145
+ }
146
+
147
+ if (view.mark.isPickingParticipant()) {
148
+ // TODO: Add CloneTransform
149
+ appendTransform(
150
+ new IdentifierTransform({ type: "identifier" })
151
+ );
152
+ }
153
+
154
+ const collector = new Collector({
155
+ type: "collect",
156
+ groupby: view.getFacetFields(),
157
+ sort: getCompareParamsForView(
158
+ view,
159
+ linearize?.rewrittenEncoding
160
+ ),
161
+ });
162
+
163
+ appendNode(collector);
164
+ dataFlow.addCollector(collector, view);
165
+ }
166
+
167
+ if (isAggregateSamplesSpec(view.spec)) {
168
+ // TODO: implement summarization of layer views
169
+ }
170
+ };
171
+
172
+ /** @param {View} view */
173
+ processView.postOrder = (view) => {
174
+ currentNode = nodeStack.pop();
175
+ };
176
+
177
+ root.visit(processView);
178
+
179
+ postProcessOps.forEach((op) => op());
180
+
181
+ return dataFlow;
182
+ }
183
+
184
+ /**
185
+ * Changes the ChromPos channelDefs into FieldDefs and returns
186
+ * LinearizeGenomicCoordinate transform(s) that should be inserted into
187
+ * the data flow.
188
+ *
189
+ * @param {View} view
190
+ */
191
+ export function linearizeLocusAccess(view) {
192
+ /** @type {FlowNode[]} */
193
+ const transforms = [];
194
+
195
+ /** @type {Encoding} */
196
+ const rewrittenEncoding = {};
197
+
198
+ /** @type {{ channel: Channel, chromPosDef: import("../spec/channel").ChromPosDef}[]} */
199
+ const channelsAndChromPosDefs = [];
200
+
201
+ // Optimize the number of transforms. Use only a single transform for positions
202
+ // that share the chromosome field and channel.
203
+ for (const [c, channelDef] of Object.entries(view.getEncoding())) {
204
+ const channel = /** @type {Channel} */ (c);
205
+ if (isPositionalChannel(channel) && isChromPosDef(channelDef)) {
206
+ channelsAndChromPosDefs.push({ channel, chromPosDef: channelDef });
207
+ }
208
+ }
209
+
210
+ // Nngh. group uses InternMap but doesn't have a way to define an interning function.
211
+ // Have to use multi-level grouping.
212
+ const grouped = group(
213
+ channelsAndChromPosDefs,
214
+ (d) => /** @type {"x" | "y"} */ (getPrimaryChannel(d.channel)),
215
+ (d) => d.chromPosDef.chrom
216
+ );
217
+
218
+ for (const [primaryChan, chromAndStuff] of grouped.entries()) {
219
+ for (const [chrom, chanChromPos] of chromAndStuff.entries()) {
220
+ /** @type {string[]} */
221
+ const pos = [];
222
+ /** @type {string[]} */
223
+ const as = [];
224
+ /** @type {number[]} */
225
+ const offset = [];
226
+
227
+ for (const { channel, chromPosDef } of chanChromPos) {
228
+ /** @param {string} str */
229
+ const strip = (str) => str.replace(/[^A-Za-z0-9_]/g, "");
230
+ const linearizedField = [
231
+ "_linearized_",
232
+ strip(chromPosDef.chrom),
233
+ "_",
234
+ strip(chromPosDef.pos),
235
+ ].join("");
236
+
237
+ // Prefer using the spec directly because getEncoding() returns inherited props too.
238
+ // TODO: I think this is not robust enough. Needs more work...
239
+ /** @type {any} */
240
+ const newFieldDef = {
241
+ ...(view.spec.encoding?.[channel] ??
242
+ view.getEncoding()[channel] ??
243
+ {}),
244
+ field: linearizedField,
245
+ };
246
+ delete newFieldDef.chrom;
247
+ delete newFieldDef.pos;
248
+ if (!newFieldDef.type && chromPosDef.type) {
249
+ newFieldDef.type = chromPosDef.type;
250
+ }
251
+
252
+ rewrittenEncoding[channel] = newFieldDef;
253
+
254
+ pos.push(chromPosDef.pos);
255
+ offset.push(chromPosDef.offset ?? 0);
256
+ as.push(linearizedField);
257
+ }
258
+
259
+ transforms.push(
260
+ new LinearizeGenomicCoordinate(
261
+ {
262
+ type: "linearizeGenomicCoordinate",
263
+ channel: primaryChan,
264
+ chrom: chrom,
265
+ pos,
266
+ offset,
267
+ as,
268
+ },
269
+ view
270
+ )
271
+ );
272
+ }
273
+ }
274
+
275
+ return transforms.length
276
+ ? {
277
+ transforms,
278
+ rewrittenEncoding,
279
+ /**
280
+ * Should be called after the whole flow has been created in order to
281
+ * not disrupt inheritance of encodings
282
+ */
283
+ rewrite: () => {
284
+ view.spec.encoding = {
285
+ ...view.spec.encoding,
286
+ ...rewrittenEncoding,
287
+ };
288
+ // This is so ugly...
289
+ invalidate(view.mark, "encoding");
290
+ },
291
+ }
292
+ : undefined;
293
+ }
294
+
295
+ /**
296
+ * @param {View} view
297
+ * @param {Record<string, import("../spec/channel").ChannelDef>} [encoding]
298
+ * @returns {import("../spec/transform").CompareParams}
299
+ */
300
+ function getCompareParamsForView(view, encoding) {
301
+ // TODO: Should sort by min(x, x2).
302
+ const e = { ...view.getEncoding(), ...encoding }.x;
303
+ if (isChannelDefWithScale(e)) {
304
+ if (view.getScaleResolution("x")?.isZoomable()) {
305
+ if (isFieldDef(e)) {
306
+ return { field: e.field };
307
+ } else if (isDatumDef(e)) {
308
+ // Nop
309
+ } else {
310
+ // TODO: Support expr by inserting a Formula transform
311
+ throw new Error(
312
+ "A zoomable x channel must be mapped to a field."
313
+ );
314
+ }
315
+ }
316
+ }
317
+ }
318
+
319
+ /**
320
+ * A helper function for creating linear data flows programmatically.
321
+ *
322
+ * @param {H} dataSource A data source or any other initial FlowNode.
323
+ * @param {...FlowNode} transforms
324
+ * @template {FlowNode} H
325
+ */
326
+ export function createChain(dataSource, ...transforms) {
327
+ /** @type {FlowNode} */
328
+ let node = dataSource;
329
+ for (const transform of transforms) {
330
+ node.addChild(transform);
331
+ node = transform;
332
+ }
333
+
334
+ /** @type {Collector} */
335
+ let collector;
336
+
337
+ if (node instanceof Collector) {
338
+ collector = node;
339
+ } else {
340
+ collector = new Collector();
341
+ node.addChild(collector);
342
+ }
343
+
344
+ /** @type {function():Promise<Iterable<import("../data/flowNode").Datum>>} */
345
+ let loadAndCollect;
346
+ if (dataSource instanceof DataSource) {
347
+ loadAndCollect = async () => {
348
+ await dataSource.load();
349
+ return collector.getData();
350
+ };
351
+ } else {
352
+ loadAndCollect = async () => {
353
+ throw new Error("The root node is not derived from DataSource!");
354
+ };
355
+ }
356
+
357
+ return {
358
+ dataSource,
359
+ collector,
360
+ loadAndCollect,
361
+ };
362
+ }
@@ -0,0 +1,124 @@
1
+ import Collector from "../data/collector";
2
+ import FlowNode from "../data/flowNode";
3
+ import FilterTransform from "../data/transforms/filter";
4
+ import FormulaTransform from "../data/transforms/formula";
5
+ import InlineSource from "../data/sources/inlineSource";
6
+ import SequenceSource from "../data/sources/sequenceSource";
7
+ import { buildDataFlow } from "./flowBuilder";
8
+ import { create } from "./testUtils";
9
+ import CloneTransform from "../data/transforms/clone";
10
+ import LayerView from "./layerView";
11
+ import UnitView from "./unitView";
12
+
13
+ /**
14
+ *
15
+ * @param {FlowNode} root
16
+ * @param {number[]} path
17
+ */
18
+ function byPath(root, path) {
19
+ for (const elem of path) {
20
+ root = root.children[elem];
21
+ }
22
+ return root;
23
+ }
24
+
25
+ /** @type {import("../spec/mark").MarkConfigAndType} */
26
+ const mark = {
27
+ type: "rect",
28
+ tooltip: null,
29
+ };
30
+
31
+ test("Trivial flow", () => {
32
+ const root = create(
33
+ {
34
+ data: { values: [3.141] },
35
+ transform: [
36
+ {
37
+ type: "formula",
38
+ expr: "datum.data * 2",
39
+ as: "x",
40
+ },
41
+ ],
42
+ mark,
43
+ },
44
+ UnitView
45
+ );
46
+
47
+ const flow = buildDataFlow(root);
48
+ const dataSource = flow.dataSources[0];
49
+
50
+ expect(dataSource).toBeInstanceOf(InlineSource);
51
+ expect(byPath(dataSource, [0])).toBeInstanceOf(CloneTransform);
52
+ expect(byPath(dataSource, [0, 0])).toBeInstanceOf(FormulaTransform);
53
+ expect(byPath(dataSource, [0, 0, 0])).toBeInstanceOf(Collector);
54
+
55
+ expect(flow.collectors[0]).toBe(byPath(dataSource, [0, 0, 0]));
56
+ });
57
+
58
+ test("Branching flow", () => {
59
+ const root = create(
60
+ {
61
+ data: { values: [3.141] },
62
+ layer: [
63
+ {
64
+ transform: [
65
+ {
66
+ type: "formula",
67
+ expr: "datum.data * 2",
68
+ as: "x",
69
+ },
70
+ ],
71
+ mark,
72
+ },
73
+ {
74
+ transform: [
75
+ {
76
+ type: "filter",
77
+ expr: "datum.data > 4",
78
+ },
79
+ ],
80
+ mark,
81
+ },
82
+ ],
83
+ },
84
+ LayerView
85
+ );
86
+
87
+ const dataSource = buildDataFlow(root).dataSources[0];
88
+
89
+ expect(dataSource).toBeInstanceOf(InlineSource);
90
+ // Formula transform modifies data and it should be implicitly preceded by CloneTransform
91
+ expect(byPath(dataSource, [0])).toBeInstanceOf(CloneTransform);
92
+ expect(byPath(dataSource, [0, 0])).toBeInstanceOf(FormulaTransform);
93
+ expect(byPath(dataSource, [0, 0, 0])).toBeInstanceOf(Collector);
94
+ expect(byPath(dataSource, [1])).toBeInstanceOf(FilterTransform);
95
+ expect(byPath(dataSource, [1, 0])).toBeInstanceOf(Collector);
96
+ });
97
+
98
+ test("Nested data sources", () => {
99
+ const root = create(
100
+ {
101
+ data: { values: [1] },
102
+ transform: [{ type: "filter", expr: "datum.data > 0" }],
103
+ layer: [
104
+ {
105
+ data: { sequence: { start: 0, stop: 5 } },
106
+ transform: [{ type: "formula", expr: "3", as: "foo" }],
107
+ mark,
108
+ },
109
+ ],
110
+ },
111
+ LayerView
112
+ );
113
+
114
+ const dataSources = buildDataFlow(root).dataSources;
115
+
116
+ expect(dataSources[0]).toBeInstanceOf(InlineSource);
117
+ expect(dataSources[0].children[0]).toBeInstanceOf(FilterTransform);
118
+ expect(dataSources[0].children[0].children.length).toEqual(0);
119
+
120
+ expect(byPath(dataSources[1], [])).toBeInstanceOf(SequenceSource);
121
+ expect(byPath(dataSources[1], [0])).toBeInstanceOf(CloneTransform);
122
+ expect(byPath(dataSources[1], [0, 0])).toBeInstanceOf(FormulaTransform);
123
+ expect(byPath(dataSources[1], [0, 0, 0])).toBeInstanceOf(Collector);
124
+ });
@@ -0,0 +1,19 @@
1
+ import View from "./view";
2
+
3
+ /**
4
+ * This is just a placeholder for custom tracks that are imported by name.
5
+ */
6
+ export default class ImportView extends View {
7
+ /**
8
+ *
9
+ * @param {import("../spec/view").ImportSpec} spec
10
+ * @param {import("./viewUtils").ViewContext} context
11
+ * @param {import("./view").default} parent
12
+ * @param {string} name
13
+ */
14
+ constructor(spec, context, parent, name) {
15
+ super(spec, context, parent, name);
16
+
17
+ this.spec = spec; // Set here again to keep types happy
18
+ }
19
+ }
@@ -0,0 +1,60 @@
1
+ import { isLayerSpec, isUnitSpec } from "./viewFactory";
2
+ import ContainerView from "./containerView";
3
+
4
+ /**
5
+ * @typedef {import("./view").default} View
6
+ */
7
+ export default class LayerView extends ContainerView {
8
+ /**
9
+ *
10
+ * @param {import("./viewUtils").LayerSpec} spec
11
+ * @param {import("./viewUtils").ViewContext} context
12
+ * @param {ContainerView} parent
13
+ * @param {string} name
14
+ */
15
+ constructor(spec, context, parent, name) {
16
+ super(spec, context, parent, name);
17
+
18
+ this.spec = spec;
19
+
20
+ /** @type {(LayerView | import("./unitView").default)[]} */
21
+ this.children = (spec.layer || []).map((childSpec, i) => {
22
+ if (isLayerSpec(childSpec) || isUnitSpec(childSpec)) {
23
+ return context.createView(childSpec, this, "layer" + i);
24
+ } else {
25
+ throw new Error(
26
+ "LayerView accepts only unit or layer specs as children!"
27
+ );
28
+ }
29
+ });
30
+ }
31
+
32
+ /**
33
+ * @returns {IterableIterator<View>}
34
+ */
35
+ *[Symbol.iterator]() {
36
+ for (const child of this.children) {
37
+ yield child;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * @param {import("./renderingContext/viewRenderingContext").default} context
43
+ * @param {import("../utils/layout/rectangle").default} coords
44
+ * @param {import("./view").RenderingOptions} [options]
45
+ */
46
+ render(context, coords, options = {}) {
47
+ if (!this.isVisible()) {
48
+ return;
49
+ }
50
+
51
+ coords = coords.shrink(this.getPadding());
52
+ context.pushView(this, coords);
53
+
54
+ for (const child of this.children) {
55
+ child.render(context, coords, options);
56
+ }
57
+
58
+ context.popView(this);
59
+ }
60
+ }
@@ -0,0 +1,44 @@
1
+ import { LocSize } from "../utils/layout/flexLayout";
2
+ import Rectangle from "../utils/layout/rectangle";
3
+
4
+ /**
5
+ * Describes the location of a sample facet. Left is the primary pos, right is for
6
+ * transitioning between two sets of samples.
7
+ */
8
+ export interface SampleFacetRenderingOptions {
9
+ /**
10
+ * Location and height on unit scale, zero at top
11
+ */
12
+ locSize: LocSize;
13
+
14
+ /**
15
+ * Target (during transition)
16
+ */
17
+ targetLocSize?: LocSize;
18
+ }
19
+
20
+ export interface RenderingOptions {
21
+ /**
22
+ * Which facet to render (if faceting is being used)
23
+ */
24
+ facetId?: any;
25
+
26
+ sampleFacetRenderingOptions?: SampleFacetRenderingOptions;
27
+
28
+ /**
29
+ * Clip rendering using the given rectangle.
30
+ * Mainly intended for clipping scrollable views.
31
+ */
32
+ clipRect?: Rectangle;
33
+ }
34
+
35
+ /**
36
+ * Options that affect the whole rendering pass.
37
+ */
38
+ export interface GlobalRenderingOptions {
39
+ /**
40
+ * Replace colors with unique ids for picking.
41
+ * Views that haven't enabled picking can be skipped.
42
+ */
43
+ picking?: boolean;
44
+ }
@@ -0,0 +1,51 @@
1
+ import ViewRenderingContext from "./viewRenderingContext";
2
+
3
+ /**
4
+ * @typedef {import("../view").default} View
5
+ */
6
+ export default class CompositeViewRenderingContext extends ViewRenderingContext {
7
+ /**
8
+ *
9
+ * @param {...ViewRenderingContext} contexts
10
+ */
11
+ constructor(...contexts) {
12
+ super({});
13
+
14
+ this.contexts = contexts;
15
+ }
16
+
17
+ /**
18
+ * Must be called when a view's render() method is entered
19
+ *
20
+ * @param {View} view
21
+ * @param {import("../../utils/layout/rectangle").default} coords View coordinates
22
+ * inside the padding.
23
+ */
24
+ pushView(view, coords) {
25
+ for (const context of this.contexts) {
26
+ context.pushView(view, coords);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Must be called when a view's render() method is being exited
32
+ *
33
+ * @param {View} view
34
+ */
35
+ popView(view) {
36
+ for (const context of this.contexts) {
37
+ context.popView(view);
38
+ }
39
+ }
40
+
41
+ /**
42
+ *
43
+ * @param {import("../../marks/mark").default} mark
44
+ * @param {import("../view").RenderingOptions} options
45
+ */
46
+ renderMark(mark, options) {
47
+ for (const context of this.contexts) {
48
+ context.renderMark(mark, options);
49
+ }
50
+ }
51
+ }