@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,23 @@
|
|
|
1
|
+
/* https://stackoverflow.com/a/41699140/1547896 */
|
|
2
|
+
|
|
3
|
+
export function escapeHtml(str) {
|
|
4
|
+
var map = {
|
|
5
|
+
"&": "&",
|
|
6
|
+
"<": "<",
|
|
7
|
+
">": ">",
|
|
8
|
+
'"': """,
|
|
9
|
+
"'": "'",
|
|
10
|
+
};
|
|
11
|
+
return str.replace(/[&<>"']/g, (m) => map[m]);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function decodeHtml(str) {
|
|
15
|
+
var map = {
|
|
16
|
+
"&": "&",
|
|
17
|
+
"<": "<",
|
|
18
|
+
">": ">",
|
|
19
|
+
""": '"',
|
|
20
|
+
"'": "'",
|
|
21
|
+
};
|
|
22
|
+
return str.replace(/&|<|>|"|'/g, (m) => map[m]);
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as html from "./html";
|
|
2
|
+
|
|
3
|
+
test("Escape HTML", () => {
|
|
4
|
+
expect(html.escapeHtml('< "x" & "y" >')).toEqual(
|
|
5
|
+
"< "x" & "y" >"
|
|
6
|
+
);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("Decode HTML", () => {
|
|
10
|
+
expect(
|
|
11
|
+
html.decodeHtml("< "x" & "y" >")
|
|
12
|
+
).toEqual('< "x" & "y" >');
|
|
13
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assigns unique values an index number in the order they are encountered.
|
|
3
|
+
*
|
|
4
|
+
* TODO: What about undefined?
|
|
5
|
+
*
|
|
6
|
+
* @template T
|
|
7
|
+
*/
|
|
8
|
+
export default function createIndexer() {
|
|
9
|
+
let counter = 0;
|
|
10
|
+
|
|
11
|
+
/** @type {Map<T, number>} */
|
|
12
|
+
const values = new Map();
|
|
13
|
+
|
|
14
|
+
/** @param {T} value */
|
|
15
|
+
const indexer = (value) => {
|
|
16
|
+
let index = values.get(value);
|
|
17
|
+
if (index === undefined) {
|
|
18
|
+
index = counter++;
|
|
19
|
+
values.set(value, index);
|
|
20
|
+
}
|
|
21
|
+
return index;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** @param {Iterable<T>} iterable */
|
|
25
|
+
indexer.addAll = (iterable) => {
|
|
26
|
+
for (const value of iterable) {
|
|
27
|
+
indexer(value);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** @param {number} value */
|
|
32
|
+
indexer.invert = (value) => {
|
|
33
|
+
for (const entry of values.entries()) {
|
|
34
|
+
if (entry[1] == value) {
|
|
35
|
+
return entry[0];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
indexer.domain = () => [...values.keys()];
|
|
41
|
+
|
|
42
|
+
return indexer;
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import createIndexer from "./indexer";
|
|
2
|
+
|
|
3
|
+
test("Index values one by one", () => {
|
|
4
|
+
const indexer = createIndexer();
|
|
5
|
+
|
|
6
|
+
expect(indexer("a")).toEqual(0);
|
|
7
|
+
expect(indexer("b")).toEqual(1);
|
|
8
|
+
expect(indexer("c")).toEqual(2);
|
|
9
|
+
expect(indexer("a")).toEqual(0);
|
|
10
|
+
expect(indexer("c")).toEqual(2);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("Index multiple values (predefined domain)", () => {
|
|
14
|
+
const indexer = createIndexer();
|
|
15
|
+
|
|
16
|
+
indexer.addAll(["a", "b", "c"]);
|
|
17
|
+
|
|
18
|
+
expect(indexer("d")).toEqual(3);
|
|
19
|
+
expect(indexer("a")).toEqual(0);
|
|
20
|
+
expect(indexer("b")).toEqual(1);
|
|
21
|
+
expect(indexer("c")).toEqual(2);
|
|
22
|
+
expect(indexer("d")).toEqual(3);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("Indexer inverts index numbers", () => {
|
|
26
|
+
const indexer = createIndexer();
|
|
27
|
+
|
|
28
|
+
indexer.addAll(["a", "b", "c"]);
|
|
29
|
+
|
|
30
|
+
expect(indexer.invert(0)).toEqual("a");
|
|
31
|
+
expect(indexer.invert(1)).toEqual("b");
|
|
32
|
+
expect(indexer.invert(2)).toEqual("c");
|
|
33
|
+
expect(indexer.invert(3)).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("Indexer return correct domain", () => {
|
|
37
|
+
const indexer = createIndexer();
|
|
38
|
+
|
|
39
|
+
expect(indexer("a")).toEqual(0);
|
|
40
|
+
expect(indexer("b")).toEqual(1);
|
|
41
|
+
expect(indexer("c")).toEqual(2);
|
|
42
|
+
expect(indexer("a")).toEqual(0);
|
|
43
|
+
expect(indexer("c")).toEqual(2);
|
|
44
|
+
|
|
45
|
+
expect(indexer.domain()).toEqual(["a", "b", "c"]);
|
|
46
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { lerp } from "vega-util";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates some inertia, mainly for zooming with a mechanical mouse wheel
|
|
5
|
+
*/
|
|
6
|
+
export default class Inertia {
|
|
7
|
+
/**
|
|
8
|
+
* @param {import("./animator").default} animator
|
|
9
|
+
* @param {boolean} [disabled] Just call the callback directly
|
|
10
|
+
*/
|
|
11
|
+
constructor(animator, disabled) {
|
|
12
|
+
this.animator = animator;
|
|
13
|
+
this.disabled = !!disabled;
|
|
14
|
+
this.damping = 0.015;
|
|
15
|
+
this.acceleration = 0.3; // per event
|
|
16
|
+
/** Use acceleration if the momentum step is greater than X */
|
|
17
|
+
this.accelerationThreshold = 100;
|
|
18
|
+
this.lowerLimit = 0.5; // When to stop updating
|
|
19
|
+
this.loop = false;
|
|
20
|
+
|
|
21
|
+
this.momentum = 0;
|
|
22
|
+
this.timestamp = 0;
|
|
23
|
+
/** @type {function(number):void} */
|
|
24
|
+
this.callback = null;
|
|
25
|
+
|
|
26
|
+
this._transitionCallback = this.animate.bind(this);
|
|
27
|
+
this.clear();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
clear() {
|
|
31
|
+
/** @type {number} */
|
|
32
|
+
this.momentum = 0;
|
|
33
|
+
this.timestamp = null;
|
|
34
|
+
this.loop = null;
|
|
35
|
+
this.callback = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
cancel() {
|
|
39
|
+
if (this.loop) {
|
|
40
|
+
this.animator.cancelTransition(this._transitionCallback);
|
|
41
|
+
this.clear();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param {number} value
|
|
48
|
+
* @param {function(number):void} callback
|
|
49
|
+
*/
|
|
50
|
+
setMomentum(value, callback) {
|
|
51
|
+
if (this.disabled) {
|
|
52
|
+
callback(value);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// This may have some use in the future to improve the behavior of
|
|
57
|
+
// a mechanical mouse wheel:
|
|
58
|
+
// https://github.com/w3c/uievents/issues/181
|
|
59
|
+
|
|
60
|
+
if (value * this.momentum < 0) {
|
|
61
|
+
this.momentum = 0; // Stop if the direction changes
|
|
62
|
+
} else if (Math.abs(value) > this.accelerationThreshold) {
|
|
63
|
+
this.momentum = lerp([this.momentum, value], this.acceleration);
|
|
64
|
+
} else {
|
|
65
|
+
this.momentum = value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.callback = callback;
|
|
69
|
+
|
|
70
|
+
if (!this.loop) {
|
|
71
|
+
this.animate();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
*
|
|
77
|
+
* @param {number} [timestamp]
|
|
78
|
+
*/
|
|
79
|
+
animate(timestamp) {
|
|
80
|
+
this.callback(this.momentum); // TODO: This is actually a delta, should take the elapsed time into account
|
|
81
|
+
|
|
82
|
+
const timeDelta = timestamp - this.timestamp || 0;
|
|
83
|
+
this.timestamp = timestamp;
|
|
84
|
+
|
|
85
|
+
const velocity = Math.abs(this.momentum);
|
|
86
|
+
|
|
87
|
+
this.momentum =
|
|
88
|
+
Math.sign(this.momentum) *
|
|
89
|
+
Math.max(
|
|
90
|
+
0,
|
|
91
|
+
velocity - ((velocity * this.damping) ** 1.5 + 0.04) * timeDelta
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (Math.abs(this.momentum) > this.lowerLimit) {
|
|
95
|
+
this.loop = true;
|
|
96
|
+
this.animator.requestTransition(this._transitionCallback);
|
|
97
|
+
} else {
|
|
98
|
+
this.clear();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {T} event
|
|
105
|
+
* @template T
|
|
106
|
+
*/
|
|
107
|
+
export function makeEventTemplate(event) {
|
|
108
|
+
/** @type {Partial<Record<keyof T, any>>} */
|
|
109
|
+
const template = {};
|
|
110
|
+
const acceptedTypes = ["string", "number", "boolean"];
|
|
111
|
+
const rejectedProps = ["wheelDelta", "wheelDeltaX", "wheelDeltaY"];
|
|
112
|
+
|
|
113
|
+
// eslint-disable-next-line guard-for-in
|
|
114
|
+
for (const key in event) {
|
|
115
|
+
const k = /** @type {keyof T} */ (key);
|
|
116
|
+
if (
|
|
117
|
+
!rejectedProps.includes(key) &&
|
|
118
|
+
acceptedTypes.includes(typeof event[k])
|
|
119
|
+
) {
|
|
120
|
+
template[k] = /** @type {any} */ (event[k]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return template;
|
|
124
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This class wraps a MouseEvent (or similar) and allows for
|
|
3
|
+
* its propagation through the view hierarchy in a similar manner
|
|
4
|
+
* as in the DOM.
|
|
5
|
+
*/
|
|
6
|
+
export default class InteractionEvent {
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {import("./layout/point").default} point Event coordinates
|
|
10
|
+
* inside the visualization canvas.
|
|
11
|
+
* @param {UIEvent} uiEvent The event to be wrapped
|
|
12
|
+
*/
|
|
13
|
+
constructor(point, uiEvent) {
|
|
14
|
+
this.point = point;
|
|
15
|
+
this.uiEvent = uiEvent;
|
|
16
|
+
this.stopped = false;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The target is known only in the bubbling phase
|
|
20
|
+
*
|
|
21
|
+
* @type {import("../view/view").default}
|
|
22
|
+
*/
|
|
23
|
+
this.target = undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
stopPropagation() {
|
|
27
|
+
this.stopped = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get type() {
|
|
31
|
+
return this.uiEvent.type;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iterates a nested Map structure created by d3-array's group() function.
|
|
3
|
+
*
|
|
4
|
+
* Yields arrays that contain the compound key and the grouped data items.
|
|
5
|
+
*
|
|
6
|
+
* @param {Map<any, any>} map The root
|
|
7
|
+
* @param {any[]} [path] The path so far.
|
|
8
|
+
* @returns {Generator<[any[], any[]]>}
|
|
9
|
+
*/
|
|
10
|
+
export default function* iterateNestedMaps(map, path = []) {
|
|
11
|
+
for (const [key, value] of map.entries()) {
|
|
12
|
+
if (value instanceof Map) {
|
|
13
|
+
for (const m of iterateNestedMaps(value, [...path, key])) {
|
|
14
|
+
yield m;
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
// TODO: Could recycle compound key arrays for better performance
|
|
18
|
+
yield [[...path, key], value];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { group } from "d3-array";
|
|
2
|
+
import iterateNestedMaps from "./iterateNestedMaps";
|
|
3
|
+
|
|
4
|
+
const data = [
|
|
5
|
+
{ name: "jim", amount: "34.0", date: "11/12/2015" },
|
|
6
|
+
{ name: "carl", amount: "120.11", date: "11/12/2015" },
|
|
7
|
+
{ name: "stacy", amount: "12.01", date: "01/04/2016" },
|
|
8
|
+
{ name: "stacy", amount: "34.05", date: "01/04/2016" },
|
|
9
|
+
{ name: "stacy", amount: "1.5", date: "02/04/2016" },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const groups = group(
|
|
13
|
+
data,
|
|
14
|
+
(d) => d.name,
|
|
15
|
+
(d) => d.date
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
test("iterateNestedMaps iterates correctly", () => {
|
|
19
|
+
const expected = [
|
|
20
|
+
[["jim", "11/12/2015"], [data[0]]],
|
|
21
|
+
[["carl", "11/12/2015"], [data[1]]],
|
|
22
|
+
[
|
|
23
|
+
["stacy", "01/04/2016"],
|
|
24
|
+
[data[2], data[3]],
|
|
25
|
+
],
|
|
26
|
+
[["stacy", "02/04/2016"], [data[4]]],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const result = [...iterateNestedMaps(groups)];
|
|
30
|
+
|
|
31
|
+
expect(result).toEqual(expected);
|
|
32
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import FlatQueue from "flatqueue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns an iterator that merges multiple sorted arrays.
|
|
5
|
+
*
|
|
6
|
+
* @param {T[][]} arrays
|
|
7
|
+
* @param {function(T):number} [accessor]
|
|
8
|
+
* @template T
|
|
9
|
+
*/
|
|
10
|
+
export default function* kWayMerge(arrays, accessor = (x) => +x) {
|
|
11
|
+
// https://www.wikiwand.com/en/K-way_merge_algorithm
|
|
12
|
+
|
|
13
|
+
// This could be optimized by implementing a tournament tree or
|
|
14
|
+
// by adding replaceTop to the Heap.
|
|
15
|
+
// https://docs.python.org/2/library/heapq.html#heapq.heapreplace
|
|
16
|
+
|
|
17
|
+
const k = arrays.length;
|
|
18
|
+
|
|
19
|
+
const heap = new FlatQueue();
|
|
20
|
+
const pointers = new Array(k).fill(0);
|
|
21
|
+
|
|
22
|
+
for (const [i, array] of arrays.entries()) {
|
|
23
|
+
if (array.length) {
|
|
24
|
+
heap.push(i, accessor(array[0]));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let i = 0;
|
|
29
|
+
while ((i = heap.pop()) !== undefined) {
|
|
30
|
+
const array = arrays[i];
|
|
31
|
+
let pointer = pointers[i];
|
|
32
|
+
const element = array[pointer++];
|
|
33
|
+
|
|
34
|
+
yield element;
|
|
35
|
+
|
|
36
|
+
if (pointer < array.length) {
|
|
37
|
+
const newValue = accessor(array[pointer]);
|
|
38
|
+
heap.push(i, newValue);
|
|
39
|
+
pointers[i] = pointer;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import kWayMerge from "./kWayMerge";
|
|
2
|
+
|
|
3
|
+
test("k-way merge merges multiple sorted arrays", () => {
|
|
4
|
+
/** @type {{a: number}[][]} */
|
|
5
|
+
const arrays = [];
|
|
6
|
+
|
|
7
|
+
for (let a = 0; a < 20; a++) {
|
|
8
|
+
/** @type {{a: number}[]} */
|
|
9
|
+
const array = [];
|
|
10
|
+
arrays.push(array);
|
|
11
|
+
|
|
12
|
+
let x = 0;
|
|
13
|
+
for (let i = 0; i < a; i++) {
|
|
14
|
+
x += Math.floor(Math.random() * 10);
|
|
15
|
+
array.push({ a: x });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const sorted = arrays.flat().sort((a, b) => a.a - b.a);
|
|
20
|
+
|
|
21
|
+
/** @type {function(any):number} */
|
|
22
|
+
const accessor = (d) => d.a;
|
|
23
|
+
|
|
24
|
+
expect([...kWayMerge(arrays, accessor)]).toEqual(sorted);
|
|
25
|
+
});
|