@genome-spy/core 0.18.1 → 0.20.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 (91) hide show
  1. package/dist/index.js +46 -119
  2. package/dist/schema.json +213 -22
  3. package/package.json +4 -3
  4. package/src/data/collector.js +9 -4
  5. package/src/data/collector.test.js +2 -0
  6. package/src/data/dataFlow.test.js +2 -0
  7. package/src/data/flow.test.js +1 -0
  8. package/src/data/flowNode.test.js +1 -0
  9. package/src/data/flowOptimizer.js +6 -0
  10. package/src/data/flowOptimizer.test.js +1 -0
  11. package/src/data/formats/fasta.test.js +1 -0
  12. package/src/data/sources/inlineSource.test.js +1 -0
  13. package/src/data/sources/sequenceSource.test.js +1 -0
  14. package/src/data/transforms/clone.test.js +1 -0
  15. package/src/data/transforms/coverage.test.js +1 -0
  16. package/src/data/transforms/filter.test.js +1 -0
  17. package/src/data/transforms/flattenDelimited.test.js +1 -0
  18. package/src/data/transforms/flattenSequence.test.js +1 -0
  19. package/src/data/transforms/formula.test.js +1 -0
  20. package/src/data/transforms/identifier.test.js +1 -0
  21. package/src/data/transforms/pileup.test.js +1 -0
  22. package/src/data/transforms/project.test.js +1 -0
  23. package/src/data/transforms/regexExtract.test.js +1 -0
  24. package/src/data/transforms/regexFold.test.js +1 -0
  25. package/src/data/transforms/sample.test.js +1 -0
  26. package/src/data/transforms/stack.test.js +1 -0
  27. package/src/encoder/accessor.test.js +1 -0
  28. package/src/encoder/encoder.test.js +1 -0
  29. package/src/genome/genome.js +14 -2
  30. package/src/genome/genome.test.js +36 -0
  31. package/src/genome/scaleIndex.js +3 -2
  32. package/src/genome/scaleIndex.test.js +23 -6
  33. package/src/genome/scaleLocus.test.js +1 -0
  34. package/src/genomeSpy.js +16 -11
  35. package/src/gl/dataToVertices.js +9 -6
  36. package/src/gl/includes/common.glsl +3 -3
  37. package/src/gl/includes/scales.glsl +33 -2
  38. package/src/gl/point.vertex.glsl +0 -2
  39. package/src/gl/rule.vertex.glsl +1 -1
  40. package/src/gl/webGLHelper.js +0 -3
  41. package/src/marks/mark.js +15 -14
  42. package/src/scale/glslScaleGenerator.js +56 -17
  43. package/src/scale/scale.test.js +1 -0
  44. package/src/scale/ticks.test.js +1 -0
  45. package/src/spec/scale.d.ts +0 -9
  46. package/src/spec/title.d.ts +102 -0
  47. package/src/spec/view.d.ts +6 -4
  48. package/src/tooltip/dataTooltipHandler.js +3 -2
  49. package/src/utils/addBaseUrl.test.js +1 -0
  50. package/src/utils/cloner.test.js +1 -0
  51. package/src/utils/coalesce.test.js +1 -0
  52. package/src/utils/concatIterables.test.js +1 -0
  53. package/src/utils/domainArray.test.js +1 -0
  54. package/src/utils/indexer.test.js +1 -0
  55. package/src/utils/iterateNestedMaps.test.js +1 -0
  56. package/src/utils/kWayMerge.test.js +1 -0
  57. package/src/utils/layout/flexLayout.js +35 -3
  58. package/src/utils/layout/flexLayout.test.js +15 -0
  59. package/src/utils/layout/grid.js +95 -0
  60. package/src/utils/layout/grid.test.js +71 -0
  61. package/src/utils/layout/padding.js +13 -0
  62. package/src/utils/layout/rectangle.js +6 -0
  63. package/src/utils/layout/rectangle.test.js +1 -0
  64. package/src/utils/mergeObjects.test.js +1 -0
  65. package/src/utils/numberExtractor.test.js +1 -0
  66. package/src/utils/propertyCacher.test.js +1 -0
  67. package/src/utils/propertyCoalescer.test.js +1 -0
  68. package/src/utils/reservationMap.test.js +1 -0
  69. package/src/utils/topK.test.js +1 -0
  70. package/src/utils/variableTools.test.js +1 -0
  71. package/src/view/axisResolution.test.js +1 -0
  72. package/src/view/axisView.js +3 -1
  73. package/src/view/concatView.js +24 -275
  74. package/src/view/flowBuilder.test.js +1 -0
  75. package/src/view/gridView.js +774 -0
  76. package/src/view/implicitRootView.js +14 -0
  77. package/src/view/layerView.js +15 -1
  78. package/src/view/scaleResolution.js +5 -11
  79. package/src/view/scaleResolution.test.js +1 -0
  80. package/src/view/title.js +165 -0
  81. package/src/view/unitView.js +17 -9
  82. package/src/view/view.js +35 -14
  83. package/src/view/view.test.js +1 -0
  84. package/src/view/viewContext.d.ts +6 -1
  85. package/src/view/viewFactory.test.js +1 -0
  86. package/src/view/viewUtils.js +1 -93
  87. package/src/view/zoom.js +89 -0
  88. package/src/gl/includes/fp64-arithmetic.glsl +0 -187
  89. package/src/gl/includes/fp64-utils.js +0 -142
  90. package/src/gl/includes/scales_fp64.glsl +0 -30
  91. package/src/view/decoratorView.js +0 -513
