@genome-spy/core 0.19.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.
Files changed (101) hide show
  1. package/dist/index.js +46 -119
  2. package/dist/schema.json +213 -25
  3. package/package.json +4 -3
  4. package/src/data/collector.test.js +2 -0
  5. package/src/data/dataFlow.test.js +2 -0
  6. package/src/data/flow.test.js +1 -0
  7. package/src/data/flowNode.test.js +1 -0
  8. package/src/data/flowOptimizer.test.js +1 -0
  9. package/src/data/formats/fasta.test.js +1 -0
  10. package/src/data/sources/inlineSource.test.js +1 -0
  11. package/src/data/sources/sequenceSource.test.js +1 -0
  12. package/src/data/transforms/clone.test.js +1 -0
  13. package/src/data/transforms/coverage.test.js +1 -0
  14. package/src/data/transforms/filter.test.js +1 -0
  15. package/src/data/transforms/flattenDelimited.test.js +1 -0
  16. package/src/data/transforms/flattenSequence.test.js +1 -0
  17. package/src/data/transforms/formula.test.js +1 -0
  18. package/src/data/transforms/identifier.test.js +1 -0
  19. package/src/data/transforms/pileup.test.js +1 -0
  20. package/src/data/transforms/project.test.js +1 -0
  21. package/src/data/transforms/regexExtract.test.js +1 -0
  22. package/src/data/transforms/regexFold.test.js +1 -0
  23. package/src/data/transforms/sample.test.js +1 -0
  24. package/src/data/transforms/stack.test.js +1 -0
  25. package/src/encoder/accessor.test.js +1 -0
  26. package/src/encoder/encoder.test.js +1 -0
  27. package/src/genome/genome.test.js +1 -0
  28. package/src/genome/scaleIndex.js +3 -2
  29. package/src/genome/scaleIndex.test.js +23 -6
  30. package/src/genome/scaleLocus.test.js +1 -0
  31. package/src/genomeSpy.js +16 -11
  32. package/src/gl/dataToVertices.js +52 -52
  33. package/src/gl/includes/common.glsl +12 -12
  34. package/src/gl/includes/picking.fragment.glsl +0 -2
  35. package/src/gl/includes/picking.vertex.glsl +0 -2
  36. package/src/gl/includes/scales.glsl +33 -2
  37. package/src/gl/point.vertex.glsl +0 -2
  38. package/src/gl/rule.vertex.glsl +1 -1
  39. package/src/gl/webGLHelper.js +0 -3
  40. package/src/marks/link.js +32 -39
  41. package/src/marks/mark.js +176 -106
  42. package/src/marks/pointMark.js +28 -59
  43. package/src/marks/rectMark.js +38 -33
  44. package/src/marks/rule.js +31 -21
  45. package/src/marks/text.js +18 -14
  46. package/src/scale/glslScaleGenerator.js +56 -17
  47. package/src/scale/scale.test.js +1 -0
  48. package/src/scale/ticks.test.js +1 -0
  49. package/src/spec/mark.d.ts +0 -3
  50. package/src/spec/scale.d.ts +0 -9
  51. package/src/spec/title.d.ts +102 -0
  52. package/src/spec/view.d.ts +6 -4
  53. package/src/tooltip/dataTooltipHandler.js +3 -2
  54. package/src/utils/addBaseUrl.test.js +1 -0
  55. package/src/utils/binnedIndex.js +147 -0
  56. package/src/utils/binnedIndex.test.js +73 -0
  57. package/src/utils/cloner.test.js +1 -0
  58. package/src/utils/coalesce.test.js +1 -0
  59. package/src/utils/concatIterables.test.js +1 -0
  60. package/src/utils/domainArray.test.js +1 -0
  61. package/src/utils/indexer.test.js +1 -0
  62. package/src/utils/iterateNestedMaps.test.js +1 -0
  63. package/src/utils/kWayMerge.test.js +1 -0
  64. package/src/utils/layout/flexLayout.js +35 -3
  65. package/src/utils/layout/flexLayout.test.js +15 -0
  66. package/src/utils/layout/grid.js +95 -0
  67. package/src/utils/layout/grid.test.js +71 -0
  68. package/src/utils/layout/padding.js +13 -0
  69. package/src/utils/layout/rectangle.js +6 -0
  70. package/src/utils/layout/rectangle.test.js +1 -0
  71. package/src/utils/mergeObjects.test.js +1 -0
  72. package/src/utils/numberExtractor.test.js +1 -0
  73. package/src/utils/propertyCacher.test.js +1 -0
  74. package/src/utils/propertyCoalescer.test.js +1 -0
  75. package/src/utils/reservationMap.test.js +1 -0
  76. package/src/utils/topK.test.js +1 -0
  77. package/src/utils/variableTools.test.js +1 -0
  78. package/src/view/axisResolution.test.js +1 -0
  79. package/src/view/axisView.js +3 -5
  80. package/src/view/concatView.js +24 -275
  81. package/src/view/flowBuilder.test.js +1 -0
  82. package/src/view/gridView.js +774 -0
  83. package/src/view/implicitRootView.js +14 -0
  84. package/src/view/layerView.js +15 -1
  85. package/src/view/renderingContext/deferredViewRenderingContext.js +3 -1
  86. package/src/view/renderingContext/simpleViewRenderingContext.js +3 -1
  87. package/src/view/scaleResolution.js +5 -11
  88. package/src/view/scaleResolution.test.js +1 -0
  89. package/src/view/title.js +165 -0
  90. package/src/view/unitView.js +9 -5
  91. package/src/view/view.js +35 -14
  92. package/src/view/view.test.js +1 -0
  93. package/src/view/viewContext.d.ts +6 -1
  94. package/src/view/viewFactory.test.js +1 -0
  95. package/src/view/viewUtils.js +1 -93
  96. package/src/view/zoom.js +89 -0
  97. package/src/gl/includes/fp64-arithmetic.glsl +0 -187
  98. package/src/gl/includes/fp64-utils.js +0 -142
  99. package/src/gl/includes/scales_fp64.glsl +0 -30
  100. package/src/utils/binnedRangeIndex.js +0 -83
  101. package/src/view/decoratorView.js +0 -513
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 = vertexData.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
- setUniforms(this.programInfo, {
177
- uDashTexture: this.dashTexture,
178
- uStrokeDashOffset: this.properties.strokeDashOffset,
179
- });
181
+ ops.push(() =>
182
+ setUniforms(this.programInfo, {
183
+ uDashTexture: this.dashTexture,
184
+ uStrokeDashOffset: this.properties.strokeDashOffset,
185
+ })
186
+ );
180
187
  }
