@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.
- package/dist/index.js +224 -0
- package/dist/style.css +1 -0
- package/package.json +54 -0
- package/src/data/collector.js +178 -0
- package/src/data/collector.test.js +82 -0
- package/src/data/dataFlow.js +109 -0
- package/src/data/dataFlow.test.js +3 -0
- package/src/data/facetNode.js +17 -0
- package/src/data/flow.test.js +71 -0
- package/src/data/flowBatch.d.ts +40 -0
- package/src/data/flowNode.js +283 -0
- package/src/data/flowNode.test.js +49 -0
- package/src/data/flowOptimizer.js +117 -0
- package/src/data/flowOptimizer.test.js +192 -0
- package/src/data/flowTestUtils.js +63 -0
- package/src/data/formats/fasta.js +32 -0
- package/src/data/formats/fasta.test.js +26 -0
- package/src/data/sources/dataSource.js +22 -0
- package/src/data/sources/dataSourceFactory.js +24 -0
- package/src/data/sources/dataUtils.js +31 -0
- package/src/data/sources/dynamicCallbackSource.js +56 -0
- package/src/data/sources/dynamicSource.js +36 -0
- package/src/data/sources/inlineSource.js +69 -0
- package/src/data/sources/inlineSource.test.js +55 -0
- package/src/data/sources/namedSource.js +74 -0
- package/src/data/sources/sequenceSource.js +46 -0
- package/src/data/sources/sequenceSource.test.js +45 -0
- package/src/data/sources/urlSource.js +74 -0
- package/src/data/transforms/aggregate.js +69 -0
- package/src/data/transforms/clone.js +40 -0
- package/src/data/transforms/clone.test.js +10 -0
- package/src/data/transforms/coverage.js +187 -0
- package/src/data/transforms/coverage.test.js +122 -0
- package/src/data/transforms/filter.js +37 -0
- package/src/data/transforms/filter.test.js +17 -0
- package/src/data/transforms/filterScoredLabels.js +134 -0
- package/src/data/transforms/flattenCompressedExons.js +57 -0
- package/src/data/transforms/flattenDelimited.js +68 -0
- package/src/data/transforms/flattenDelimited.test.js +86 -0
- package/src/data/transforms/flattenSequence.js +39 -0
- package/src/data/transforms/flattenSequence.test.js +33 -0
- package/src/data/transforms/formula.js +39 -0
- package/src/data/transforms/formula.test.js +18 -0
- package/src/data/transforms/identifier.js +108 -0
- package/src/data/transforms/identifier.test.js +82 -0
- package/src/data/transforms/linearizeGenomicCoordinate.js +101 -0
- package/src/data/transforms/measureText.js +44 -0
- package/src/data/transforms/pileup.js +128 -0
- package/src/data/transforms/pileup.test.js +69 -0
- package/src/data/transforms/project.js +41 -0
- package/src/data/transforms/project.test.js +31 -0
- package/src/data/transforms/regexExtract.js +61 -0
- package/src/data/transforms/regexExtract.test.js +66 -0
- package/src/data/transforms/regexFold.js +141 -0
- package/src/data/transforms/regexFold.test.js +159 -0
- package/src/data/transforms/sample.js +101 -0
- package/src/data/transforms/sample.test.js +37 -0
- package/src/data/transforms/stack.js +137 -0
- package/src/data/transforms/stack.test.js +90 -0
- package/src/data/transforms/transformFactory.js +60 -0
- package/src/encoder/accessor.js +82 -0
- package/src/encoder/accessor.test.js +46 -0
- package/src/encoder/encoder.js +369 -0
- package/src/encoder/encoder.test.js +97 -0
- package/src/fonts/Lato-Regular.json +1267 -0
- package/src/fonts/Lato-Regular.png +0 -0
- package/src/fonts/OFL.txt +93 -0
- package/src/fonts/README.md +3 -0
- package/src/fonts/bmFont.d.ts +58 -0
- package/src/fonts/bmFontManager.js +357 -0
- package/src/fonts/bmFontMetrics.js +108 -0
- package/src/genome/genome.js +305 -0
- package/src/genome/genome.test.js +152 -0
- package/src/genome/genomeStore.js +54 -0
- package/src/genome/locusFormat.js +31 -0
- package/src/genome/scaleIndex.js +199 -0
- package/src/genome/scaleIndex.test.js +61 -0
- package/src/genome/scaleLocus.js +112 -0
- package/src/genome/scaleLocus.test.js +3 -0
- package/src/genomeSpy.js +753 -0
- package/src/gl/arrayBuilder.js +199 -0
- package/src/gl/dataToVertices.js +621 -0
- package/src/gl/includes/common.glsl +63 -0
- package/src/gl/includes/fp64-arithmetic.glsl +187 -0
- package/src/gl/includes/fp64-utils.js +132 -0
- package/src/gl/includes/picking.fragment.glsl +3 -0
- package/src/gl/includes/picking.vertex.glsl +29 -0
- package/src/gl/includes/sampleFacet.glsl +107 -0
- package/src/gl/includes/scales.glsl +79 -0
- package/src/gl/includes/scales_fp64.glsl +30 -0
- package/src/gl/link.fragment.glsl +18 -0
- package/src/gl/link.vertex.glsl +111 -0
- package/src/gl/point.fragment.glsl +123 -0
- package/src/gl/point.vertex.glsl +128 -0
- package/src/gl/rect.fragment.glsl +51 -0
- package/src/gl/rect.vertex.glsl +114 -0
- package/src/gl/rule.fragment.glsl +52 -0
- package/src/gl/rule.vertex.glsl +89 -0
- package/src/gl/text.fragment.glsl +31 -0
- package/src/gl/text.vertex.glsl +246 -0
- package/src/gl/webGLHelper.js +490 -0
- package/src/img/bowtie.svg +1 -0
- package/src/img/genomespy-favicon.svg +34 -0
- package/src/index.html +11 -0
- package/src/index.js +151 -0
- package/src/marks/link.js +189 -0
- package/src/marks/mark.js +867 -0
- package/src/marks/markUtils.js +109 -0
- package/src/marks/pointMark.js +279 -0
- package/src/marks/rectMark.js +236 -0
- package/src/marks/rule.js +231 -0
- package/src/marks/text.js +274 -0
- package/src/options.d.ts +9 -0
- package/src/scale/colorUtils.js +184 -0
- package/src/scale/glslScaleGenerator.js +462 -0
- package/src/scale/scale.js +441 -0
- package/src/scale/scale.test.js +323 -0
- package/src/scale/ticks.js +198 -0
- package/src/scale/ticks.test.js +39 -0
- package/src/singlePageApp.js +13 -0
- package/src/spec/axis.d.ts +296 -0
- package/src/spec/channel.d.ts +127 -0
- package/src/spec/data.d.ts +185 -0
- package/src/spec/font.d.ts +15 -0
- package/src/spec/genome.d.ts +35 -0
- package/src/spec/mark.d.ts +432 -0
- package/src/spec/root.d.ts +22 -0
- package/src/spec/scale.d.ts +265 -0
- package/src/spec/tooltip.d.ts +9 -0
- package/src/spec/transform.d.ts +479 -0
- package/src/spec/view.d.ts +215 -0
- package/src/styles/genome-spy.scss +153 -0
- package/src/tooltip/dataTooltipHandler.js +59 -0
- package/src/tooltip/refseqGeneTooltipHandler.js +77 -0
- package/src/tooltip/tooltipHandler.ts +12 -0
- package/src/types/filetypes.d.ts +4 -0
- package/src/types/flatqueue.d.ts +53 -0
- package/src/types/glsl.d.ts +4 -0
- package/src/types/object.d.ts +21 -0
- package/src/types/vega-scale.d.ts +60 -0
- package/src/utils/animator.js +83 -0
- package/src/utils/arrayUtils.js +55 -0
- package/src/utils/binnedRangeIndex.js +83 -0
- package/src/utils/clamp.js +8 -0
- package/src/utils/cloner.js +32 -0
- package/src/utils/cloner.test.js +23 -0
- package/src/utils/coalesce.js +11 -0
- package/src/utils/coalesce.test.js +15 -0
- package/src/utils/concatIterables.js +26 -0
- package/src/utils/concatIterables.test.js +7 -0
- package/src/utils/debounce.js +37 -0
- package/src/utils/domainArray.js +224 -0
- package/src/utils/domainArray.test.js +129 -0
- package/src/utils/eerp.js +13 -0
- package/src/utils/expression.js +32 -0
- package/src/utils/field.js +28 -0
- package/src/utils/fisheye.js +60 -0
- package/src/utils/formatObject.js +31 -0
- package/src/utils/html.js +23 -0
- package/src/utils/html.test.js +13 -0
- package/src/utils/indexer.js +43 -0
- package/src/utils/indexer.test.js +46 -0
- package/src/utils/inertia.js +124 -0
- package/src/utils/interactionEvent.js +33 -0
- package/src/utils/iterateNestedMaps.js +21 -0
- package/src/utils/iterateNestedMaps.test.js +32 -0
- package/src/utils/kWayMerge.js +42 -0
- package/src/utils/kWayMerge.test.js +25 -0
- package/src/utils/layout/flexLayout.js +336 -0
- package/src/utils/layout/flexLayout.test.js +296 -0
- package/src/utils/layout/padding.js +107 -0
- package/src/utils/layout/point.js +23 -0
- package/src/utils/layout/rectangle.js +282 -0
- package/src/utils/layout/rectangle.test.js +171 -0
- package/src/utils/mergeObjects.js +99 -0
- package/src/utils/mergeObjects.test.js +41 -0
- package/src/utils/numberExtractor.js +24 -0
- package/src/utils/numberExtractor.test.js +5 -0
- package/src/utils/point.js +14 -0
- package/src/utils/propertyCacher.js +70 -0
- package/src/utils/propertyCacher.test.js +84 -0
- package/src/utils/propertyCoalescer.js +37 -0
- package/src/utils/propertyCoalescer.test.js +21 -0
- package/src/utils/reservationMap.js +103 -0
- package/src/utils/reservationMap.test.js +19 -0
- package/src/utils/scaleNull.js +19 -0
- package/src/utils/setOperations.js +75 -0
- package/src/utils/smoothstep.js +10 -0
- package/src/utils/throttle.js +34 -0
- package/src/utils/topK.js +76 -0
- package/src/utils/topK.test.js +63 -0
- package/src/utils/transition.js +74 -0
- package/src/utils/ui/tooltip.js +189 -0
- package/src/utils/url.js +22 -0
- package/src/utils/variableTools.js +24 -0
- package/src/utils/variableTools.test.js +12 -0
- package/src/view/axisResolution.js +135 -0
- package/src/view/axisResolution.test.js +200 -0
- package/src/view/axisView.js +746 -0
- package/src/view/channel.js +5 -0
- package/src/view/concatView.js +296 -0
- package/src/view/containerView.js +141 -0
- package/src/view/decoratorView.js +510 -0
- package/src/view/facetView.js +488 -0
- package/src/view/flowBuilder.js +362 -0
- package/src/view/flowBuilder.test.js +124 -0
- package/src/view/importView.js +19 -0
- package/src/view/layerView.js +60 -0
- package/src/view/rendering.d.ts +44 -0
- package/src/view/renderingContext/compositeViewRenderingContext.js +51 -0
- package/src/view/renderingContext/deferredViewRenderingContext.js +174 -0
- package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +128 -0
- package/src/view/renderingContext/simpleViewRenderingContext.js +62 -0
- package/src/view/renderingContext/svgViewRenderingContext.js +121 -0
- package/src/view/renderingContext/viewRenderingContext.js +41 -0
- package/src/view/scaleResolution.js +756 -0
- package/src/view/scaleResolution.test.js +571 -0
- package/src/view/scaleResolutionApi.d.ts +40 -0
- package/src/view/testUtils.js +48 -0
- package/src/view/unitView.js +368 -0
- package/src/view/view.js +589 -0
- package/src/view/view.test.js +213 -0
- package/src/view/viewContext.d.ts +57 -0
- package/src/view/viewFactory.js +179 -0
- package/src/view/viewFactory.test.js +16 -0
- 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,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;
|