@genome-spy/core 0.20.0 → 0.21.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 +38 -38
- package/dist/schema.json +0 -3
- package/package.json +2 -2
- package/src/gl/dataToVertices.js +43 -46
- package/src/gl/includes/common.glsl +12 -12
- package/src/gl/includes/picking.fragment.glsl +0 -2
- package/src/gl/includes/picking.vertex.glsl +0 -2
- package/src/marks/link.js +32 -39
- package/src/marks/mark.js +167 -98
- package/src/marks/pointMark.js +28 -59
- package/src/marks/rectMark.js +38 -33
- package/src/marks/rule.js +31 -21
- package/src/marks/text.js +18 -14
- package/src/spec/mark.d.ts +0 -3
- package/src/utils/binnedIndex.js +147 -0
- package/src/utils/binnedIndex.test.js +73 -0
- package/src/view/axisView.js +0 -4
- package/src/view/renderingContext/deferredViewRenderingContext.js +3 -1
- package/src/view/renderingContext/simpleViewRenderingContext.js +3 -1
- package/src/utils/binnedRangeIndex.js +0 -83
package/dist/schema.json
CHANGED
|
@@ -1816,9 +1816,6 @@
|
|
|
1816
1816
|
"description": "The vertical offset between the text and its anchor point, in pixels. Applied after the rotation by `angle`.",
|
|
1817
1817
|
"type": "number"
|
|
1818
1818
|
},
|
|
1819
|
-
"dynamicData": {
|
|
1820
|
-
"type": "boolean"
|
|
1821
|
-
},
|
|
1822
1819
|
"fill": {
|
|
1823
1820
|
"type": "string"
|
|
1824
1821
|
},
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"contributors": [],
|
|
9
9
|
"license": "BSD-2-Clause",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.21.0",
|
|
11
11
|
"main": "dist/index.js",
|
|
12
12
|
"module": "src/index.js",
|
|
13
13
|
"exports": {
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"vega-scale": "^7.1.1",
|
|
54
54
|
"vega-util": "^1.16.0"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "293e743d4b887427d8fadc49460a08a572eaff12"
|
|
57
57
|
}
|
package/src/gl/dataToVertices.js
CHANGED
|
@@ -3,19 +3,19 @@ import { format } from "d3-format";
|
|
|
3
3
|
import { isString } from "vega-util";
|
|
4
4
|
import ArrayBuilder from "./arrayBuilder";
|
|
5
5
|
import { SDF_PADDING } from "../fonts/bmFontMetrics";
|
|
6
|
-
import {
|
|
7
|
-
import createBinningRangeIndexer from "../utils/binnedRangeIndex";
|
|
6
|
+
import { createBinningRangeIndexer } from "../utils/binnedIndex";
|
|
8
7
|
import { isValueDef } from "../encoder/encoder";
|
|
9
8
|
import {
|
|
10
9
|
isHighPrecisionScale,
|
|
11
10
|
splitHighPrecision,
|
|
12
11
|
} from "../scale/glslScaleGenerator";
|
|
12
|
+
import { isContinuous } from "vega-scale";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @typedef {object} RangeEntry Represents a location of a vertex subset
|
|
16
16
|
* @prop {number} offset in vertices
|
|
17
17
|
* @prop {number} count in vertices
|
|
18
|
-
* @prop {import("../utils/
|
|
18
|
+
* @prop {import("../utils/binnedIndex").Lookup} xIndex
|
|
19
19
|
*
|
|
20
20
|
* @typedef {import("./arraybuilder").ConverterMetadata} Converter
|
|
21
21
|
* @typedef {import("../encoder/encoder").Encoder} Encoder
|
|
@@ -28,16 +28,9 @@ export class GeometryBuilder {
|
|
|
28
28
|
* @param {string[]} [object.attributes]
|
|
29
29
|
* @param {number} [object.numVertices] If the number of data items is known, a
|
|
30
30
|
* preallocated TypedArray is used
|
|
31
|
-
* @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
|
|
32
31
|
*/
|
|
33
|
-
constructor({
|
|
34
|
-
encoders,
|
|
35
|
-
numVertices = undefined,
|
|
36
|
-
attributes = [],
|
|
37
|
-
buildXIndex = false,
|
|
38
|
-
}) {
|
|
32
|
+
constructor({ encoders, numVertices = undefined, attributes = [] }) {
|
|
39
33
|
this.encoders = encoders;
|
|
40
|
-
this._buildXIndex = buildXIndex;
|
|
41
34
|
|
|
42
35
|
// Encoders for variable channels
|
|
43
36
|
this.variableEncoders = Object.fromEntries(
|
|
@@ -84,7 +77,7 @@ export class GeometryBuilder {
|
|
|
84
77
|
|
|
85
78
|
this.lastOffset = 0;
|
|
86
79
|
|
|
87
|
-
/** @type {Map<any, RangeEntry>} keep track of
|
|
80
|
+
/** @type {Map<any, RangeEntry>} keep track of facet locations within the vertex array */
|
|
88
81
|
this.rangeMap = new InternMap([], JSON.stringify);
|
|
89
82
|
}
|
|
90
83
|
|
|
@@ -121,32 +114,50 @@ export class GeometryBuilder {
|
|
|
121
114
|
* @param {object[]} data
|
|
122
115
|
*/
|
|
123
116
|
addBatch(key, data, lo = 0, hi = data.length) {
|
|
117
|
+
this.prepareXIndexer(data, lo, hi);
|
|
118
|
+
|
|
124
119
|
for (let i = lo; i < hi; i++) {
|
|
125
|
-
|
|
120
|
+
const d = data[i];
|
|
121
|
+
this.variableBuilder.pushFromDatum(d);
|
|
122
|
+
this.addToXIndex(d);
|
|
126
123
|
}
|
|
127
124
|
|
|
128
125
|
this.registerBatch(key);
|
|
129
126
|
}
|
|
130
127
|
|
|
131
128
|
/**
|
|
132
|
-
* @param {
|
|
129
|
+
* @param {import("../data/flowNode").Data} data Domain, but specified using datums
|
|
130
|
+
* @param {number} [lo]
|
|
131
|
+
* @param {number} [hi]
|
|
133
132
|
*/
|
|
134
|
-
prepareXIndexer(data) {
|
|
135
|
-
if (!
|
|
133
|
+
prepareXIndexer(data, lo = 0, hi = lo + data.length) {
|
|
134
|
+
if (!data.length || hi - lo < 0) {
|
|
135
|
+
/**
|
|
136
|
+
* @param {import("../data/flowNode").Datum} datum
|
|
137
|
+
*/
|
|
138
|
+
this.addToXIndex = (datum) => {
|
|
139
|
+
// nop
|
|
140
|
+
};
|
|
136
141
|
return;
|
|
137
142
|
}
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
const
|
|
144
|
+
/** @param {Encoder} encoder */
|
|
145
|
+
const getContinuousEncoder = (encoder) =>
|
|
146
|
+
encoder && isContinuous(encoder.scale?.type) && encoder;
|
|
147
|
+
|
|
148
|
+
const xe = getContinuousEncoder(this.variableEncoders.x);
|
|
149
|
+
const x2e = getContinuousEncoder(this.variableEncoders.x2);
|
|
141
150
|
|
|
142
|
-
if (xe
|
|
151
|
+
if (xe) {
|
|
143
152
|
const xa = xe.accessor;
|
|
144
|
-
const x2a = x2e.accessor;
|
|
153
|
+
const x2a = x2e ? x2e.accessor : xa;
|
|
145
154
|
|
|
146
|
-
this.xIndexer = createBinningRangeIndexer(
|
|
147
|
-
|
|
148
|
-
x2a(
|
|
149
|
-
|
|
155
|
+
this.xIndexer = createBinningRangeIndexer(
|
|
156
|
+
50,
|
|
157
|
+
[xa(data[lo]), x2a(data[hi - 1])],
|
|
158
|
+
xa,
|
|
159
|
+
x2a
|
|
160
|
+
);
|
|
150
161
|
|
|
151
162
|
let lastVertexCount = this.variableBuilder.vertexCount;
|
|
152
163
|
|
|
@@ -155,21 +166,16 @@ export class GeometryBuilder {
|
|
|
155
166
|
*/
|
|
156
167
|
this.addToXIndex = (datum) => {
|
|
157
168
|
let currentVertexCount = this.variableBuilder.vertexCount;
|
|
158
|
-
this.xIndexer(
|
|
159
|
-
xa(datum),
|
|
160
|
-
x2a(datum),
|
|
161
|
-
lastVertexCount,
|
|
162
|
-
currentVertexCount
|
|
163
|
-
);
|
|
169
|
+
this.xIndexer(datum, lastVertexCount, currentVertexCount);
|
|
164
170
|
lastVertexCount = currentVertexCount;
|
|
165
171
|
};
|
|
166
172
|
} else {
|
|
167
173
|
this.xIndexer = undefined;
|
|
168
174
|
/**
|
|
169
|
-
* @param {
|
|
175
|
+
* @param {import("../data/flowNode").Datum} datum
|
|
170
176
|
*/
|
|
171
177
|
this.addToXIndex = (datum) => {
|
|
172
|
-
//
|
|
178
|
+
// nop
|
|
173
179
|
};
|
|
174
180
|
}
|
|
175
181
|
}
|
|
@@ -178,7 +184,7 @@ export class GeometryBuilder {
|
|
|
178
184
|
* Add the datum to an index, which allows for efficient rendering of ranges
|
|
179
185
|
* on the x axis. Must be called after a datum has been pushed to the ArrayBuilder.
|
|
180
186
|
*
|
|
181
|
-
* @param {
|
|
187
|
+
* @param {import("../data/flowNode").Datum} datum
|
|
182
188
|
*/
|
|
183
189
|
addToXIndex(datum) {
|
|
184
190
|
//
|
|
@@ -207,7 +213,6 @@ export class RectVertexBuilder extends GeometryBuilder {
|
|
|
207
213
|
* If the rect is wider than the threshold, tessellate it into pieces
|
|
208
214
|
* @param {number[]} [object.visibleRange]
|
|
209
215
|
* @param {number} [object.numItems] Number of data items
|
|
210
|
-
* @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
|
|
211
216
|
*/
|
|
212
217
|
constructor({
|
|
213
218
|
encoders,
|
|
@@ -215,14 +220,12 @@ export class RectVertexBuilder extends GeometryBuilder {
|
|
|
215
220
|
tessellationThreshold = Infinity,
|
|
216
221
|
visibleRange = [-Infinity, Infinity],
|
|
217
222
|
numItems,
|
|
218
|
-
buildXIndex = false,
|
|
219
223
|
}) {
|
|
220
224
|
super({
|
|
221
225
|
encoders,
|
|
222
226
|
attributes,
|
|
223
227
|
numVertices:
|
|
224
228
|
tessellationThreshold == Infinity ? numItems * 6 : undefined,
|
|
225
|
-
buildXIndex,
|
|
226
229
|
});
|
|
227
230
|
|
|
228
231
|
this.visibleRange = visibleRange;
|
|
@@ -256,7 +259,7 @@ export class RectVertexBuilder extends GeometryBuilder {
|
|
|
256
259
|
const xAccessor = a(e.x);
|
|
257
260
|
const x2Accessor = a(e.x2);
|
|
258
261
|
|
|
259
|
-
this.prepareXIndexer(data);
|
|
262
|
+
this.prepareXIndexer(data, lo, hi);
|
|
260
263
|
|
|
261
264
|
const frac = [0, 0];
|
|
262
265
|
this.updateFrac(frac);
|
|
@@ -322,7 +325,6 @@ export class RuleVertexBuilder extends GeometryBuilder {
|
|
|
322
325
|
* If the rule is wider than the threshold, tessellate it into pieces
|
|
323
326
|
* @param {number[]} [object.visibleRange]
|
|
324
327
|
* @param {number} [object.numItems] Number of data items
|
|
325
|
-
* @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
|
|
326
328
|
*/
|
|
327
329
|
constructor({
|
|
328
330
|
encoders,
|
|
@@ -330,14 +332,12 @@ export class RuleVertexBuilder extends GeometryBuilder {
|
|
|
330
332
|
tessellationThreshold = Infinity,
|
|
331
333
|
visibleRange = [-Infinity, Infinity],
|
|
332
334
|
numItems,
|
|
333
|
-
buildXIndex,
|
|
334
335
|
}) {
|
|
335
336
|
super({
|
|
336
337
|
encoders,
|
|
337
338
|
attributes,
|
|
338
339
|
numVertices:
|
|
339
340
|
tessellationThreshold == Infinity ? numItems * 6 : undefined,
|
|
340
|
-
buildXIndex,
|
|
341
341
|
});
|
|
342
342
|
|
|
343
343
|
this.visibleRange = visibleRange;
|
|
@@ -357,7 +357,7 @@ export class RuleVertexBuilder extends GeometryBuilder {
|
|
|
357
357
|
addBatch(key, data, lo = 0, hi = data.length) {
|
|
358
358
|
//const [lower, upper] = this.visibleRange; // TODO
|
|
359
359
|
|
|
360
|
-
this.prepareXIndexer(data);
|
|
360
|
+
this.prepareXIndexer(data, lo, hi);
|
|
361
361
|
|
|
362
362
|
for (let i = lo; i < hi; i++) {
|
|
363
363
|
const d = data[i];
|
|
@@ -443,7 +443,6 @@ export class TextVertexBuilder extends GeometryBuilder {
|
|
|
443
443
|
* @param {import("../fonts/bmFontMetrics").BMFontMetrics} object.fontMetrics
|
|
444
444
|
* @param {Record<string, any>} object.properties
|
|
445
445
|
* @param {number} [object.numCharacters] number of characters
|
|
446
|
-
* @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
|
|
447
446
|
* @param {boolean} [object.logoLetters]
|
|
448
447
|
*/
|
|
449
448
|
constructor({
|
|
@@ -452,13 +451,11 @@ export class TextVertexBuilder extends GeometryBuilder {
|
|
|
452
451
|
fontMetrics,
|
|
453
452
|
properties,
|
|
454
453
|
numCharacters = undefined,
|
|
455
|
-
buildXIndex = false,
|
|
456
454
|
}) {
|
|
457
455
|
super({
|
|
458
456
|
encoders,
|
|
459
457
|
attributes,
|
|
460
458
|
numVertices: numCharacters * 6, // six vertices per quad (character)
|
|
461
|
-
buildXIndex,
|
|
462
459
|
});
|
|
463
460
|
|
|
464
461
|
this.metadata = fontMetrics;
|
|
@@ -524,7 +521,7 @@ export class TextVertexBuilder extends GeometryBuilder {
|
|
|
524
521
|
const textureCoord = [0, 0];
|
|
525
522
|
this.updateTextureCoord(textureCoord);
|
|
526
523
|
|
|
527
|
-
this.prepareXIndexer(data);
|
|
524
|
+
this.prepareXIndexer(data, lo, hi);
|
|
528
525
|
|
|
529
526
|
for (let i = lo; i < hi; i++) {
|
|
530
527
|
const d = data[i];
|
|
@@ -624,7 +621,7 @@ export class TextVertexBuilder extends GeometryBuilder {
|
|
|
624
621
|
x += advance;
|
|
625
622
|
}
|
|
626
623
|
|
|
627
|
-
this.addToXIndex(
|
|
624
|
+
this.addToXIndex(d);
|
|
628
625
|
}
|
|
629
626
|
|
|
630
627
|
this.registerBatch(key);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#define PI 3.141593
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
uniform View {
|
|
4
|
+
/** Offset in "unit" units */
|
|
5
|
+
mediump vec2 uViewOffset;
|
|
6
|
+
mediump vec2 uViewScale;
|
|
7
|
+
/** Size of the logical viewport in pixels, i.e., the view */
|
|
8
|
+
mediump vec2 uViewportSize;
|
|
9
|
+
lowp float uDevicePixelRatio;
|
|
10
|
+
// TODO: Views with opacity less than 1.0 should be rendered into a texture
|
|
11
|
+
// that is rendered with the specified opacity.
|
|
12
|
+
lowp float uViewOpacity;
|
|
13
|
+
bool uPickingEnabled;
|
|
14
|
+
};
|
|
5
15
|
|
|
6
|
-
uniform mediump vec2 uViewScale;
|
|
7
|
-
|
|
8
|
-
/** Size of the logical viewport in pixels, i.e., the view */
|
|
9
|
-
uniform mediump 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
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Maps a coordinate on the unit scale to a normalized device coordinate.
|
package/src/marks/link.js
CHANGED
|
@@ -104,7 +104,7 @@ export default class LinkMark extends Mark {
|
|
|
104
104
|
numComponents: 2,
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
this.rangeMap
|
|
107
|
+
this.rangeMap.migrateEntries(vertexData.rangeMap);
|
|
108
108
|
|
|
109
109
|
this.arrays = Object.fromEntries(
|
|
110
110
|
Object.entries(vertexData.arrays).map(([k, v]) => [
|
|
@@ -124,45 +124,38 @@ export default class LinkMark extends Mark {
|
|
|
124
124
|
|
|
125
125
|
// TODO: Vertical clipping in faceted view
|
|
126
126
|
|
|
127
|
-
return this.createRenderCallback(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.arrays[attribute].numComponents
|
|
147
|
-
) {
|
|
148
|
-
attribInfo.offset =
|
|
149
|
-
offset * this.arrays[attribute].numComponents * 4; // gl.FLOAT in bytes
|
|
150
|
-
}
|
|
127
|
+
return this.createRenderCallback((offset, count) => {
|
|
128
|
+
// We are using instanced drawing here.
|
|
129
|
+
// However, WebGL does not provide glDrawArraysInstancedBaseInstance and thus,
|
|
130
|
+
// we have to hack with offsets in vertexAttribPointer
|
|
131
|
+
// TODO: Use VAOs more intelligently to reduce WebGL calls
|
|
132
|
+
// TODO: Explore multiDrawArraysInstancedWEBGL
|
|
133
|
+
// There's also a promising extension draft:
|
|
134
|
+
// https://www.khronos.org/registry/webgl/extensions/WEBGL_draw_instanced_base_vertex_base_instance/
|
|
135
|
+
// (and https://www.khronos.org/registry/webgl/extensions/WEBGL_multi_draw_instanced_base_vertex_base_instance/)
|
|
136
|
+
|
|
137
|
+
this.gl.bindVertexArray(this.vertexArrayInfo.vertexArrayObject);
|
|
138
|
+
|
|
139
|
+
for (const attribInfoObject of Object.entries(
|
|
140
|
+
this.bufferInfo.attribs
|
|
141
|
+
)) {
|
|
142
|
+
const [attribute, attribInfo] = attribInfoObject;
|
|
143
|
+
if (attribInfo.buffer && this.arrays[attribute].numComponents) {
|
|
144
|
+
attribInfo.offset =
|
|
145
|
+
offset * this.arrays[attribute].numComponents * 4; // gl.FLOAT in bytes
|
|
151
146
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
() => this.rangeMap
|
|
165
|
-
);
|
|
147
|
+
}
|
|
148
|
+
setBuffersAndAttributes(gl, this.programInfo, this.bufferInfo);
|
|
149
|
+
|
|
150
|
+
drawBufferInfo(
|
|
151
|
+
gl,
|
|
152
|
+
this.bufferInfo,
|
|
153
|
+
gl.TRIANGLE_STRIP,
|
|
154
|
+
(this.properties.segments + 1) * 2, // number of vertices in a triangle strip
|
|
155
|
+
0,
|
|
156
|
+
count
|
|
157
|
+
);
|
|
158
|
+
}, options);
|
|
166
159
|
}
|
|
167
160
|
}
|
|
168
161
|
|