@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/genomeSpy.js CHANGED
@@ -8,7 +8,6 @@ import Tooltip from "./utils/ui/tooltip";
8
8
  import AccessorFactory from "./encoder/accessor";
9
9
  import {
10
10
  resolveScalesAndAxes,
11
- addDecorators,
12
11
  processImports,
13
12
  setImplicitScaleNames,
14
13
  } from "./view/viewUtils";
@@ -17,7 +16,6 @@ import UnitView from "./view/unitView";
17
16
  import WebGLHelper from "./gl/webGLHelper";
18
17
  import Rectangle from "./utils/layout/rectangle";
19
18
  import DeferredViewRenderingContext from "./view/renderingContext/deferredViewRenderingContext";
20
- import LayoutRecorderViewRenderingContext from "./view/renderingContext/layoutRecorderViewRenderingContext";
21
19
  import CompositeViewRenderingContext from "./view/renderingContext/compositeViewRenderingContext";
22
20
  import InteractionEvent from "./utils/interactionEvent";
23
21
  import Point from "./utils/layout/point";
@@ -36,6 +34,8 @@ import refseqGeneTooltipHandler from "./tooltip/refseqGeneTooltipHandler";
36
34
  import dataTooltipHandler from "./tooltip/dataTooltipHandler";
37
35
  import { invalidatePrefix } from "./utils/propertyCacher";
38
36
  import { ViewFactory } from "./view/viewFactory";
37
+ import LayerView from "./view/layerView";
38
+ import ImplicitRootView from "./view/implicitRootView";
39
39
 
40
40
  /**
41
41
  * @typedef {import("./spec/view").UnitSpec} UnitSpec
@@ -166,7 +166,9 @@ export default class GenomeSpy {
166
166
 
167
167
  this._glHelper = new WebGLHelper(this.container, () => {
168
168
  if (this.viewRoot) {
169
- const size = this.viewRoot.getSize();
169
+ const size = this.viewRoot
170
+ .getSize()
171
+ .addPadding(this.viewRoot.getOverhang());
170
172
 
171
173
  // If a dimension has an absolutely specified size (in pixels), use it for the canvas size.
172
174
  // However, if the dimension has a growing component, the canvas should be fit to the
@@ -283,12 +285,19 @@ export default class GenomeSpy {
283
285
  // Replace placeholder ImportViews with actual views.
284
286
  await processImports(this.viewRoot);
285
287
 
288
+ if (
289
+ this.viewRoot instanceof UnitView ||
290
+ this.viewRoot instanceof LayerView
291
+ ) {
292
+ this.viewRoot = new ImplicitRootView(context, this.viewRoot);
293
+ }
294
+
286
295
  // Resolve scales, i.e., if possible, pull them towards the root
287
296
  resolveScalesAndAxes(this.viewRoot);
288
297
  setImplicitScaleNames(this.viewRoot);
289
298
 
290
299
  // Wrap unit or layer views that need axes
291
- this.viewRoot = addDecorators(this.viewRoot);
300
+ //this.viewRoot = addDecorators(this.viewRoot);
292
301
 
293
302
  // We should now have a complete view hierarchy. Let's update the canvas size
294
303
  // and ensure that the loading message is visible.
@@ -424,7 +433,7 @@ export default class GenomeSpy {
424
433
 
425
434
  /** @param {Event} event */
