@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,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a shallow-cloner function that ensures (hopefully) that the properties
|
|
3
|
+
* end up into the in-object storage. Offers better performance than Object.assign
|
|
4
|
+
* and saves memory.
|
|
5
|
+
*
|
|
6
|
+
* Read more at:
|
|
7
|
+
* https://mrale.ph/blog/2014/07/30/constructor-vs-objectcreate.html
|
|
8
|
+
*
|
|
9
|
+
* @param {T} template The template object that
|
|
10
|
+
* @returns {function(T):T}
|
|
11
|
+
* @template T
|
|
12
|
+
*/
|
|
13
|
+
export default function createCloner(template) {
|
|
14
|
+
// TODO: Check that only properties, not methods get cloned
|
|
15
|
+
const properties = Object.keys(template);
|
|
16
|
+
|
|
17
|
+
const cloner = /** @type {function(T):T} */ (
|
|
18
|
+
new Function(
|
|
19
|
+
"source",
|
|
20
|
+
"return { " +
|
|
21
|
+
properties
|
|
22
|
+
.map((prop) => JSON.stringify(prop))
|
|
23
|
+
.map((prop) => `${prop}: source[${prop}]`)
|
|
24
|
+
.join(",\n") +
|
|
25
|
+
" };"
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
cloner.properties = properties;
|
|
30
|
+
|
|
31
|
+
return cloner;
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import createCloner from "./cloner";
|
|
2
|
+
|
|
3
|
+
const template = {
|
|
4
|
+
a: 1,
|
|
5
|
+
b: "xyzzy",
|
|
6
|
+
3: "iddqd",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
test("Cloner clones object properly", () => {
|
|
10
|
+
const makeClone = createCloner(template);
|
|
11
|
+
|
|
12
|
+
expect(makeClone(template)).toEqual(template);
|
|
13
|
+
expect(makeClone(template)).not.toBe(template);
|
|
14
|
+
|
|
15
|
+
const another = {
|
|
16
|
+
a: 2,
|
|
17
|
+
b: "idkfa",
|
|
18
|
+
3: "hello",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
expect(makeClone(another)).toEqual(another);
|
|
22
|
+
expect(makeClone(another)).not.toBe(another);
|
|
23
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import coalesce from "./coalesce";
|
|
2
|
+
|
|
3
|
+
test("Coalesce returns first defined value", () => {
|
|
4
|
+
expect(coalesce(0, 1, 2, 3)).toEqual(0);
|
|
5
|
+
expect(coalesce(undefined, 1, 2, 3)).toEqual(1);
|
|
6
|
+
expect(coalesce(undefined, undefined, 2, 3)).toEqual(2);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("Coalesce returns undefined if input is all-undefined", () => {
|
|
10
|
+
expect(coalesce(undefined, undefined)).toBeUndefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("Coalesce returns undefined on empty input", () => {
|
|
14
|
+
expect(coalesce()).toBeUndefined();
|
|
15
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {...Iterable} iterables
|
|
4
|
+
*/
|
|
5
|
+
export default function concatIterables(...iterables) {
|
|
6
|
+
if (iterables.length <= 0) {
|
|
7
|
+
return {
|
|
8
|
+
*[Symbol.iterator]() {
|
|
9
|
+
//
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let currentIterable = iterables.shift();
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
*[Symbol.iterator]() {
|
|
18
|
+
do {
|
|
19
|
+
for (const value of currentIterable) {
|
|
20
|
+
yield value;
|
|
21
|
+
}
|
|
22
|
+
currentIterable = iterables.shift();
|
|
23
|
+
} while (currentIterable);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {(...args:T) => R} func
|
|
3
|
+
* @param {number} wait
|
|
4
|
+
* @template {any[]} T
|
|
5
|
+
* @template R
|
|
6
|
+
*/
|
|
7
|
+
export function debounce(func, wait, rejectOnDebounce = true) {
|
|
8
|
+
/** @type {number} */
|
|
9
|
+
let timeout;
|
|
10
|
+
|
|
11
|
+
/** @type {(reason?: any) => void} */
|
|
12
|
+
let rejectPrevious = (_) => undefined;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {T} args
|
|
16
|
+
* @return {Promise<R>} */
|
|
17
|
+
const debouncer = function debouncer(...args) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const later = () => {
|
|
20
|
+
clearTimeout(timeout);
|
|
21
|
+
rejectPrevious = (_) => undefined;
|
|
22
|
+
|
|
23
|
+
resolve(func(...args));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (rejectOnDebounce) {
|
|
27
|
+
rejectPrevious("debounced");
|
|
28
|
+
}
|
|
29
|
+
clearTimeout(timeout);
|
|
30
|
+
|
|
31
|
+
rejectPrevious = reject;
|
|
32
|
+
timeout = setTimeout(later, wait);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return debouncer;
|
|
37
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {boolean | number | string} scalar
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class DomainArray /** @type {Array<scalar>} */ extends Array {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
/** @type {string} */
|
|
9
|
+
this.type = undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
*
|
|
14
|
+
* @param {scalar} value
|
|
15
|
+
* @returns {DomainArray}
|
|
16
|
+
*/
|
|
17
|
+
extend(value) {
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param {Iterable<scalar>} values
|
|
24
|
+
* @returns {DomainArray}
|
|
25
|
+
*/
|
|
26
|
+
extendAll(values) {
|
|
27
|
+
if (values instanceof DomainArray && values.type != this.type) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Cannot combine different types of domains: ${this.type} and ${values.type}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const value of values) {
|
|
34
|
+
this.extend(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Iterable<T>} values
|
|
42
|
+
* @param {function(T):scalar} [accessor]
|
|
43
|
+
* @returns {DomainArray}
|
|
44
|
+
* @template T
|
|
45
|
+
*/
|
|
46
|
+
extendAllWithAccessor(values, accessor) {
|
|
47
|
+
for (const value of values) {
|
|
48
|
+
this.extend(accessor(value));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class QuantitativeDomain extends DomainArray {
|
|
56
|
+
constructor() {
|
|
57
|
+
super();
|
|
58
|
+
this.type = "quantitative";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
* @param {scalar} value
|
|
64
|
+
* @returns {DomainArray}
|
|
65
|
+
*/
|
|
66
|
+
extend(value) {
|
|
67
|
+
if (value === null || value === undefined || Number.isNaN(value)) {
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
value = +value;
|
|
72
|
+
|
|
73
|
+
if (this.length) {
|
|
74
|
+
if (value < this[0]) {
|
|
75
|
+
this[0] = value;
|
|
76
|
+
} else if (value > this[1]) {
|
|
77
|
+
this[1] = value;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
this.push(value);
|
|
81
|
+
this.push(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Builder that tries to preserve the order
|
|
90
|
+
*/
|
|
91
|
+
export class OrdinalDomain extends DomainArray {
|
|
92
|
+
constructor() {
|
|
93
|
+
super();
|
|
94
|
+
this.type = "ordinal";
|
|
95
|
+
|
|
96
|
+
/** @type {Set<scalar>} */
|
|
97
|
+
this.uniqueValues = new Set();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
*
|
|
102
|
+
* @param {scalar} value
|
|
103
|
+
* @returns {DomainArray}
|
|
104
|
+
*/
|
|
105
|
+
extend(value) {
|
|
106
|
+
if (value === null || value === undefined || Number.isNaN(value)) {
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!this.uniqueValues.has(value)) {
|
|
111
|
+
this.uniqueValues.add(value);
|
|
112
|
+
this.push(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class NominalDomain extends OrdinalDomain {
|
|
120
|
+
constructor() {
|
|
121
|
+
super();
|
|
122
|
+
this.type = "nominal";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class PiecewiseDomain extends DomainArray {
|
|
127
|
+
/**
|
|
128
|
+
*
|
|
129
|
+
* @param {number[]} initialDomain
|
|
130
|
+
*/
|
|
131
|
+
constructor(initialDomain) {
|
|
132
|
+
super();
|
|
133
|
+
|
|
134
|
+
let sum = 0;
|
|
135
|
+
for (let i = 1; i < initialDomain.length; i++) {
|
|
136
|
+
sum += Math.sign(initialDomain[i] - initialDomain[i - 1]);
|
|
137
|
+
}
|
|
138
|
+
if (Math.abs(sum) != initialDomain.length - 1) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
"Piecewise domain must be strictly increasing or decreasing: " +
|
|
141
|
+
JSON.stringify(initialDomain)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
initialDomain.forEach((x) => this.push(x));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
* @param {scalar} value
|
|
151
|
+
* @returns {DomainArray}
|
|
152
|
+
*/
|
|
153
|
+
extend(value) {
|
|
154
|
+
if (this.includes(value)) {
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
throw new Error(
|
|
159
|
+
"Piecewise domains are immutable and cannot be unioned!"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @type Object.<string, typeof DomainArray>
|
|
166
|
+
*/
|
|
167
|
+
const domainTypes = {
|
|
168
|
+
quantitative: QuantitativeDomain,
|
|
169
|
+
index: QuantitativeDomain,
|
|
170
|
+
locus: QuantitativeDomain,
|
|
171
|
+
nominal: NominalDomain,
|
|
172
|
+
ordinal: OrdinalDomain,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
*
|
|
177
|
+
* @param {string} type
|
|
178
|
+
* @param {scalar[]} [initialDomain]
|
|
179
|
+
*/
|
|
180
|
+
export default function createDomain(type, initialDomain) {
|
|
181
|
+
if (type == "quantitative" && isPiecewiseArray(initialDomain)) {
|
|
182
|
+
const b = new PiecewiseDomain(/** @type {number[]} */ (initialDomain));
|
|
183
|
+
b.type = type;
|
|
184
|
+
return b;
|
|
185
|
+
} else if (domainTypes[type]) {
|
|
186
|
+
const b = new domainTypes[type]();
|
|
187
|
+
b.type = type;
|
|
188
|
+
if (initialDomain) {
|
|
189
|
+
b.extendAll(initialDomain);
|
|
190
|
+
}
|
|
191
|
+
return b;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
throw new Error("Unknown type: " + type);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
*
|
|
199
|
+
* @param {array} array
|
|
200
|
+
*/
|
|
201
|
+
export function isDomainArray(array) {
|
|
202
|
+
return array instanceof DomainArray;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* For unit tests
|
|
207
|
+
*
|
|
208
|
+
* @param {DomainArray | any[]} domainArray
|
|
209
|
+
*/
|
|
210
|
+
export function toRegularArray(domainArray) {
|
|
211
|
+
return [...domainArray];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @param {scalar[]} array
|
|
216
|
+
*/
|
|
217
|
+
function isPiecewiseArray(array) {
|
|
218
|
+
return (
|
|
219
|
+
array &&
|
|
220
|
+
array.length > 0 &&
|
|
221
|
+
array.length != 2 &&
|
|
222
|
+
array.every((x) => typeof x === "number")
|
|
223
|
+
);
|
|
224
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import createDomain, {
|
|
2
|
+
toRegularArray as r,
|
|
3
|
+
PiecewiseDomain,
|
|
4
|
+
} from "./domainArray";
|
|
5
|
+
|
|
6
|
+
describe("Build quantitative domains", () => {
|
|
7
|
+
test("Empty domain", () => {
|
|
8
|
+
const b = createDomain("quantitative");
|
|
9
|
+
expect(r(b)).toEqual([]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("Extends by one value at a time", () => {
|
|
13
|
+
const b = createDomain("quantitative");
|
|
14
|
+
b.extend(2);
|
|
15
|
+
b.extend(1);
|
|
16
|
+
b.extend(null);
|
|
17
|
+
b.extend(undefined);
|
|
18
|
+
b.extend(NaN);
|
|
19
|
+
b.extend(5);
|
|
20
|
+
b.extend(4);
|
|
21
|
+
expect(r(b)).toEqual([1, 5]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("Extends with an iterable", () => {
|
|
25
|
+
const b = createDomain("quantitative");
|
|
26
|
+
b.extendAll([2, 1, null, undefined, NaN, 5, 4]);
|
|
27
|
+
expect(r(b)).toEqual([1, 5]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("Extends with an iterable and an accessor", () => {
|
|
31
|
+
const b = createDomain("quantitative");
|
|
32
|
+
b.extendAllWithAccessor(
|
|
33
|
+
[
|
|
34
|
+
{ x: 2 },
|
|
35
|
+
{ x: 1 },
|
|
36
|
+
{ x: null },
|
|
37
|
+
{ x: undefined },
|
|
38
|
+
{ x: NaN },
|
|
39
|
+
{ x: 5 },
|
|
40
|
+
{ x: 4 },
|
|
41
|
+
],
|
|
42
|
+
(d) => d.x
|
|
43
|
+
);
|
|
44
|
+
expect(r(b)).toEqual([1, 5]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("Coerces to number", () => {
|
|
48
|
+
const b = createDomain("quantitative");
|
|
49
|
+
expect(r(b.extend("123"))).toEqual([123, 123]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("Build ordinal domains", () => {
|
|
54
|
+
// Note: nominal is an unordered abstraction of ordinal. Testing just ordinal is enough.
|
|
55
|
+
|
|
56
|
+
test("Empty domain", () => {
|
|
57
|
+
const b = createDomain("ordinal");
|
|
58
|
+
expect(r(b)).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("Extends by one value at a time, preserves order", () => {
|
|
62
|
+
const b = createDomain("ordinal");
|
|
63
|
+
b.extend("a");
|
|
64
|
+
b.extend("b");
|
|
65
|
+
b.extend("c");
|
|
66
|
+
b.extend("b");
|
|
67
|
+
b.extend(null);
|
|
68
|
+
b.extend(undefined);
|
|
69
|
+
b.extend(NaN);
|
|
70
|
+
b.extend("d");
|
|
71
|
+
expect(r(b)).toEqual(["a", "b", "c", "d"]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("Build piecewise domains", () => {
|
|
76
|
+
test("Creates a piecewise domain", () => {
|
|
77
|
+
expect(createDomain("quantitative", [1])).toBeInstanceOf(
|
|
78
|
+
PiecewiseDomain
|
|
79
|
+
);
|
|
80
|
+
expect(createDomain("quantitative", [1, 2, 3])).toBeInstanceOf(
|
|
81
|
+
PiecewiseDomain
|
|
82
|
+
);
|
|
83
|
+
expect(createDomain("quantitative", [1, 2, 3, 4])).toBeInstanceOf(
|
|
84
|
+
PiecewiseDomain
|
|
85
|
+
);
|
|
86
|
+
expect(createDomain("quantitative", [3, 2, 1])).toBeInstanceOf(
|
|
87
|
+
PiecewiseDomain
|
|
88
|
+
);
|
|
89
|
+
expect(r(createDomain("quantitative", [3, 2, 1]))).toEqual([3, 2, 1]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("Throws on domain that is not stricly increasing or decreasing", () => {
|
|
93
|
+
expect(() => createDomain("quantitative", [2, 1, 3])).toThrow();
|
|
94
|
+
expect(() => createDomain("quantitative", [2, 3, 1])).toThrow();
|
|
95
|
+
expect(() => createDomain("quantitative", [3, 0, 2])).toThrow();
|
|
96
|
+
expect(() => createDomain("quantitative", [0, 3, 2])).toThrow();
|
|
97
|
+
expect(() => createDomain("quantitative", [1, 2, 2, 3])).toThrow();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("Throws on mutation attempts", () => {
|
|
101
|
+
expect(() =>
|
|
102
|
+
createDomain("quantitative", [1, 2, 3]).extend(4)
|
|
103
|
+
).toThrow();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("Does not throw when extending with existing value", () => {
|
|
107
|
+
expect(r(createDomain("quantitative", [1, 2, 3]).extend(2))).toEqual([
|
|
108
|
+
1, 2, 3,
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("Annotations", () => {
|
|
114
|
+
test("Quantitative domain is annotated", () =>
|
|
115
|
+
expect(createDomain("quantitative").type).toEqual("quantitative"));
|
|
116
|
+
|
|
117
|
+
test("Ordinal domain is annotated", () =>
|
|
118
|
+
expect(createDomain("ordinal").type).toEqual("ordinal"));
|
|
119
|
+
|
|
120
|
+
test("Nominal domain is annotated", () =>
|
|
121
|
+
expect(createDomain("nominal").type).toEqual("nominal"));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("Other stuff", () => {
|
|
125
|
+
test("Throws on extending by other type of domain array", () =>
|
|
126
|
+
expect(() =>
|
|
127
|
+
createDomain("quantitative").extendAll(createDomain("nominal"))
|
|
128
|
+
).toThrow());
|
|
129
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exponential interpolation
|
|
3
|
+
*
|
|
4
|
+
* https://twitter.com/FreyaHolmer/status/1068293398073929728
|
|
5
|
+
*
|
|
6
|
+
* @param {number} a
|
|
7
|
+
* @param {number} b
|
|
8
|
+
* @param {number} t
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
export default function (a, b, t) {
|
|
12
|
+
return a * Math.pow(b / a, t);
|
|
13
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parseExpression, codegenExpression } from "vega-expression";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {string} expr
|
|
6
|
+
*/
|
|
7
|
+
export default function createFunction(expr, global = {}) {
|
|
8
|
+
const cg = codegenExpression({
|
|
9
|
+
forbidden: [],
|
|
10
|
+
allowed: ["datum"],
|
|
11
|
+
globalvar: "global",
|
|
12
|
+
fieldvar: "datum",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const parsed = parseExpression(expr);
|
|
17
|
+
const generatedCode = cg(parsed);
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line no-new-func
|
|
20
|
+
const fn = Function(
|
|
21
|
+
"datum",
|
|
22
|
+
"global",
|
|
23
|
+
`"use strict"; return (${generatedCode.code});`
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const exprFunction = /** @param {object} x */ (x) => fn(x, global);
|
|
27
|
+
exprFunction.fields = generatedCode.fields;
|
|
28
|
+
return exprFunction;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
throw new Error(`Invalid expression: ${expr}, ${e.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { field as vegaField, accessor } from "vega-util";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates an accessor function based on the field expression.
|
|
5
|
+
* This is equivalent to vega-util's field function but generates optimized
|
|
6
|
+
* accessors for trivial cases.
|
|
7
|
+
*
|
|
8
|
+
* Function calls with polymorphic objects spoil the inline caching that
|
|
9
|
+
* virtually all JavaScript engines use. Thus, we generate code and compile
|
|
10
|
+
* always a new function to "guarantee" homomorphims, given that only same
|
|
11
|
+
* type of objects are even passed to the accessor.
|
|
12
|
+
*
|
|
13
|
+
* Read more at: https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html
|
|
14
|
+
*
|
|
15
|
+
* @param {string} fieldExpr
|
|
16
|
+
* @param {string} [name]
|
|
17
|
+
*/
|
|
18
|
+
export function field(fieldExpr, name = fieldExpr) {
|
|
19
|
+
if (/^[A-Za-z0-9_]+$/.test(fieldExpr)) {
|
|
20
|
+
// eslint-disable-next-line no-new-func
|
|
21
|
+
const fn = /** @type {import("vega-util").AccessorFn} */ (
|
|
22
|
+
new Function("datum", `return datum[${JSON.stringify(fieldExpr)}]`)
|
|
23
|
+
);
|
|
24
|
+
return accessor(fn, [fieldExpr], name);
|
|
25
|
+
} else {
|
|
26
|
+
return vegaField(fieldExpr);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on:
|
|
3
|
+
* fisheye d3-plugin (c) Mike Bostock
|
|
4
|
+
* https://github.com/d3/d3-plugins/blob/master/fisheye/
|
|
5
|
+
*/
|
|
6
|
+
export default function () {
|
|
7
|
+
let radius = 200;
|
|
8
|
+
let distortion = 2;
|
|
9
|
+
let k0 = 1,
|
|
10
|
+
k1 = 1;
|
|
11
|
+
let focus = 0;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param {number} x
|
|
16
|
+
*/
|
|
17
|
+
function fisheye(x) {
|
|
18
|
+
const dx = x - focus;
|
|
19
|
+
const dd = Math.abs(dx);
|
|
20
|
+
if (!dd || dd >= radius) return x;
|
|
21
|
+
const k = ((k0 * (1 - Math.exp(-dd * k1))) / dd) * 0.75 + 0.25;
|
|
22
|
+
return focus + dx * k;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function rescale() {
|
|
26
|
+
k0 = Math.exp(distortion);
|
|
27
|
+
k0 = (k0 / (k0 - 1)) * radius;
|
|
28
|
+
k1 = distortion / radius;
|
|
29
|
+
return fisheye;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {number} _
|
|
34
|
+
*/
|
|
35
|
+
fisheye.radius = function (_) {
|
|
36
|
+
if (!arguments.length) return radius;
|
|
37
|
+
radius = +_;
|
|
38
|
+
return rescale();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {number} _
|
|
43
|
+
*/
|
|
44
|
+
fisheye.distortion = function (_) {
|
|
45
|
+
if (!arguments.length) return distortion;
|
|
46
|
+
distortion = +_;
|
|
47
|
+
return rescale();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {number} _
|
|
52
|
+
*/
|
|
53
|
+
fisheye.focus = function (_) {
|
|
54
|
+
if (!arguments.length) return focus;
|
|
55
|
+
focus = _;
|
|
56
|
+
return fisheye;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return rescale();
|
|
60
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isNumber, isString, isBoolean } from "vega-util";
|
|
2
|
+
import { format as d3format } from "d3-format";
|
|
3
|
+
import { html } from "lit-html";
|
|
4
|
+
|
|
5
|
+
const numberFormat = d3format(".4~r");
|
|
6
|
+
const exponentNumberFormat = d3format(".4~e");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param {any} object Object to format
|
|
11
|
+
*/
|
|
12
|
+
export default function formatObject(object) {
|
|
13
|
+
if (object === null) {
|
|
14
|
+
return html` <span class="na">NA</span> `;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (isString(object)) {
|
|
18
|
+
return object.substring(0, 30);
|
|
19
|
+
} else if (Number.isInteger(object)) {
|
|
20
|
+
return "" + object;
|
|
21
|
+
} else if (isNumber(object)) {
|
|
22
|
+
return Math.abs(object) > Math.pow(10, 8) ||
|
|
23
|
+
Math.abs(object) < Math.pow(10, -8)
|
|
24
|
+
? exponentNumberFormat(object)
|
|
25
|
+
: numberFormat(object);
|
|
26
|
+
} else if (isBoolean(object)) {
|
|
27
|
+
return object ? "True" : "False";
|
|
28
|
+
} else {
|
|
29
|
+
return "?" + typeof object + " " + object;
|
|
30
|
+
}
|
|
31
|
+
}
|