@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/src/marks/rule.js
CHANGED
|
@@ -139,6 +139,20 @@ export default class RuleMark extends Mark {
|
|
|
139
139
|
this.createAndLinkShaders(VERTEX_SHADER, FRAGMENT_SHADER);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
finalizeGraphicsInitialization() {
|
|
143
|
+
super.finalizeGraphicsInitialization();
|
|
144
|
+
|
|
145
|
+
this.gl.useProgram(this.programInfo.program);
|
|
146
|
+
|
|
147
|
+
const props = this.properties;
|
|
148
|
+
|
|
149
|
+
setUniforms(this.programInfo, {
|
|
150
|
+
uMinLength: props.minLength,
|
|
151
|
+
uDashTextureSize: this.dashTextureSize,
|
|
152
|
+
uStrokeCap: ["butt", "square", "round"].indexOf(props.strokeCap),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
142
156
|
updateGraphicsData() {
|
|
143
157
|
const collector = this.unitView.getCollector();
|
|
144
158
|
const itemCount = collector.getItemCount();
|
|
@@ -147,13 +161,12 @@ export default class RuleMark extends Mark {
|
|
|
147
161
|
encoders: this.encoders,
|
|
148
162
|
attributes: this.getAttributes(),
|
|
149
163
|
numItems: Math.max(itemCount, this.properties.minBufferSize || 0),
|
|
150
|
-
buildXIndex: this.properties.buildIndex,
|
|
151
164
|
});
|
|
152
165
|
|
|
153
166
|
builder.addBatches(collector.facetBatches);
|
|
154
167
|
|
|
155
168
|
const vertexData = builder.toArrays();
|
|
156
|
-
this.rangeMap
|
|
169
|
+
this.rangeMap.migrateEntries(vertexData.rangeMap);
|
|
157
170
|
|
|
158
171
|
this.updateBufferInfo(vertexData);
|
|
159
172
|
}
|
|
@@ -162,28 +175,26 @@ export default class RuleMark extends Mark {
|
|
|
162
175
|
* @param {import("../view/rendering").GlobalRenderingOptions} options
|
|
163
176
|
*/
|
|
164
177
|
prepareRender(options) {
|
|
165
|
-
super.prepareRender(options);
|
|
166
|
-
|
|
167
|
-
setUniforms(this.programInfo, {
|
|
168
|
-
uMinLength: this.properties.minLength,
|
|
169
|
-
uDashTextureSize: this.dashTextureSize,
|
|
170
|
-
uStrokeCap: ["butt", "square", "round"].indexOf(
|
|
171
|
-
this.properties.strokeCap
|
|
172
|
-
),
|
|
173
|
-
});
|
|
178
|
+
const ops = super.prepareRender(options);
|
|
174
179
|
|
|
175
180
|
if (this.dashTexture) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
181
|
+
ops.push(() =>
|
|
182
|
+
setUniforms(this.programInfo, {
|
|
183
|
+
uDashTexture: this.dashTexture,
|
|
184
|
+
uStrokeDashOffset: this.properties.strokeDashOffset,
|
|
185
|
+
})
|
|
186
|
+
);
|
|
180
187
|
}
|
|
181
188
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
ops.push(() =>
|
|
190
|
+
setBuffersAndAttributes(
|
|
191
|
+
this.gl,
|
|
192
|
+
this.programInfo,
|
|
193
|
+
this.vertexArrayInfo
|
|
194
|
+
)
|
|
186
195
|
);
|
|
196
|
+
|
|
197
|
+
return ops;
|
|
187
198
|
}
|
|
188
199
|
|
|
189
200
|
/**
|
|
@@ -201,8 +212,7 @@ export default class RuleMark extends Mark {
|
|
|
201
212
|
count,
|
|
202
213
|
offset
|
|
203
214
|
),
|
|
204
|
-
options
|
|
205
|
-
() => this.rangeMap
|
|
215
|
+
options
|
|
206
216
|
);
|
|
207
217
|
}
|
|
208
218
|
}
|
package/src/marks/text.js
CHANGED
|
@@ -214,13 +214,12 @@ export default class TextMark extends Mark {
|
|
|
214
214
|
charCount,
|
|
215
215
|
this.properties.minBufferSize || 0
|
|
216
216
|
),
|
|
217
|
-
buildXIndex: this.properties.buildIndex,
|
|
218
217
|
});
|
|
219
218
|
|
|
220
219
|
builder.addBatches(collector.facetBatches);
|
|
221
220
|
|
|
222
221
|
const vertexData = builder.toArrays();
|
|
223
|
-
this.rangeMap
|
|
222
|
+
this.rangeMap.migrateEntries(vertexData.rangeMap);
|
|
224
223
|
|
|
225
224
|
this.updateBufferInfo(vertexData);
|
|
226
225
|
}
|
|
@@ -229,7 +228,7 @@ export default class TextMark extends Mark {
|
|
|
229
228
|
* @param {import("../view/rendering").GlobalRenderingOptions} options
|
|
230
229
|
*/
|
|
231
230
|
prepareRender(options) {
|
|
232
|
-
super.prepareRender(options);
|
|
231
|
+
const ops = super.prepareRender(options);
|
|
233
232
|
|
|
234
233
|
let q = 0.35; // TODO: Ensure that this makes sense. Now chosen by trial & error
|
|
235
234
|
if (this.properties.logoLetters) {
|
|
@@ -239,17 +238,23 @@ export default class TextMark extends Mark {
|
|
|
239
238
|
q /= 2;
|
|
240
239
|
}
|
|
241
240
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
241
|
+
ops.push(() =>
|
|
242
|
+
setUniforms(this.programInfo, {
|
|
243
|
+
uTexture: this.font.texture,
|
|
244
|
+
uSdfNumerator:
|
|
245
|
+
this.font.metrics.common.base / (this.glHelper.dpr / q),
|
|
246
|
+
})
|
|
247
|
+
);
|
|
247
248
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
ops.push(() =>
|
|
250
|
+
setBuffersAndAttributes(
|
|
251
|
+
this.gl,
|
|
252
|
+
this.programInfo,
|
|
253
|
+
this.vertexArrayInfo
|
|
254
|
+
)
|
|
252
255
|
);
|
|
256
|
+
|
|
257
|
+
return ops;
|
|
253
258
|
}
|
|
254
259
|
|
|
255
260
|
/**
|
|
@@ -267,8 +272,7 @@ export default class TextMark extends Mark {
|
|
|
267
272
|
count,
|
|
268
273
|
offset
|
|
269
274
|
),
|
|
270
|
-
options
|
|
271
|
-
() => this.rangeMap
|
|
275
|
+
options
|
|
272
276
|
);
|
|
273
277
|
}
|
|
274
278
|
}
|
package/src/spec/mark.d.ts
CHANGED
|
@@ -407,9 +407,6 @@ export interface MarkConfig
|
|
|
407
407
|
*/
|
|
408
408
|
strokeWidth?: number;
|
|
409
409
|
|
|
410
|
-
// TODO: get rid of this
|
|
411
|
-
dynamicData?: boolean;
|
|
412
|
-
|
|
413
410
|
/**
|
|
414
411
|
* Minimum size for WebGL buffers (number of data items).
|
|
415
412
|
* Allows for using `bufferSubData()` to update graphics.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import clamp from "./clamp";
|
|
2
|
+
|
|
3
|
+
const MAX_INTEGER = 2 ** 31 - 1;
|
|
4
|
+
const MIN_INTEGER = -(2 ** 31);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @callback Lookup
|
|
8
|
+
* @param {number} start
|
|
9
|
+
* @param {number} end
|
|
10
|
+
* @param {[number, number]} [arr] Store the result into this array (and return it)
|
|
11
|
+
* @returns {[number, number]}
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A binned index for (overlapping) ranges that are sorted by their start position.
|
|
16
|
+
*
|
|
17
|
+
* @param {number} size Number of bins
|
|
18
|
+
* @param {[number, number]} domain
|
|
19
|
+
* @param {(datum: T) => number} accessor
|
|
20
|
+
* @param {(datum: T) => number} [accessor2]
|
|
21
|
+
* @template T
|
|
22
|
+
*/
|
|
23
|
+
export function createBinningRangeIndexer(
|
|
24
|
+
size,
|
|
25
|
+
domain,
|
|
26
|
+
accessor,
|
|
27
|
+
accessor2 = accessor
|
|
28
|
+
) {
|
|
29
|
+
const startIndices = new Int32Array(size);
|
|
30
|
+
startIndices.fill(MAX_INTEGER);
|
|
31
|
+
|
|
32
|
+
let lastIndex = MIN_INTEGER;
|
|
33
|
+
let unordered = false;
|
|
34
|
+
|
|
35
|
+
const endIndices = new Int32Array(size);
|
|
36
|
+
|
|
37
|
+
const start = domain[0];
|
|
38
|
+
const domainLength = domain[1] - domain[0];
|
|
39
|
+
const divisor = domainLength / size;
|
|
40
|
+
|
|
41
|
+
/** @param {number} pos */
|
|
42
|
+
const getBin = (pos) =>
|
|
43
|
+
clamp(Math.floor((pos - start) / divisor), 0, size - 1);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param {T} datum
|
|
48
|
+
* @param {number} startIndex
|
|
49
|
+
* @param {number} endIndex
|
|
50
|
+
*/
|
|
51
|
+
function binningIndexer(datum, startIndex, endIndex) {
|
|
52
|
+
if (startIndex > lastIndex) {
|
|
53
|
+
lastIndex = startIndex;
|
|
54
|
+
} else if (!unordered) {
|
|
55
|
+
unordered = true;
|
|
56
|
+
// TODO: Contextual info like view path
|
|
57
|
+
console.debug(
|
|
58
|
+
"Items are not ordered properly. Disabling binned index."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const value = accessor(datum);
|
|
63
|
+
const bin = getBin(value);
|
|
64
|
+
|
|
65
|
+
if (startIndices[bin] > startIndex) {
|
|
66
|
+
startIndices[bin] = startIndex;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (endIndices[bin] < endIndex) {
|
|
70
|
+
endIndices[bin] = endIndex;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
*
|
|
76
|
+
* @param {T} datum
|
|
77
|
+
* @param {number} startIndex
|
|
78
|
+
* @param {number} endIndex
|
|
79
|
+
*/
|
|
80
|
+
function binningRangeIndexer(datum, startIndex, endIndex) {
|
|
81
|
+
if (startIndex > lastIndex) {
|
|
82
|
+
lastIndex = startIndex;
|
|
83
|
+
} else if (!unordered) {
|
|
84
|
+
unordered = true;
|
|
85
|
+
// TODO: Contextual info like view path
|
|
86
|
+
console.debug(
|
|
87
|
+
"Items are not ordered properly. Disabling binned index."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const start = accessor(datum);
|
|
92
|
+
const end = accessor2(datum);
|
|
93
|
+
const startBin = getBin(start);
|
|
94
|
+
const endBin = getBin(end);
|
|
95
|
+
|
|
96
|
+
// TODO: This loop could probably be done as a more efficient post processing
|
|
97
|
+
// step.
|
|
98
|
+
for (let bin = startBin; bin <= endBin; bin++) {
|
|
99
|
+
if (startIndices[bin] > startIndex) {
|
|
100
|
+
startIndices[bin] = startIndex;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (endIndices[bin] < endIndex) {
|
|
104
|
+
endIndices[bin] = endIndex;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @type {Lookup}
|
|
111
|
+
*/
|
|
112
|
+
const lookup = (start, end, arr = [0, 0]) => {
|
|
113
|
+
arr[0] = startIndices[getBin(start)];
|
|
114
|
+
arr[1] = Math.max(endIndices[getBin(end)], arr[0]);
|
|
115
|
+
return arr;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const getIndex = () => {
|
|
119
|
+
for (let i = 1; i < endIndices.length; i++) {
|
|
120
|
+
if (endIndices[i] < endIndices[i - 1]) {
|
|
121
|
+
endIndices[i] = endIndices[i - 1];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let tail = true;
|
|
126
|
+
|
|
127
|
+
for (let i = startIndices.length - 1; i > 0; i--) {
|
|
128
|
+
if (tail && startIndices[i] == MAX_INTEGER) {
|
|
129
|
+
startIndices[i] = endIndices[i];
|
|
130
|
+
tail = false;
|
|
131
|
+
} else if (startIndices[i - 1] > startIndices[i]) {
|
|
132
|
+
startIndices[i - 1] = startIndices[i];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return lookup;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
binningIndexer.getIndex = getIndex;
|
|
140
|
+
binningRangeIndexer.getIndex = getIndex;
|
|
141
|
+
|
|
142
|
+
if (unordered) {
|
|
143
|
+
return undefined;
|
|
144
|
+
} else {
|
|
145
|
+
return accessor == accessor2 ? binningIndexer : binningRangeIndexer;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { createBinningRangeIndexer } from "./binnedIndex.js";
|
|
3
|
+
|
|
4
|
+
describe("Binning Indexer", () => {
|
|
5
|
+
test("Points are binned correctly", () => {
|
|
6
|
+
const items = [0, 1, 4, 10, 35, 36, 80];
|
|
7
|
+
const indexer = createBinningRangeIndexer(10, [0, 100], (x) => x);
|
|
8
|
+
|
|
9
|
+
items.forEach((i) => indexer(i, i, i + 1));
|
|
10
|
+
|
|
11
|
+
const index = indexer.getIndex();
|
|
12
|
+
|
|
13
|
+
expect(index(0, 1)).toEqual([0, 5]);
|
|
14
|
+
expect(index(1, 2)).toEqual([0, 5]);
|
|
15
|
+
expect(index(1, 15)).toEqual([0, 11]);
|
|
16
|
+
expect(index(10, 15)).toEqual([10, 11]);
|
|
17
|
+
expect(index(11, 38)).toEqual([10, 37]);
|
|
18
|
+
expect(index(11, 45)).toEqual([10, 37]);
|
|
19
|
+
expect(index(40, 85)).toEqual([80, 81]);
|
|
20
|
+
expect(index(90, 100)).toEqual([81, 81]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("Non-overlapping ranges are binned correctly", () => {
|
|
24
|
+
const items = [
|
|
25
|
+
[0, 5],
|
|
26
|
+
[25, 50],
|
|
27
|
+
[50, 55],
|
|
28
|
+
];
|
|
29
|
+
const indexer = createBinningRangeIndexer(
|
|
30
|
+
10,
|
|
31
|
+
[0, 100],
|
|
32
|
+
(x) => x[0],
|
|
33
|
+
(x) => x[1]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
items.forEach((x) => indexer(x, x[0], x[1]));
|
|
37
|
+
|
|
38
|
+
const index = indexer.getIndex();
|
|
39
|
+
|
|
40
|
+
// TODO: More tests. Doesn't work 100%
|
|
41
|
+
|
|
42
|
+
expect(index(0, 1)).toEqual([0, 5]);
|
|
43
|
+
expect(index(3, 40)).toEqual([0, 50]);
|
|
44
|
+
expect(index(6, 40)).toEqual([0, 50]);
|
|
45
|
+
// fails: expect(index(50, 57)).toEqual([50, 55]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("Overlapping ranges are binned correctly", () => {
|
|
49
|
+
const items = [
|
|
50
|
+
[10, 30],
|
|
51
|
+
[25, 50],
|
|
52
|
+
];
|
|
53
|
+
const indexer = createBinningRangeIndexer(
|
|
54
|
+
10,
|
|
55
|
+
[0, 100],
|
|
56
|
+
(x) => x[0],
|
|
57
|
+
(x) => x[1]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
items.forEach((x) => indexer(x, x[0], x[1]));
|
|
61
|
+
|
|
62
|
+
const index = indexer.getIndex();
|
|
63
|
+
|
|
64
|
+
// TODO: More tests. Doesn't work 100%
|
|
65
|
+
|
|
66
|
+
expect(index(0, 5)).toEqual([10, 10]);
|
|
67
|
+
expect(index(0, 15)).toEqual([10, 30]);
|
|
68
|
+
expect(index(27, 40)).toEqual([10, 50]);
|
|
69
|
+
expect(index(40, 50)).toEqual([25, 50]);
|
|
70
|
+
expect(index(40, 80)).toEqual([25, 50]);
|
|
71
|
+
expect(index(10, 29)).toEqual([10, 50]);
|
|
72
|
+
});
|
|
73
|
+
});
|
package/src/view/axisView.js
CHANGED
|
@@ -429,7 +429,6 @@ function createAxis(axisProps) {
|
|
|
429
429
|
size: ap.labelFontSize,
|
|
430
430
|
color: ap.labelColor,
|
|
431
431
|
minBufferSize: 1500, // to prevent GPU buffer reallocation when zooming
|
|
432
|
-
dynamicData: true,
|
|
433
432
|
},
|
|
434
433
|
encoding: {
|
|
435
434
|
[main]: { field: "value", type: "quantitative" },
|
|
@@ -450,7 +449,6 @@ function createAxis(axisProps) {
|
|
|
450
449
|
color: ap.tickColor,
|
|
451
450
|
size: ap.tickWidth,
|
|
452
451
|
minBufferSize: 300,
|
|
453
|
-
dynamicData: true,
|
|
454
452
|
},
|
|
455
453
|
encoding: {
|
|
456
454
|
[secondary]: { value: anchor },
|
|
@@ -576,7 +574,6 @@ export function createGenomeAxis(axisProps) {
|
|
|
576
574
|
anchor - (ap.chromTickSize / ap.extent) * (anchor ? 1 : -1),
|
|
577
575
|
color: axisProps.chromTickColor,
|
|
578
576
|
size: ap.chromTickWidth,
|
|
579
|
-
dynamicData: true,
|
|
580
577
|
},
|
|
581
578
|
});
|
|
582
579
|
|
|
@@ -649,7 +646,6 @@ export function createGenomeAxis(axisProps) {
|
|
|
649
646
|
align: axisProps.chromLabelAlign,
|
|
650
647
|
baseline: "alphabetic",
|
|
651
648
|
clip: false,
|
|
652
|
-
dynamicData: true,
|
|
653
649
|
...chromLabelMarkProps,
|
|
654
650
|
},
|
|
655
651
|
encoding: {
|
|
@@ -146,7 +146,9 @@ export default class DeferredViewRenderingContext extends ViewRenderingContext {
|
|
|
146
146
|
});
|
|
147
147
|
// Change program, set common uniforms (mark properties, shared domains)
|
|
148
148
|
this.batch.push(
|
|
149
|
-
|
|
149
|
+
...mark
|
|
150
|
+
.prepareRender(this.globalOptions)
|
|
151
|
+
.map((op) => ifEnabled(op))
|
|
150
152
|
);
|
|
151
153
|
|
|
152
154
|
/** @type {import("../../utils/layout/rectangle").default} */
|
|
@@ -55,7 +55,9 @@ export default class SimpleViewRenderingContext extends ViewRenderingContext {
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
mark.prepareRender(this.globalOptions)
|
|
58
|
+
for (const op of mark.prepareRender(this.globalOptions)) {
|
|
59
|
+
op();
|
|
60
|
+
}
|
|
59
61
|
mark.setViewport(this.coords, options.clipRect);
|
|
60
62
|
mark.render(options)();
|
|
61
63
|
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import clamp from "./clamp";
|
|
2
|
-
|
|
3
|
-
const MAX_INTEGER = 2 ** 31 - 1;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @callback Lookup
|
|
7
|
-
* @param {number} start
|
|
8
|
-
* @param {number} end
|
|
9
|
-
* @returns {[number, number]}
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A binned index for (overlapping) ranges that are sorted by their start position.
|
|
14
|
-
* Allows for indexing vertices of mark instances.
|
|
15
|
-
*
|
|
16
|
-
* @param {number} size Number of bins
|
|
17
|
-
* @param {[number, number]} domain
|
|
18
|
-
*/
|
|
19
|
-
export default function createBinningRangeIndexer(size, domain) {
|
|
20
|
-
const startIndices = new Int32Array(size);
|
|
21
|
-
startIndices.fill(MAX_INTEGER);
|
|
22
|
-
|
|
23
|
-
const endIndices = new Int32Array(size);
|
|
24
|
-
|
|
25
|
-
const start = domain[0];
|
|
26
|
-
const domainLength = domain[1] - domain[0];
|
|
27
|
-
const divisor = domainLength / size;
|
|
28
|
-
|
|
29
|
-
/** @param {number} pos */
|
|
30
|
-
const getBin = (pos) =>
|
|
31
|
-
clamp(Math.floor((pos - start) / divisor), 0, size - 1);
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
*
|
|
35
|
-
* @param {number} start
|
|
36
|
-
* @param {number} end
|
|
37
|
-
* @param {number} startIndex
|
|
38
|
-
* @param {number} endIndex
|
|
39
|
-
*/
|
|
40
|
-
const indexer = (start, end, startIndex, endIndex) => {
|
|
41
|
-
const startBin = getBin(start);
|
|
42
|
-
const endBin = getBin(end);
|
|
43
|
-
|
|
44
|
-
// TODO: This loop could probably be done as a more efficient post processing
|
|
45
|
-
// step.
|
|
46
|
-
for (let bin = startBin; bin <= endBin; bin++) {
|
|
47
|
-
if (startIndices[bin] > startIndex) {
|
|
48
|
-
startIndices[bin] = startIndex;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (endIndices[bin] < endIndex) {
|
|
52
|
-
endIndices[bin] = endIndex;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @type {Lookup}
|
|
59
|
-
*/
|
|
60
|
-
const lookup = (start, end) => [
|
|
61
|
-
startIndices[getBin(start)],
|
|
62
|
-
endIndices[getBin(end)],
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
const getIndex = () => {
|
|
66
|
-
for (let i = 1; i < endIndices.length; i++) {
|
|
67
|
-
if (endIndices[i] < endIndices[i - 1]) {
|
|
68
|
-
endIndices[i] = endIndices[i - 1];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
for (let i = endIndices.length - 1; i > 0; i--) {
|
|
72
|
-
if (endIndices[i - 1] > endIndices[i]) {
|
|
73
|
-
endIndices[i - 1] = endIndices[i];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return lookup;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
indexer.getIndex = getIndex;
|
|
81
|
-
|
|
82
|
-
return indexer;
|
|
83
|
-
}
|