@@ -1,3 +1,4 @@
1
+ import { describe, expect, test } from "vitest";
1
2
  import Genome from "./genome";
2
3
 
3
4
  describe("Human genome, chromosome names prefixed with 'chr'", () => {
@@ -150,3 +151,38 @@ describe("C. elegans genome, chromosome names prefixed with 'chr'", () => {
150
151
  expect(g.toContinuous("III", 10)).toEqual(30351865);
151
152
  });
152
153
  });
154
+
155
+ describe("Parse interval strings", () => {
156
+ const chromosomes = [
157
+ { name: "chr1", size: 1000 },
158
+ { name: "chr2", size: 2000 },
159
+ { name: "chr3", size: 3000 },
160
+ { name: "chrX", size: 4000 },
161
+ ];
162
+
163
+ const g = new Genome({ name: "random", contigs: chromosomes });
164
+
165
+ test("Parses a single chromosome, returns an interval spanning the chromosome", () => {
166
+ expect(g.parseInterval("chr2")).toEqual([1000, 3000]);
167
+ });
168
+
169
+ test("Returns undefined on unknown chromosome", () => {
170
+ expect(g.parseInterval("chrZ")).toBeUndefined();
171
+ });
172
+
173
+ test("Parses a single coordinate without a thousand separator", () => {
174
+ expect(g.parseInterval("chr2:1500")).toEqual([2499, 2500]);
175
+ });
176
+
177
+ test("Parses a single coordinate with a thousand separator", () => {
178
+ expect(g.parseInterval("chr2:1,500")).toEqual([2499, 2500]);
179
+ });
180
+
181
+ test("Parses an interval within a single chromosome", () => {
182
+ expect(g.parseInterval("chr2:1,500-1,700")).toEqual([2499, 2700]);
183
+ });
184
+
185
+ test("Parses an interval spanning multiple chromosomes", () => {
186
+ expect(g.parseInterval("chr2:1,500-chr3:1,500")).toEqual([2499, 4500]);
187
+ });
188
+ });
@@ -24,7 +24,7 @@ export default function scaleIndex() {
24
24
  let numberingOffset = 0;
25
25
 
26
26
  const scaleFunction = (/** @type {number} */ x) =>
27
- ((x - domain[0]) / domainSpan) * rangeSpan + range[0];
27
+ ((x + align - domain[0]) / domainSpan) * rangeSpan + range[0];
28
28
 
29
29
  /**
30
30
  * In principle, the domain consists of integer indices. However,
@@ -34,7 +34,8 @@ export default function scaleIndex() {
34
34
  */
35
35
  const scale = /** @type {any} */ (scaleFunction);
36
36
 
37
- scale.invert = (y) => ((y - range[0]) / rangeSpan) * domainSpan + domain[0];
37
+ scale.invert = (y) =>
38
+ ((y - range[0]) / rangeSpan) * domainSpan + domain[0] - align;
38
39
 
39
40
  // @ts-expect-error
40
41
  scale.domain = function (_) {
@@ -1,23 +1,40 @@
1
+ import { expect, test } from "vitest";
1
2
  import scaleIndex from "./scaleIndex";
2
3
 
3
4
  test("Scale with defaults works as expected", () => {
4
5
  const scale = scaleIndex();
5
6
 
6
- expect(scale(-1)).toEqual(-1);
7
- expect(scale(0)).toEqual(0);
8
- expect(scale(1)).toEqual(1);
9
- expect(scale(2)).toEqual(2);
7
+ // Align is 0.5 by default
8
+ expect(scale(-1)).toEqual(-0.5);
9
+ expect(scale(0)).toEqual(0.5);
10
+ expect(scale(1)).toEqual(1.5);
11
+ expect(scale(2)).toEqual(2.5);
10
12
  });
11
13
 
12
14
  test("Scale scales correctly with custom domain and range", () => {
13
- const scale = scaleIndex().domain([0, 10]).range([100, 200]);
15
+ const scale = scaleIndex().domain([0, 10]).range([100, 200]).align(0.0);
14
16
 
15
17
  expect(scale(0)).toEqual(100);
16
18
  expect(scale(10)).toEqual(200);
17
19
  });
18
20
 
19
21
  test("Invert works as expected", () => {
20
- const scale = scaleIndex().domain([0, 10]).range([100, 200]);
22
+ const scale = scaleIndex().domain([0, 10]).range([100, 200]).align(0.0);
23
+
24
+ expect(scale.invert(scale(0))).toEqual(0);
25
+ expect(scale.invert(scale(5))).toEqual(5);
26
+ expect(scale.invert(scale(10))).toEqual(10);
27
+ });
28
+
29
+ test("Scale scales correctly with custom domain, range, and align", () => {
30
+ const scale = scaleIndex().domain([0, 10]).range([100, 200]).align(0.5);
31
+
32
+ expect(scale(0)).toEqual(105);
33
+ expect(scale(10)).toEqual(205);
34
+ });
35
+
36
+ test("Invert works as expected with align", () => {
37
+ const scale = scaleIndex().domain([0, 10]).range([100, 200]).align(0.5);
21
38
 
22
39
  expect(scale.invert(scale(0))).toEqual(0);
23
40
  expect(scale.invert(scale(5))).toEqual(5);
@@ -1,3 +1,4 @@
1
+ import { test } from "vitest";
1
2
  import scaleLocus from "./scaleLocus";
2
3
 
3
4
  test.todo("Write some tests!");
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,12 +1,15 @@
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
6
  import { peek } from "../utils/arrayUtils";
8
7
  import createBinningRangeIndexer from "../utils/binnedRangeIndex";
9
8
  import { isValueDef } from "../encoder/encoder";
9
+ import {
10
+ isHighPrecisionScale,
11
+ splitHighPrecision,
12
+ } from "../scale/glslScaleGenerator";
10
13
 
11
14
  /**
12
15
  * @typedef {object} RangeEntry Represents a location of a vertex subset
@@ -55,7 +58,7 @@ export class GeometryBuilder {
55
58
  const accessor = ce.accessor;
56
59
 
57
60
  const doubleArray = [0, 0];
58
- const fp64 = ce.scale.fp64;
61
+ const hp = isHighPrecisionScale(ce.scale.type);
59
62
 
60
63
  const indexer = ce.indexer;
61
64
 
@@ -68,14 +71,14 @@ export class GeometryBuilder {
68
71
  */
69
72
  const f = indexer
70
73
  ? (d) => indexer(accessor(d))
71
- : fp64
72
- ? (d) => fp64ify(accessor(d), doubleArray)
74
+ : hp
75
+ ? (d) => splitHighPrecision(accessor(d), doubleArray)
73
76
  : accessor;
74
77
 
75
78
  this.variableBuilder.addConverter(channel, {
76
79
  f,
77
- numComponents: fp64 ? 2 : 1,
78
- arrayReference: fp64 ? doubleArray : undefined,
80
+ numComponents: hp ? 2 : 1,
81
+ arrayReference: hp ? doubleArray : undefined,
79
82
  });
80
83
  }
81
84
 
@@ -1,12 +1,12 @@
1
1
  #define PI 3.141593
2
2
 
3
3
  /** Offset in "unit" units */
4
- uniform vec2 uViewOffset;
4
+ uniform mediump vec2 uViewOffset;
5
5
 
6
- uniform vec2 uViewScale;
6
+ uniform mediump vec2 uViewScale;
7
7
 
8
8
  /** Size of the logical viewport in pixels, i.e., the view */
9
- uniform vec2 uViewportSize;
9
+ uniform mediump vec2 uViewportSize;
10
10
 
11
11
  uniform lowp float uDevicePixelRatio;
12
12
 
@@ -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/mark.js CHANGED
@@ -8,7 +8,6 @@ import {
8
8
  setUniforms,
9
9
  } from "twgl.js";
10
10
  import { isDiscrete } from "vega-scale";
11
- import { fp64ify } from "../gl/includes/fp64-utils";
12
11
  import createEncoders, {
13
12
  isChannelDefWithScale,
14
13
  isDatumDef,
@@ -20,11 +19,12 @@ import {
20
19
  generateScaleGlsl,
21
20
  RANGE_TEXTURE_PREFIX,
22
21
  ATTRIBUTE_PREFIX,
22
+ isHighPrecisionScale,
23
+ toHighPrecisionDomainUniform,
24
+ splitHighPrecision,
23
25
  } from "../scale/glslScaleGenerator";
24
- import FP64 from "../gl/includes/fp64-arithmetic.glsl";
25
26
  import GLSL_COMMON from "../gl/includes/common.glsl";
26
27
  import GLSL_SCALES from "../gl/includes/scales.glsl";
27
- import GLSL_SCALES_FP64 from "../gl/includes/scales_fp64.glsl";
28
28
  import GLSL_SAMPLE_FACET from "../gl/includes/sampleFacet.glsl";
29
29
  import GLSL_PICKING_VERTEX from "../gl/includes/picking.vertex.glsl";
30
30
  import GLSL_PICKING_FRAGMENT from "../gl/includes/picking.fragment.glsl";
@@ -334,7 +334,10 @@ export default class Mark {
334
334
  "};\n\n"
335
335
  : "";
336
336
 
337
+ const vertexPrecision = "precision highp float;\n";
338
+
337
339
  const vertexParts = [
340
+ vertexPrecision,
338
341
  ...extraHeaders,
339
342
  GLSL_COMMON,
340
343
  GLSL_SCALES,
@@ -345,11 +348,6 @@ export default class Mark {
345
348
  vertexShader,
346
349
  ];
347
350
 
348
- if (vertexParts.some((code) => /[Ff]p64/.test(code))) {
349
- vertexParts.unshift(GLSL_SCALES_FP64);
350
- vertexParts.unshift(FP64);
351
- }
352
-
353
351
  const fragmentParts = [
354
352
  ...extraHeaders,
355
353
  GLSL_COMMON,
@@ -413,8 +411,8 @@ export default class Mark {
413
411
 
414
412
  const datum = encoder.indexer
415
413
  ? encoder.indexer(channelDef.datum)
416
- : encoder.scale.fp64
417
- ? fp64ify(+channelDef.datum)
414
+ : isHighPrecisionScale(encoder.scale.type)
415
+ ? splitHighPrecision(+channelDef.datum)
418
416
  : +channelDef.datum;
419
417
 
420
418
  setUniforms(this.programInfo, {
@@ -579,8 +577,8 @@ export default class Mark {
579
577
  : scale.domain();
580
578
 
581
579
  setter(
582
- scale.fp64
583
- ? domain.map((x) => fp64ify(x)).flat()
580
+ isHighPrecisionScale(scale.type)
581
+ ? toHighPrecisionDomainUniform(domain)
584
582
  : domain
585
583
  );
586
584
  }
@@ -618,13 +616,16 @@ export default class Mark {
618
616
  }
619
617
  }
620
618
 
619
+ if (!facetTexture) {
620
+ throw new Error("No facet texture available. This is bug.");
621
+ }
622
+
621
623
  setUniforms(this.programInfo, {
622
624
  uSampleFacetTexture: facetTexture,
623
625
  });
624
626
  }
625
627
 
626
628
  setUniforms(this.programInfo, {
627
- ONE: 1.0, // a hack needed by emulated 64 bit floats
628
629
  uDevicePixelRatio: this.glHelper.dpr,
629
630
  uViewOpacity: this.unitView.getEffectiveOpacity(),
630
631
  // TODO: Rendering of the mark should be completely skipped if it doesn't
@@ -748,7 +749,7 @@ export default class Mark {
748
749
  }
749
750
  };
750
751
  } else {
751
- const rangeEntry = rangeMapSource().get(options.facetId);
752
+ const rangeEntry = rangeMapSource()?.get(options.facetId);
752
753
  if (rangeEntry && rangeEntry.count) {
753
754
  return function renderStatic() {
754
755
  if (self.prepareSampleFacetRendering(options)) {
@@ -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
@@ -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 {