181
188
 
182
- setBuffersAndAttributes(
183
- this.gl,
184
- this.programInfo,
185
- this.vertexArrayInfo
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 = vertexData.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
- setUniforms(this.programInfo, {
243
- uTexture: this.font.texture,
244
- uSdfNumerator:
245
- this.font.metrics.common.base / (this.glHelper.dpr / q),
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
- setBuffersAndAttributes(
249
- this.gl,
250
- this.programInfo,
251
- this.vertexArrayInfo
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
  }
@@ -109,8 +109,8 @@ export function generateScaleGlsl(channel, scale, channelDef) {
109
109
  const domainUniformName = DOMAIN_PREFIX + primary;
110
110
  const rangeName = RANGE_PREFIX + primary;
111
111
 
112
- const fp64 = !!scale.fp64;
113
- const attributeType = fp64 ? "vec2" : "float";
112
+ const hp = isHighPrecisionScale(scale.type);
113
+ const attributeType = hp ? "vec2" : "float";
114
114
 
115
115
  const domainLength = scale.domain ? scale.domain().length : undefined;
116
116
 
@@ -127,9 +127,6 @@ export function generateScaleGlsl(channel, scale, channelDef) {
127
127
  glsl.push("");
128
128
 
129
129
  glsl.push(`#define ${channel}_DEFINED`);
130
- if (fp64) {
131
- glsl.push(`#define ${channel}_FP64`);
132
- }
133
130
 
134
131
  const { transform } = splitScaleType(scale.type);
135
132
 
@@ -139,11 +136,7 @@ export function generateScaleGlsl(channel, scale, channelDef) {
139
136
  */
140
137
  const makeScaleCall = (name, ...args) =>
141
138
  // eslint-disable-next-line no-useless-call
142
- makeFunctionCall.apply(null, [
143
- name + (fp64 ? "Fp64" : ""),
144
- "value",
145
- ...args,
146
- ]);
139
+ makeFunctionCall.apply(null, [name, "value", ...args]);
147
140
 
148
141
  let functionCall;
149
142
  switch (transform) {
@@ -181,6 +174,17 @@ export function generateScaleGlsl(channel, scale, channelDef) {
181
174
 
182
175
  case "index":
183
176
  case "locus":
177
+ functionCall = makeScaleCall(
178
+ "scaleBandHp",
179
+ "domain",
180
+ rangeName,
181
+ scale.paddingInner(),
182
+ scale.paddingOuter(),
183
+ scale.align(),
184
+ // @ts-expect-error TODO: fix typing
185
+ channelDef.band ?? 0.5
186
+ );
187
+ break;
184
188
  case "point":
185
189
  case "band":
186
190
  functionCall = makeScaleCall(
@@ -296,10 +300,13 @@ export function generateScaleGlsl(channel, scale, channelDef) {
296
300
  if (functionCall) {
297
301
  const name = domainUniformName;
298
302
  if (usesDomain) {
299
- const dtype = fp64 ? "vec4" : "vec2";
300
- scaleBody.push(
301
- `${dtype} domain = ${dtype}(${name}[slot], ${name}[slot + 1]);`
302
- );
303
+ if (hp) {
304
+ scaleBody.push(`vec3 domain = ${name};`);
305
+ } else {
306
+ scaleBody.push(
307
+ `vec2 domain = vec2(${name}[slot], ${name}[slot + 1]);`
308
+ );
309
+ }
303
310
  }
304
311
 
305
312
  scaleBody.push(`float transformed = ${functionCall};`);
@@ -344,9 +351,9 @@ ${returnType} ${SCALED_FUNCTION_PREFIX}${channel}() {
344
351
  isContinuous(scale.type) || isDiscretizing(scale.type)
345
352
  ? domainLength
346
353
  : 2;
347
- domainUniform = `${
348
- fp64 ? "vec2" : "float"
349
- } ${domainUniformName}[${length}];`;
354
+ domainUniform = hp
355
+ ? `highp vec3 ${domainUniformName};`
356
+ : `mediump float ${domainUniformName}[${length}];`;
350
357
  }
351
358
 
352
359
  return {
@@ -447,3 +454,35 @@ function makeFunctionCall(name, ...args) {
447
454
 
448
455
  return `${name}(${fixedArgs.join(", ")})`;
449
456
  }
457
+
458
+ /**
459
+ *
460
+ * @param {string} type
461
+ */
462
+ export function isHighPrecisionScale(type) {
463
+ return type == "index" || type == "locus";
464
+ }
465
+
466
+ /**
467
+ * @param {number} x
468
+ * @param {number[]} [arr]
469
+ */
470
+ export function splitHighPrecision(x, arr) {
471
+ // Maximum precise index number is 2^(23 + 11) ~ 17G
472
+ // Higher number increases precision but makes zooming unstable
473
+ const bs = 2 ** 11;
474
+
475
+ const lo = x % bs;
476
+ const hi = Math.round(x - lo);
477
+ arr ??= [];
478
+ arr[0] = hi;
479
+ arr[1] = lo;
480
+ return arr;
481
+ }
482
+
483
+ /**
484
+ * @param {number[]} domain
485
+ */
486
+ export function toHighPrecisionDomainUniform(domain) {
487
+ return [...splitHighPrecision(domain[0]), domain[1] - domain[0]];
488
+ }
@@ -1,3 +1,4 @@
1
+ import { expect, test } from "vitest";
1
2
  /*!
2
3
  * Adapted from vega-encode:
3
4
  * https://github.com/vega/vega/blob/master/packages/vega-encode/test/scale-test.js
@@ -1,3 +1,4 @@
1
+ import { expect, test } from "vitest";
1
2
  /*!
2
3
  * Adapted from vega-encode:
3
4
  * https://github.com/vega/vega/blob/master/packages/vega-encode/test/scale-test.js
@@ -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.
@@ -221,15 +221,6 @@ export interface Scale {
221
221
  * If `true` and the scale is used on a positional channel, it can bee zoomed and translated interactively.
222
222
  */
223
223
  zoom?: boolean | ZoomParams;
224
-
225
- /**
226
- * Use emulated 64bit floating points on the GPU to increase precision.
227
- *
228
- * Emulation has a performance cost when compared to the native 32bit processing, but the effect is negligible in the most cases.
229
- *
230
- * __Default value:__ `true` for `"locus"` scale, `false` for others.
231
- */
232
- fp64?: boolean;
233
224
  }
234
225
 
235
226
  export interface SchemeParams {
@@ -0,0 +1,102 @@
1
+ /*!
2
+ * Adapted from
3
+ * https://github.com/vega/vega/blob/main/packages/vega-typings/types/spec/title.d.ts
4
+ *
5
+ * Copyright (c) 2015-2018, University of Washington Interactive Data Lab
6
+ * All rights reserved.
7
+ *
8
+ * BSD-3-Clause License: https://github.com/vega/vega-lite/blob/master/LICENSE
9
+ */
10
+
11
+ import { Align, Baseline, FontStyle, FontWeight } from "./font";
12
+
13
+ export type TitleOrient = "none" | "left" | "right" | "top" | "bottom";
14
+ export type TitleAnchor = null | "start" | "middle" | "end";
15
+ export type TitleFrame = "bounds" | "group";
16
+
17
+ export interface Title {
18
+ /**
19
+ * The title text.
20
+ */
21
+ text: string;
22
+
23
+ /**
24
+ * A mark style property to apply to the title text mark. If not specified, a default style of `"group-title"` is applied.
25
+ */
26
+ style?: string;
27
+
28
+ /**
29
+ * The anchor position for placing the title and subtitle text. One of `"start"`, `"middle"`, or `"end"`. For example, with an orientation of top these anchor positions map to a left-, center-, or right-aligned title.
30
+ */
31
+ anchor?: TitleAnchor;
32
+
33
+ /**
34
+ * The reference frame for the anchor position, one of `"bounds"` (to anchor relative to the full bounding box) or `"group"` (to anchor relative to the group width or height).
35
+ */
36
+ frame?: TitleFrame;
37
+
38
+ /**
39
+ * The orthogonal offset in pixels by which to displace the title group from its position along the edge of the chart.
40
+ */
41
+ offset?: number;
42
+
43
+ /**
44
+ * Default title orientation (`"top"`, `"bottom"`, `"left"`, or `"right"`)
45
+ */
46
+ orient?: TitleOrient;
47
+
48
+ // ---------- Shared Text Properties ----------
49
+ /**
50
+ * Horizontal text alignment for title text. One of `"left"`, `"center"`, or `"right"`.
51
+ */
52
+ align?: Align;
53
+
54
+ /**
55
+ * Angle in degrees of title and subtitle text.
56
+ */
57
+ angle?: number;
58
+
59
+ /**
60
+ * Vertical text baseline for title and subtitle text. One of `"alphabetic"` (default), `"top"`, `"middle"`, or `"bottom"`.
61
+ */
62
+ baseline?: Baseline;
63
+
64
+ /**
65
+ * Delta offset for title and subtitle text x-coordinate.
66
+ */
67
+ dx?: number;
68
+
69
+ /**
70
+ * Delta offset for title and subtitle text y-coordinate.
71
+ */
72
+ dy?: number;
73
+
74
+ // ---------- Title Text ----------
75
+ /**
76
+ * Text color for title text.
77
+ */
78
+ color?: string;
79
+
80
+ /**
81
+ * Font name for title text.
82
+ */
83
+ font?: string;
84
+
85
+ /**
86
+ * Font size in pixels for title text.
87
+ *
88
+ * @minimum 0
89
+ */
90
+ fontSize?: number;
91
+
92
+ /**
93
+ * Font style for title text.
94
+ */
95
+ fontStyle?: FontStyle;
96
+
97
+ /**
98
+ * Font weight for title text.
99
+ * This can be either a string (e.g `"bold"`, `"normal"`) or a number (`100`, `200`, `300`, ..., `900` where `"normal"` = `400` and `"bold"` = `700`).
100
+ */
101
+ fontWeight?: FontWeight;
102
+ }
@@ -14,6 +14,7 @@ import {
14
14
  MarkType,
15
15
  RectProps,
16
16
  } from "./mark";
17
+ import { Title } from "./title";
17
18
 
18
19
  export interface SizeDef {
19
20
  /** Size in pixels */
@@ -55,7 +56,7 @@ export type Paddings = Partial<Record<Side, number>>;
55
56
 
56
57
  export type PaddingConfig = Paddings | number;
57
58
 
58
- export interface ViewConfig extends RectProps, FillAndStrokeProps {
59
+ export interface ViewBackground extends RectProps, FillAndStrokeProps {
59
60
  // TODO: style?: string | string[];
60
61
 
61
62
  // TODO: Move to FillAndStrokeProps or something
@@ -78,7 +79,7 @@ export interface ViewSpecBase extends ResolveSpec {
78
79
  data?: Data;
79
80
  transform?: TransformParams[];
80
81
  encoding?: Encoding;
81
- title?: string;
82
+ title?: string | Title;
82
83
 
83
84
  /**
84
85
  * A description of the view. Multiple lines can be provided as an array.
@@ -115,7 +116,7 @@ export interface ViewSpecBase extends ResolveSpec {
115
116
  }
116
117
 
117
118
  export interface UnitSpec extends ViewSpecBase, AggregateSamplesSpec {
118
- view?: ViewConfig;
119
+ view?: ViewBackground;
119
120
  mark: MarkType | MarkConfigAndType;
120
121
  }
121
122
 
@@ -125,7 +126,7 @@ export interface AggregateSamplesSpec {
125
126
  }
126
127
 
127
128
  export interface LayerSpec extends ViewSpecBase, AggregateSamplesSpec {
128
- view?: ViewConfig;
129
+ view?: ViewBackground;
129
130
  layer: (LayerSpec | UnitSpec)[];
130
131
  }
131
132
 
@@ -218,4 +219,5 @@ export interface HConcatSpec extends ConcatBase {
218
219
 
219
220
  export interface ConcatSpec extends ConcatBase {
220
221
  concat: (ViewSpec | ImportSpec)[];
222
+ columns: number;
221
223
  }
@@ -47,10 +47,11 @@ export default async function dataTooltipHandler(datum, mark, params) {
47
47
  </table>
48
48
  `;
49
49
 
50
- const title = mark.unitView.spec.title
50
+ const titleText = mark.unitView.getTitleText();
51
+ const title = titleText
51
52
  ? html`
52
53
  <div class="title">
53
- <strong>${mark.unitView.spec.title}</strong>
54
+ <strong>${titleText}</strong>
54
55
  </div>
55
56
  `
56
57
  : "";
@@ -1,3 +1,4 @@
1
+ import { expect, test } from "vitest";
1
2
  import addBaseUrl from "./addBaseUrl";
2
3
 
3
4
  test("addBaseUrl adds baseUrl when needed", () => {
@@ -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
+ }