@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
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .genome-spy{font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";position:relative}.genome-spy canvas{transform:scale(1);opacity:1;transition:transform .6s,opacity .6s}.genome-spy .loading-message{position:absolute;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:center}.genome-spy .loading-message .message{color:#666;opacity:0;transition:opacity .7s}.genome-spy.loading canvas{transform:scale(.95);opacity:0}.genome-spy.loading .loading-message .message{opacity:1}.genome-spy.loading .ellipsis{animation:blinker 1s linear infinite}@keyframes blinker{50%{opacity:0}}.genome-spy .tooltip{position:absolute;max-width:450px;overflow:hidden;background:#f6f6f6;padding:10px;font-size:13px;box-shadow:0 3px 15px #00000036;pointer-events:none;z-index:100}.genome-spy .tooltip>:last-child{margin-bottom:0}.genome-spy .tooltip>.title{padding-bottom:5px;margin-bottom:5px;border-bottom:1px dashed #b6b6b6}.genome-spy .tooltip .summary{font-size:12px}.genome-spy .tooltip table{border-collapse:collapse}.genome-spy .tooltip table:first-child{margin-top:0}.genome-spy .tooltip table th,.genome-spy .tooltip table td{padding:2px .4em;vertical-align:top}.genome-spy .tooltip table th:first-child,.genome-spy .tooltip table td:first-child{padding-left:0}.genome-spy .tooltip table th{text-align:left;font-weight:bold}.genome-spy .tooltip .color-legend{display:inline-block;width:.8em;height:.8em;margin-left:.4em;box-shadow:0 0 3px 1px #fff}.genome-spy .tooltip .attributes .hovered{background-color:#e0e0e0}.genome-spy .tooltip .na{color:#aaa;font-style:italic;font-size:80%}.genome-spy .gene-track-tooltip .summary{font-size:90%}.genome-spy .message-box{display:flex;align-items:center;justify-content:center;position:absolute;top:0;height:100%;width:100%}.genome-spy .message-box>div{border:1px solid red;padding:10px;background:#fff0f0}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@genome-spy/core",
3
+ "description": "GenomeSpy, a visualization grammar and a GPU-accelerated rendering engine for genomic (and other) data",
4
+ "author": {
5
+ "name": "Kari Lavikka",
6
+ "email": "kari.lavikka@helsinki.fi"
7
+ },
8
+ "contributors": [],
9
+ "license": "BSD-2-Clause",
10
+ "version": "0.14.0",
11
+ "main": "dist/index.js",
12
+ "module": "src/index.js",
13
+ "exports": {
14
+ ".": "./src/index.js",
15
+ "./*": "./src/*"
16
+ },
17
+ "files": [
18
+ "dist/",
19
+ "src/"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "github:tuner/genome-spy",
24
+ "directory": "packages/core"
25
+ },
26
+ "scripts": {
27
+ "dev": "node dev-server.js",
28
+ "build": "vite build",
29
+ "prepublishOnly": "npm run build",
30
+ "checkSpec": "tsc --allowJs --checkJs --strict --noEmit --moduleResolution node --target es6 src/spec/root.d.ts",
31
+ "buildSchema": "ts-json-schema-generator --path 'src/spec/*.ts' --type ViewSpec > dist/genome-spy-schema.json"
32
+ },
33
+ "dependencies": {
34
+ "@types/d3-array": "^3.0.2",
35
+ "@types/d3-dsv": "^3.0.0",
36
+ "@types/d3-ease": "^3.0.0",
37
+ "@types/d3-format": "^3.0.1",
38
+ "@types/d3-interpolate": "^3.0.1",
39
+ "@types/d3-scale": "^4.0.2",
40
+ "d3-array": "^3.1.1",
41
+ "d3-color": "^3.0.1",
42
+ "d3-ease": "^3.0.1",
43
+ "d3-format": "^3.0.1",
44
+ "flatqueue": "^1.2.1",
45
+ "internmap": "^2.0.3",
46
+ "lit-html": "^2.0.2",
47
+ "twgl.js": "^4.19.1",
48
+ "vega-expression": "^5.0.0",
49
+ "vega-loader": "^4.4.0",
50
+ "vega-scale": "^7.1.1",
51
+ "vega-util": "^1.16.0"
52
+ },
53
+ "gitHead": "f7b1df1d4279575e6a1c0a4ced4a1fb0fe05db0e"
54
+ }
@@ -0,0 +1,178 @@
1
+ import { InternMap } from "internmap";
2
+ import { group } from "d3-array";
3
+ import { compare } from "vega-util";
4
+ import iterateNestedMaps from "../utils/iterateNestedMaps";
5
+ import FlowNode, { isFacetBatch } from "./flowNode";
6
+ import { field } from "../utils/field";
7
+ import { asArray } from "../utils/arrayUtils";
8
+
9
+ /**
10
+ * Collects (materializes) the data that flows through this node.
11
+ * The collected data can be optionally grouped and sorted.
12
+ *
13
+ * Grouping is primarily intended for handling faceted data.
14
+ *
15
+ * @typedef {import("../spec/transform").CollectParams} CollectParams
16
+ * @typedef {import("./flowNode").Data} Data
17
+ */
18
+ export default class Collector extends FlowNode {
19
+ /**
20
+ * @param {CollectParams} [params]
21
+ */
22
+ constructor(params) {
23
+ super();
24
+
25
+ this.params = params ?? { type: "collect" };
26
+
27
+ /** @type {(function(Collector):void)[]} */
28
+ this.observers = [];
29
+
30
+ /** @type {Map<any | any[], Data>} */
31
+ this.facetBatches = undefined;
32
+
33
+ this._init();
34
+ }
35
+
36
+ _init() {
37
+ /** @type {Data} */
38
+ this._data = [];
39
+
40
+ // TODO: Consider nested maps
41
+ this.facetBatches = new InternMap([], JSON.stringify);
42
+ this.facetBatches.set(undefined, this._data);
43
+ }
44
+
45
+ reset() {
46
+ super.reset();
47
+ this._init();
48
+ }
49
+
50
+ /**
51
+ *
52
+ * @param {import("./flowNode").Datum} datum
53
+ */
54
+ handle(datum) {
55
+ this._data.push(datum);
56
+ }
57
+
58
+ /**
59
+ * @param {import("./flowBatch").FlowBatch} flowBatch
60
+ */
61
+ beginBatch(flowBatch) {
62
+ // TODO: Propagate batches to children(?)
63
+
64
+ if (isFacetBatch(flowBatch)) {
65
+ this._data = [];
66
+ this.facetBatches.set(asArray(flowBatch.facetId), this._data);
67
+ }
68
+ }
69
+
70
+ complete() {
71
+ const sort = this.params?.sort;
72
+ // Vega's "compare" function is incredibly slow (uses megamorphic field accessor)
73
+ // TODO: Implement a replacement for static data types
74
+ const comparator = sort ? compare(sort.field, sort.order) : undefined;
75
+
76
+ /** @param {any[]} data */
77
+ const sortData = (data) => {
78
+ if (comparator) {
79
+ data.sort(comparator);
80
+ }
81
+ };
82
+
83
+ if (this.params.groupby?.length) {
84
+ if (this.facetBatches.size > 1) {
85
+ throw new Error("TODO: Support faceted data!");
86
+ }
87
+
88
+ const accessors = this.params.groupby.map((fieldName) =>
89
+ field(fieldName)
90
+ );
91
+ // @ts-ignore
92
+ const groups = group(this._data, ...accessors);
93
+
94
+ this.facetBatches.clear();
95
+ for (const [key, data] of iterateNestedMaps(groups)) {
96
+ this.facetBatches.set(key, data);
97
+ }
98
+ }
99
+
100
+ for (const data of this.facetBatches.values()) {
101
+ // TODO: Only sort if not already sorted
102
+ sortData(data);
103
+ }
104
+
105
+ if (this.children.length) {
106
+ for (const data of this.facetBatches.values()) {
107
+ for (const datum of data) {
108
+ this._propagate(datum);
109
+ }
110
+ }
111
+ }
112
+
113
+ super.complete();
114
+
115
+ for (const observer of this.observers) {
116
+ observer(this);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * @returns {Iterable<import("./flowNode").Datum>}
122
+ */
123
+ getData() {
124
+ this._checkStatus();
125
+
126
+ switch (this.facetBatches.size) {
127
+ case 0:
128
+ return [];
129
+ case 1:
130
+ return [...this.facetBatches.values()][0];
131
+ default: {
132
+ const groups = this.facetBatches;
133
+ return {
134
+ [Symbol.iterator]: function* generator() {
135
+ for (const data of groups.values()) {
136
+ for (let i = 0; i < data.length; i++) {
137
+ yield data[i];
138
+ }
139
+ }
140
+ },
141
+ };
142
+ }
143
+ }
144
+ }
145
+
146
+ /**
147
+ *
148
+ * @param {(datum: import("./flowNode").Datum) => void} visitor
149
+ */
150
+ visitData(visitor) {
151
+ this._checkStatus();
152
+
153
+ for (const data of this.facetBatches.values()) {
154
+ for (let i = 0; i < data.length; i++) {
155
+ visitor(data[i]);
156
+ }
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Returns the total number of data items collected.
162
+ */
163
+ getItemCount() {
164
+ let count = 0;
165
+ for (const data of this.facetBatches.values()) {
166
+ count += data.length;
167
+ }
168
+ return count;
169
+ }
170
+
171
+ _checkStatus() {
172
+ if (!this.completed) {
173
+ throw new Error(
174
+ "Data propagation is not completed! No data are available."
175
+ );
176
+ }
177
+ }
178
+ }
@@ -0,0 +1,82 @@
1
+ import Collector from "./collector";
2
+
3
+ const data = [1, 5, 2, 4, 3].map((x) => ({ x }));
4
+
5
+ test("Collector collects data", () => {
6
+ const collector = new Collector();
7
+
8
+ for (const d of data) {
9
+ collector.handle(d);
10
+ }
11
+ collector.complete();
12
+
13
+ expect(collector.getData()).toEqual(data);
14
+ });
15
+
16
+ test("Collector collects and sorts data", () => {
17
+ const collector = new Collector({
18
+ type: "collect",
19
+ sort: { field: ["x"] },
20
+ });
21
+
22
+ for (const d of data) {
23
+ collector.handle(d);
24
+ }
25
+ collector.complete();
26
+
27
+ expect([...collector.getData()]).toEqual(
28
+ [1, 2, 3, 4, 5].map((x) => ({ x }))
29
+ );
30
+ });
31
+
32
+ test("Collector collects, groups, and sorts data", () => {
33
+ const collector = new Collector({
34
+ type: "collect",
35
+ sort: { field: ["x"] },
36
+ groupby: ["a", "b"],
37
+ });
38
+
39
+ const data = [
40
+ { a: 1, b: 1, x: 1 },
41
+ { a: 1, b: 2, x: 2 },
42
+ { a: 1, b: 2, x: 3 },
43
+ { a: 2, b: 1, x: 4 },
44
+ { a: 2, b: 1, x: 5 },
45
+ { a: 2, b: 2, x: 6 },
46
+ ];
47
+
48
+ for (const d of data) {
49
+ collector.handle(d);
50
+ }
51
+ collector.complete();
52
+
53
+ const cd = [...collector.getData()];
54
+
55
+ expect(cd.map((d) => ({ x: d.x }))).toEqual(
56
+ [1, 2, 3, 4, 5, 6].map((x) => ({ x }))
57
+ );
58
+
59
+ /** @param {any[]} group*/
60
+ const getGroupX = (group) =>
61
+ collector.facetBatches.get(group).map((d) => d.x);
62
+
63
+ expect(getGroupX([1, 1])).toEqual([1]);
64
+ expect(getGroupX([1, 2])).toEqual([2, 3]);
65
+ expect(getGroupX([2, 1])).toEqual([4, 5]);
66
+ expect(getGroupX([2, 2])).toEqual([6]);
67
+
68
+ expect(new Set(collector.facetBatches.keys())).toEqual(
69
+ new Set([
70
+ [1, 1],
71
+ [1, 2],
72
+ [2, 1],
73
+ [2, 2],
74
+ ])
75
+ );
76
+ });
77
+
78
+ test("Collector throws on incomplete flow", () => {
79
+ const collector = new Collector();
80
+
81
+ expect(() => collector.getData()).toThrow();
82
+ });
@@ -0,0 +1,109 @@
1
+ /**
2
+ *
3
+ * @typedef {import("./sources/dataSource").default} DataSource
4
+ * @typedef {import("./collector").default} Collector
5
+ */
6
+
7
+ /**
8
+ * @template H A key (string, object, whatever) that is used to retrieve
9
+ * data sources and collectors.
10
+ */
11
+ export default class DataFlow {
12
+ constructor() {
13
+ /** @type {Map<H, DataSource>} */
14
+ this._dataSourcesByHost = new Map();
15
+
16
+ /** @type {Map<H, Collector>} */
17
+ this._collectorsByHost = new Map();
18
+
19
+ /** @type {Map<H, (function(Collector):void)[]>} */
20
+ this._observers = new Map();
21
+ }
22
+
23
+ get dataSources() {
24
+ return [...new Set(this._dataSourcesByHost.values()).values()];
25
+ }
26
+
27
+ get collectors() {
28
+ return [...this._collectorsByHost.values()];
29
+ }
30
+
31
+ /**
32
+ * Adds a callback function that will be called when a collector has completed.
33
+ *
34
+ * @param {function(Collector):void} callback
35
+ * @param {H} key
36
+ */
37
+ addObserver(callback, key) {
38
+ let arr = this._observers.get(key);
39
+ if (!arr) {
40
+ arr = [];
41
+ this._observers.set(key, arr);
42
+ }
43
+
44
+ arr.push(callback);
45
+ }
46
+
47
+ /**
48
+ *
49
+ * @param {Collector} collector
50
+ * @param {H} key
51
+ */
52
+ _relayObserverCallback(collector, key) {
53
+ const arr = this._observers.get(key);
54
+ if (arr) {
55
+ for (const callback of arr) {
56
+ // eslint-disable-next-line callback-return
57
+ callback(collector);
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ *
64
+ * @param {DataSource} dataSource
65
+ * @param {H} key
66
+ */
67
+ addDataSource(dataSource, key) {
68
+ this._dataSourcesByHost.set(key, dataSource);
69
+ }
70
+
71
+ /**
72
+ *
73
+ * @param {H} key
74
+ */
75
+ findDataSourceByKey(key) {
76
+ return this._dataSourcesByHost.get(key);
77
+ }
78
+
79
+ /**
80
+ *
81
+ * @param {Collector} collector
82
+ * @param {H} key
83
+ */
84
+ addCollector(collector, key) {
85
+ this._collectorsByHost.set(key, collector);
86
+ collector.observers.push((collector) =>
87
+ this._relayObserverCallback(collector, key)
88
+ );
89
+ }
90
+
91
+ /**
92
+ *
93
+ * @param {H} key
94
+ */
95
+ findCollectorByKey(key) {
96
+ return this._collectorsByHost.get(key);
97
+ }
98
+
99
+ /**
100
+ * Allows the flow nodes to perform final initialization after the flow
101
+ * structure has been built and optimized.
102
+ * Must be called before any data are to be propagated.
103
+ */
104
+ initialize() {
105
+ for (const ds of this.dataSources) {
106
+ ds.visit((node) => node.initialize());
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,3 @@
1
+ describe("DataFlow", () => {
2
+ test.todo("TODO");
3
+ });
@@ -0,0 +1,17 @@
1
+ import FlowNode from "./flowNode";
2
+
3
+ /**
4
+ * @typedef {object} FacetParams
5
+ * @prop {string[]} groupby
6
+ */
7
+ export default class FacetNode extends FlowNode {
8
+ /**
9
+ * @param {FacetParams} params
10
+ */
11
+ constructor(params) {
12
+ super();
13
+
14
+ /** @type {Map<any | any[], FlowNode>} */
15
+ this.subflows = new Map();
16
+ }
17
+ }
@@ -0,0 +1,71 @@
1
+ import FilterTransform from "./transforms/filter";
2
+ import FormulaTransform from "./transforms/formula";
3
+ import Collector from "./collector";
4
+ import { SynchronousSequenceSource } from "./flowTestUtils";
5
+
6
+ describe("Test flow graphs", () => {
7
+ test("Trivial graph: sequence to collector", () => {
8
+ const source = new SynchronousSequenceSource(5);
9
+ const collector = new Collector();
10
+ source.addChild(collector);
11
+
12
+ source.dispatch();
13
+
14
+ expect(collector.getData()).toEqual(
15
+ [0, 1, 2, 3, 4].map((d) => ({
16
+ data: d,
17
+ }))
18
+ );
19
+ });
20
+
21
+ test("Trivial branching: sequence to two collectors", () => {
22
+ const source = new SynchronousSequenceSource(5);
23
+ const collector1 = new Collector();
24
+ source.addChild(collector1);
25
+ const collector2 = new Collector();
26
+ source.addChild(collector2);
27
+
28
+ source.dispatch();
29
+
30
+ expect(collector1.getData()).not.toBe(collector2._data);
31
+
32
+ expect(collector1.getData()).toEqual(
33
+ [0, 1, 2, 3, 4].map((d) => ({
34
+ data: d,
35
+ }))
36
+ );
37
+
38
+ expect(collector2.getData()).toEqual(
39
+ [0, 1, 2, 3, 4].map((d) => ({
40
+ data: d,
41
+ }))
42
+ );
43
+ });
44
+
45
+ test("Longer chain of nodes", () => {
46
+ const source = new SynchronousSequenceSource(10);
47
+ const filter = new FilterTransform({
48
+ type: "filter",
49
+ expr: "datum.data < 5",
50
+ });
51
+ const formula = new FormulaTransform({
52
+ type: "formula",
53
+ expr: "datum.data * 2",
54
+ as: "data",
55
+ });
56
+ const collector = new Collector();
57
+
58
+ source.addChild(filter);
59
+ filter.addChild(formula);
60
+ formula.addChild(collector);
61
+
62
+ source.visit((node) => node.initialize());
63
+ source.dispatch();
64
+
65
+ expect(collector.getData()).toEqual(
66
+ [0, 2, 4, 6, 8].map((d) => ({
67
+ data: d,
68
+ }))
69
+ );
70
+ });
71
+ });
@@ -0,0 +1,40 @@
1
+ import { Field } from "../spec/transform";
2
+
3
+ export interface FlowBatchBase {
4
+ type: string;
5
+ }
6
+
7
+ /**
8
+ * Indicates that the contents of a new file will be propagated. The fields of
9
+ * the file may or may not differ from the previous file. The data items within
10
+ * a single batch must have homogenous fields.
11
+ *
12
+ * FlowNodes can react to FileBatches and, for example, clear their internal
13
+ * states that expect specific fields in a specific layout.
14
+ */
15
+ export interface FileBatch {
16
+ type: "file";
17
+
18
+ /**
19
+ * An absolute or relative url where the file was loaded from.
20
+ */
21
+ url?: string;
22
+ }
23
+
24
+ /**
25
+ * Indicates that a new group/facet will be propagated. All data items that
26
+ * belong to a specific facet must be propagated within a single batch.
27
+ */
28
+ export interface FacetBatch {
29
+ type: "facet";
30
+
31
+ facetId: any | any[];
32
+
33
+ /**
34
+ * The field or fields that were used for partitioning the data.
35
+ * May be missing if partitioning is not based on the fields.
36
+ */
37
+ facetField?: Field | Field[];
38
+ }
39
+
40
+ export type FlowBatch = FileBatch | FacetBatch;