426
435
  const listener = (event) => {
427
- if (this.layout && event instanceof MouseEvent) {
436
+ if (event instanceof MouseEvent) {
428
437
  if (event.type == "mousemove") {
429
438
  this.tooltip.handleMouseMove(event);
430
439
  this._tooltipUpdateRequested = false;
@@ -445,7 +454,7 @@ export default class GenomeSpy {
445
454
  * @param {MouseEvent} event
446
455
  */
447
456
  const dispatchEvent = (event) => {
448
- this.layout.dispatchInteractionEvent(
457
+ this.viewRoot.propagateInteractionEvent(
449
458
  new InteractionEvent(point, event)
450
459
  );
451
460
 
@@ -677,20 +686,16 @@ export default class GenomeSpy {
677
686
  },
678
687
  this._glHelper
679
688
  );
680
- const layoutRecorder = new LayoutRecorderViewRenderingContext({});
681
689
 
682
690
  root.render(
683
691
  new CompositeViewRenderingContext(
684
692
  this._renderingContext,
685
- this._pickingContext,
686
- layoutRecorder
693
+ this._pickingContext
687
694
  ),
688
695
  // Canvas should now be sized based on the root view or the container
689
696
  Rectangle.create(0, 0, canvasSize.width, canvasSize.height)
690
697
  );
691
698
 
692
- this.layout = layoutRecorder.getLayout();
693
-
694
699
  this.broadcast("layoutComputed");
695
700
  }
696
701
 
@@ -1,18 +1,21 @@
1
1
  import { InternMap } from "internmap";
2
2
  import { format } from "d3-format";
3
3
  import { isString } from "vega-util";
4
- import { fp64ify } from "./includes/fp64-utils";
5
4
  import ArrayBuilder from "./arrayBuilder";
6
5
  import { SDF_PADDING } from "../fonts/bmFontMetrics";
7
- import { peek } from "../utils/arrayUtils";
8
- import createBinningRangeIndexer from "../utils/binnedRangeIndex";
6
+ import { createBinningRangeIndexer } from "../utils/binnedIndex";
9
7
  import { isValueDef } from "../encoder/encoder";
8
+ import {
9
+ isHighPrecisionScale,
10
+ splitHighPrecision,
11
+ } from "../scale/glslScaleGenerator";
12
+ import { isContinuous } from "vega-scale";
10
13
 
11
14
  /**
12
15
  * @typedef {object} RangeEntry Represents a location of a vertex subset
13
16
  * @prop {number} offset in vertices
14
17
  * @prop {number} count in vertices
15
- * @prop {import("../utils/binnedRangeIndex").Lookup} xIndex
18
+ * @prop {import("../utils/binnedIndex").Lookup} xIndex
16
19
  *
17
20
  * @typedef {import("./arraybuilder").ConverterMetadata} Converter
18
21
  * @typedef {import("../encoder/encoder").Encoder} Encoder
@@ -25,16 +28,9 @@ export class GeometryBuilder {
25
28
  * @param {string[]} [object.attributes]
26
29
  * @param {number} [object.numVertices] If the number of data items is known, a
27
30
  * preallocated TypedArray is used
28
- * @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
29
31
  */
30
- constructor({
31
- encoders,
32
- numVertices = undefined,
33
- attributes = [],
34
- buildXIndex = false,
35
- }) {
32
+ constructor({ encoders, numVertices = undefined, attributes = [] }) {
36
33
  this.encoders = encoders;
37
- this._buildXIndex = buildXIndex;
38
34
 
39
35
  // Encoders for variable channels
40
36
  this.variableEncoders = Object.fromEntries(
@@ -55,7 +51,7 @@ export class GeometryBuilder {
55
51
  const accessor = ce.accessor;
56
52
 
57
53
  const doubleArray = [0, 0];
58
- const fp64 = ce.scale.fp64;
54
+ const hp = isHighPrecisionScale(ce.scale.type);
59
55
 
60
56
  const indexer = ce.indexer;
61
57
 
@@ -68,20 +64,20 @@ export class GeometryBuilder {
68
64
  */
69
65
  const f = indexer
70
66
  ? (d) => indexer(accessor(d))
71
- : fp64
72
- ? (d) => fp64ify(accessor(d), doubleArray)
67
+ : hp
68
+ ? (d) => splitHighPrecision(accessor(d), doubleArray)
73
69
  : accessor;
74
70
 
75
71
  this.variableBuilder.addConverter(channel, {
76
72
  f,
77
- numComponents: fp64 ? 2 : 1,
78
- arrayReference: fp64 ? doubleArray : undefined,
73
+ numComponents: hp ? 2 : 1,
74
+ arrayReference: hp ? doubleArray : undefined,
79
75
  });
80
76
  }
81
77
 
82
78
  this.lastOffset = 0;
83
79
 
84
- /** @type {Map<any, RangeEntry>} keep track of sample locations within the vertex array */
80
+ /** @type {Map<any, RangeEntry>} keep track of facet locations within the vertex array */
85
81
  this.rangeMap = new InternMap([], JSON.stringify);
86
82
  }
87
83
 
@@ -118,32 +114,50 @@ export class GeometryBuilder {
118
114
  * @param {object[]} data
119
115
  */
120
116
  addBatch(key, data, lo = 0, hi = data.length) {
117
+ this.prepareXIndexer(data, lo, hi);
118
+
121
119
  for (let i = lo; i < hi; i++) {
122
- this.variableBuilder.pushFromDatum(data[i]);
120
+ const d = data[i];
121
+ this.variableBuilder.pushFromDatum(d);
122
+ this.addToXIndex(d);
123
123
  }
124
124
 
125
125
  this.registerBatch(key);
126
126
  }
127
127
 
128
128
  /**
129
- * @param {any[]} data
129
+ * @param {import("../data/flowNode").Data} data Domain, but specified using datums
130
+ * @param {number} [lo]
131
+ * @param {number} [hi]
130
132
  */
131
- prepareXIndexer(data) {
132
- if (!this._buildXIndex) {
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
+ };
133
141
  return;
134
142
  }
135
143
 
136
- const xe = this.variableEncoders.x;
137
- const x2e = this.variableEncoders.x2;
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);
138
150
 
139
- if (xe && x2e) {
151
+ if (xe) {
140
152
  const xa = xe.accessor;
141
- const x2a = x2e.accessor;
153
+ const x2a = x2e ? x2e.accessor : xa;
142
154
 
143
- this.xIndexer = createBinningRangeIndexer(50, [
144
- xa(data[0]),
145
- x2a(peek(data)),
146
- ]);
155
+ this.xIndexer = createBinningRangeIndexer(
156
+ 50,
157
+ [xa(data[lo]), x2a(data[hi - 1])],
158
+ xa,
159
+ x2a
160
+ );
147
161
 
148
162
  let lastVertexCount = this.variableBuilder.vertexCount;
149
163
 
@@ -152,21 +166,16 @@ export class GeometryBuilder {
152
166
  */
153
167
  this.addToXIndex = (datum) => {
154
168
  let currentVertexCount = this.variableBuilder.vertexCount;
155
- this.xIndexer(
156
- xa(datum),
157
- x2a(datum),
158
- lastVertexCount,
159
- currentVertexCount
160
- );
169
+ this.xIndexer(datum, lastVertexCount, currentVertexCount);
161
170
  lastVertexCount = currentVertexCount;
162
171
  };
163
172
  } else {
164
173
  this.xIndexer = undefined;
165
174
  /**
166
- * @param {any} datum
175
+ * @param {import("../data/flowNode").Datum} datum
167
176
  */
168
177
  this.addToXIndex = (datum) => {
169
- //
178
+ // nop
170
179
  };
171
180
  }
172
181
  }
@@ -175,7 +184,7 @@ export class GeometryBuilder {
175
184
  * Add the datum to an index, which allows for efficient rendering of ranges
176
185
  * on the x axis. Must be called after a datum has been pushed to the ArrayBuilder.
177
186
  *
178
- * @param {any} datum
187
+ * @param {import("../data/flowNode").Datum} datum
179
188
  */
180
189
  addToXIndex(datum) {
181
190
  //
@@ -204,7 +213,6 @@ export class RectVertexBuilder extends GeometryBuilder {
204
213
  * If the rect is wider than the threshold, tessellate it into pieces
205
214
  * @param {number[]} [object.visibleRange]
206
215
  * @param {number} [object.numItems] Number of data items
207
- * @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
208
216
  */
209
217
  constructor({
210
218
  encoders,
@@ -212,14 +220,12 @@ export class RectVertexBuilder extends GeometryBuilder {
212
220
  tessellationThreshold = Infinity,
213
221
  visibleRange = [-Infinity, Infinity],
214
222
  numItems,
215
- buildXIndex = false,
216
223
  }) {
217
224
  super({
218
225
  encoders,
219
226
  attributes,
220
227
  numVertices:
221
228
  tessellationThreshold == Infinity ? numItems * 6 : undefined,
222
- buildXIndex,
223
229
  });
224
230
 
225
231
  this.visibleRange = visibleRange;
@@ -253,7 +259,7 @@ export class RectVertexBuilder extends GeometryBuilder {
253
259
  const xAccessor = a(e.x);
254
260
  const x2Accessor = a(e.x2);
255
261
 
256
- this.prepareXIndexer(data);
262
+ this.prepareXIndexer(data, lo, hi);
257
263
 
258
264
  const frac = [0, 0];
259
265
  this.updateFrac(frac);
@@ -319,7 +325,6 @@ export class RuleVertexBuilder extends GeometryBuilder {
319
325
  * If the rule is wider than the threshold, tessellate it into pieces
320
326
  * @param {number[]} [object.visibleRange]
321
327
  * @param {number} [object.numItems] Number of data items
322
- * @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
323
328
  */
324
329
  constructor({
325
330
  encoders,
@@ -327,14 +332,12 @@ export class RuleVertexBuilder extends GeometryBuilder {
327
332
  tessellationThreshold = Infinity,
328
333
  visibleRange = [-Infinity, Infinity],
329
334
  numItems,
330
- buildXIndex,
331
335
  }) {
332
336
  super({
333
337
  encoders,
334
338
  attributes,
335
339
  numVertices:
336
340
  tessellationThreshold == Infinity ? numItems * 6 : undefined,
337
- buildXIndex,
338
341
  });
339
342
 
340
343
  this.visibleRange = visibleRange;
@@ -354,7 +357,7 @@ export class RuleVertexBuilder extends GeometryBuilder {
354
357
  addBatch(key, data, lo = 0, hi = data.length) {
355
358
  //const [lower, upper] = this.visibleRange; // TODO
356
359
 
357
- this.prepareXIndexer(data);
360
+ this.prepareXIndexer(data, lo, hi);
358
361
 
359
362
  for (let i = lo; i < hi; i++) {
360
363
  const d = data[i];
@@ -440,7 +443,6 @@ export class TextVertexBuilder extends GeometryBuilder {
440
443
  * @param {import("../fonts/bmFontMetrics").BMFontMetrics} object.fontMetrics
441
444
  * @param {Record<string, any>} object.properties
442
445
  * @param {number} [object.numCharacters] number of characters
443
- * @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
444
446
  * @param {boolean} [object.logoLetters]
445
447
  */
446
448
  constructor({
@@ -449,13 +451,11 @@ export class TextVertexBuilder extends GeometryBuilder {
449
451
  fontMetrics,
450
452
  properties,
451
453
  numCharacters = undefined,
452
- buildXIndex = false,
453
454
  }) {
454
455
  super({
455
456
  encoders,
456
457
  attributes,
457
458
  numVertices: numCharacters * 6, // six vertices per quad (character)
458
- buildXIndex,
459
459
  });
460
460
 
461
461
  this.metadata = fontMetrics;
@@ -521,7 +521,7 @@ export class TextVertexBuilder extends GeometryBuilder {
521
521
  const textureCoord = [0, 0];
522
522
  this.updateTextureCoord(textureCoord);
523
523
 
524
- this.prepareXIndexer(data);
524
+ this.prepareXIndexer(data, lo, hi);
525
525
 
526
526
  for (let i = lo; i < hi; i++) {
527
527
  const d = data[i];
@@ -621,7 +621,7 @@ export class TextVertexBuilder extends GeometryBuilder {
621
621
  x += advance;
622
622
  }
623
623
 
624
- this.addToXIndex(data);
624
+ this.addToXIndex(d);
625
625
  }
626
626
 
627
627
  this.registerBatch(key);
@@ -1,18 +1,18 @@
1
1
  #define PI 3.141593
2
2
 
3
- /** Offset in "unit" units */
4
- uniform vec2 uViewOffset;
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 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
16
 
17
17
  /**
18
18
  * Maps a coordinate on the unit scale to a normalized device coordinate.
@@ -1,3 +1 @@
1
- uniform bool uPickingEnabled;
2
-
3
1
  flat in highp vec4 vPickingColor;
@@ -4,8 +4,6 @@
4
4
  * https://deck.gl/docs/developer-guide/custom-layers/picking
5
5
  */
6
6
 
7
- uniform bool uPickingEnabled;
8
-
9
7
  flat out highp vec4 vPickingColor;
10
8
 
11
9
  /**
@@ -1,3 +1,5 @@
1
+ const float inf = 1.0 / 0.0;
2
+
1
3
  // Utils ------------
2
4
 
3
5
  vec3 getDiscreteColor(sampler2D s, int index) {
@@ -67,15 +69,44 @@ float scaleBand(float value, vec2 domainExtent, vec2 range,
67
69
  // TODO: reverse
68
70
  float start = range[0];
69
71
  float stop = range[1];
72
+ float rangeSpan = stop - start;
70
73
 
71
74
  float n = domainExtent[1] - domainExtent[0];
72
75
 
76
+ // This fix departs from Vega and d3: https://github.com/vega/vega/issues/3357#issuecomment-1063253596
73
77
  paddingInner = int(n) > 1 ? paddingInner : 0.0;
74
78
 
75
79
  // Adapted from: https://github.com/d3/d3-scale/blob/master/src/band.js
76
- float step = (stop - start) / max(1.0, n - paddingInner + paddingOuter * 2.0);
77
- start += (stop - start - step * (n - paddingInner)) * align;
80
+ float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
81
+ start += (rangeSpan - step * (n - paddingInner)) * align;
78
82
  float bandwidth = step * (1.0 - paddingInner);
79
83
 
80
84
  return start + (value - domainExtent[0]) * step + bandwidth * band;
81
85
  }
86
+
87
+ // High precision variant of scaleBand for index/locus scales
88
+ float scaleBandHp(vec2 value, vec3 domainExtent, vec2 range,
89
+ float paddingInner, float paddingOuter,
90
+ float align, float band) {
91
+
92
+ // TODO: reverse
93
+ float start = range[0];
94
+ float stop = range[1];
95
+ float rangeSpan = stop - start;
96
+
97
+ vec2 domainStart = domainExtent.xy;
98
+ float n = domainExtent[2];
99
+
100
+ // The following computation is identical for every vertex. Could be done on the JS side.
101
+ float step = rangeSpan / max(1.0, n - paddingInner + paddingOuter * 2.0);
102
+ start += (rangeSpan - step * (n - paddingInner)) * align;
103
+ float bandwidth = step * (1.0 - paddingInner);
104
+
105
+ // Using max to prevent the shader compiler from wrecking the precision.
106
+ // Othwewise the compiler could optimize the sum of the four terms into
107
+ // some equivalent form that does premature rounding.
108
+ float hi = max(value[0] - domainStart[0], -inf);
109
+ float lo = max(value[1] - domainStart[1], -inf);
110
+
111
+ return dot(vec4(start, hi, lo, bandwidth), vec4(1.0, step, step, band));
112
+ }
@@ -1,5 +1,3 @@
1
- precision mediump float;
2
-
3
1
  /**
4
2
  * The stroke should only grow inwards, e.g, the diameter/outline is not affected by the stroke width.
5
3
  * Thus, a point that has a zero size has no visible stroke. This allows strokes to be used with
@@ -12,7 +12,7 @@ in float side;
12
12
  /** Minimum rule length in pixels */
13
13
  uniform float uMinLength;
14
14
 
15
- uniform float uDashTextureSize;
15
+ uniform mediump float uDashTextureSize;
16
16
  uniform lowp int uStrokeCap;
17
17
 
18
18
  flat out vec4 vColor;
@@ -8,7 +8,6 @@ import {
8
8
  setTextureFromArray,
9
9
  } from "twgl.js";
10
10
  import { isArray, isString } from "vega-util";
11
- import { getPlatformShaderDefines } from "./includes/fp64-utils";
12
11
 
13
12
  import { isDiscrete, isDiscretizing, isInterpolating } from "vega-scale";
14
13
  import {
@@ -81,8 +80,6 @@ export default class WebGLHelper {
81
80
  // Always use pre-multiplied alpha
82
81
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
83
82
 
84
- this._shaderDefines = getPlatformShaderDefines(gl);
85
-
86
83
  this.canvas = canvas;
87
84
  this.gl = gl;
88
85
 
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 = vertexData.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
- (offset, count) => {
129
- // We are using instanced drawing here.
130
- // However, WebGL does not provide glDrawArraysInstancedBaseInstance and thus,
131
- // we have to hack with offsets in vertexAttribPointer
132
- // TODO: Use VAOs more intelligently to reduce WebGL calls
133
- // TODO: Explore multiDrawArraysInstancedWEBGL
134
- // There's also a promising extension draft:
135
- // https://www.khronos.org/registry/webgl/extensions/WEBGL_draw_instanced_base_vertex_base_instance/
136
- // (and https://www.khronos.org/registry/webgl/extensions/WEBGL_multi_draw_instanced_base_vertex_base_instance/)
137
-
138
- this.gl.bindVertexArray(this.vertexArrayInfo.vertexArrayObject);
139
-
140
- for (const attribInfoObject of Object.entries(
141
- this.bufferInfo.attribs
142
- )) {
143
- const [attribute, attribInfo] = attribInfoObject;
144
- if (
145
- attribInfo.buffer &&
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
- setBuffersAndAttributes(gl, this.programInfo, this.bufferInfo);
153
-
154
- drawBufferInfo(
155
- gl,
156
- this.bufferInfo,
157
- gl.TRIANGLE_STRIP,
158
- (this.properties.segments + 1) * 2, // number of vertices in a triangle strip
159
- 0,
160
- count
161
- );
162
- },
163
- options,
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