@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,621 @@
|
|
|
1
|
+
import { InternMap } from "internmap";
|
|
2
|
+
import { format } from "d3-format";
|
|
3
|
+
import { isString } from "vega-util";
|
|
4
|
+
import { fp64ify } from "./includes/fp64-utils";
|
|
5
|
+
import ArrayBuilder from "./arrayBuilder";
|
|
6
|
+
import { SDF_PADDING } from "../fonts/bmFontMetrics";
|
|
7
|
+
import { peek } from "../utils/arrayUtils";
|
|
8
|
+
import createBinningRangeIndexer from "../utils/binnedRangeIndex";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} RangeEntry Represents a location of a vertex subset
|
|
12
|
+
* @prop {number} offset in vertices
|
|
13
|
+
* @prop {number} count in vertices
|
|
14
|
+
* @prop {import("../utils/binnedRangeIndex").Lookup} xIndex
|
|
15
|
+
*
|
|
16
|
+
* @typedef {import("./arraybuilder").ConverterMetadata} Converter
|
|
17
|
+
* @typedef {import("../encoder/encoder").Encoder} Encoder
|
|
18
|
+
*/
|
|
19
|
+
export class GeometryBuilder {
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {object} object
|
|
23
|
+
* @param {Record<string, Encoder>} object.encoders
|
|
24
|
+
* @param {string[]} [object.attributes]
|
|
25
|
+
* @param {number} [object.numVertices] If the number of data items is known, a
|
|
26
|
+
* preallocated TypedArray is used
|
|
27
|
+
* @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
|
|
28
|
+
*/
|
|
29
|
+
constructor({
|
|
30
|
+
encoders,
|
|
31
|
+
numVertices = undefined,
|
|
32
|
+
attributes = [],
|
|
33
|
+
buildXIndex = false,
|
|
34
|
+
}) {
|
|
35
|
+
this.encoders = encoders;
|
|
36
|
+
this._buildXIndex = buildXIndex;
|
|
37
|
+
|
|
38
|
+
// Encoders for variable channels
|
|
39
|
+
this.variableEncoders = Object.fromEntries(
|
|
40
|
+
Object.entries(encoders).filter(
|
|
41
|
+
([channel, e]) =>
|
|
42
|
+
attributes.includes(channel) && e && e.scale && !e.constant
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
this.allocatedVertices = numVertices;
|
|
47
|
+
|
|
48
|
+
this.variableBuilder = new ArrayBuilder(numVertices);
|
|
49
|
+
|
|
50
|
+
// Create converters and updaters for all variable channels.
|
|
51
|
+
// TODO: If more than one channels use the same field with the same data type, convert the field only once.
|
|
52
|
+
|
|
53
|
+
for (const [channel, ce] of Object.entries(this.variableEncoders)) {
|
|
54
|
+
const accessor = ce.accessor;
|
|
55
|
+
|
|
56
|
+
const doubleArray = [0, 0];
|
|
57
|
+
const fp64 = ce.scale.fp64;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Discrete variables both numeric and strings must be "indexed",
|
|
61
|
+
* 64 bit floats must be converted to vec2.
|
|
62
|
+
* 32 bit continuous variables go to GPU as is.
|
|
63
|
+
*
|
|
64
|
+
* @type {function(any):(number | number[])}
|
|
65
|
+
*/
|
|
66
|
+
const f = ce.indexer
|
|
67
|
+
? ce.indexer
|
|
68
|
+
: fp64
|
|
69
|
+
? (d) => fp64ify(accessor(d), doubleArray)
|
|
70
|
+
: accessor;
|
|
71
|
+
|
|
72
|
+
this.variableBuilder.addConverter(channel, {
|
|
73
|
+
f,
|
|
74
|
+
numComponents: fp64 ? 2 : 1,
|
|
75
|
+
arrayReference: fp64 ? doubleArray : undefined,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.lastOffset = 0;
|
|
80
|
+
|
|
81
|
+
/** @type {Map<any, RangeEntry>} keep track of sample locations within the vertex array */
|
|
82
|
+
this.rangeMap = new InternMap([], JSON.stringify);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Must be called at the end of `addBatch`
|
|
87
|
+
*
|
|
88
|
+
* @param {any} key
|
|
89
|
+
*/
|
|
90
|
+
registerBatch(key) {
|
|
91
|
+
const offset = this.lastOffset;
|
|
92
|
+
const index = this.variableBuilder.vertexCount;
|
|
93
|
+
const size = index - offset;
|
|
94
|
+
if (size) {
|
|
95
|
+
this.rangeMap.set(key, {
|
|
96
|
+
offset,
|
|
97
|
+
count: size,
|
|
98
|
+
xIndex: this.xIndexer?.getIndex(),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
this.lastOffset = index;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {Map<any, object[]>} batches
|
|
106
|
+
*/
|
|
107
|
+
addBatches(batches) {
|
|
108
|
+
for (const [key, data] of batches) {
|
|
109
|
+
this.addBatch(key, data);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {any} key The facet id, for example
|
|
115
|
+
* @param {object[]} data
|
|
116
|
+
*/
|
|
117
|
+
addBatch(key, data, lo = 0, hi = data.length) {
|
|
118
|
+
for (let i = lo; i < hi; i++) {
|
|
119
|
+
this.variableBuilder.pushFromDatum(data[i]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.registerBatch(key);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {any[]} data
|
|
127
|
+
*/
|
|
128
|
+
prepareXIndexer(data) {
|
|
129
|
+
if (!this._buildXIndex) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const xe = this.variableEncoders.x;
|
|
134
|
+
const x2e = this.variableEncoders.x2;
|
|
135
|
+
|
|
136
|
+
if (xe && x2e) {
|
|
137
|
+
const xa = xe.accessor;
|
|
138
|
+
const x2a = x2e.accessor;
|
|
139
|
+
|
|
140
|
+
this.xIndexer = createBinningRangeIndexer(50, [
|
|
141
|
+
xa(data[0]),
|
|
142
|
+
x2a(peek(data)),
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
let lastVertexCount = this.variableBuilder.vertexCount;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {any} datum
|
|
149
|
+
*/
|
|
150
|
+
this.addToXIndex = (datum) => {
|
|
151
|
+
let currentVertexCount = this.variableBuilder.vertexCount;
|
|
152
|
+
this.xIndexer(
|
|
153
|
+
xa(datum),
|
|
154
|
+
x2a(datum),
|
|
155
|
+
lastVertexCount,
|
|
156
|
+
currentVertexCount
|
|
157
|
+
);
|
|
158
|
+
lastVertexCount = currentVertexCount;
|
|
159
|
+
};
|
|
160
|
+
} else {
|
|
161
|
+
this.xIndexer = undefined;
|
|
162
|
+
/**
|
|
163
|
+
* @param {any} datum
|
|
164
|
+
*/
|
|
165
|
+
this.addToXIndex = (datum) => {
|
|
166
|
+
//
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Add the datum to an index, which allows for efficient rendering of ranges
|
|
173
|
+
* on the x axis. Must be called after a datum has been pushed to the ArrayBuilder.
|
|
174
|
+
*
|
|
175
|
+
* @param {any} datum
|
|
176
|
+
*/
|
|
177
|
+
addToXIndex(datum) {
|
|
178
|
+
//
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
toArrays() {
|
|
182
|
+
return {
|
|
183
|
+
/** @type {Record<string, {data: number[] | Float32Array, numComponents: number, divisor?: number}>} */
|
|
184
|
+
arrays: this.variableBuilder.arrays,
|
|
185
|
+
/** Number of vertices used */
|
|
186
|
+
vertexCount: this.variableBuilder.vertexCount,
|
|
187
|
+
/** Number of vertices allocated in buffers */
|
|
188
|
+
allocatedVertices: this.allocatedVertices,
|
|
189
|
+
rangeMap: this.rangeMap,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export class RectVertexBuilder extends GeometryBuilder {
|
|
195
|
+
/**
|
|
196
|
+
*
|
|
197
|
+
* @param {Object} object
|
|
198
|
+
* @param {Record<string, Encoder>} object.encoders
|
|
199
|
+
* @param {string[]} object.attributes
|
|
200
|
+
* @param {number} [object.tessellationThreshold]
|
|
201
|
+
* If the rect is wider than the threshold, tessellate it into pieces
|
|
202
|
+
* @param {number[]} [object.visibleRange]
|
|
203
|
+
* @param {number} [object.numItems] Number of data items
|
|
204
|
+
* @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
|
|
205
|
+
*/
|
|
206
|
+
constructor({
|
|
207
|
+
encoders,
|
|
208
|
+
attributes,
|
|
209
|
+
tessellationThreshold = Infinity,
|
|
210
|
+
visibleRange = [-Infinity, Infinity],
|
|
211
|
+
numItems,
|
|
212
|
+
buildXIndex = false,
|
|
213
|
+
}) {
|
|
214
|
+
super({
|
|
215
|
+
encoders,
|
|
216
|
+
attributes,
|
|
217
|
+
numVertices:
|
|
218
|
+
tessellationThreshold == Infinity ? numItems * 6 : undefined,
|
|
219
|
+
buildXIndex,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
this.visibleRange = visibleRange;
|
|
223
|
+
|
|
224
|
+
this.tessellationThreshold = tessellationThreshold || Infinity;
|
|
225
|
+
|
|
226
|
+
this.updateFrac = this.variableBuilder.createUpdater("frac", 2);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
*
|
|
231
|
+
* @param {any} key
|
|
232
|
+
* @param {object[]} data
|
|
233
|
+
*/
|
|
234
|
+
addBatch(key, data, lo = 0, hi = data.length) {
|
|
235
|
+
if (hi <= lo) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const e =
|
|
240
|
+
/** @type {Object.<string, import("../encoder/encoder").NumberEncoder>} */ (
|
|
241
|
+
this.encoders
|
|
242
|
+
);
|
|
243
|
+
const [lower, upper] = this.visibleRange;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @param {import("../encoder/encoder").Encoder} encoder
|
|
247
|
+
*/
|
|
248
|
+
const a = (encoder) => encoder.accessor || ((x) => 0);
|
|
249
|
+
|
|
250
|
+
const xAccessor = a(e.x);
|
|
251
|
+
const x2Accessor = a(e.x2);
|
|
252
|
+
|
|
253
|
+
this.prepareXIndexer(data);
|
|
254
|
+
|
|
255
|
+
const frac = [0, 0];
|
|
256
|
+
this.updateFrac(frac);
|
|
257
|
+
|
|
258
|
+
for (let i = lo; i < hi; i++) {
|
|
259
|
+
const d = data[i];
|
|
260
|
+
|
|
261
|
+
let x = xAccessor(d),
|
|
262
|
+
x2 = x2Accessor(d);
|
|
263
|
+
|
|
264
|
+
if (x > x2) {
|
|
265
|
+
[x, x2] = [x2, x];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Skip rects that fall outside the visible range. TODO: Optimize by using binary search / interval tree
|
|
269
|
+
if (x2 < lower || x > upper) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Truncate to prevent tessellation of parts that are outside the viewport
|
|
274
|
+
if (x < lower) x = lower;
|
|
275
|
+
if (x2 > upper) x2 = upper;
|
|
276
|
+
|
|
277
|
+
// Start a new segment.
|
|
278
|
+
this.variableBuilder.updateFromDatum(d);
|
|
279
|
+
|
|
280
|
+
frac[0] = 0;
|
|
281
|
+
frac[1] = 0;
|
|
282
|
+
|
|
283
|
+
// Tessellate segments
|
|
284
|
+
const tileCount = 1;
|
|
285
|
+
// width < Infinity
|
|
286
|
+
// ? Math.ceil(width / this.tessellationThreshold)
|
|
287
|
+
// : 1;
|
|
288
|
+
|
|
289
|
+
// Duplicate the first vertex to produce degenerate triangles
|
|
290
|
+
this.variableBuilder.pushAll();
|
|
291
|
+
|
|
292
|
+
for (let i = 0; i <= tileCount; i++) {
|
|
293
|
+
frac[0] = i / tileCount;
|
|
294
|
+
frac[1] = 0;
|
|
295
|
+
this.variableBuilder.pushAll();
|
|
296
|
+
frac[1] = 1;
|
|
297
|
+
this.variableBuilder.pushAll();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Duplicate the last vertex to produce a degenerate triangle between the segments
|
|
301
|
+
this.variableBuilder.pushAll();
|
|
302
|
+
this.addToXIndex(d);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.registerBatch(key);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export class RuleVertexBuilder extends GeometryBuilder {
|
|
310
|
+
/**
|
|
311
|
+
*
|
|
312
|
+
* @param {Object} object
|
|
313
|
+
* @param {Record<string, Encoder>} object.encoders
|
|
314
|
+
* @param {string[]} object.attributes
|
|
315
|
+
* @param {number} [object.tessellationThreshold]
|
|
316
|
+
* If the rule is wider than the threshold, tessellate it into pieces
|
|
317
|
+
* @param {number[]} [object.visibleRange]
|
|
318
|
+
* @param {number} [object.numItems] Number of data items
|
|
319
|
+
* @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
|
|
320
|
+
*/
|
|
321
|
+
constructor({
|
|
322
|
+
encoders,
|
|
323
|
+
attributes,
|
|
324
|
+
tessellationThreshold = Infinity,
|
|
325
|
+
visibleRange = [-Infinity, Infinity],
|
|
326
|
+
numItems,
|
|
327
|
+
buildXIndex,
|
|
328
|
+
}) {
|
|
329
|
+
super({
|
|
330
|
+
encoders,
|
|
331
|
+
attributes,
|
|
332
|
+
numVertices:
|
|
333
|
+
tessellationThreshold == Infinity ? numItems * 6 : undefined,
|
|
334
|
+
buildXIndex,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
this.visibleRange = visibleRange;
|
|
338
|
+
|
|
339
|
+
this.tessellationThreshold = tessellationThreshold || Infinity;
|
|
340
|
+
|
|
341
|
+
this.updateSide = this.variableBuilder.createUpdater("side", 1);
|
|
342
|
+
this.updatePos = this.variableBuilder.createUpdater("pos", 1);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/* eslint-disable complexity */
|
|
346
|
+
/**
|
|
347
|
+
*
|
|
348
|
+
* @param {any} key
|
|
349
|
+
* @param {object[]} data
|
|
350
|
+
*/
|
|
351
|
+
addBatch(key, data, lo = 0, hi = data.length) {
|
|
352
|
+
//const [lower, upper] = this.visibleRange; // TODO
|
|
353
|
+
|
|
354
|
+
this.prepareXIndexer(data);
|
|
355
|
+
|
|
356
|
+
for (let i = lo; i < hi; i++) {
|
|
357
|
+
const d = data[i];
|
|
358
|
+
|
|
359
|
+
// Start a new rule. Duplicate the first vertex to produce degenerate triangles
|
|
360
|
+
this.variableBuilder.updateFromDatum(d);
|
|
361
|
+
this.updateSide(-0.5);
|
|
362
|
+
this.updatePos(0);
|
|
363
|
+
this.variableBuilder.pushAll();
|
|
364
|
+
|
|
365
|
+
// Tesselate segments
|
|
366
|
+
const tileCount = 1;
|
|
367
|
+
// width < Infinity
|
|
368
|
+
// ? Math.ceil(width / this.tessellationThreshold)
|
|
369
|
+
// : 1;
|
|
370
|
+
for (let i = 0; i <= tileCount; i++) {
|
|
371
|
+
this.updatePos(i / tileCount);
|
|
372
|
+
this.updateSide(-0.5);
|
|
373
|
+
this.variableBuilder.pushAll();
|
|
374
|
+
this.updateSide(0.5);
|
|
375
|
+
this.variableBuilder.pushAll();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Duplicate the last vertex to produce a degenerate triangle between the rules
|
|
379
|
+
this.variableBuilder.pushAll();
|
|
380
|
+
this.addToXIndex(d);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
this.registerBatch(key);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export class PointVertexBuilder extends GeometryBuilder {
|
|
388
|
+
/**
|
|
389
|
+
*
|
|
390
|
+
* @param {object} object
|
|
391
|
+
* @param {Record<string, Encoder>} object.encoders
|
|
392
|
+
* @param {string[]} object.attributes
|
|
393
|
+
* @param {number} [object.numItems] Number of points if known, uses TypedArray
|
|
394
|
+
*/
|
|
395
|
+
constructor({ encoders, attributes, numItems = undefined }) {
|
|
396
|
+
super({
|
|
397
|
+
encoders,
|
|
398
|
+
attributes,
|
|
399
|
+
numVertices: numItems,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export class ConnectionVertexBuilder extends GeometryBuilder {
|
|
405
|
+
/**
|
|
406
|
+
* @param {object} object
|
|
407
|
+
* @param {Record<string, Encoder>} object.encoders
|
|
408
|
+
* @param {string[]} object.attributes
|
|
409
|
+
* @param {number} [object.numItems ] Number of points if known, uses TypedArray
|
|
410
|
+
*/
|
|
411
|
+
constructor({ encoders, attributes, numItems = undefined }) {
|
|
412
|
+
super({
|
|
413
|
+
encoders,
|
|
414
|
+
attributes,
|
|
415
|
+
numVertices: numItems,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
toArrays() {
|
|
420
|
+
const arrays = this.variableBuilder.arrays;
|
|
421
|
+
|
|
422
|
+
// Prepare for instanced rendering
|
|
423
|
+
for (let a of Object.values(arrays)) {
|
|
424
|
+
a.divisor = 1;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return super.toArrays();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export class TextVertexBuilder extends GeometryBuilder {
|
|
432
|
+
/**
|
|
433
|
+
*
|
|
434
|
+
* @param {object} object
|
|
435
|
+
* @param {Record<string, Encoder>} object.encoders
|
|
436
|
+
* @param {string[]} object.attributes
|
|
437
|
+
* @param {import("../fonts/bmFontMetrics").BMFontMetrics} object.fontMetrics
|
|
438
|
+
* @param {Record<string, any>} object.properties
|
|
439
|
+
* @param {number} [object.numCharacters] number of characters
|
|
440
|
+
* @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
|
|
441
|
+
* @param {boolean} [object.logoLetters]
|
|
442
|
+
*/
|
|
443
|
+
constructor({
|
|
444
|
+
encoders,
|
|
445
|
+
attributes,
|
|
446
|
+
fontMetrics,
|
|
447
|
+
properties,
|
|
448
|
+
numCharacters = undefined,
|
|
449
|
+
buildXIndex = false,
|
|
450
|
+
}) {
|
|
451
|
+
super({
|
|
452
|
+
encoders,
|
|
453
|
+
attributes,
|
|
454
|
+
numVertices: numCharacters * 6, // six vertices per quad (character)
|
|
455
|
+
buildXIndex,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
this.metadata = fontMetrics;
|
|
459
|
+
this.metrics = fontMetrics;
|
|
460
|
+
|
|
461
|
+
this.properties = properties;
|
|
462
|
+
|
|
463
|
+
const e = encoders;
|
|
464
|
+
|
|
465
|
+
/** @type {function(any):any} */
|
|
466
|
+
this.numberFormat = e.text.channelDef.format
|
|
467
|
+
? format(e.text.channelDef.format)
|
|
468
|
+
: (d) => d;
|
|
469
|
+
|
|
470
|
+
this.updateVertexCoord = this.variableBuilder.createUpdater(
|
|
471
|
+
"vertexCoord",
|
|
472
|
+
2
|
|
473
|
+
);
|
|
474
|
+
this.updateTextureCoord = this.variableBuilder.createUpdater(
|
|
475
|
+
"textureCoord",
|
|
476
|
+
2
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
this.updateWidth = this.variableBuilder.createUpdater("width", 1);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
*
|
|
484
|
+
* @param {any} key
|
|
485
|
+
* @param {object[]} data
|
|
486
|
+
*/
|
|
487
|
+
addBatch(key, data, lo = 0, hi = data.length) {
|
|
488
|
+
const align = this.properties.align || "left";
|
|
489
|
+
const logoLetters = this.properties.logoLetters ?? false;
|
|
490
|
+
|
|
491
|
+
const base = this.metadata.common.base;
|
|
492
|
+
const scale = this.metadata.common.scaleH; // Assume square textures
|
|
493
|
+
|
|
494
|
+
let baseline = -SDF_PADDING;
|
|
495
|
+
switch (this.properties.baseline) {
|
|
496
|
+
case "top":
|
|
497
|
+
baseline += this.metrics.capHeight;
|
|
498
|
+
break;
|
|
499
|
+
case "middle":
|
|
500
|
+
baseline += this.metrics.capHeight / 2;
|
|
501
|
+
break;
|
|
502
|
+
case "bottom":
|
|
503
|
+
baseline -= this.metrics.descent;
|
|
504
|
+
break;
|
|
505
|
+
default:
|
|
506
|
+
// alphabetic
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const accessor = this.encoders.text.accessor || this.encoders.text; // accessor or constant value
|
|
510
|
+
|
|
511
|
+
const vertexCoord = [0, 0];
|
|
512
|
+
this.updateVertexCoord(vertexCoord);
|
|
513
|
+
const textureCoord = [0, 0];
|
|
514
|
+
this.updateTextureCoord(textureCoord);
|
|
515
|
+
|
|
516
|
+
this.prepareXIndexer(data);
|
|
517
|
+
|
|
518
|
+
for (let i = lo; i < hi; i++) {
|
|
519
|
+
const d = data[i];
|
|
520
|
+
|
|
521
|
+
const value = this.numberFormat(accessor(d));
|
|
522
|
+
const str = isString(value)
|
|
523
|
+
? value
|
|
524
|
+
: value === null
|
|
525
|
+
? ""
|
|
526
|
+
: "" + value;
|
|
527
|
+
if (str.length == 0) continue;
|
|
528
|
+
|
|
529
|
+
this.variableBuilder.updateFromDatum(d);
|
|
530
|
+
|
|
531
|
+
const textWidth = logoLetters
|
|
532
|
+
? str.length
|
|
533
|
+
: this.metrics.measureWidth(str);
|
|
534
|
+
|
|
535
|
+
this.updateWidth(textWidth); // TODO: Check if one letter space should be reduced
|
|
536
|
+
|
|
537
|
+
let x =
|
|
538
|
+
align == "right"
|
|
539
|
+
? -textWidth
|
|
540
|
+
: align == "center"
|
|
541
|
+
? -textWidth / 2
|
|
542
|
+
: 0;
|
|
543
|
+
|
|
544
|
+
if (!logoLetters) {
|
|
545
|
+
const firstChar = this.metrics.getCharByCode(str.charCodeAt(0));
|
|
546
|
+
x -= (firstChar.width - firstChar.xadvance) / base / 2; // TODO: Fix, this is a bit off..
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
let bottom = -0.5,
|
|
550
|
+
height = 1,
|
|
551
|
+
normalWidth = 1;
|
|
552
|
+
|
|
553
|
+
for (let i = 0; i < str.length; i++) {
|
|
554
|
+
const c = this.metrics.getCharByCode(str.charCodeAt(i));
|
|
555
|
+
|
|
556
|
+
const advance = logoLetters ? 1 : c.xadvance / base;
|
|
557
|
+
|
|
558
|
+
if (c.id == 32) {
|
|
559
|
+
x += advance;
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (!logoLetters) {
|
|
564
|
+
height = c.height / base;
|
|
565
|
+
bottom = -(c.height + c.yoffset + baseline) / base;
|
|
566
|
+
normalWidth = c.width / base;
|
|
567
|
+
} else {
|
|
568
|
+
normalWidth = (c.width + SDF_PADDING * 2) / c.width;
|
|
569
|
+
x = -normalWidth / 2;
|
|
570
|
+
height = (c.height + SDF_PADDING * 2) / c.height;
|
|
571
|
+
bottom = -0.5 - SDF_PADDING / c.height;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const tx = c.x;
|
|
575
|
+
const ty = c.y;
|
|
576
|
+
|
|
577
|
+
vertexCoord[0] = x;
|
|
578
|
+
vertexCoord[1] = bottom + height;
|
|
579
|
+
textureCoord[0] = tx / scale;
|
|
580
|
+
textureCoord[1] = ty / scale;
|
|
581
|
+
this.variableBuilder.pushAll();
|
|
582
|
+
|
|
583
|
+
vertexCoord[0] = x + normalWidth;
|
|
584
|
+
vertexCoord[1] = bottom + height;
|
|
585
|
+
textureCoord[0] = (tx + c.width) / scale;
|
|
586
|
+
textureCoord[1] = ty / scale;
|
|
587
|
+
this.variableBuilder.pushAll();
|
|
588
|
+
|
|
589
|
+
vertexCoord[0] = x;
|
|
590
|
+
vertexCoord[1] = bottom;
|
|
591
|
+
textureCoord[0] = tx / scale;
|
|
592
|
+
textureCoord[1] = (ty + c.height) / scale;
|
|
593
|
+
this.variableBuilder.pushAll();
|
|
594
|
+
|
|
595
|
+
vertexCoord[0] = x + normalWidth;
|
|
596
|
+
vertexCoord[1] = bottom + height;
|
|
597
|
+
textureCoord[0] = (tx + c.width) / scale;
|
|
598
|
+
textureCoord[1] = ty / scale;
|
|
599
|
+
this.variableBuilder.pushAll();
|
|
600
|
+
|
|
601
|
+
vertexCoord[0] = x;
|
|
602
|
+
vertexCoord[1] = bottom;
|
|
603
|
+
textureCoord[0] = tx / scale;
|
|
604
|
+
textureCoord[1] = (ty + c.height) / scale;
|
|
605
|
+
this.variableBuilder.pushAll();
|
|
606
|
+
|
|
607
|
+
vertexCoord[0] = x + normalWidth;
|
|
608
|
+
vertexCoord[1] = bottom;
|
|
609
|
+
textureCoord[0] = (tx + c.width) / scale;
|
|
610
|
+
textureCoord[1] = (ty + c.height) / scale;
|
|
611
|
+
this.variableBuilder.pushAll();
|
|
612
|
+
|
|
613
|
+
x += advance;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
this.addToXIndex(data);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
this.registerBatch(key);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#define PI 3.141593
|
|
2
|
+
|
|
3
|
+
/** Offset in "unit" units */
|
|
4
|
+
uniform vec2 uViewOffset;
|
|
5
|
+
|
|
6
|
+
uniform vec2 uViewScale;
|
|
7
|
+
|
|
8
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
9
|
+
uniform vec2 uViewportSize;
|
|
10
|
+
|
|
11
|
+
uniform lowp float uDevicePixelRatio;
|
|
12
|
+
|
|
13
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
14
|
+
// that is rendered with the specified opacity.
|
|
15
|
+
uniform lowp float uViewOpacity;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
|
19
|
+
* (0, 0) is at the bottom left corner.
|
|
20
|
+
*/
|
|
21
|
+
vec4 unitToNdc(vec2 coord) {
|
|
22
|
+
return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
vec4 unitToNdc(float x, float y) {
|
|
26
|
+
return unitToNdc(vec2(x, y));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
vec4 pixelsToNdc(vec2 coord) {
|
|
30
|
+
return unitToNdc(coord / uViewportSize);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
vec4 pixelsToNdc(float x, float y) {
|
|
34
|
+
return pixelsToNdc(vec2(x, y));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
float linearstep(float edge0, float edge1, float x) {
|
|
38
|
+
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Fragment shader stuff ////////////////////////////////////////////////////////
|
|
42
|
+
|
|
43
|
+
// TODO: include the following only in fragment shaders
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Specialized linearstep for doing antialiasing
|
|
47
|
+
*/
|
|
48
|
+
float distanceToRatio(float d) {
|
|
49
|
+
return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
vec4 distanceToColor(float d, vec4 fill, vec4 stroke, float halfStrokeWidth) {
|
|
53
|
+
if (halfStrokeWidth > 0.0) {
|
|
54
|
+
// Distance to stroke's edge. Negative inside the stroke.
|
|
55
|
+
float sd = abs(d) - halfStrokeWidth;
|
|
56
|
+
return mix(
|
|
57
|
+
stroke,
|
|
58
|
+
d <= 0.0 ? fill : vec4(0.0),
|
|
59
|
+
distanceToRatio(sd));
|
|
60
|
+
} else {
|
|
61
|
+
return fill * distanceToRatio(-d);
|
|
62
|
+
}
|
|
63
|
+
}
|