@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.
- package/dist/index.js +46 -119
- package/dist/schema.json +213 -22
- package/package.json +4 -3
- package/src/data/collector.js +9 -4
- package/src/data/collector.test.js +2 -0
- package/src/data/dataFlow.test.js +2 -0
- package/src/data/flow.test.js +1 -0
- package/src/data/flowNode.test.js +1 -0
- package/src/data/flowOptimizer.js +6 -0
- package/src/data/flowOptimizer.test.js +1 -0
- package/src/data/formats/fasta.test.js +1 -0
- package/src/data/sources/inlineSource.test.js +1 -0
- package/src/data/sources/sequenceSource.test.js +1 -0
- package/src/data/transforms/clone.test.js +1 -0
- package/src/data/transforms/coverage.test.js +1 -0
- package/src/data/transforms/filter.test.js +1 -0
- package/src/data/transforms/flattenDelimited.test.js +1 -0
- package/src/data/transforms/flattenSequence.test.js +1 -0
- package/src/data/transforms/formula.test.js +1 -0
- package/src/data/transforms/identifier.test.js +1 -0
- package/src/data/transforms/pileup.test.js +1 -0
- package/src/data/transforms/project.test.js +1 -0
- package/src/data/transforms/regexExtract.test.js +1 -0
- package/src/data/transforms/regexFold.test.js +1 -0
- package/src/data/transforms/sample.test.js +1 -0
- package/src/data/transforms/stack.test.js +1 -0
- package/src/encoder/accessor.test.js +1 -0
- package/src/encoder/encoder.test.js +1 -0
- package/src/genome/genome.js +14 -2
- package/src/genome/genome.test.js +36 -0
- package/src/genome/scaleIndex.js +3 -2
- package/src/genome/scaleIndex.test.js +23 -6
- package/src/genome/scaleLocus.test.js +1 -0
- package/src/genomeSpy.js +16 -11
- package/src/gl/dataToVertices.js +9 -6
- package/src/gl/includes/common.glsl +3 -3
- package/src/gl/includes/scales.glsl +33 -2
- package/src/gl/point.vertex.glsl +0 -2
- package/src/gl/rule.vertex.glsl +1 -1
- package/src/gl/webGLHelper.js +0 -3
- package/src/marks/mark.js +15 -14
- package/src/scale/glslScaleGenerator.js +56 -17
- package/src/scale/scale.test.js +1 -0
- package/src/scale/ticks.test.js +1 -0
- package/src/spec/scale.d.ts +0 -9
- package/src/spec/title.d.ts +102 -0
- package/src/spec/view.d.ts +6 -4
- package/src/tooltip/dataTooltipHandler.js +3 -2
- package/src/utils/addBaseUrl.test.js +1 -0
- package/src/utils/cloner.test.js +1 -0
- package/src/utils/coalesce.test.js +1 -0
- package/src/utils/concatIterables.test.js +1 -0
- package/src/utils/domainArray.test.js +1 -0
- package/src/utils/indexer.test.js +1 -0
- package/src/utils/iterateNestedMaps.test.js +1 -0
- package/src/utils/kWayMerge.test.js +1 -0
- package/src/utils/layout/flexLayout.js +35 -3
- package/src/utils/layout/flexLayout.test.js +15 -0
- package/src/utils/layout/grid.js +95 -0
- package/src/utils/layout/grid.test.js +71 -0
- package/src/utils/layout/padding.js +13 -0
- package/src/utils/layout/rectangle.js +6 -0
- package/src/utils/layout/rectangle.test.js +1 -0
- package/src/utils/mergeObjects.test.js +1 -0
- package/src/utils/numberExtractor.test.js +1 -0
- package/src/utils/propertyCacher.test.js +1 -0
- package/src/utils/propertyCoalescer.test.js +1 -0
- package/src/utils/reservationMap.test.js +1 -0
- package/src/utils/topK.test.js +1 -0
- package/src/utils/variableTools.test.js +1 -0
- package/src/view/axisResolution.test.js +1 -0
- package/src/view/axisView.js +3 -1
- package/src/view/concatView.js +24 -275
- package/src/view/flowBuilder.test.js +1 -0
- package/src/view/gridView.js +774 -0
- package/src/view/implicitRootView.js +14 -0
- package/src/view/layerView.js +15 -1
- package/src/view/scaleResolution.js +5 -11
- package/src/view/scaleResolution.test.js +1 -0
- package/src/view/title.js +165 -0
- package/src/view/unitView.js +17 -9
- package/src/view/view.js +35 -14
- package/src/view/view.test.js +1 -0
- package/src/view/viewContext.d.ts +6 -1
- package/src/view/viewFactory.test.js +1 -0
- package/src/view/viewUtils.js +1 -93
- package/src/view/zoom.js +89 -0
- package/src/gl/includes/fp64-arithmetic.glsl +0 -187
- package/src/gl/includes/fp64-utils.js +0 -142
- package/src/gl/includes/scales_fp64.glsl +0 -30
- 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
|
+
});
|
package/src/genome/scaleIndex.js
CHANGED
|
@@ -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) =>
|
|
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
|
-
|
|
7
|
-
expect(scale(
|
|
8
|
-
expect(scale(
|
|
9
|
-
expect(scale(
|
|
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);
|
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
|
|
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 (
|
|
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.
|
|
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
|
|
package/src/gl/dataToVertices.js
CHANGED
|
@@ -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
|
|
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
|
-
:
|
|
72
|
-
? (d) =>
|
|
74
|
+
: hp
|
|
75
|
+
? (d) => splitHighPrecision(accessor(d), doubleArray)
|
|
73
76
|
: accessor;
|
|
74
77
|
|
|
75
78
|
this.variableBuilder.addConverter(channel, {
|
|
76
79
|
f,
|
|
77
|
-
numComponents:
|
|
78
|
-
arrayReference:
|
|
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 =
|
|
77
|
-
start += (
|
|
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
|
+
}
|
package/src/gl/point.vertex.glsl
CHANGED
package/src/gl/rule.vertex.glsl
CHANGED
package/src/gl/webGLHelper.js
CHANGED
|
@@ -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.
|
|
417
|
-
?
|
|
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.
|
|
583
|
-
? domain
|
|
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()
|
|
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
|
|
113
|
-
const attributeType =
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
349
|
-
|
|
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
|
+
}
|
package/src/scale/scale.test.js
CHANGED
package/src/scale/ticks.test.js
CHANGED
package/src/spec/scale.d.ts
CHANGED
|
@@ -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 {
|