@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
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import SampleTransform from "./sample";
|
|
2
|
+
import { extent } from "d3-array";
|
|
3
|
+
import { createChain } from "../../view/flowBuilder";
|
|
4
|
+
|
|
5
|
+
test("SampleTransform produces roughly uniform distributions", () => {
|
|
6
|
+
const size = 10;
|
|
7
|
+
const n = 20;
|
|
8
|
+
const rounds = 10000;
|
|
9
|
+
|
|
10
|
+
const freqs = [];
|
|
11
|
+
for (let i = 0; i < n; i++) {
|
|
12
|
+
freqs[i] = 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { dataSource, collector } = createChain(
|
|
16
|
+
new SampleTransform({ type: "sample", size })
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
for (let r = 0; r < rounds; r++) {
|
|
20
|
+
for (let i = 0; i < n; i++) {
|
|
21
|
+
dataSource.handle({ data: i });
|
|
22
|
+
}
|
|
23
|
+
dataSource.complete();
|
|
24
|
+
|
|
25
|
+
for (const datum of collector.getData()) {
|
|
26
|
+
freqs[datum.data] = freqs[datum.data] + 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
dataSource.reset();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const e = extent(freqs);
|
|
33
|
+
|
|
34
|
+
// Not a deterministic test! TODO: Come up with some sensical testing method
|
|
35
|
+
expect(e[0]).toBeGreaterThan(4800);
|
|
36
|
+
expect(e[1]).toBeLessThan(5200);
|
|
37
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { compare } from "vega-util";
|
|
2
|
+
import { groups as d3groups, sum as d3sum } from "d3-array";
|
|
3
|
+
import FlowNode, { BEHAVIOR_MODIFIES } from "../flowNode";
|
|
4
|
+
import { field } from "../../utils/field";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import("../../spec/transform").StackParams} StackParams
|
|
8
|
+
*/
|
|
9
|
+
export default class StackTransform extends FlowNode {
|
|
10
|
+
get behavior() {
|
|
11
|
+
return BEHAVIOR_MODIFIES;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {StackParams} params
|
|
16
|
+
*/
|
|
17
|
+
constructor(params) {
|
|
18
|
+
super();
|
|
19
|
+
this.params = params;
|
|
20
|
+
|
|
21
|
+
/** @type {any[]} */
|
|
22
|
+
this.buffer = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
reset() {
|
|
26
|
+
this.buffer = [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
* @param {import("../flowNode").Datum} datum
|
|
32
|
+
*/
|
|
33
|
+
handle(datum) {
|
|
34
|
+
this.buffer.push(datum);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
complete() {
|
|
38
|
+
const params = this.params;
|
|
39
|
+
|
|
40
|
+
const as = params.as || ["y0", "y1"]; // TODO: Validate
|
|
41
|
+
|
|
42
|
+
const comparator = params.sort
|
|
43
|
+
? compare(params.sort.field, params.sort.order)
|
|
44
|
+
: undefined;
|
|
45
|
+
|
|
46
|
+
const valueAccessor = params.field ? field(params.field) : (d) => 1;
|
|
47
|
+
|
|
48
|
+
const groupFields = params.groupby.map((f) => field(f));
|
|
49
|
+
|
|
50
|
+
const groups = d3groups(this.buffer, (row) =>
|
|
51
|
+
groupFields.map((f) => f(row)).join()
|
|
52
|
+
).map((a) => a[1]);
|
|
53
|
+
|
|
54
|
+
/** @type {(datum: any) => boolean} */
|
|
55
|
+
let inclusionPredicate = (datum) => true;
|
|
56
|
+
|
|
57
|
+
if (params.baseField) {
|
|
58
|
+
const baseAccessor = field(params.baseField);
|
|
59
|
+
inclusionPredicate = (datum) => baseAccessor(datum) !== null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @type {(value: number, sum: number) => number} */
|
|
63
|
+
let offsetF;
|
|
64
|
+
|
|
65
|
+
/** @type {(values: number[], accessor: (datum: any) => number) => number} */
|
|
66
|
+
let sumF;
|
|
67
|
+
|
|
68
|
+
switch (params.offset) {
|
|
69
|
+
case "normalize":
|
|
70
|
+
offsetF = (value, sum) => value / sum;
|
|
71
|
+
sumF = (values, accessor) => d3sum(values, accessor);
|
|
72
|
+
break;
|
|
73
|
+
case "center":
|
|
74
|
+
offsetF = (value, sum) => value - sum / 2;
|
|
75
|
+
sumF = (values, accessor) => d3sum(values, accessor);
|
|
76
|
+
break;
|
|
77
|
+
case "information":
|
|
78
|
+
{
|
|
79
|
+
// Sequence logos: a new way to display consensus sequences (Schneider and Stephens)
|
|
80
|
+
// doi://10.1093/nar/18.20.6097
|
|
81
|
+
|
|
82
|
+
const maxBits = Math.log2(params.cardinality ?? 4);
|
|
83
|
+
offsetF = (value, sum) => value / sum;
|
|
84
|
+
sumF = (values, accessor) => {
|
|
85
|
+
const e = 0; // TODO: Correction factor for small sample sizes
|
|
86
|
+
|
|
87
|
+
const gaps = d3sum(
|
|
88
|
+
values,
|
|
89
|
+
(d) => +!inclusionPredicate(d)
|
|
90
|
+
);
|
|
91
|
+
const total = d3sum(values, accessor);
|
|
92
|
+
const nonGaps = total - gaps;
|
|
93
|
+
|
|
94
|
+
let H = 0;
|
|
95
|
+
for (let i = 0; i < values.length; i++) {
|
|
96
|
+
const datum = values[i];
|
|
97
|
+
if (inclusionPredicate(datum)) {
|
|
98
|
+
const b = accessor(datum) / nonGaps;
|
|
99
|
+
H -= b * Math.log2(b);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
(nonGaps / (maxBits - (H + e))) * (nonGaps / total)
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
offsetF = (value, sum) => value;
|
|
111
|
+
sumF = (values, accessor) => 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const group of groups) {
|
|
115
|
+
if (comparator) {
|
|
116
|
+
group.sort(comparator);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const sum = sumF(group, valueAccessor);
|
|
120
|
+
|
|
121
|
+
let prev = 0;
|
|
122
|
+
for (const datum of group) {
|
|
123
|
+
const current = prev + valueAccessor(datum);
|
|
124
|
+
|
|
125
|
+
if (inclusionPredicate(datum)) {
|
|
126
|
+
datum[as[0]] = offsetF(prev, sum);
|
|
127
|
+
datum[as[1]] = offsetF(current, sum);
|
|
128
|
+
|
|
129
|
+
this._propagate(datum);
|
|
130
|
+
prev = current;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
super.complete();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { processData } from "../flowTestUtils";
|
|
2
|
+
import StackTransform from "./stack";
|
|
3
|
+
|
|
4
|
+
const sampleData = [
|
|
5
|
+
{ group: "a", choice: "q", value: 1 },
|
|
6
|
+
{ group: "b", choice: "x", value: 1 },
|
|
7
|
+
{ group: "b", choice: "y", value: 3 },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
/** @type {import("./stack").StackParams} */
|
|
11
|
+
const baseConf = {
|
|
12
|
+
type: "stack",
|
|
13
|
+
field: "value",
|
|
14
|
+
groupby: ["group"],
|
|
15
|
+
sort: {
|
|
16
|
+
field: "value",
|
|
17
|
+
order: "ascending",
|
|
18
|
+
},
|
|
19
|
+
offset: "zero",
|
|
20
|
+
as: ["z0", "z1"],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @param {import("./stack").StackParams} params
|
|
26
|
+
* @param {any[]} data
|
|
27
|
+
*/
|
|
28
|
+
function transform(params, data) {
|
|
29
|
+
return processData(new StackTransform(params), data);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("Stack transform", () => {
|
|
33
|
+
test("No field", () => {
|
|
34
|
+
const conf = Object.assign({}, baseConf, {
|
|
35
|
+
field: undefined,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(transform(conf, sampleData)).toEqual([
|
|
39
|
+
{ group: "a", choice: "q", value: 1, z0: 0, z1: 1 },
|
|
40
|
+
{ group: "b", choice: "x", value: 1, z0: 0, z1: 1 },
|
|
41
|
+
{ group: "b", choice: "y", value: 3, z0: 1, z1: 2 },
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("Zero offset", () => {
|
|
46
|
+
expect(transform(baseConf, sampleData)).toEqual([
|
|
47
|
+
{ group: "a", choice: "q", value: 1, z0: 0, z1: 1 },
|
|
48
|
+
{ group: "b", choice: "x", value: 1, z0: 0, z1: 1 },
|
|
49
|
+
{ group: "b", choice: "y", value: 3, z0: 1, z1: 4 },
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("Normalize offset", () => {
|
|
54
|
+
const conf = Object.assign({}, baseConf, {
|
|
55
|
+
offset: "normalize",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(transform(conf, sampleData)).toEqual([
|
|
59
|
+
{ group: "a", choice: "q", value: 1, z0: 0, z1: 1 },
|
|
60
|
+
{ group: "b", choice: "x", value: 1, z0: 0, z1: 0.25 },
|
|
61
|
+
{ group: "b", choice: "y", value: 3, z0: 0.25, z1: 1 },
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("Center offset", () => {
|
|
66
|
+
const conf = Object.assign({}, baseConf, {
|
|
67
|
+
offset: "center",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(transform(conf, sampleData)).toEqual([
|
|
71
|
+
{ group: "a", choice: "q", value: 1, z0: -0.5, z1: 0.5 },
|
|
72
|
+
{ group: "b", choice: "x", value: 1, z0: -2, z1: -1 },
|
|
73
|
+
{ group: "b", choice: "y", value: 3, z0: -1, z1: 2 },
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("Descending sort", () => {
|
|
78
|
+
const conf = Object.assign({}, baseConf, {
|
|
79
|
+
sort: {
|
|
80
|
+
field: "value",
|
|
81
|
+
order: "descending",
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
expect(transform(conf, sampleData)).toEqual([
|
|
85
|
+
{ group: "a", choice: "q", value: 1, z0: 0, z1: 1 },
|
|
86
|
+
{ group: "b", choice: "y", value: 3, z0: 0, z1: 3 },
|
|
87
|
+
{ group: "b", choice: "x", value: 1, z0: 3, z1: 4 },
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Collector from "../collector";
|
|
2
|
+
import CoverageTransform from "./coverage";
|
|
3
|
+
import FilterScoredLabelsTransform from "./filterScoredLabels";
|
|
4
|
+
import FilterTransform from "./filter";
|
|
5
|
+
import FlattenCompressedExonsTransform from "./flattenCompressedExons";
|
|
6
|
+
import FlattenDelimitedTransform from "./flattenDelimited";
|
|
7
|
+
import FormulaTransform from "./formula";
|
|
8
|
+
import LinearizeGenomicCoordinate from "./linearizeGenomicCoordinate";
|
|
9
|
+
import MeasureTextTransform from "./measureText";
|
|
10
|
+
import PileupTransform from "./pileup";
|
|
11
|
+
import ProjectTransform from "./project";
|
|
12
|
+
import RegexExtractTransform from "./regexExtract";
|
|
13
|
+
import RegexFoldTransform from "./regexFold";
|
|
14
|
+
import SampleTransform from "./sample";
|
|
15
|
+
import StackTransform from "./stack";
|
|
16
|
+
import FlattenSequenceTransform from "./flattenSequence";
|
|
17
|
+
import AggregateTransform from "./aggregate";
|
|
18
|
+
import IdentifierTransform from "./identifier";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* TODO: Make this dynamic
|
|
22
|
+
*
|
|
23
|
+
* @typedef {import("../../view/view").default} View
|
|
24
|
+
* @typedef {import("../flowNode").default} FlowNode
|
|
25
|
+
*
|
|
26
|
+
* @type {Record<string, new (params: any, view?: View) => FlowNode>}
|
|
27
|
+
*/
|
|
28
|
+
export const transforms = {
|
|
29
|
+
aggregate: AggregateTransform,
|
|
30
|
+
collect: Collector,
|
|
31
|
+
coverage: CoverageTransform,
|
|
32
|
+
filterScoredLabels: FilterScoredLabelsTransform,
|
|
33
|
+
filter: FilterTransform,
|
|
34
|
+
flattenCompressedExons: FlattenCompressedExonsTransform,
|
|
35
|
+
flattenDelimited: FlattenDelimitedTransform,
|
|
36
|
+
flattenSequence: FlattenSequenceTransform,
|
|
37
|
+
formula: FormulaTransform,
|
|
38
|
+
identifier: IdentifierTransform,
|
|
39
|
+
linearizeGenomicCoordinate: LinearizeGenomicCoordinate,
|
|
40
|
+
measureText: MeasureTextTransform,
|
|
41
|
+
pileup: PileupTransform,
|
|
42
|
+
project: ProjectTransform,
|
|
43
|
+
regexExtract: RegexExtractTransform,
|
|
44
|
+
regexFold: RegexFoldTransform,
|
|
45
|
+
sample: SampleTransform,
|
|
46
|
+
stack: StackTransform,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {import("../../spec/transform").TransformParamsBase} params
|
|
51
|
+
* @param {View} [view]
|
|
52
|
+
*/
|
|
53
|
+
export default function createTransform(params, view) {
|
|
54
|
+
const Transform = transforms[params.type];
|
|
55
|
+
if (Transform) {
|
|
56
|
+
return new Transform(params, view);
|
|
57
|
+
} else {
|
|
58
|
+
throw new Error("Unknown transform: " + params.type);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import createFunction from "../utils/expression";
|
|
2
|
+
|
|
3
|
+
import { accessorFields, constant } from "vega-util";
|
|
4
|
+
import { isDatumDef, isExprDef, isFieldDef } from "./encoder";
|
|
5
|
+
import { field } from "../utils/field";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} AccessorMetadata
|
|
9
|
+
* @prop {boolean} constant True if the accessor returns the same value for all objects
|
|
10
|
+
* @prop {string[]} fields The fields that the return value is based on (if any)
|
|
11
|
+
*
|
|
12
|
+
* @typedef {(function(any):any) & AccessorMetadata} Accessor
|
|
13
|
+
*
|
|
14
|
+
* @typedef {import("../view/viewUtils").ChannelDef} ChannelDef
|
|
15
|
+
*/
|
|
16
|
+
export default class AccessorFactory {
|
|
17
|
+
constructor() {
|
|
18
|
+
/** @type {(function(ChannelDef):Accessor)[]} */
|
|
19
|
+
this.accessorCreators = [];
|
|
20
|
+
|
|
21
|
+
this.register((channelDef) => {
|
|
22
|
+
if (isFieldDef(channelDef)) {
|
|
23
|
+
try {
|
|
24
|
+
/** @type {Accessor} */
|
|
25
|
+
const accessor = field(channelDef.field);
|
|
26
|
+
accessor.constant = false;
|
|
27
|
+
accessor.fields = accessorFields(accessor);
|
|
28
|
+
return accessor;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
throw new Error(`Invalid field definition: ${e.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
this.register((channelDef) =>
|
|
36
|
+
isExprDef(channelDef)
|
|
37
|
+
? createExpressionAccessor(channelDef.expr)
|
|
38
|
+
: undefined
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
this.register((channelDef) => {
|
|
42
|
+
if (isDatumDef(channelDef)) {
|
|
43
|
+
/** @type {Accessor} */
|
|
44
|
+
const accessor = constant(channelDef.datum);
|
|
45
|
+
accessor.constant = true; // Can be optimized downstream
|
|
46
|
+
accessor.fields = [];
|
|
47
|
+
return accessor;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @param {function(ChannelDef):Accessor} creator
|
|
55
|
+
*/
|
|
56
|
+
register(creator) {
|
|
57
|
+
this.accessorCreators.push(creator);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
*
|
|
62
|
+
* @param {ChannelDef} encoding
|
|
63
|
+
*/
|
|
64
|
+
createAccessor(encoding) {
|
|
65
|
+
for (const creator of this.accessorCreators) {
|
|
66
|
+
const accessor = creator(encoding);
|
|
67
|
+
if (accessor) {
|
|
68
|
+
return accessor;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {string} expr
|
|
76
|
+
*/
|
|
77
|
+
function createExpressionAccessor(expr) {
|
|
78
|
+
/** @type {Accessor} */
|
|
79
|
+
const accessor = createFunction(expr);
|
|
80
|
+
accessor.constant = accessor.fields.length == 0; // Not bulletproof, eh
|
|
81
|
+
return accessor;
|
|
82
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import AccessorFactory from "./accessor";
|
|
2
|
+
|
|
3
|
+
const af = new AccessorFactory();
|
|
4
|
+
|
|
5
|
+
const datum = {
|
|
6
|
+
a: 1,
|
|
7
|
+
b: 2,
|
|
8
|
+
"x.c": 3,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
test("Creates a field accessor", () => {
|
|
12
|
+
const a = af.createAccessor({ field: "a" });
|
|
13
|
+
expect(a(datum)).toEqual(1);
|
|
14
|
+
expect(a.constant).toBeFalsy();
|
|
15
|
+
expect(a.fields).toEqual(["a"]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("Creates an expression accessor", () => {
|
|
19
|
+
const a = af.createAccessor({ expr: `datum.b + datum['x\.c']` });
|
|
20
|
+
expect(a(datum)).toEqual(5);
|
|
21
|
+
expect(a.constant).toBeFalsy();
|
|
22
|
+
expect(a.fields.sort()).toEqual(["b", "x.c"].sort());
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("Creates a constant accessor", () => {
|
|
26
|
+
const a = af.createAccessor({ datum: 0 });
|
|
27
|
+
expect(a(datum)).toEqual(0);
|
|
28
|
+
expect(a.constant).toBeTruthy();
|
|
29
|
+
expect(a.fields).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("Returns undefined on incomplete encoding spec", () => {
|
|
33
|
+
expect(af.createAccessor({})).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("Registers and creates a custom accessor", () => {
|
|
37
|
+
const af = new AccessorFactory();
|
|
38
|
+
af.register((encoding) => {
|
|
39
|
+
if (encoding.iddqd && encoding.idkfa) {
|
|
40
|
+
return (datum) =>
|
|
41
|
+
`${datum[encoding.iddqd]}-${datum[encoding.idkfa]}`;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(af.createAccessor({ iddqd: "a", idkfa: "b" })(datum)).toEqual("1-2");
|
|
46
|
+
});
|