@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,32 @@
1
+ /**
2
+ * A bare-bones FASTA-parser
3
+ *
4
+ * @typedef {object} SequenceEntry
5
+ * @prop {string} identifier
6
+ * @prop {string} sequence
7
+ *
8
+ * @param {string} data
9
+ * @param {any} options
10
+ * @returns {SequenceEntry[]}
11
+ */
12
+ export default function fasta(data, options) {
13
+ /** @type {SequenceEntry[]} */
14
+ const sequences = [];
15
+
16
+ /** @type {SequenceEntry} */
17
+ let currentEntry;
18
+
19
+ for (const line of data.split("\n")) {
20
+ if (line.startsWith(">")) {
21
+ const identifier = line.match(/>(\S+)/)[1];
22
+ currentEntry = { identifier, sequence: "" };
23
+ sequences.push(currentEntry);
24
+ } else if (currentEntry) {
25
+ currentEntry.sequence += line.trim();
26
+ } else {
27
+ throw new Error("Invalid fasta file!");
28
+ }
29
+ }
30
+
31
+ return sequences;
32
+ }
@@ -0,0 +1,26 @@
1
+ import fasta from "./fasta";
2
+
3
+ test("fasta", () => {
4
+ const fileContent = `>A stuff
5
+ --------------------------AGAGTTTGATCCTGGCTCAGGGTGAACGCTGGCG
6
+ GCGTGC----TTAAGACATGCAAGTCGAACGG-CCT------TCTTCG-G-AAGGC-AGT
7
+ ---------------------------------
8
+ >B other stuff
9
+ --------------------------AGAGTTTGATCATGGCTCAGGGTGAACGCTGGCG
10
+ GCGTGC----TTAAGACATGCAAGTCGGACGA-TCG------GCTTCG---GCCGGTAGT
11
+ ---------------------------------
12
+ `;
13
+
14
+ expect(fasta(fileContent)).toEqual([
15
+ {
16
+ identifier: "A",
17
+ sequence:
18
+ "--------------------------AGAGTTTGATCCTGGCTCAGGGTGAACGCTGGCGGCGTGC----TTAAGACATGCAAGTCGAACGG-CCT------TCTTCG-G-AAGGC-AGT---------------------------------",
19
+ },
20
+ {
21
+ identifier: "B",
22
+ sequence:
23
+ "--------------------------AGAGTTTGATCATGGCTCAGGGTGAACGCTGGCGGCGTGC----TTAAGACATGCAAGTCGGACGA-TCG------GCTTCG---GCCGGTAGT---------------------------------",
24
+ },
25
+ ]);
26
+ });
@@ -0,0 +1,22 @@
1
+ import FlowNode from "../flowNode";
2
+
3
+ export default class DataSource extends FlowNode {
4
+ /**
5
+ * @return {string}
6
+ */
7
+ get identifier() {
8
+ return undefined;
9
+ }
10
+
11
+ /**
12
+ *
13
+ * @param {import("../flowNode").Datum} datum
14
+ */
15
+ handle(datum) {
16
+ throw new Error("Source does not handle incoming data!");
17
+ }
18
+
19
+ async load() {
20
+ // override
21
+ }
22
+ }
@@ -0,0 +1,24 @@
1
+ import InlineSource, { isInlineData } from "./inlineSource";
2
+ import UrlSource, { isUrlData } from "./urlSource";
3
+ import SequenceSource, { isSequenceGenerator } from "./sequenceSource";
4
+ import DynamicSource, { isDynamicData } from "./dynamicSource";
5
+
6
+ /**
7
+ * @param {Partial<import("../../spec/data").Data>} params
8
+ * @param {string} [baseUrl]
9
+ */
10
+ export default function createDataSource(params, baseUrl) {
11
+ if (isInlineData(params)) {
12
+ return new InlineSource(params);
13
+ } else if (isUrlData(params)) {
14
+ return new UrlSource(params, baseUrl);
15
+ } else if (isSequenceGenerator(params)) {
16
+ return new SequenceSource(params);
17
+ } else if (isDynamicData(params)) {
18
+ return new DynamicSource();
19
+ }
20
+
21
+ throw new Error(
22
+ "Cannot figure out the data source type: " + JSON.stringify(params)
23
+ );
24
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Validates data source params, infers format if not specified explicitly,
3
+ * returns a complete DataSource params object.
4
+ *
5
+ * @param {import("../../spec/data").DataSource} params
6
+ * DataSource parameters
7
+ */
8
+ export function getFormat(params) {
9
+ const format = { ...params.format };
10
+
11
+ format.type = format.type || extractTypeFromUrl(params.url);
12
+ format.parse = format.parse || "auto";
13
+
14
+ if (!format.type) {
15
+ throw new Error(
16
+ "Format for the data source was not defined and it could not be inferred: " +
17
+ JSON.stringify(params)
18
+ );
19
+ }
20
+
21
+ return format;
22
+ }
23
+
24
+ /**
25
+ * @param {string} url
26
+ */
27
+ export function extractTypeFromUrl(url) {
28
+ if (url) {
29
+ return url.match(/\.(csv|tsv|json)/)?.[1];
30
+ }
31
+ }
@@ -0,0 +1,56 @@
1
+ import DataSource from "./dataSource";
2
+
3
+ /**
4
+ * @param {Partial<import("../../spec/data").Data>} data
5
+ * @returns {data is import("../../spec/data").DynamicCallbackData}
6
+ */
7
+ export function isDynamicCallbackData(data) {
8
+ return "dynamicCallbackSource" in data;
9
+ }
10
+
11
+ /**
12
+ * A data source that retrieves the data from a callback that returns an iterable.
13
+ */
14
+ export default class DynamicCallbackSource extends DataSource {
15
+ /**
16
+ * @param {function():Iterable<any>} [callback] Function that provides the data
17
+ */
18
+ constructor(callback) {
19
+ super();
20
+
21
+ this.callback = callback;
22
+ }
23
+
24
+ loadSynchronously() {
25
+ if (!this.callback) {
26
+ return;
27
+ }
28
+
29
+ const iterable = this.callback();
30
+
31
+ if (!iterable || typeof iterable[Symbol.iterator] !== "function") {
32
+ throw new Error(
33
+ "Dynamic data callback didn't return iterable data!"
34
+ );
35
+ }
36
+
37
+ this.reset();
38
+ this.beginBatch({ type: "file" });
39
+
40
+ let wrap;
41
+
42
+ for (const d of iterable) {
43
+ if (!wrap) {
44
+ wrap = typeof d != "object" ? (x) => ({ data: x }) : (x) => x;
45
+ }
46
+
47
+ this._propagate(wrap(d));
48
+ }
49
+
50
+ this.complete();
51
+ }
52
+
53
+ async load() {
54
+ this.loadSynchronously();
55
+ }
56
+ }
@@ -0,0 +1,36 @@
1
+ import DataSource from "./dataSource";
2
+
3
+ /**
4
+ * @param {Partial<import("../../spec/data").Data>} data
5
+ * @returns {data is import("../../spec/data").DynamicData}
6
+ */
7
+ export function isDynamicData(data) {
8
+ return "dynamicSource" in data;
9
+ }
10
+
11
+ export default class DynamicSource extends DataSource {
12
+ /**
13
+ *
14
+ * @param {Iterable<any>} iterable
15
+ */
16
+ publishData(iterable) {
17
+ this.reset();
18
+ this.beginBatch({ type: "file" });
19
+
20
+ let wrap;
21
+
22
+ for (const d of iterable) {
23
+ if (!wrap) {
24
+ wrap = typeof d != "object" ? (x) => ({ data: x }) : (x) => x;
25
+ }
26
+
27
+ this._propagate(wrap(d));
28
+ }
29
+
30
+ this.complete();
31
+ }
32
+
33
+ async load() {
34
+ // nop
35
+ }
36
+ }
@@ -0,0 +1,69 @@
1
+ import { read } from "vega-loader";
2
+ import { getFormat } from "./dataUtils";
3
+ import DataSource from "./dataSource";
4
+
5
+ /**
6
+ * @param {Partial<import("../../spec/data").Data>} data
7
+ * @returns {data is import("../../spec/data").InlineData}
8
+ */
9
+ export function isInlineData(data) {
10
+ return "values" in data;
11
+ }
12
+
13
+ export default class InlineSource extends DataSource {
14
+ /**
15
+ * @param {import("../../spec/data").InlineData} params
16
+ */
17
+ constructor(params) {
18
+ super();
19
+
20
+ this.params = params;
21
+
22
+ if (typeof params.values == "string" && !params?.format?.type) {
23
+ throw new Error(
24
+ "Data format type (csv, dsv, ...) must be specified if a string is provided!"
25
+ );
26
+ }
27
+ }
28
+
29
+ loadSynchronously() {
30
+ const values = this.params.values;
31
+
32
+ let data = [];
33
+
34
+ let wrap = (x) => x;
35
+
36
+ if (Array.isArray(values)) {
37
+ if (values.length > 0) {
38
+ data = values;
39
+ // TODO: Should check the whole array and abort if types are heterogeneous
40
+ if (typeof values[0] != "object") {
41
+ // Wrap scalars to objects
42
+ wrap = (d) => ({ data: d });
43
+ }
44
+ }
45
+ } else if (typeof values == "object") {
46
+ data = [values];
47
+ } else if (typeof values == "string") {
48
+ // It's a string that needs to be parsed
49
+ data = read(values, getFormat(this.params));
50
+ } else {
51
+ throw new Error(
52
+ '"values" in data configuration is not an array, object, or a string!'
53
+ );
54
+ }
55
+
56
+ this.reset();
57
+ this.beginBatch({ type: "file" });
58
+
59
+ for (const d of data) {
60
+ this._propagate(wrap(d));
61
+ }
62
+
63
+ this.complete();
64
+ }
65
+
66
+ async load() {
67
+ this.loadSynchronously();
68
+ }
69
+ }
@@ -0,0 +1,55 @@
1
+ import Collector from "../collector";
2
+ import InlineSource from "./inlineSource";
3
+
4
+ /**
5
+ * @param {InlineSource} source
6
+ */
7
+ async function collectSource(source) {
8
+ const collector = new Collector();
9
+ source.addChild(collector);
10
+
11
+ await source.load();
12
+
13
+ return [...collector.getData()];
14
+ }
15
+
16
+ test("InlineSource propagates an object", async () => {
17
+ expect(await collectSource(new InlineSource({ values: { x: 1 } }))).toEqual(
18
+ [{ x: 1 }]
19
+ );
20
+ });
21
+
22
+ test("InlineSource propagates an array of objects", async () => {
23
+ expect(
24
+ await collectSource(new InlineSource({ values: [{ x: 1 }, { x: 2 }] }))
25
+ ).toEqual([{ x: 1 }, { x: 2 }]);
26
+ });
27
+
28
+ test("InlineSource wraps scalars to objects", async () => {
29
+ expect(await collectSource(new InlineSource({ values: [1, 2] }))).toEqual([
30
+ { data: 1 },
31
+ { data: 2 },
32
+ ]);
33
+ });
34
+
35
+ test("InlineSource parses a string", async () => {
36
+ expect(
37
+ await collectSource(
38
+ new InlineSource({
39
+ values: "a\n1\n2\n3",
40
+ format: {
41
+ type: "csv",
42
+ },
43
+ })
44
+ )
45
+ ).toEqual([{ a: 1 }, { a: 2 }, { a: 3 }]);
46
+ });
47
+
48
+ test("InlineSource throws on a string and a missing format specifier", () => {
49
+ expect(
50
+ () =>
51
+ new InlineSource({
52
+ values: "a\n1\n2\n3",
53
+ })
54
+ ).toThrow();
55
+ });
@@ -0,0 +1,74 @@
1
+ import DataSource from "./dataSource";
2
+
3
+ /**
4
+ * @param {Partial<import("../../spec/data").Data>} data
5
+ * @returns {data is import("../../spec/data").NamedData}
6
+ */
7
+ export function isNamedData(data) {
8
+ return "name" in data;
9
+ }
10
+
11
+ export default class NamedSource extends DataSource {
12
+ /**
13
+ * @param {import("../../spec/data").NamedData} params
14
+ * @param {function(string):any[]} getNamedData
15
+ */
16
+ constructor(params, getNamedData) {
17
+ super();
18
+
19
+ this.getNamedData = getNamedData;
20
+ this.params = params;
21
+ }
22
+
23
+ /**
24
+ * @return {string}
25
+ */
26
+ get identifier() {
27
+ return this.params.name;
28
+ }
29
+
30
+ _getValues() {
31
+ const data = this.getNamedData(this.params.name);
32
+ if (data) {
33
+ return data;
34
+ } else {
35
+ throw new Error("Cannot find named data: " + this.params.name);
36
+ }
37
+ }
38
+
39
+ loadSynchronously() {
40
+ const data = this._getValues();
41
+
42
+ /**
43
+ * @param {any} x
44
+ */
45
+ let wrap = (x) => x;
46
+
47
+ if (Array.isArray(data)) {
48
+ if (data.length > 0) {
49
+ // TODO: Should check the whole array and abort if types are heterogeneous
50
+ if (typeof data[0] != "object") {
51
+ // Wrap scalars to objects
52
+ wrap = (d) => ({ data: d });
53
+ }
54
+ }
55
+ } else {
56
+ throw new Error(
57
+ `Named data "${this.params.name}" is not an array!`
58
+ );
59
+ }
60
+
61
+ this.reset();
62
+ this.beginBatch({ type: "file" });
63
+
64
+ for (const d of data) {
65
+ this._propagate(wrap(d));
66
+ }
67
+
68
+ this.complete();
69
+ }
70
+
71
+ async load() {
72
+ this.loadSynchronously();
73
+ }
74
+ }
@@ -0,0 +1,46 @@
1
+ import DataSource from "./dataSource";
2
+
3
+ /**
4
+ * @param {Partial<import("../../spec/data").Data>} data
5
+ * @returns {data is import("../../spec/data").SequenceGenerator}
6
+ */
7
+ export function isSequenceGenerator(data) {
8
+ return "sequence" in data;
9
+ }
10
+
11
+ export default class SequenceSource extends DataSource {
12
+ /**
13
+ *
14
+ * @param {import("../../spec/data").SequenceGenerator} params
15
+ */
16
+ constructor(params) {
17
+ super();
18
+ this.sequence = params.sequence;
19
+
20
+ if (!("start" in this.sequence)) {
21
+ throw new Error("'start' is missing from sequence parameters!");
22
+ }
23
+ if (!("stop" in this.sequence)) {
24
+ throw new Error("'stop' is missing from sequence parameters!");
25
+ }
26
+ }
27
+
28
+ loadSynchronously() {
29
+ const as = this.sequence.as || "data";
30
+ const step = this.sequence.step || 1;
31
+ const stop = this.sequence.stop;
32
+
33
+ this.reset();
34
+ this.beginBatch({ type: "file" });
35
+
36
+ for (let x = this.sequence.start; x < stop; x += step) {
37
+ this._propagate({ [as]: x });
38
+ }
39
+
40
+ this.complete();
41
+ }
42
+
43
+ async load() {
44
+ this.loadSynchronously();
45
+ }
46
+ }
@@ -0,0 +1,45 @@
1
+ import Collector from "../collector";
2
+ import SequenceSource from "./sequenceSource";
3
+
4
+ /**
5
+ * @param {SequenceSource} source
6
+ */
7
+ async function collectSource(source) {
8
+ const collector = new Collector();
9
+ source.addChild(collector);
10
+
11
+ await source.load();
12
+
13
+ return [...collector.getData()];
14
+ }
15
+
16
+ test("SequenceSource generates a sequence", async () => {
17
+ expect(
18
+ await collectSource(
19
+ new SequenceSource({ sequence: { start: 0, stop: 3 } })
20
+ )
21
+ ).toEqual([{ data: 0 }, { data: 1 }, { data: 2 }]);
22
+ });
23
+
24
+ test("SequenceSource generates a sequence with a custom step", async () => {
25
+ expect(
26
+ await collectSource(
27
+ new SequenceSource({ sequence: { start: 0, stop: 5, step: 2 } })
28
+ )
29
+ ).toEqual([{ data: 0 }, { data: 2 }, { data: 4 }]);
30
+ });
31
+
32
+ test("SequenceSource generates a sequence with a custom field name", async () => {
33
+ expect(
34
+ await collectSource(
35
+ new SequenceSource({ sequence: { start: 0, stop: 3, as: "x" } })
36
+ )
37
+ ).toEqual([{ x: 0 }, { x: 1 }, { x: 2 }]);
38
+ });
39
+
40
+ test("SequenceSource throws on missing 'start' parameter", () => {
41
+ expect(() => new SequenceSource({ sequence: { stop: 3 } })).toThrow();
42
+ });
43
+ test("SequenceSource throws on missing 'stop' parameter", () => {
44
+ expect(() => new SequenceSource({ sequence: { start: 0 } })).toThrow();
45
+ });
@@ -0,0 +1,74 @@
1
+ import { loader as vegaLoader, read } from "vega-loader";
2
+ import { getFormat } from "./dataUtils";
3
+ import DataSource from "./dataSource";
4
+
5
+ /**
6
+ * @param {Partial<import("../../spec/data").Data>} data
7
+ * @returns {data is import("../../spec/data").UrlData}
8
+ */
9
+ export function isUrlData(data) {
10
+ return "url" in data;
11
+ }
12
+
13
+ export default class UrlSource extends DataSource {
14
+ /**
15
+ * @param {import("../../spec/data").UrlData} params
16
+ * @param {string} [baseUrl]
17
+ */
18
+ constructor(params, baseUrl) {
19
+ super();
20
+
21
+ this.params = params;
22
+ this.baseUrl = baseUrl;
23
+ }
24
+
25
+ get identifier() {
26
+ return JSON.stringify({ params: this.params, baseUrl: this.baseUrl });
27
+ }
28
+
29
+ async load() {
30
+ const url = this.params.url;
31
+
32
+ /** @type {string[]} */
33
+ const urls = Array.isArray(url) ? url : [url];
34
+
35
+ /** @param {string} url */
36
+ const load = async (url) =>
37
+ // TODO: Support chunked loading
38
+ /** @type {string} */ (
39
+ vegaLoader({
40
+ baseURL: this.baseUrl,
41
+ })
42
+ .load(url)
43
+ .catch((e) => {
44
+ // TODO: Include baseurl in the error message. Should be normalized, however.
45
+ throw new Error(
46
+ `Cannot fetch: ${this.baseUrl}${url}: ${e.message}`
47
+ );
48
+ })
49
+ );
50
+
51
+ /**
52
+ * @param {string} text
53
+ * @param {string} [url]
54
+ */
55
+ const readAndParse = (text, url) => {
56
+ try {
57
+ /** @type {any[]} */
58
+ const data = read(text, getFormat(this.params));
59
+ this.beginBatch({ type: "file", url: url });
60
+ for (const d of data) {
61
+ this._propagate(d);
62
+ }
63
+ } catch (e) {
64
+ throw new Error(`Cannot parse: ${url}: ${e.message}`);
65
+ }
66
+ };
67
+
68
+ this.reset();
69
+
70
+ await Promise.all(urls.map((url) => load(url).then(readAndParse)));
71
+
72
+ this.complete();
73
+ }
74
+ }
@@ -0,0 +1,69 @@
1
+ import { group as d3group } from "d3-array";
2
+ import FlowNode, { BEHAVIOR_CLONES } from "../flowNode";
3
+ import { field } from "../../utils/field";
4
+ import iterateNestedMaps from "../../utils/iterateNestedMaps";
5
+
6
+ /**
7
+ * A minimal aggregate transform that just counts grouped (by a single field) data items.
8
+ * Work in progress.
9
+ *
10
+ * Eventually this will implement the most of Vega's aggregate transform:
11
+ * https://vega.github.io/vega/docs/transforms/aggregate/
12
+ *
13
+ * @typedef {import("../../spec/transform").AggregateParams} AggregateParams
14
+ */
15
+
16
+ export default class AggregateTransform extends FlowNode {
17
+ get behavior() {
18
+ return BEHAVIOR_CLONES;
19
+ }
20
+
21
+ /**
22
+ * @param {AggregateParams} params
23
+ */
24
+ constructor(params) {
25
+ super();
26
+ this.params = params;
27
+
28
+ /** @type {any[]} */
29
+ this.buffer = [];
30
+ }
31
+
32
+ reset() {
33
+ this.buffer = [];
34
+ }
35
+
36
+ /**
37
+ *
38
+ * @param {import("../flowNode").Datum} datum
39
+ */
40
+ handle(datum) {
41
+ this.buffer.push(datum);
42
+ }
43
+
44
+ complete() {
45
+ const params = this.params;
46
+
47
+ const groupby = params.groupby;
48
+
49
+ const groupFieldAccessors = groupby.map((f) => field(f));
50
+
51
+ // TODO: Fix case where no group fields are specified
52
+ const groups = d3group(this.buffer, ...groupFieldAccessors);
53
+
54
+ for (const [group, data] of iterateNestedMaps(groups)) {
55
+ /** @type {any} */
56
+ const datum = {
57
+ count: data.length,
58
+ };
59
+
60
+ for (let i = 0; i < groupby.length; i++) {
61
+ datum[groupby[i]] = group[i];
62
+ }
63
+
64
+ this._propagate(datum);
65
+ }
66
+
67
+ super.complete();
68
+ }
69
+ }