@genome-spy/core 0.30.0 → 0.30.2
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.es.js +16373 -0
- package/dist/index.js +43 -43
- package/package.json +10 -7
- package/src/data/collector.js +0 -183
- package/src/data/collector.test.js +0 -84
- package/src/data/dataFlow.js +0 -148
- package/src/data/dataFlow.test.js +0 -5
- package/src/data/facetNode.js +0 -17
- package/src/data/flow.test.js +0 -72
- package/src/data/flowBatch.d.ts +0 -40
- package/src/data/flowNode.js +0 -283
- package/src/data/flowNode.test.js +0 -50
- package/src/data/flowOptimizer.js +0 -123
- package/src/data/flowOptimizer.test.js +0 -193
- package/src/data/flowTestUtils.js +0 -63
- package/src/data/formats/fasta.js +0 -32
- package/src/data/formats/fasta.test.js +0 -27
- package/src/data/sources/dataSource.js +0 -22
- package/src/data/sources/dataSourceFactory.js +0 -24
- package/src/data/sources/dataUtils.js +0 -78
- package/src/data/sources/dynamicCallbackSource.js +0 -57
- package/src/data/sources/dynamicSource.js +0 -37
- package/src/data/sources/inlineSource.js +0 -67
- package/src/data/sources/inlineSource.test.js +0 -56
- package/src/data/sources/namedSource.js +0 -79
- package/src/data/sources/sequenceSource.js +0 -46
- package/src/data/sources/sequenceSource.test.js +0 -46
- package/src/data/sources/urlSource.js +0 -74
- package/src/data/transforms/aggregate.js +0 -70
- package/src/data/transforms/clone.js +0 -40
- package/src/data/transforms/clone.test.js +0 -11
- package/src/data/transforms/coverage.js +0 -187
- package/src/data/transforms/coverage.test.js +0 -123
- package/src/data/transforms/filter.js +0 -37
- package/src/data/transforms/filter.test.js +0 -18
- package/src/data/transforms/filterScoredLabels.js +0 -134
- package/src/data/transforms/flattenCompressedExons.js +0 -57
- package/src/data/transforms/flattenDelimited.js +0 -74
- package/src/data/transforms/flattenDelimited.test.js +0 -87
- package/src/data/transforms/flattenSequence.js +0 -39
- package/src/data/transforms/flattenSequence.test.js +0 -34
- package/src/data/transforms/formula.js +0 -39
- package/src/data/transforms/formula.test.js +0 -19
- package/src/data/transforms/identifier.js +0 -108
- package/src/data/transforms/identifier.test.js +0 -83
- package/src/data/transforms/linearizeGenomicCoordinate.js +0 -101
- package/src/data/transforms/measureText.js +0 -44
- package/src/data/transforms/pileup.js +0 -128
- package/src/data/transforms/pileup.test.js +0 -70
- package/src/data/transforms/project.js +0 -41
- package/src/data/transforms/project.test.js +0 -32
- package/src/data/transforms/regexExtract.js +0 -61
- package/src/data/transforms/regexExtract.test.js +0 -67
- package/src/data/transforms/regexFold.js +0 -141
- package/src/data/transforms/regexFold.test.js +0 -160
- package/src/data/transforms/sample.js +0 -101
- package/src/data/transforms/sample.test.js +0 -38
- package/src/data/transforms/stack.js +0 -137
- package/src/data/transforms/stack.test.js +0 -91
- package/src/data/transforms/transformFactory.js +0 -60
- package/src/embedApi.d.ts +0 -67
- package/src/encoder/accessor.js +0 -82
- package/src/encoder/accessor.test.js +0 -47
- package/src/encoder/encoder.js +0 -394
- package/src/encoder/encoder.test.js +0 -98
- package/src/fonts/Lato-Regular.json +0 -1267
- package/src/fonts/Lato-Regular.png +0 -0
- package/src/fonts/OFL.txt +0 -93
- package/src/fonts/README.md +0 -3
- package/src/fonts/bmFont.d.ts +0 -58
- package/src/fonts/bmFontManager.js +0 -357
- package/src/fonts/bmFontMetrics.js +0 -108
- package/src/genome/genome.js +0 -317
- package/src/genome/genome.test.js +0 -188
- package/src/genome/genomeStore.js +0 -54
- package/src/genome/locusFormat.js +0 -31
- package/src/genome/scaleIndex.d.ts +0 -38
- package/src/genome/scaleIndex.js +0 -166
- package/src/genome/scaleIndex.test.js +0 -78
- package/src/genome/scaleLocus.d.ts +0 -11
- package/src/genome/scaleLocus.js +0 -108
- package/src/genome/scaleLocus.test.js +0 -4
- package/src/genomeSpy.js +0 -785
- package/src/gl/arrayBuilder.js +0 -199
- package/src/gl/dataToVertices.js +0 -636
- package/src/gl/includes/common.glsl +0 -63
- package/src/gl/includes/picking.fragment.glsl +0 -1
- package/src/gl/includes/picking.vertex.glsl +0 -27
- package/src/gl/includes/sampleFacet.glsl +0 -107
- package/src/gl/includes/scales.glsl +0 -112
- package/src/gl/link.fragment.glsl +0 -18
- package/src/gl/link.vertex.glsl +0 -111
- package/src/gl/point.fragment.glsl +0 -123
- package/src/gl/point.vertex.glsl +0 -129
- package/src/gl/rect.fragment.glsl +0 -51
- package/src/gl/rect.vertex.glsl +0 -114
- package/src/gl/rule.fragment.glsl +0 -52
- package/src/gl/rule.vertex.glsl +0 -89
- package/src/gl/text.fragment.glsl +0 -31
- package/src/gl/text.vertex.glsl +0 -246
- package/src/gl/webGLHelper.js +0 -504
- package/src/img/bowtie.svg +0 -1
- package/src/img/genomespy-favicon.svg +0 -34
- package/src/index.html +0 -11
- package/src/index.js +0 -128
- package/src/marks/link.js +0 -175
- package/src/marks/mark.js +0 -975
- package/src/marks/markUtils.js +0 -125
- package/src/marks/pointMark.js +0 -251
- package/src/marks/rectMark.js +0 -241
- package/src/marks/rule.js +0 -250
- package/src/marks/text.js +0 -278
- package/src/node_modules/.vitest/results.json +0 -1
- package/src/scale/colorUtils.js +0 -184
- package/src/scale/glslScaleGenerator.js +0 -502
- package/src/scale/scale.js +0 -451
- package/src/scale/scale.test.js +0 -324
- package/src/scale/ticks.js +0 -203
- package/src/scale/ticks.test.js +0 -40
- package/src/singlePageApp.js +0 -13
- package/src/spec/axis.d.ts +0 -296
- package/src/spec/channel.d.ts +0 -430
- package/src/spec/data.d.ts +0 -196
- package/src/spec/font.d.ts +0 -15
- package/src/spec/genome.d.ts +0 -35
- package/src/spec/mark.d.ts +0 -429
- package/src/spec/root.d.ts +0 -17
- package/src/spec/sampleView.d.ts +0 -180
- package/src/spec/scale.d.ts +0 -273
- package/src/spec/title.d.ts +0 -102
- package/src/spec/tooltip.d.ts +0 -9
- package/src/spec/transform.d.ts +0 -479
- package/src/spec/view.d.ts +0 -201
- package/src/styles/genome-spy.scss +0 -153
- package/src/tooltip/dataTooltipHandler.js +0 -64
- package/src/tooltip/refseqGeneTooltipHandler.js +0 -78
- package/src/tooltip/tooltipHandler.ts +0 -12
- package/src/types/filetypes.d.ts +0 -14
- package/src/types/flatqueue.d.ts +0 -53
- package/src/types/glsl.d.ts +0 -4
- package/src/types/internmap.d.ts +0 -22
- package/src/types/object.d.ts +0 -21
- package/src/types/vega-loader.d.ts +0 -1
- package/src/types/vega-scale.d.ts +0 -60
- package/src/utils/addBaseUrl.js +0 -19
- package/src/utils/addBaseUrl.test.js +0 -22
- package/src/utils/animator.js +0 -83
- package/src/utils/arrayUtils.js +0 -61
- package/src/utils/binnedIndex.js +0 -167
- package/src/utils/binnedIndex.test.js +0 -155
- package/src/utils/clamp.js +0 -8
- package/src/utils/cloner.js +0 -34
- package/src/utils/cloner.test.js +0 -24
- package/src/utils/coalesce.js +0 -11
- package/src/utils/coalesce.test.js +0 -16
- package/src/utils/concatIterables.js +0 -26
- package/src/utils/concatIterables.test.js +0 -8
- package/src/utils/debounce.js +0 -37
- package/src/utils/domainArray.js +0 -216
- package/src/utils/domainArray.test.js +0 -130
- package/src/utils/eerp.js +0 -13
- package/src/utils/expression.js +0 -32
- package/src/utils/field.js +0 -28
- package/src/utils/formatObject.js +0 -31
- package/src/utils/indexer.js +0 -43
- package/src/utils/indexer.test.js +0 -47
- package/src/utils/inertia.js +0 -124
- package/src/utils/interactionEvent.js +0 -33
- package/src/utils/iterateNestedMaps.js +0 -21
- package/src/utils/iterateNestedMaps.test.js +0 -33
- package/src/utils/kWayMerge.js +0 -42
- package/src/utils/kWayMerge.test.js +0 -26
- package/src/utils/layout/flexLayout.js +0 -368
- package/src/utils/layout/flexLayout.test.js +0 -311
- package/src/utils/layout/grid.js +0 -95
- package/src/utils/layout/grid.test.js +0 -71
- package/src/utils/layout/padding.js +0 -120
- package/src/utils/layout/point.js +0 -23
- package/src/utils/layout/rectangle.js +0 -288
- package/src/utils/layout/rectangle.test.js +0 -172
- package/src/utils/mergeObjects.js +0 -99
- package/src/utils/mergeObjects.test.js +0 -42
- package/src/utils/numberExtractor.js +0 -24
- package/src/utils/numberExtractor.test.js +0 -6
- package/src/utils/point.js +0 -14
- package/src/utils/propertyCacher.js +0 -70
- package/src/utils/propertyCacher.test.js +0 -85
- package/src/utils/propertyCoalescer.js +0 -42
- package/src/utils/propertyCoalescer.test.js +0 -22
- package/src/utils/reservationMap.js +0 -103
- package/src/utils/reservationMap.test.js +0 -20
- package/src/utils/scaleNull.js +0 -19
- package/src/utils/setOperations.js +0 -75
- package/src/utils/smoothstep.js +0 -10
- package/src/utils/throttle.js +0 -34
- package/src/utils/topK.js +0 -76
- package/src/utils/topK.test.js +0 -64
- package/src/utils/transition.js +0 -74
- package/src/utils/ui/tooltip.js +0 -189
- package/src/utils/url.js +0 -22
- package/src/utils/variableTools.js +0 -24
- package/src/utils/variableTools.test.js +0 -13
- package/src/view/axisResolution.js +0 -140
- package/src/view/axisResolution.test.js +0 -201
- package/src/view/axisView.js +0 -747
- package/src/view/concatView.js +0 -45
- package/src/view/containerView.js +0 -159
- package/src/view/facetView.js +0 -491
- package/src/view/flowBuilder.js +0 -367
- package/src/view/flowBuilder.test.js +0 -125
- package/src/view/gridView.js +0 -786
- package/src/view/implicitRootView.js +0 -14
- package/src/view/importView.js +0 -19
- package/src/view/layerView.js +0 -74
- package/src/view/rendering.d.ts +0 -44
- package/src/view/renderingContext/compositeViewRenderingContext.js +0 -51
- package/src/view/renderingContext/deferredViewRenderingContext.js +0 -176
- package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +0 -128
- package/src/view/renderingContext/simpleViewRenderingContext.js +0 -64
- package/src/view/renderingContext/svgViewRenderingContext.js +0 -125
- package/src/view/renderingContext/viewRenderingContext.js +0 -41
- package/src/view/scaleResolution.js +0 -797
- package/src/view/scaleResolution.test.js +0 -572
- package/src/view/scaleResolutionApi.d.ts +0 -40
- package/src/view/testUtils.js +0 -51
- package/src/view/title.js +0 -165
- package/src/view/unitView.js +0 -382
- package/src/view/view.js +0 -612
- package/src/view/view.test.js +0 -214
- package/src/view/viewContext.d.ts +0 -62
- package/src/view/viewFactory.js +0 -181
- package/src/view/viewFactory.test.js +0 -17
- package/src/view/viewUtils.js +0 -327
- package/src/view/zoom.js +0 -89
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-unmodified-loop-condition */
|
|
2
|
-
import FlatQueue from "flatqueue";
|
|
3
|
-
import { isNumber } from "vega-util";
|
|
4
|
-
import { field } from "../../utils/field";
|
|
5
|
-
import FlowNode, { BEHAVIOR_MODIFIES } from "../flowNode";
|
|
6
|
-
|
|
7
|
-
const maxDepth = 65536;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @typedef {import("../../spec/transform").PileupParams} PileupParams
|
|
11
|
-
*/
|
|
12
|
-
export default class PileupTransform extends FlowNode {
|
|
13
|
-
get behavior() {
|
|
14
|
-
return BEHAVIOR_MODIFIES;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
19
|
-
* @param {PileupParams} params
|
|
20
|
-
*/
|
|
21
|
-
constructor(params) {
|
|
22
|
-
super();
|
|
23
|
-
|
|
24
|
-
this.params = params;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
reset() {
|
|
28
|
-
this.initialize();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
initialize() {
|
|
32
|
-
const params = this.params;
|
|
33
|
-
|
|
34
|
-
const laneField = params.as || "lane";
|
|
35
|
-
const spacing = isNumber(params.spacing) ? params.spacing : 1;
|
|
36
|
-
const startAccessor = field(params.start);
|
|
37
|
-
const endAccessor = field(params.end);
|
|
38
|
-
|
|
39
|
-
// We choose the implementation based on the need of order preference.
|
|
40
|
-
// The preference-aware algorithm has a lousy O(n^2) time complexity but
|
|
41
|
-
// it's acceptable for finding lanes for genes based on their strands.
|
|
42
|
-
|
|
43
|
-
// Both implementations expect the items to be sorted by their start
|
|
44
|
-
// coordinates.
|
|
45
|
-
|
|
46
|
-
if (!params.preference !== !params.preferredOrder) {
|
|
47
|
-
throw new Error(
|
|
48
|
-
`Must specify both "preference" and "preferredOrder"`
|
|
49
|
-
);
|
|
50
|
-
} else if (params.preference) {
|
|
51
|
-
const freeLaneMap = new Float64Array(maxDepth);
|
|
52
|
-
|
|
53
|
-
const preferenceAccessor = field(params.preference);
|
|
54
|
-
/** @type {any[]} */
|
|
55
|
-
const preferredOrder = params.preferredOrder;
|
|
56
|
-
|
|
57
|
-
let lastStart = Infinity;
|
|
58
|
-
|
|
59
|
-
/** @param {Record<string, any>} datum */
|
|
60
|
-
this.handle = (datum) => {
|
|
61
|
-
const start = startAccessor(datum);
|
|
62
|
-
if (start < lastStart) {
|
|
63
|
-
// Reset if encountered a new chromosome...
|
|
64
|
-
freeLaneMap.fill(-Infinity);
|
|
65
|
-
}
|
|
66
|
-
lastStart = start;
|
|
67
|
-
|
|
68
|
-
// Linear search, but the number of preferences is likely be low
|
|
69
|
-
const preferredLane = preferredOrder.indexOf(
|
|
70
|
-
preferenceAccessor(datum)
|
|
71
|
-
);
|
|
72
|
-
let lane = -1;
|
|
73
|
-
if (preferredLane >= 0 && freeLaneMap[preferredLane] < start) {
|
|
74
|
-
lane = preferredLane;
|
|
75
|
-
} else {
|
|
76
|
-
const start = startAccessor(datum);
|
|
77
|
-
for (lane = 0; lane < freeLaneMap.length; lane++) {
|
|
78
|
-
if (freeLaneMap[lane] < start) {
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (lane >= freeLaneMap.length) {
|
|
83
|
-
throw new Error("Out of lanes!");
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
freeLaneMap[lane] = endAccessor(datum) + spacing;
|
|
87
|
-
datum[laneField] = lane;
|
|
88
|
-
this._propagate(datum);
|
|
89
|
-
};
|
|
90
|
-
} else {
|
|
91
|
-
/** @type {FlatQueue<number>} */
|
|
92
|
-
const ends = new FlatQueue();
|
|
93
|
-
|
|
94
|
-
/** @type {FlatQueue<number>} */
|
|
95
|
-
const freeLanes = new FlatQueue();
|
|
96
|
-
|
|
97
|
-
// Keep track of the last processed element. Flush the queues if the start
|
|
98
|
-
// pos suddenly decreases. This happens when piling up consecutive chromosomes.
|
|
99
|
-
let lastStart = -Infinity;
|
|
100
|
-
|
|
101
|
-
let maxLane = 0;
|
|
102
|
-
|
|
103
|
-
/** @param {Record<string, any>} datum */
|
|
104
|
-
this.handle = (datum) => {
|
|
105
|
-
const start = startAccessor(datum);
|
|
106
|
-
while (
|
|
107
|
-
ends.length &&
|
|
108
|
-
(ends.peekValue() <= start || start < lastStart)
|
|
109
|
-
) {
|
|
110
|
-
const freeLane = ends.pop();
|
|
111
|
-
freeLanes.push(freeLane, freeLane);
|
|
112
|
-
}
|
|
113
|
-
lastStart = start;
|
|
114
|
-
|
|
115
|
-
let lane = freeLanes.pop();
|
|
116
|
-
if (lane === undefined) {
|
|
117
|
-
lane = maxLane++;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
datum[laneField] = lane;
|
|
121
|
-
|
|
122
|
-
this._propagate(datum);
|
|
123
|
-
|
|
124
|
-
ends.push(lane, endAccessor(datum) + spacing);
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "vitest";
|
|
2
|
-
import PileupTransform from "./pileup";
|
|
3
|
-
import { processData } from "../flowTestUtils";
|
|
4
|
-
|
|
5
|
-
// TODO: Test for lane preferences
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {import("../../spec/transform").PileupParams} PileupParams
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const reads = [
|
|
12
|
-
[0, 4],
|
|
13
|
-
[1, 3],
|
|
14
|
-
[2, 6],
|
|
15
|
-
[4, 8],
|
|
16
|
-
[8, 10],
|
|
17
|
-
[11, 14],
|
|
18
|
-
[11, 13],
|
|
19
|
-
[11, 12],
|
|
20
|
-
[15, 18],
|
|
21
|
-
[16, 18],
|
|
22
|
-
[17, 18],
|
|
23
|
-
].map((d) => ({
|
|
24
|
-
start: d[0],
|
|
25
|
-
end: d[1],
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
const lanes = [0, 1, 2, 1, 0, 0, 1, 2, 0, 1, 2];
|
|
29
|
-
|
|
30
|
-
/** @type {PileupParams} */
|
|
31
|
-
const params = {
|
|
32
|
-
type: "pileup",
|
|
33
|
-
start: "start",
|
|
34
|
-
end: "end",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @param {PileupParams} params
|
|
39
|
-
* @param {any[]} data
|
|
40
|
-
*/
|
|
41
|
-
function pileupTransform(params, data) {
|
|
42
|
-
const t = new PileupTransform(params);
|
|
43
|
-
t.initialize();
|
|
44
|
-
return processData(t, data);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
test("Pileup transform produces correct pileup", () => {
|
|
48
|
-
const piledUp = lanes.map((d, i) => ({
|
|
49
|
-
...reads[i],
|
|
50
|
-
lane: d,
|
|
51
|
-
}));
|
|
52
|
-
|
|
53
|
-
expect(pileupTransform(params, reads)).toEqual(piledUp);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("Pileup transform produces correct pileup with consecutive contigs", () => {
|
|
57
|
-
// Simulate data having multiple chromosomes, sorted by [chrom, pos].
|
|
58
|
-
// Piling should handle suddenly decreasing start positions by freeing all
|
|
59
|
-
// reserved lanes.
|
|
60
|
-
|
|
61
|
-
const repeatedReads = [...reads, ...reads];
|
|
62
|
-
const repeatedLanes = [...lanes, ...lanes];
|
|
63
|
-
|
|
64
|
-
const piledUp = repeatedLanes.map((d, i) => ({
|
|
65
|
-
...repeatedReads[i],
|
|
66
|
-
lane: d,
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
expect(pileupTransform(params, repeatedReads)).toEqual(piledUp);
|
|
70
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { accessorName } from "vega-util";
|
|
2
|
-
import { field } from "../../utils/field";
|
|
3
|
-
import FlowNode, { BEHAVIOR_CLONES } from "../flowNode";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @typedef {import("../../spec/transform").ProjectParams} ProjectParams
|
|
7
|
-
*/
|
|
8
|
-
export default class ProjectTransform extends FlowNode {
|
|
9
|
-
get behavior() {
|
|
10
|
-
return BEHAVIOR_CLONES;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {ProjectParams} params
|
|
16
|
-
*/
|
|
17
|
-
constructor(params) {
|
|
18
|
-
super();
|
|
19
|
-
|
|
20
|
-
if (params.as && params.as.length != params.fields.length) {
|
|
21
|
-
throw new Error(`"fields" and "as" have unequal lengths!`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// TODO: "If unspecified, all fields will be copied using their existing names."
|
|
25
|
-
|
|
26
|
-
const accessors = params.fields.map((f) => field(f));
|
|
27
|
-
const as = params.as ? params.as : accessors.map(accessorName);
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @param {any} datum
|
|
31
|
-
*/
|
|
32
|
-
this.handle = (datum) => {
|
|
33
|
-
/** @type {Record<string, any>} */
|
|
34
|
-
const projected = {};
|
|
35
|
-
for (let i = 0; i < accessors.length; i++) {
|
|
36
|
-
projected[as[i]] = accessors[i](datum);
|
|
37
|
-
}
|
|
38
|
-
this._propagate(projected);
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "vitest";
|
|
2
|
-
import { processData } from "../flowTestUtils";
|
|
3
|
-
import ProjectTransform from "./project";
|
|
4
|
-
|
|
5
|
-
test("Project", () => {
|
|
6
|
-
const data = [
|
|
7
|
-
{
|
|
8
|
-
foo: "FOO",
|
|
9
|
-
bar: "BAR",
|
|
10
|
-
baz: { a: "A" },
|
|
11
|
-
},
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
/** @param {import("./project").ProjectParams} params */
|
|
15
|
-
const p = (params) => processData(new ProjectTransform(params), data);
|
|
16
|
-
|
|
17
|
-
expect(p({ type: "project", fields: ["bar"] })).toEqual([{ bar: "BAR" }]);
|
|
18
|
-
|
|
19
|
-
expect(
|
|
20
|
-
p({ type: "project", fields: ["bar", "foo"], as: ["xBar", "xFoo"] })
|
|
21
|
-
).toEqual([{ xBar: "BAR", xFoo: "FOO" }]);
|
|
22
|
-
|
|
23
|
-
expect(p({ type: "project", fields: ["baz.a"] })).toEqual([
|
|
24
|
-
{ "baz.a": "A" },
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
expect(p({ type: "project", fields: ["baz.a"], as: ["a"] })).toEqual([
|
|
28
|
-
{ a: "A" },
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
expect(() => p({ type: "project", fields: ["bar"], as: [] })).toThrow();
|
|
32
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { isString } from "vega-util";
|
|
2
|
-
import { field } from "../../utils/field";
|
|
3
|
-
import FlowNode, { BEHAVIOR_MODIFIES } from "../flowNode";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @typedef {import("../../spec/transform").RegexExtractParams} RegexExtractParams
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export default class RegexExtractTransform extends FlowNode {
|
|
10
|
-
get behavior() {
|
|
11
|
-
return BEHAVIOR_MODIFIES;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param {RegexExtractParams} params
|
|
16
|
-
*/
|
|
17
|
-
constructor(params) {
|
|
18
|
-
super();
|
|
19
|
-
|
|
20
|
-
const re = new RegExp(params.regex);
|
|
21
|
-
const as = typeof params.as == "string" ? [params.as] : params.as;
|
|
22
|
-
const accessor = field(params.field);
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
* @param {any} datum
|
|
27
|
-
*/
|
|
28
|
-
this.handle = (datum) => {
|
|
29
|
-
const value = accessor(datum);
|
|
30
|
-
if (isString(value)) {
|
|
31
|
-
const m = value.match(re);
|
|
32
|
-
|
|
33
|
-
if (m) {
|
|
34
|
-
if (m.length - 1 != as.length) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
'The number of RegEx groups and the length of "as" do not match!'
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
for (let i = 0; i < as.length; i++) {
|
|
41
|
-
datum[as[i]] = m[i + 1];
|
|
42
|
-
}
|
|
43
|
-
} else if (params.skipInvalidInput) {
|
|
44
|
-
for (let i = 0; i < as.length; i++) {
|
|
45
|
-
datum[as[i]] = undefined;
|
|
46
|
-
}
|
|
47
|
-
} else {
|
|
48
|
-
throw new Error(
|
|
49
|
-
`"${value}" does not match the given regex: ${re.toString()}`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
} else if (!params.skipInvalidInput) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Trying to match a non-string field. Encountered type: ${typeof value}, field content: "${value}".`
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
this._propagate(datum);
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
-
import { processData } from "../flowTestUtils";
|
|
3
|
-
|
|
4
|
-
import RegexExtractTransform from "./regexExtract";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @param {import("./regexExtract").RegexExtractParams} params
|
|
8
|
-
* @param {any[]} data
|
|
9
|
-
*/
|
|
10
|
-
function transform(params, data) {
|
|
11
|
-
return processData(new RegexExtractTransform(params), data);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe("RegexExtractTransform", () => {
|
|
15
|
-
const rows = [{ a: "12-34" }, { a: "23-45" }];
|
|
16
|
-
|
|
17
|
-
/** @type {import("./regexExtract").RegexExtractParams} */
|
|
18
|
-
const params = {
|
|
19
|
-
type: "regexExtract",
|
|
20
|
-
regex: "^(\\d+)-(\\d+)$",
|
|
21
|
-
field: "a",
|
|
22
|
-
as: ["b", "c"],
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
test("Valid config and input", () => {
|
|
26
|
-
expect(transform(params, rows)).toEqual([
|
|
27
|
-
{ a: "12-34", b: "12", c: "34" },
|
|
28
|
-
{ a: "23-45", b: "23", c: "45" },
|
|
29
|
-
]);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("Invalid config", () => {
|
|
33
|
-
/** @type {import("./regexExtract").RegexExtractParams} */
|
|
34
|
-
const config2 = {
|
|
35
|
-
type: "regexExtract",
|
|
36
|
-
regex: "^(\\d+)-(\\d+)$",
|
|
37
|
-
field: "a",
|
|
38
|
-
as: ["b", "c", "d"],
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
expect(() => transform(config2, rows)).toThrow();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("Invalid data", () => {
|
|
45
|
-
const rows2 = [{ a: "12--34" }];
|
|
46
|
-
|
|
47
|
-
expect(() => transform(params, rows2)).toThrow();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("Invalid, non-string data", () => {
|
|
51
|
-
const rows2 = [{ a: 123 }];
|
|
52
|
-
|
|
53
|
-
expect(() => transform(params, rows2)).toThrow();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("Skip invalid or non-string data", () => {
|
|
57
|
-
const rows2 = [{ a: 123 }, { a: "xyzzy" }, { a: "12-34" }];
|
|
58
|
-
|
|
59
|
-
expect(transform({ ...params, skipInvalidInput: true }, rows2)).toEqual(
|
|
60
|
-
[
|
|
61
|
-
{ a: 123, b: undefined, c: undefined },
|
|
62
|
-
{ a: "xyzzy", b: undefined, c: undefined },
|
|
63
|
-
{ a: "12-34", b: "12", c: "34" },
|
|
64
|
-
]
|
|
65
|
-
);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { asArray } from "../../utils/arrayUtils";
|
|
2
|
-
import FlowNode, { BEHAVIOR_CLONES, isFileBatch } from "../flowNode";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Folds fields using a regex
|
|
6
|
-
*
|
|
7
|
-
* See: https://vega.github.io/vega/docs/transforms/fold/
|
|
8
|
-
*
|
|
9
|
-
* @typedef {import("../../spec/transform").RegexFoldParams} RegexFoldParams
|
|
10
|
-
*/
|
|
11
|
-
export default class RegexFoldTransform extends FlowNode {
|
|
12
|
-
get behavior() {
|
|
13
|
-
return BEHAVIOR_CLONES;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @param {RegexFoldParams} params
|
|
18
|
-
*/
|
|
19
|
-
constructor(params) {
|
|
20
|
-
super();
|
|
21
|
-
|
|
22
|
-
const columnRegex = asArray(params.columnRegex).map(
|
|
23
|
-
(re) => new RegExp(re)
|
|
24
|
-
);
|
|
25
|
-
// TODO: Consider using named groups
|
|
26
|
-
const as = asArray(params.asValue);
|
|
27
|
-
|
|
28
|
-
if (columnRegex.length != as.length) {
|
|
29
|
-
throw new Error('Lengths of "columnRegex" and "as" are not equal!');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const skipRegex = params.skipRegex
|
|
33
|
-
? new RegExp(params.skipRegex)
|
|
34
|
-
: undefined;
|
|
35
|
-
|
|
36
|
-
const sampleKey = params.asKey || "sample";
|
|
37
|
-
|
|
38
|
-
/** @type {[string, string[]][]} */
|
|
39
|
-
let sampleAttrs;
|
|
40
|
-
|
|
41
|
-
/** @type {string[]} */
|
|
42
|
-
let includedColumns;
|
|
43
|
-
|
|
44
|
-
/** @type {(datum: any, sampleId: string) => Record<string, any>} */
|
|
45
|
-
let create;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @param {any} datum
|
|
49
|
-
*/
|
|
50
|
-
const detectColumns = (datum) => {
|
|
51
|
-
const colNames = /** @type {string[]} */ (Object.keys(datum));
|
|
52
|
-
|
|
53
|
-
/** @type {Map<string, string[]>} */
|
|
54
|
-
const sampleColMap = new Map();
|
|
55
|
-
|
|
56
|
-
for (const [i, re] of columnRegex.entries()) {
|
|
57
|
-
for (const colName of colNames) {
|
|
58
|
-
const sampleId = re.exec(colName)?.[1];
|
|
59
|
-
if (sampleId !== undefined) {
|
|
60
|
-
let attrs = sampleColMap.get(sampleId);
|
|
61
|
-
if (!attrs) {
|
|
62
|
-
attrs = [];
|
|
63
|
-
sampleColMap.set(sampleId, attrs);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
attrs[i] = colName;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
sampleAttrs = [...sampleColMap.entries()];
|
|
72
|
-
|
|
73
|
-
includedColumns = colNames.filter(
|
|
74
|
-
(colName) =>
|
|
75
|
-
!columnRegex.some((re) => re.test(colName)) &&
|
|
76
|
-
!(skipRegex && skipRegex.test(colName))
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const props = [
|
|
80
|
-
...includedColumns.map(
|
|
81
|
-
(prop) =>
|
|
82
|
-
JSON.stringify(prop) +
|
|
83
|
-
": datum[" +
|
|
84
|
-
JSON.stringify(prop) +
|
|
85
|
-
"]"
|
|
86
|
-
),
|
|
87
|
-
JSON.stringify(sampleKey) + ": sampleId",
|
|
88
|
-
...as.map((a) => JSON.stringify(a) + ": null"),
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
// eslint-disable-next-line no-new-func
|
|
92
|
-
create = /** @type {any} */ (
|
|
93
|
-
new Function(
|
|
94
|
-
"datum",
|
|
95
|
-
"sampleId",
|
|
96
|
-
"return {\n" + props.join(",\n") + "\n};"
|
|
97
|
-
)
|
|
98
|
-
);
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* @param {any} datum
|
|
103
|
-
*/
|
|
104
|
-
const doRegexFold = (datum) => {
|
|
105
|
-
if (!sampleAttrs) {
|
|
106
|
-
detectColumns(datum);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
for (const [sampleId, attrs] of sampleAttrs) {
|
|
110
|
-
const tidyRow = create(datum, sampleId);
|
|
111
|
-
for (let i = 0; i < attrs.length; i++) {
|
|
112
|
-
tidyRow[as[i]] = datum[attrs[i]];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
this._propagate(tidyRow);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* @param {any} datum
|
|
121
|
-
*/
|
|
122
|
-
const detectAndHandle = (datum) => {
|
|
123
|
-
detectColumns(datum);
|
|
124
|
-
doRegexFold(datum);
|
|
125
|
-
this.handle = doRegexFold;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
this.handle = detectAndHandle;
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
*
|
|
132
|
-
* @param {import("../flowNode").FlowBatch} flowBatch
|
|
133
|
-
*/
|
|
134
|
-
this.beginBatch = (flowBatch) => {
|
|
135
|
-
if (isFileBatch(flowBatch)) {
|
|
136
|
-
this.handle = detectAndHandle;
|
|
137
|
-
}
|
|
138
|
-
super.beginBatch(flowBatch);
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
-
import { processData } from "../flowTestUtils";
|
|
3
|
-
import RegexFoldTransform from "./regexFold";
|
|
4
|
-
|
|
5
|
-
describe("RegexFold", () => {
|
|
6
|
-
test("Transform single variable", () => {
|
|
7
|
-
const sampleData = [
|
|
8
|
-
{
|
|
9
|
-
row: 1,
|
|
10
|
-
sample1_a: "r1s1a",
|
|
11
|
-
sample2_a: "r1s2a",
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
row: 2,
|
|
15
|
-
sample1_a: "r2s1a",
|
|
16
|
-
sample2_a: "r2s2a",
|
|
17
|
-
},
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
/** @type { import("../../spec/transform").RegexFoldParams } */
|
|
21
|
-
const singleGatherConfig = {
|
|
22
|
-
type: "regexFold",
|
|
23
|
-
columnRegex: "^(.*)_a$",
|
|
24
|
-
asValue: "a",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const result = processData(
|
|
28
|
-
new RegexFoldTransform(singleGatherConfig),
|
|
29
|
-
sampleData
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
expect(result).toEqual([
|
|
33
|
-
{
|
|
34
|
-
row: 1,
|
|
35
|
-
sample: "sample1",
|
|
36
|
-
a: "r1s1a",
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
row: 1,
|
|
40
|
-
sample: "sample2",
|
|
41
|
-
a: "r1s2a",
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
row: 2,
|
|
45
|
-
sample: "sample1",
|
|
46
|
-
a: "r2s1a",
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
row: 2,
|
|
50
|
-
sample: "sample2",
|
|
51
|
-
a: "r2s2a",
|
|
52
|
-
},
|
|
53
|
-
]);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("Transform single variable and skip specific columns", () => {
|
|
57
|
-
const sampleData = [
|
|
58
|
-
{
|
|
59
|
-
row: 1,
|
|
60
|
-
sample1_a: "r1s1a",
|
|
61
|
-
sample2_a: "r1s2a",
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
row: 2,
|
|
65
|
-
sample1_a: "r2s1a",
|
|
66
|
-
sample2_a: "r2s2a",
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
/** @type { import("../../spec/transform").RegexFoldParams } */
|
|
71
|
-
const singleGatherConfig = {
|
|
72
|
-
type: "regexFold",
|
|
73
|
-
columnRegex: "^(.*)_a$",
|
|
74
|
-
asValue: "a",
|
|
75
|
-
skipRegex: "^row$",
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const result = processData(
|
|
79
|
-
new RegexFoldTransform(singleGatherConfig),
|
|
80
|
-
sampleData
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
expect(result).toEqual([
|
|
84
|
-
{
|
|
85
|
-
sample: "sample1",
|
|
86
|
-
a: "r1s1a",
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
sample: "sample2",
|
|
90
|
-
a: "r1s2a",
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
sample: "sample1",
|
|
94
|
-
a: "r2s1a",
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
sample: "sample2",
|
|
98
|
-
a: "r2s2a",
|
|
99
|
-
},
|
|
100
|
-
]);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("Transform multiple variables", () => {
|
|
104
|
-
const sampleData = [
|
|
105
|
-
{
|
|
106
|
-
row: 1,
|
|
107
|
-
sample1_a: "r1s1a",
|
|
108
|
-
sample2_a: "r1s2a",
|
|
109
|
-
sample1_b: "r1s1b",
|
|
110
|
-
sample2_b: "r1s2b",
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
row: 2,
|
|
114
|
-
sample1_a: "r2s1a",
|
|
115
|
-
sample2_a: "r2s2a",
|
|
116
|
-
sample1_b: "r2s1b",
|
|
117
|
-
sample2_b: "r2s2b",
|
|
118
|
-
},
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
/** @type { import("../../spec/transform").RegexFoldParams } */
|
|
122
|
-
const singleGatherConfig = {
|
|
123
|
-
type: "regexFold",
|
|
124
|
-
columnRegex: ["^(.*)_a$", "^(.*)_b$"],
|
|
125
|
-
asValue: ["a", "b"],
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const result = processData(
|
|
129
|
-
new RegexFoldTransform(singleGatherConfig),
|
|
130
|
-
sampleData
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
expect(result).toEqual([
|
|
134
|
-
{
|
|
135
|
-
row: 1,
|
|
136
|
-
sample: "sample1",
|
|
137
|
-
a: "r1s1a",
|
|
138
|
-
b: "r1s1b",
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
row: 1,
|
|
142
|
-
sample: "sample2",
|
|
143
|
-
a: "r1s2a",
|
|
144
|
-
b: "r1s2b",
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
row: 2,
|
|
148
|
-
sample: "sample1",
|
|
149
|
-
a: "r2s1a",
|
|
150
|
-
b: "r2s1b",
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
row: 2,
|
|
154
|
-
sample: "sample2",
|
|
155
|
-
a: "r2s2a",
|
|
156
|
-
b: "r2s2b",
|
|
157
|
-
},
|
|
158
|
-
]);
|
|
159
|
-
});
|
|
160
|
-
});
|