@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,109 @@
|
|
|
1
|
+
import { isValueDef, getSecondaryChannel } from "../encoder/encoder";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @typedef {import("../spec/channel").Encoding} Encoding
|
|
6
|
+
* @typedef {import("../spec/channel").Channel} Channel
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {Encoding} encoding
|
|
11
|
+
* @param {Channel} channel
|
|
12
|
+
*/
|
|
13
|
+
export function fixPositional(encoding, channel) {
|
|
14
|
+
const secondary = getSecondaryChannel(channel);
|
|
15
|
+
if (encoding[channel]) {
|
|
16
|
+
if (!encoding[secondary]) {
|
|
17
|
+
if (encoding[channel].type == "quantitative") {
|
|
18
|
+
// Bar plot, anchor the other end to zero
|
|
19
|
+
encoding[secondary] = {
|
|
20
|
+
datum: 0,
|
|
21
|
+
};
|
|
22
|
+
} else {
|
|
23
|
+
// Must make copies because the definition may be shared with other views/marks
|
|
24
|
+
encoding[channel] = { ...encoding[channel] };
|
|
25
|
+
encoding[secondary] = { ...encoding[channel] };
|
|
26
|
+
|
|
27
|
+
// Fill the bands (bar plot / heatmap)
|
|
28
|
+
// We are following the Vega-Lite convention:
|
|
29
|
+
// the band property works differently on rectangular marks, i.e., it adjusts the band coverage.
|
|
30
|
+
const adjustment = (1 - (encoding[channel].band || 1)) / 2;
|
|
31
|
+
encoding[channel].band = 0 + adjustment;
|
|
32
|
+
encoding[secondary].band = 1 - adjustment;
|
|
33
|
+
|
|
34
|
+
// TODO: If the secondary channel duplicates the primary channel
|
|
35
|
+
// the data should be uploaded to the GPU only once.
|
|
36
|
+
}
|
|
37
|
+
} else if (encoding[channel].type != "quantitative") {
|
|
38
|
+
const adjustment = (1 - (encoding[channel].band || 1)) / 2;
|
|
39
|
+
encoding[channel].band = adjustment;
|
|
40
|
+
encoding[secondary].band = -adjustment;
|
|
41
|
+
}
|
|
42
|
+
} else if (encoding[secondary]) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Only secondary channel ${secondary} has been specified!`
|
|
45
|
+
);
|
|
46
|
+
} else {
|
|
47
|
+
// Nothing specified, fill the whole viewport
|
|
48
|
+
encoding[channel] = { value: 0 };
|
|
49
|
+
encoding[secondary] = { value: 1 };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {import("../spec/channel").Encoding} encoding
|
|
55
|
+
* @param {boolean} filled
|
|
56
|
+
*/
|
|
57
|
+
export function fixStroke(encoding, filled) {
|
|
58
|
+
if (!encoding.stroke) {
|
|
59
|
+
if (filled) {
|
|
60
|
+
encoding.stroke = { value: null };
|
|
61
|
+
} else {
|
|
62
|
+
encoding.stroke = {
|
|
63
|
+
resolutionChannel: "color",
|
|
64
|
+
...encoding.color,
|
|
65
|
+
};
|
|
66
|
+
// TODO: Whattabout default strokeWidth?
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (isValueDef(encoding.stroke) && encoding.stroke.value === null) {
|
|
71
|
+
encoding.strokeWidth = { value: 0 };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!encoding.strokeOpacity) {
|
|
75
|
+
encoding.strokeOpacity = {
|
|
76
|
+
resolutionChannel: "opacity",
|
|
77
|
+
...encoding.opacity,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {import("../spec/channel").Encoding} encoding
|
|
84
|
+
* @param {boolean} filled
|
|
85
|
+
*/
|
|
86
|
+
export function fixFill(encoding, filled) {
|
|
87
|
+
if (isValueDef(encoding.fill) && encoding.fill.value === null) {
|
|
88
|
+
encoding.fillOpacity = { value: 0 };
|
|
89
|
+
} else if (!encoding.fill) {
|
|
90
|
+
encoding.fill = {
|
|
91
|
+
resolutionChannel: "color",
|
|
92
|
+
...encoding.color,
|
|
93
|
+
};
|
|
94
|
+
if (!filled && !encoding.fillOpacity) {
|
|
95
|
+
encoding.fillOpacity = { value: 0 };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!encoding.fillOpacity) {
|
|
100
|
+
if (filled) {
|
|
101
|
+
encoding.fillOpacity = {
|
|
102
|
+
resolutionChannel: "opacity",
|
|
103
|
+
...encoding.opacity,
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
encoding.fillOpacity = { value: 0 };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { drawBufferInfo, setBuffersAndAttributes, setUniforms } from "twgl.js";
|
|
2
|
+
import { bisector, quantileSorted } from "d3-array";
|
|
3
|
+
import { zoomLinear } from "vega-util";
|
|
4
|
+
import { PointVertexBuilder } from "../gl/dataToVertices";
|
|
5
|
+
import VERTEX_SHADER from "../gl/point.vertex.glsl";
|
|
6
|
+
import FRAGMENT_SHADER from "../gl/point.fragment.glsl";
|
|
7
|
+
|
|
8
|
+
import Mark from "./mark";
|
|
9
|
+
import { sampleIterable } from "../data/transforms/sample";
|
|
10
|
+
import { fixFill, fixStroke } from "./markUtils";
|
|
11
|
+
|
|
12
|
+
/** @type {Record<string, import("../view/viewUtils").ChannelDef>} */
|
|
13
|
+
const defaultEncoding = {};
|
|
14
|
+
|
|
15
|
+
export default class PointMark extends Mark {
|
|
16
|
+
/**
|
|
17
|
+
* @param {import("../view/unitView").default} unitView
|
|
18
|
+
*/
|
|
19
|
+
constructor(unitView) {
|
|
20
|
+
super(unitView);
|
|
21
|
+
|
|
22
|
+
Object.defineProperties(
|
|
23
|
+
this.defaultProperties,
|
|
24
|
+
Object.getOwnPropertyDescriptors({
|
|
25
|
+
x: 0.5,
|
|
26
|
+
y: 0.5,
|
|
27
|
+
color: "#4c78a8",
|
|
28
|
+
filled: true,
|
|
29
|
+
opacity: 1.0,
|
|
30
|
+
size: 100.0,
|
|
31
|
+
semanticScore: 0.0, // TODO: Should be datum instead of value. But needs fixing.
|
|
32
|
+
shape: "circle",
|
|
33
|
+
strokeWidth: 2.0,
|
|
34
|
+
fillGradientStrength: 0.0,
|
|
35
|
+
dx: 0,
|
|
36
|
+
dy: 0,
|
|
37
|
+
angle: 0,
|
|
38
|
+
|
|
39
|
+
sampleFacetPadding: 0.1,
|
|
40
|
+
|
|
41
|
+
semanticZoomFraction: 0.02,
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getAttributes() {
|
|
47
|
+
return [
|
|
48
|
+
"inwardStroke",
|
|
49
|
+
"uniqueId",
|
|
50
|
+
"facetIndex",
|
|
51
|
+
"x",
|
|
52
|
+
"y",
|
|
53
|
+
"size",
|
|
54
|
+
"semanticScore",
|
|
55
|
+
"shape",
|
|
56
|
+
"strokeWidth",
|
|
57
|
+
"gradientStrength",
|
|
58
|
+
"dx",
|
|
59
|
+
"dy",
|
|
60
|
+
"fill",
|
|
61
|
+
"stroke",
|
|
62
|
+
"fillOpacity",
|
|
63
|
+
"strokeOpacity",
|
|
64
|
+
"angle",
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @returns {import("../spec/channel").Channel[]}
|
|
70
|
+
*/
|
|
71
|
+
getSupportedChannels() {
|
|
72
|
+
return [
|
|
73
|
+
...super.getSupportedChannels(),
|
|
74
|
+
"size",
|
|
75
|
+
"semanticScore",
|
|
76
|
+
"shape",
|
|
77
|
+
"strokeWidth",
|
|
78
|
+
"dx",
|
|
79
|
+
"dy",
|
|
80
|
+
"fill",
|
|
81
|
+
"stroke",
|
|
82
|
+
"fillOpacity",
|
|
83
|
+
"strokeOpacity",
|
|
84
|
+
"angle",
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getDefaultEncoding() {
|
|
89
|
+
return { ...super.getDefaultEncoding(), ...defaultEncoding };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {import("../spec/channel").Encoding} encoding
|
|
94
|
+
* @returns {import("../spec/channel").Encoding}
|
|
95
|
+
*/
|
|
96
|
+
fixEncoding(encoding) {
|
|
97
|
+
fixStroke(encoding, this.properties.filled);
|
|
98
|
+
fixFill(encoding, this.properties.filled);
|
|
99
|
+
|
|
100
|
+
// TODO: Function for getting rid of extras. Also should validate that all attributes are defined
|
|
101
|
+
delete encoding.color;
|
|
102
|
+
delete encoding.opacity;
|
|
103
|
+
|
|
104
|
+
return encoding;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
initializeData() {
|
|
108
|
+
super.initializeData();
|
|
109
|
+
|
|
110
|
+
// Semantic zooming is currently solely a feature of point mark.
|
|
111
|
+
// Build a sorted sample that allows for computing p-quantiles
|
|
112
|
+
const semanticScoreAccessor =
|
|
113
|
+
this.unitView.getAccessor("semanticScore");
|
|
114
|
+
if (semanticScoreAccessor) {
|
|
115
|
+
// n chosen using Stetson-Harrison
|
|
116
|
+
// TODO: Throw on missing scores
|
|
117
|
+
this.sampledSemanticScores = Float32Array.from(
|
|
118
|
+
sampleIterable(
|
|
119
|
+
10000,
|
|
120
|
+
this.unitView.getCollector().getData(),
|
|
121
|
+
semanticScoreAccessor
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
this.sampledSemanticScores.sort((a, b) => a - b);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async initializeGraphics() {
|
|
129
|
+
await super.initializeGraphics();
|
|
130
|
+
this.createAndLinkShaders(VERTEX_SHADER, FRAGMENT_SHADER);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
finalizeGraphicsInitialization() {
|
|
134
|
+
super.finalizeGraphicsInitialization();
|
|
135
|
+
|
|
136
|
+
this.gl.useProgram(this.programInfo.program);
|
|
137
|
+
|
|
138
|
+
const props = this.properties;
|
|
139
|
+
setUniforms(this.programInfo, {
|
|
140
|
+
uInwardStroke: props.inwardStroke,
|
|
141
|
+
uGradientStrength: props.fillGradientStrength,
|
|
142
|
+
uMaxRelativePointDiameter: 1 - 2 * props.sampleFacetPadding,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
updateGraphicsData() {
|
|
147
|
+
const collector = this.unitView.getCollector();
|
|
148
|
+
const itemCount = collector.getItemCount();
|
|
149
|
+
|
|
150
|
+
const builder = new PointVertexBuilder({
|
|
151
|
+
encoders: this.encoders,
|
|
152
|
+
attributes: this.getAttributes(),
|
|
153
|
+
numItems: Math.max(itemCount, this.properties.minBufferSize || 0),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
builder.addBatches(collector.facetBatches);
|
|
157
|
+
|
|
158
|
+
const vertexData = builder.toArrays();
|
|
159
|
+
this.rangeMap = vertexData.rangeMap;
|
|
160
|
+
this.updateBufferInfo(vertexData);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_getGeometricScaleFactor() {
|
|
164
|
+
const zoomLevel = Math.pow(2, this.properties.geometricZoomBound || 0);
|
|
165
|
+
|
|
166
|
+
return Math.pow(
|
|
167
|
+
Math.min(1, this.unitView.getZoomLevel() / zoomLevel),
|
|
168
|
+
1 / 3
|
|
169
|
+
// note: 1/3 appears to yield perceptually more uniform result than 1/2. I don't know why!
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Returns the maximum size of the points in the data, before any scaling
|
|
175
|
+
*/
|
|
176
|
+
_getMaxPointSize() {
|
|
177
|
+
const e = this.encoders.size;
|
|
178
|
+
if (e.constant) {
|
|
179
|
+
return e(null);
|
|
180
|
+
} else {
|
|
181
|
+
return e.scale.range().reduce((a, b) => Math.max(a, b));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
getSemanticThreshold() {
|
|
186
|
+
if (this.sampledSemanticScores) {
|
|
187
|
+
const p = Math.max(
|
|
188
|
+
0,
|
|
189
|
+
1 -
|
|
190
|
+
this.properties.semanticZoomFraction *
|
|
191
|
+
this.unitView.getZoomLevel()
|
|
192
|
+
);
|
|
193
|
+
if (p <= 0) {
|
|
194
|
+
// The sampled scores may be missing the min/max values
|
|
195
|
+
return -Infinity;
|
|
196
|
+
} else if (p >= 1) {
|
|
197
|
+
return Infinity;
|
|
198
|
+
} else {
|
|
199
|
+
return quantileSorted(this.sampledSemanticScores, p);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
return -1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {import("../view/rendering").GlobalRenderingOptions} options
|
|
208
|
+
*/
|
|
209
|
+
prepareRender(options) {
|
|
210
|
+
super.prepareRender(options);
|
|
211
|
+
|
|
212
|
+
setUniforms(this.programInfo, {
|
|
213
|
+
uMaxPointSize: this._getMaxPointSize(),
|
|
214
|
+
uScaleFactor: this._getGeometricScaleFactor(),
|
|
215
|
+
uSemanticThreshold: this.getSemanticThreshold(),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
setBuffersAndAttributes(
|
|
219
|
+
this.gl,
|
|
220
|
+
this.programInfo,
|
|
221
|
+
this.vertexArrayInfo
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Setup bisector that allows for searching the points that reside within the viewport.
|
|
225
|
+
const xEncoder = this.encoders.x;
|
|
226
|
+
if (xEncoder && !xEncoder.constant) {
|
|
227
|
+
const bisect = bisector(xEncoder.accessor).left;
|
|
228
|
+
const visibleDomain = this.unitView
|
|
229
|
+
.getScaleResolution("x")
|
|
230
|
+
.getScale()
|
|
231
|
+
.domain();
|
|
232
|
+
|
|
233
|
+
// A hack to include points that are just beyond the borders. TODO: Compute based on maxPointSize
|
|
234
|
+
const paddedDomain = zoomLinear(visibleDomain, null, 1.01);
|
|
235
|
+
|
|
236
|
+
/** @param {any[]} facetId */
|
|
237
|
+
this._findIndices = (facetId) => {
|
|
238
|
+
const data = this.unitView
|
|
239
|
+
.getCollector()
|
|
240
|
+
.facetBatches.get(facetId);
|
|
241
|
+
|
|
242
|
+
return [
|
|
243
|
+
bisect(data, paddedDomain[0]),
|
|
244
|
+
bisect(data, paddedDomain[paddedDomain.length - 1]),
|
|
245
|
+
];
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @param {import("./Mark").MarkRenderingOptions} options
|
|
252
|
+
*/
|
|
253
|
+
render(options) {
|
|
254
|
+
const gl = this.gl;
|
|
255
|
+
|
|
256
|
+
return this.createRenderCallback(
|
|
257
|
+
(offset, count) => {
|
|
258
|
+
// TODO: findIndices is rather slow. Consider a more coarse-grained, "tiled" solution.
|
|
259
|
+
const [lower, upper] = this._findIndices
|
|
260
|
+
? this._findIndices(options.facetId)
|
|
261
|
+
: [0, count];
|
|
262
|
+
|
|
263
|
+
const length = upper - lower;
|
|
264
|
+
|
|
265
|
+
if (length) {
|
|
266
|
+
drawBufferInfo(
|
|
267
|
+
gl,
|
|
268
|
+
this.vertexArrayInfo,
|
|
269
|
+
gl.POINTS,
|
|
270
|
+
length,
|
|
271
|
+
offset + lower
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
options,
|
|
276
|
+
() => this.rangeMap
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { drawBufferInfo, setBuffersAndAttributes, setUniforms } from "twgl.js";
|
|
2
|
+
import VERTEX_SHADER from "../gl/rect.vertex.glsl";
|
|
3
|
+
import FRAGMENT_SHADER from "../gl/rect.fragment.glsl";
|
|
4
|
+
import { RectVertexBuilder } from "../gl/dataToVertices";
|
|
5
|
+
|
|
6
|
+
import Mark from "./mark";
|
|
7
|
+
import { fixFill, fixPositional, fixStroke } from "./markUtils";
|
|
8
|
+
import { asArray } from "../utils/arrayUtils";
|
|
9
|
+
import { isValueDef } from "../encoder/encoder";
|
|
10
|
+
import { getCachedOrCall } from "../utils/propertyCacher";
|
|
11
|
+
|
|
12
|
+
export default class RectMark extends Mark {
|
|
13
|
+
/**
|
|
14
|
+
* @param {import("../view/unitView").default} unitView
|
|
15
|
+
*/
|
|
16
|
+
constructor(unitView) {
|
|
17
|
+
super(unitView);
|
|
18
|
+
|
|
19
|
+
Object.defineProperties(
|
|
20
|
+
this.defaultProperties,
|
|
21
|
+
Object.getOwnPropertyDescriptors({
|
|
22
|
+
x2: undefined,
|
|
23
|
+
y2: undefined,
|
|
24
|
+
filled: true,
|
|
25
|
+
color: "#4c78a8",
|
|
26
|
+
opacity: 1.0,
|
|
27
|
+
strokeWidth: 3,
|
|
28
|
+
cornerRadius: 0.0,
|
|
29
|
+
|
|
30
|
+
minWidth: 0.5, // Minimum width/height prevents annoying flickering when zooming
|
|
31
|
+
minHeight: 0.5,
|
|
32
|
+
minOpacity: 1.0,
|
|
33
|
+
|
|
34
|
+
tessellationZoomThreshold: 10, // This works with genomes, but likely breaks with other data. TODO: Fix, TODO: log2
|
|
35
|
+
tessellationTiles: 35, // TODO: Tiles per unit (bp)
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getAttributes() {
|
|
41
|
+
return [
|
|
42
|
+
"uniqueId",
|
|
43
|
+
"facetIndex",
|
|
44
|
+
"x",
|
|
45
|
+
"x2",
|
|
46
|
+
"y",
|
|
47
|
+
"y2",
|
|
48
|
+
"fill",
|
|
49
|
+
"stroke",
|
|
50
|
+
"fillOpacity",
|
|
51
|
+
"strokeOpacity",
|
|
52
|
+
"strokeWidth",
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @returns {import("../spec/channel").Channel[]}
|
|
58
|
+
*/
|
|
59
|
+
getSupportedChannels() {
|
|
60
|
+
return [
|
|
61
|
+
...super.getSupportedChannels(),
|
|
62
|
+
"x2",
|
|
63
|
+
"y2",
|
|
64
|
+
"fill",
|
|
65
|
+
"stroke",
|
|
66
|
+
"fillOpacity",
|
|
67
|
+
"strokeOpacity",
|
|
68
|
+
"strokeWidth",
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get opaque() {
|
|
73
|
+
return (
|
|
74
|
+
getCachedOrCall(
|
|
75
|
+
this,
|
|
76
|
+
"opaque",
|
|
77
|
+
() =>
|
|
78
|
+
!this._isRoundedCorners() &&
|
|
79
|
+
!this._isStroked() &&
|
|
80
|
+
isValueDef(this.encoding.fillOpacity) &&
|
|
81
|
+
this.encoding.fillOpacity.value == 1.0 &&
|
|
82
|
+
this.properties.minOpacity == 1.0
|
|
83
|
+
) && this.unitView.getEffectiveOpacity() == 1
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {import("../spec/channel").Encoding} encoding
|
|
89
|
+
* @returns {import("../spec/channel").Encoding}
|
|
90
|
+
*/
|
|
91
|
+
fixEncoding(encoding) {
|
|
92
|
+
// TODO: Ensure that both the primary and secondary channel are either variables or constants (values)
|
|
93
|
+
fixPositional(encoding, "x");
|
|
94
|
+
fixPositional(encoding, "y");
|
|
95
|
+
|
|
96
|
+
fixStroke(encoding, this.properties.filled);
|
|
97
|
+
fixFill(encoding, this.properties.filled);
|
|
98
|
+
|
|
99
|
+
// TODO: Function for getting rid of extras. Also should validate that all attributes are defined
|
|
100
|
+
delete encoding.color;
|
|
101
|
+
delete encoding.opacity;
|
|
102
|
+
|
|
103
|
+
return encoding;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onBeforeSampleAnimation() {
|
|
107
|
+
// TODO: Tessellate rects inside the viewport
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onAfterSampleAnimation() {
|
|
111
|
+
// TODO: Pop the previous buffers
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_isRoundedCorners() {
|
|
115
|
+
return ["", "TopLeft", "TopRight", "BottomLeft", "BottomRight"]
|
|
116
|
+
.map(
|
|
117
|
+
(c) =>
|
|
118
|
+
/** @type {keyof import("../spec/mark").MarkConfig} */ (
|
|
119
|
+
"cornerRadius" + c
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
.some((c) => this.properties[c] > 0);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_isStroked() {
|
|
126
|
+
const sw = this.encoding.strokeWidth;
|
|
127
|
+
return !(isValueDef(sw) && !sw.value);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async initializeGraphics() {
|
|
131
|
+
await super.initializeGraphics();
|
|
132
|
+
|
|
133
|
+
/** @type {string[]} */
|
|
134
|
+
const defines = [];
|
|
135
|
+
if (this._isRoundedCorners()) {
|
|
136
|
+
defines.push("ROUNDED_CORNERS");
|
|
137
|
+
}
|
|
138
|
+
if (this._isStroked()) {
|
|
139
|
+
defines.push("STROKED");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.createAndLinkShaders(
|
|
143
|
+
VERTEX_SHADER,
|
|
144
|
+
FRAGMENT_SHADER,
|
|
145
|
+
defines.map((d) => "#define " + d)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
updateGraphicsData() {
|
|
150
|
+
const collector = this.unitView.getCollector();
|
|
151
|
+
const numItems = collector.getItemCount();
|
|
152
|
+
|
|
153
|
+
// TODO: Disable tessellation on SimpleTrack - no need for it
|
|
154
|
+
const builder = new RectVertexBuilder({
|
|
155
|
+
encoders: this.encoders,
|
|
156
|
+
attributes: this.getAttributes(),
|
|
157
|
+
numItems,
|
|
158
|
+
buildXIndex: this.properties.buildIndex,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
builder.addBatches(collector.facetBatches);
|
|
162
|
+
|
|
163
|
+
const vertexData = builder.toArrays();
|
|
164
|
+
this.rangeMap = vertexData.rangeMap;
|
|
165
|
+
this.updateBufferInfo(vertexData);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {import("../view/rendering").GlobalRenderingOptions} options
|
|
170
|
+
*/
|
|
171
|
+
prepareRender(options) {
|
|
172
|
+
super.prepareRender(options);
|
|
173
|
+
|
|
174
|
+
const props = this.properties;
|
|
175
|
+
|
|
176
|
+
setUniforms(this.programInfo, {
|
|
177
|
+
uMinSize: [props.minWidth, props.minHeight], // in pixels
|
|
178
|
+
uMinOpacity: props.minOpacity,
|
|
179
|
+
uCornerRadii: [
|
|
180
|
+
props.cornerRadiusTopRight ?? props.cornerRadius,
|
|
181
|
+
props.cornerRadiusBottomRight ?? props.cornerRadius,
|
|
182
|
+
props.cornerRadiusTopLeft ?? props.cornerRadius,
|
|
183
|
+
props.cornerRadiusBottomLeft ?? props.cornerRadius,
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
setBuffersAndAttributes(
|
|
188
|
+
this.gl,
|
|
189
|
+
this.programInfo,
|
|
190
|
+
this.vertexArrayInfo
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @param {import("./Mark").MarkRenderingOptions} options
|
|
196
|
+
*/
|
|
197
|
+
render(options) {
|
|
198
|
+
const gl = this.gl;
|
|
199
|
+
|
|
200
|
+
return this.createRenderCallback(
|
|
201
|
+
(offset, count) => {
|
|
202
|
+
drawBufferInfo(
|
|
203
|
+
gl,
|
|
204
|
+
this.vertexArrayInfo,
|
|
205
|
+
gl.TRIANGLE_STRIP,
|
|
206
|
+
count,
|
|
207
|
+
offset
|
|
208
|
+
);
|
|
209
|
+
},
|
|
210
|
+
options,
|
|
211
|
+
() => this.rangeMap
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Finds a datum that overlaps the given value on the x domain.
|
|
217
|
+
* The result is unspecified if multiple data are found.
|
|
218
|
+
*
|
|
219
|
+
* This is highly specific to SampleView and its sorting/filtering functionality.
|
|
220
|
+
*
|
|
221
|
+
* @param {any} facetId
|
|
222
|
+
* @param {number} x position on the x domain
|
|
223
|
+
* @returns {any}
|
|
224
|
+
*/
|
|
225
|
+
findDatumAt(facetId, x) {
|
|
226
|
+
facetId = asArray(facetId); // TODO: Do at the call site
|
|
227
|
+
const e = this.encoders;
|
|
228
|
+
const data = this.unitView.getCollector().facetBatches.get(facetId);
|
|
229
|
+
const a = e.x.accessor;
|
|
230
|
+
const a2 = e.x2.accessor;
|
|
231
|
+
if (data) {
|
|
232
|
+
// TODO: Binary search
|
|
233
|
+
return data.find((d) => x >= a(d) && x < a2(d));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|