@jbrowse/render-core 5.0.0-beta.7 → 5.0.0-beta.9
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/esm/RenderCanvas.d.ts +2 -2
- package/esm/RenderLifecycleMixin.d.ts +7 -6
- package/esm/RenderLifecycleMixin.js +7 -6
- package/esm/canvas2dUtils.d.ts +58 -17
- package/esm/canvas2dUtils.js +82 -30
- package/esm/computePipeline.d.ts +4 -3
- package/esm/computePipeline.js +21 -14
- package/esm/coverageBand.d.ts +3 -3
- package/esm/coverageBand.js +2 -2
- package/esm/coverageBandBuffers.d.ts +2 -3
- package/esm/createRenderingBackend.d.ts +1 -1
- package/esm/displayPhase.d.ts +79 -109
- package/esm/displayPhase.js +42 -83
- package/esm/gpuDevice.d.ts +11 -10
- package/esm/gpuDevice.js +10 -11
- package/esm/graphicsCapabilities.d.ts +6 -7
- package/esm/graphicsCapabilities.js +3 -4
- package/esm/hal/createHal.js +1 -1
- package/esm/hal/deviceGpuCache.d.ts +25 -30
- package/esm/hal/deviceGpuCache.js +85 -97
- package/esm/hal/index.d.ts +2 -2
- package/esm/hal/index.js +1 -1
- package/esm/hal/mockHal.d.ts +8 -6
- package/esm/hal/mockHal.js +7 -2
- package/esm/hal/passIds.d.ts +8 -7
- package/esm/hal/passIds.js +11 -10
- package/esm/hal/types.d.ts +28 -12
- package/esm/hal/webgl2Hal.d.ts +11 -3
- package/esm/hal/webgl2Hal.js +116 -145
- package/esm/hal/webgpuHal.d.ts +8 -8
- package/esm/hal/webgpuHal.js +97 -124
- package/esm/instancePass.d.ts +2 -1
- package/esm/mapUploadSync.d.ts +2 -2
- package/esm/mapUploadSync.js +2 -2
- package/esm/marks/barMark.d.ts +6 -3
- package/esm/marks/barMark.js +19 -13
- package/esm/marks/drawAgainstHit.d.ts +58 -39
- package/esm/marks/drawAgainstHit.js +136 -96
- package/esm/marks/glyphPaint.d.ts +14 -2
- package/esm/marks/glyphPaint.js +16 -2
- package/esm/marks/index.d.ts +4 -2
- package/esm/marks/index.js +3 -2
- package/esm/marks/markBackend.d.ts +2 -2
- package/esm/marks/markBackend.js +2 -2
- package/esm/marks/markHit.d.ts +2 -2
- package/esm/marks/markHit.js +2 -2
- package/esm/marks/markPlan.d.ts +2 -2
- package/esm/marks/markPlan.js +2 -2
- package/esm/marks/markRamp.d.ts +1 -3
- package/esm/marks/markRamp.js +1 -5
- package/esm/marks/nearestMarkHit.d.ts +49 -0
- package/esm/marks/nearestMarkHit.js +126 -0
- package/esm/marks/pointMark.d.ts +9 -2
- package/esm/marks/pointMark.js +36 -31
- package/esm/marks/rowLane.d.ts +17 -0
- package/esm/marks/rowLane.js +11 -0
- package/esm/marks/spanMark.d.ts +6 -26
- package/esm/marks/spanMark.js +34 -31
- package/esm/marks/types.d.ts +51 -181
- package/esm/marks/types.js +91 -131
- package/esm/namedReactions.d.ts +3 -4
- package/esm/namedReactions.js +3 -4
- package/esm/scoreScale.d.ts +20 -0
- package/esm/scoreScale.js +77 -0
- package/esm/shaders/barMark.generated.d.ts +2 -3
- package/esm/shaders/barMark.generated.js +4 -3
- package/esm/shaders/barMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/barMark.glsl.generated.js +4 -0
- package/esm/shaders/barMark.iface.generated.d.ts +12 -6
- package/esm/shaders/barMark.iface.generated.js +27 -15
- package/esm/shaders/barMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/barMark.wgsl.generated.js +3 -0
- package/esm/shaders/coverageBar.generated.d.ts +2 -3
- package/esm/shaders/coverageBar.generated.js +4 -3
- package/esm/shaders/coverageBar.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageBar.glsl.generated.js +4 -0
- package/esm/shaders/coverageBar.iface.generated.js +1 -1
- package/esm/shaders/coverageBar.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageBar.wgsl.generated.js +3 -0
- package/esm/shaders/coverageIndicator.generated.d.ts +2 -3
- package/esm/shaders/coverageIndicator.generated.js +4 -3
- package/esm/shaders/coverageIndicator.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageIndicator.glsl.generated.js +4 -0
- package/esm/shaders/coverageIndicator.iface.generated.js +1 -1
- package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageIndicator.wgsl.generated.js +3 -0
- package/esm/shaders/coverageInterbase.generated.d.ts +2 -3
- package/esm/shaders/coverageInterbase.generated.js +4 -3
- package/esm/shaders/coverageInterbase.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageInterbase.glsl.generated.js +4 -0
- package/esm/shaders/coverageInterbase.iface.generated.js +1 -1
- package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageInterbase.wgsl.generated.js +3 -0
- package/esm/shaders/coverageMod.generated.d.ts +2 -3
- package/esm/shaders/coverageMod.generated.js +4 -3
- package/esm/shaders/coverageMod.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageMod.glsl.generated.js +4 -0
- package/esm/shaders/coverageMod.iface.generated.js +1 -1
- package/esm/shaders/coverageMod.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageMod.wgsl.generated.js +3 -0
- package/esm/shaders/coverageSnp.generated.d.ts +2 -3
- package/esm/shaders/coverageSnp.generated.js +4 -3
- package/esm/shaders/coverageSnp.glsl.generated.d.ts +2 -0
- package/esm/shaders/coverageSnp.glsl.generated.js +4 -0
- package/esm/shaders/coverageSnp.iface.generated.js +1 -1
- package/esm/shaders/coverageSnp.wgsl.generated.d.ts +1 -0
- package/esm/shaders/coverageSnp.wgsl.generated.js +3 -0
- package/esm/shaders/pointMark.generated.d.ts +2 -3
- package/esm/shaders/pointMark.generated.js +4 -3
- package/esm/shaders/pointMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/pointMark.glsl.generated.js +4 -0
- package/esm/shaders/pointMark.iface.generated.d.ts +11 -3
- package/esm/shaders/pointMark.iface.generated.js +23 -9
- package/esm/shaders/pointMark.js.generated.d.ts +1 -1
- package/esm/shaders/pointMark.js.generated.js +51 -29
- package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/pointMark.wgsl.generated.js +3 -0
- package/esm/shaders/scoreScale.js.generated.js +46 -22
- package/esm/shaders/spanMark.generated.d.ts +2 -3
- package/esm/shaders/spanMark.generated.js +4 -3
- package/esm/shaders/spanMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/spanMark.glsl.generated.js +4 -0
- package/esm/shaders/spanMark.iface.generated.js +1 -1
- package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/spanMark.wgsl.generated.js +3 -0
- package/esm/slangPass.d.ts +9 -10
- package/esm/slangPass.js +9 -3
- package/esm/useGraphicsCapabilities.d.ts +1 -1
- package/esm/useGraphicsCapabilities.js +1 -1
- package/esm/webgpuUtils.d.ts +3 -11
- package/esm/webgpuUtils.js +12 -33
- package/package.json +3 -2
- package/src/shaders/barMark.generated.ts +6 -5
- package/src/shaders/barMark.glsl.generated.ts +6 -0
- package/src/shaders/barMark.iface.generated.ts +31 -15
- package/src/shaders/barMark.slang +11 -3
- package/src/shaders/barMark.wgsl.generated.ts +4 -0
- package/src/shaders/colorPack.slang +3 -2
- package/src/shaders/coverageBand.slang +5 -11
- package/src/shaders/coverageBar.generated.ts +6 -5
- package/src/shaders/coverageBar.glsl.generated.ts +6 -0
- package/src/shaders/coverageBar.iface.generated.ts +1 -1
- package/src/shaders/coverageBar.slang +1 -1
- package/src/shaders/coverageBar.wgsl.generated.ts +4 -0
- package/src/shaders/coverageIndicator.generated.ts +6 -5
- package/src/shaders/coverageIndicator.glsl.generated.ts +6 -0
- package/src/shaders/coverageIndicator.iface.generated.ts +1 -1
- package/src/shaders/coverageIndicator.wgsl.generated.ts +4 -0
- package/src/shaders/coverageInterbase.generated.ts +6 -5
- package/src/shaders/coverageInterbase.glsl.generated.ts +6 -0
- package/src/shaders/coverageInterbase.iface.generated.ts +1 -1
- package/src/shaders/coverageInterbase.wgsl.generated.ts +4 -0
- package/src/shaders/coverageMod.generated.ts +6 -5
- package/src/shaders/coverageMod.glsl.generated.ts +6 -0
- package/src/shaders/coverageMod.iface.generated.ts +1 -1
- package/src/shaders/coverageMod.wgsl.generated.ts +4 -0
- package/src/shaders/coverageSnp.generated.ts +6 -5
- package/src/shaders/coverageSnp.glsl.generated.ts +6 -0
- package/src/shaders/coverageSnp.iface.generated.ts +1 -1
- package/src/shaders/coverageSnp.wgsl.generated.ts +4 -0
- package/src/shaders/hpmath.slang +18 -6
- package/src/shaders/pointMark.generated.ts +6 -6
- package/src/shaders/pointMark.glsl.generated.ts +6 -0
- package/src/shaders/pointMark.iface.generated.ts +28 -9
- package/src/shaders/pointMark.js.generated.ts +48 -29
- package/src/shaders/pointMark.slang +18 -5
- package/src/shaders/pointMark.wgsl.generated.ts +4 -0
- package/src/shaders/scoreScale.js.generated.ts +43 -22
- package/src/shaders/scoreScale.slang +37 -53
- package/src/shaders/spanMark.generated.ts +6 -5
- package/src/shaders/spanMark.glsl.generated.ts +6 -0
- package/src/shaders/spanMark.iface.generated.ts +1 -1
- package/src/shaders/spanMark.wgsl.generated.ts +4 -0
- package/src/shaders/valueScale.slang +8 -45
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { bpAtPxExact } from '../canvas2dUtils.js';
|
|
2
|
+
import { denormalizeScore, scaleTypeCode } from '../scoreScale.js';
|
|
3
|
+
const EVERY_VALUE = [-Infinity, Infinity];
|
|
4
|
+
/**
|
|
5
|
+
* The instance nearest `(xPx, yPx)` within `radiusPx`, over the blocks the
|
|
6
|
+
* radius reaches. Marks are asked last to first and only a strictly nearer hit
|
|
7
|
+
* replaces the best, so a tie goes to the mark on top. `candidates` names what
|
|
8
|
+
* a mark is asked about in a block: what an index finds in the reach, or
|
|
9
|
+
* `backToFront` over every instance. `undefined` leaves the mark out, as a
|
|
10
|
+
* closed gate does.
|
|
11
|
+
*/
|
|
12
|
+
export function nearestMarkHit(marks, blocks, regionOf, state, xPx, yPx, { radiusPx, candidates, }) {
|
|
13
|
+
let best;
|
|
14
|
+
let bestDistSq = radiusPx * radiusPx;
|
|
15
|
+
for (const block of blocks) {
|
|
16
|
+
const region = regionOf(block.displayedRegionIndex);
|
|
17
|
+
const widthPx = block.screenEndPx - block.screenStartPx;
|
|
18
|
+
if (region === undefined || widthPx <= 0) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const halfBp = (radiusPx * (block.end - block.start)) / widthPx;
|
|
22
|
+
const cursorBp = bpAtPxExact(xPx, block);
|
|
23
|
+
const bpMin = cursorBp - halfBp;
|
|
24
|
+
const bpMax = cursorBp + halfBp;
|
|
25
|
+
if (bpMax < block.start || bpMin > block.end) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (let m = marks.length - 1; m >= 0; m--) {
|
|
29
|
+
const mark = marks[m];
|
|
30
|
+
if (!mark.hitNearest) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const values = mark.valueWindow
|
|
34
|
+
? mark.valueWindow(region, block, state, yPx, radiusPx)
|
|
35
|
+
: EVERY_VALUE;
|
|
36
|
+
const asked = values &&
|
|
37
|
+
candidates(region, m, {
|
|
38
|
+
block,
|
|
39
|
+
bpMin,
|
|
40
|
+
bpMax,
|
|
41
|
+
valueMin: values[0],
|
|
42
|
+
valueMax: values[1],
|
|
43
|
+
});
|
|
44
|
+
const hit = asked &&
|
|
45
|
+
mark.hitNearest(region, block, state, xPx, yPx, asked, bestDistSq);
|
|
46
|
+
if (hit) {
|
|
47
|
+
bestDistSq = hit.distSq;
|
|
48
|
+
best = {
|
|
49
|
+
mark: m,
|
|
50
|
+
block,
|
|
51
|
+
region,
|
|
52
|
+
index: hit.index,
|
|
53
|
+
x: hit.x,
|
|
54
|
+
y: hit.y,
|
|
55
|
+
distSq: hit.distSq,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return best;
|
|
61
|
+
}
|
|
62
|
+
/** `[end - 1 .. start]`: every instance as candidates, back to front. */
|
|
63
|
+
export function backToFront(start, end) {
|
|
64
|
+
return new ReverseRange(start, end);
|
|
65
|
+
}
|
|
66
|
+
// A class rather than a generator: `for..of` steps this in a few ns where a
|
|
67
|
+
// generator yields in tens.
|
|
68
|
+
class ReverseRange {
|
|
69
|
+
i;
|
|
70
|
+
start;
|
|
71
|
+
result = { value: 0, done: false };
|
|
72
|
+
constructor(start, end) {
|
|
73
|
+
this.start = start;
|
|
74
|
+
this.i = end;
|
|
75
|
+
}
|
|
76
|
+
[Symbol.iterator]() {
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
next() {
|
|
80
|
+
const r = this.result;
|
|
81
|
+
if (this.i > this.start) {
|
|
82
|
+
this.i--;
|
|
83
|
+
r.value = this.i;
|
|
84
|
+
r.done = false;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
r.done = true;
|
|
88
|
+
}
|
|
89
|
+
return r;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A value-scaled shape's `valueWindow`, read back through `denormalizeScore`
|
|
94
|
+
* inside each `rowHeight` band the radius touches — the whole canvas for a
|
|
95
|
+
* shape with no rows. An end within reach of a band edge opens to infinity,
|
|
96
|
+
* where an out-of-domain value clamps, so a cursor near the seam reaches the
|
|
97
|
+
* neighbouring band too. A point passes its `insetPx`; a bar passes its
|
|
98
|
+
* `origin`, and its window opens away from the origin on the cursor's side.
|
|
99
|
+
*/
|
|
100
|
+
export function valueWindow(yPx, radiusPx, { canvasHeight }, scale) {
|
|
101
|
+
const { domain, insetPx = 0, origin } = scale;
|
|
102
|
+
const code = scaleTypeCode(scale.scaleType);
|
|
103
|
+
const height = scale.rowHeight ?? canvasHeight;
|
|
104
|
+
const inset = Math.min(insetPx, height / 2);
|
|
105
|
+
const bands = height > 0 ? Math.ceil(canvasHeight / height) : 1;
|
|
106
|
+
const bandAt = (y) => bands > 1 ? Math.max(0, Math.min(bands - 1, Math.floor(y / height))) : 0;
|
|
107
|
+
const lastBand = bandAt(yPx + radiusPx);
|
|
108
|
+
let valueMin = Infinity;
|
|
109
|
+
let valueMax = -Infinity;
|
|
110
|
+
for (let b = bandAt(yPx - radiusPx); b <= lastBand; b++) {
|
|
111
|
+
const top = b * height;
|
|
112
|
+
const valueAt = (y) => denormalizeScore(1 - (y - top - inset) / (height - 2 * inset), domain[0], domain[1], code, 1);
|
|
113
|
+
const lo = yPx + radiusPx >= top + height - inset
|
|
114
|
+
? -Infinity
|
|
115
|
+
: valueAt(yPx + radiusPx);
|
|
116
|
+
const hi = yPx - radiusPx <= top + inset ? Infinity : valueAt(yPx - radiusPx);
|
|
117
|
+
const [bandMin, bandMax] = origin === undefined
|
|
118
|
+
? [lo, hi]
|
|
119
|
+
: valueAt(yPx) >= origin
|
|
120
|
+
? [lo, Infinity]
|
|
121
|
+
: [-Infinity, hi];
|
|
122
|
+
valueMin = Math.min(valueMin, bandMin);
|
|
123
|
+
valueMax = Math.max(valueMax, bandMax);
|
|
124
|
+
}
|
|
125
|
+
return [valueMin, valueMax];
|
|
126
|
+
}
|
package/esm/marks/pointMark.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ColorChannel } from './markRamp.ts';
|
|
2
|
+
import type { RowChannel, RowParams } from './rowLane.ts';
|
|
2
3
|
import type { MarkRamp, MarkShape, MarkValueScaleType } from './types.ts';
|
|
3
4
|
/**
|
|
4
5
|
* The `point` shape's channels: a glyph per instance at `x`, on the `y` scale,
|
|
@@ -8,14 +9,14 @@ import type { MarkRamp, MarkShape, MarkValueScaleType } from './types.ts';
|
|
|
8
9
|
* one array holds SNPs (`x2 === x + 1`) and structural variants together, and
|
|
9
10
|
* the shader, the painter and the hit test all take the same branch off it.
|
|
10
11
|
*/
|
|
11
|
-
export interface PointChannels extends ColorChannel {
|
|
12
|
+
export interface PointChannels extends ColorChannel, RowChannel {
|
|
12
13
|
x: Uint32Array;
|
|
13
14
|
x2: Uint32Array;
|
|
14
15
|
y: Float32Array;
|
|
15
16
|
glyph: Uint8Array;
|
|
16
17
|
count: number;
|
|
17
18
|
}
|
|
18
|
-
export interface PointParams {
|
|
19
|
+
export interface PointParams extends RowParams {
|
|
19
20
|
/** `[min, max]` `y` is read through. */
|
|
20
21
|
domain: [number, number];
|
|
21
22
|
/** How that domain is read; linear when absent. */
|
|
@@ -24,5 +25,11 @@ export interface PointParams {
|
|
|
24
25
|
ramp?: MarkRamp;
|
|
25
26
|
/** Glyph diameter in CSS px. */
|
|
26
27
|
diameterPx: number;
|
|
28
|
+
/**
|
|
29
|
+
* How far inside the plot the value range ends, so a point at a domain
|
|
30
|
+
* endpoint draws whole — `glyphPaint`'s `pointInsetPx`, and the same number
|
|
31
|
+
* the display's axis takes as its offset. Absent centres it on the edge.
|
|
32
|
+
*/
|
|
33
|
+
insetPx?: number;
|
|
27
34
|
}
|
|
28
35
|
export declare const pointMark: MarkShape<PointChannels, PointParams>;
|
package/esm/marks/pointMark.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { bpRangeXTuple } from '../blockClipUtils.js';
|
|
2
|
-
import { getDpr, makeBpMapper } from '../canvas2dUtils.js';
|
|
2
|
+
import { CappedPath, getDpr, makeBpMapper } from '../canvas2dUtils.js';
|
|
3
|
+
import { scaleTypeCode } from '../scoreScale.js';
|
|
3
4
|
import * as shader from '../shaders/pointMark.generated.js';
|
|
4
|
-
import { pointDrawsBar,
|
|
5
|
+
import { pointDrawsBar, pointYPx } from '../shaders/pointMark.js.generated.js';
|
|
5
6
|
import { slangPass } from '../slangPass.js';
|
|
6
7
|
import { abgrToCssRgba } from './colorFill.js';
|
|
7
8
|
import { appendGlyph, glyphBox } from './glyphPaint.js';
|
|
8
9
|
import { inkAtPoint, inkOnRect, nearestInk } from './markHit.js';
|
|
9
|
-
import { colorBits, paintColors, rampUniforms
|
|
10
|
-
import {
|
|
10
|
+
import { colorBits, paintColors, rampUniforms } from './markRamp.js';
|
|
11
|
+
import { valueWindow } from './nearestMarkHit.js';
|
|
12
|
+
import { bandHeightPx, bandTopPx, rowLane } from './rowLane.js';
|
|
11
13
|
export const pointMark = {
|
|
12
14
|
id: 'point',
|
|
13
15
|
pass: {
|
|
14
16
|
...slangPass({ id: 'point', mod: shader }),
|
|
15
|
-
pack: c => shader.packInstances({ ...c, color: colorBits(c) }, c.count),
|
|
17
|
+
pack: c => shader.packInstances({ ...c, color: colorBits(c), row: rowLane(c.row, c.count) }, c.count),
|
|
16
18
|
},
|
|
17
19
|
writeUniforms(scratch, clip, block, frame, params) {
|
|
18
20
|
shader.writeUniforms(scratch, {
|
|
@@ -20,7 +22,7 @@ export const pointMark = {
|
|
|
20
22
|
canvasHeight: frame.canvasHeight,
|
|
21
23
|
domainMin: params.domain[0],
|
|
22
24
|
domainMax: params.domain[1],
|
|
23
|
-
valueScaleType:
|
|
25
|
+
valueScaleType: scaleTypeCode(params.scaleType),
|
|
24
26
|
...rampUniforms(params.ramp),
|
|
25
27
|
zero: 0,
|
|
26
28
|
// viewportWidth and radiusPx stay in CSS units to match canvasHeight:
|
|
@@ -28,41 +30,39 @@ export const pointMark = {
|
|
|
28
30
|
// stretched ellipses on hi-DPI.
|
|
29
31
|
viewportWidth: clip.scissorW,
|
|
30
32
|
radiusPx: params.diameterPx / 2,
|
|
33
|
+
rowHeight: bandHeightPx(params, frame.canvasHeight),
|
|
34
|
+
insetPx: params.insetPx ?? 0,
|
|
31
35
|
devicePixelRatio: getDpr(),
|
|
32
36
|
});
|
|
33
37
|
},
|
|
34
38
|
paintBlock(ctx, channels, block, frame, params) {
|
|
35
|
-
const { x, x2, y, glyph, count } = channels;
|
|
39
|
+
const { x, x2, y, glyph, row, count } = channels;
|
|
36
40
|
if (count === 0) {
|
|
37
41
|
return;
|
|
38
42
|
}
|
|
39
43
|
const color = paintColors(channels, count, params.ramp);
|
|
40
|
-
const { diameterPx, domain } = params;
|
|
44
|
+
const { diameterPx, domain, insetPx = 0 } = params;
|
|
41
45
|
const r = diameterPx / 2;
|
|
42
|
-
const
|
|
46
|
+
const band = bandHeightPx(params, frame.canvasHeight);
|
|
43
47
|
const domainMin = domain[0];
|
|
44
48
|
const domainMax = domain[1];
|
|
45
|
-
const st =
|
|
46
|
-
// The per-block closure, not the six-argument `bpToScreenPx`: that spelling
|
|
47
|
-
// re-derives the region span and the block width at every call, and this
|
|
48
|
-
// loop makes two calls per instance. Measured at 1.67x on 100K points.
|
|
49
|
+
const st = scaleTypeCode(params.scaleType);
|
|
49
50
|
const bpToPx = makeBpMapper(block);
|
|
50
|
-
// Batched by colour: a run of one colour is one fillStyle write and one
|
|
51
|
-
// fill() over a shared path, which is most of a painting.
|
|
52
51
|
let current = color[0];
|
|
53
52
|
ctx.fillStyle = abgrToCssRgba(current);
|
|
54
|
-
ctx
|
|
53
|
+
const path = new CappedPath(ctx, 'fill');
|
|
55
54
|
for (let i = 0; i < count; i++) {
|
|
56
55
|
const abgr = color[i];
|
|
57
56
|
if (abgr !== current) {
|
|
58
|
-
|
|
57
|
+
path.flush();
|
|
59
58
|
current = abgr;
|
|
60
59
|
ctx.fillStyle = abgrToCssRgba(abgr);
|
|
61
|
-
ctx.beginPath();
|
|
62
60
|
}
|
|
61
|
+
path.add();
|
|
63
62
|
const xStart = bpToPx(x[i]);
|
|
64
63
|
const xEnd = bpToPx(x2[i]);
|
|
65
|
-
const yPx =
|
|
64
|
+
const yPx = bandTopPx(row, i, band) +
|
|
65
|
+
pointYPx(y[i], domainMin, domainMax, band, st, insetPx);
|
|
66
66
|
const widthPx = Math.abs(xEnd - xStart);
|
|
67
67
|
if (pointDrawsBar(widthPx, r)) {
|
|
68
68
|
ctx.rect(Math.min(xStart, xEnd), yPx - r, widthPx, diameterPx);
|
|
@@ -71,19 +71,22 @@ export const pointMark = {
|
|
|
71
71
|
appendGlyph(ctx, glyph[i], xStart, yPx, diameterPx);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
path.flush();
|
|
75
75
|
},
|
|
76
76
|
// A bar is the rect the painter fills, unpadded on every side; a glyph is
|
|
77
77
|
// the box `appendGlyph` paints inside.
|
|
78
78
|
ink(channels, block, frame, params, i) {
|
|
79
|
-
const { x, x2, y, glyph } = channels;
|
|
80
|
-
const { diameterPx, domain } = params;
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
79
|
+
const { x, x2, y, glyph, row } = channels;
|
|
80
|
+
const { diameterPx, domain, insetPx = 0 } = params;
|
|
81
|
+
const bpToPx = makeBpMapper(block);
|
|
82
|
+
const xStart = bpToPx(x[i]);
|
|
83
|
+
const xEnd = bpToPx(x2[i]);
|
|
84
|
+
const r = diameterPx / 2;
|
|
85
|
+
const band = bandHeightPx(params, frame.canvasHeight);
|
|
86
|
+
const cy = bandTopPx(row, i, band) +
|
|
87
|
+
pointYPx(y[i], domain[0], domain[1], band, scaleTypeCode(params.scaleType), insetPx);
|
|
84
88
|
const lo = Math.min(xStart, xEnd);
|
|
85
89
|
const hi = Math.max(xStart, xEnd);
|
|
86
|
-
const r = diameterPx / 2;
|
|
87
90
|
return pointDrawsBar(hi - lo, r)
|
|
88
91
|
? { left: lo, top: cy - r, width: hi - lo, height: diameterPx }
|
|
89
92
|
: glyphBox(glyph[i], xStart, cy, diameterPx);
|
|
@@ -93,17 +96,18 @@ export const pointMark = {
|
|
|
93
96
|
// one under the cursor is the nearest CENTRE, which no box can say. A bar
|
|
94
97
|
// is grabbed anywhere inside the rect it fills, as the derived test would.
|
|
95
98
|
hitNearest(channels, block, frame, params, xPx, yPx, candidates, maxDistSq) {
|
|
96
|
-
const { x, x2, y } = channels;
|
|
99
|
+
const { x, x2, y, row } = channels;
|
|
97
100
|
const bpToPx = makeBpMapper(block);
|
|
98
|
-
const { diameterPx, domain } = params;
|
|
101
|
+
const { diameterPx, domain, insetPx = 0 } = params;
|
|
99
102
|
const domainMin = domain[0];
|
|
100
103
|
const domainMax = domain[1];
|
|
101
|
-
const
|
|
102
|
-
const st =
|
|
104
|
+
const band = bandHeightPx(params, frame.canvasHeight);
|
|
105
|
+
const st = scaleTypeCode(params.scaleType);
|
|
103
106
|
return nearestInk(candidates, maxDistSq, i => {
|
|
104
107
|
const xStart = bpToPx(x[i]);
|
|
105
108
|
const xEnd = bpToPx(x2[i]);
|
|
106
|
-
const cy =
|
|
109
|
+
const cy = bandTopPx(row, i, band) +
|
|
110
|
+
pointYPx(y[i], domainMin, domainMax, band, st, insetPx);
|
|
107
111
|
const lo = Math.min(xStart, xEnd);
|
|
108
112
|
const hi = Math.max(xStart, xEnd);
|
|
109
113
|
return pointDrawsBar(hi - lo, diameterPx / 2)
|
|
@@ -111,4 +115,5 @@ export const pointMark = {
|
|
|
111
115
|
: inkAtPoint(xPx, yPx, xStart, cy);
|
|
112
116
|
});
|
|
113
117
|
},
|
|
118
|
+
valueWindow,
|
|
114
119
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `row` lane a value shape may carry: which band an instance stands in,
|
|
3
|
+
* `rowHeight` px each, the value scale ruling every band on its own. A caller
|
|
4
|
+
* that declares no rows is on row 0 with the whole canvas for a band, which is
|
|
5
|
+
* the same arithmetic, so the shaders take the lane unconditionally.
|
|
6
|
+
*/
|
|
7
|
+
export interface RowChannel {
|
|
8
|
+
row?: Uint32Array;
|
|
9
|
+
}
|
|
10
|
+
export interface RowParams {
|
|
11
|
+
/** CSS px per row band; the canvas height when absent. */
|
|
12
|
+
rowHeight?: number;
|
|
13
|
+
}
|
|
14
|
+
/** The lane the instance buffer packs: zeros where the caller sent none. */
|
|
15
|
+
export declare function rowLane(row: Uint32Array | undefined, count: number): Uint32Array<ArrayBufferLike>;
|
|
16
|
+
export declare function bandHeightPx(params: RowParams, canvasHeight: number): number;
|
|
17
|
+
export declare function bandTopPx(row: Uint32Array | undefined, i: number, band: number): number;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const NO_ROWS = new Uint32Array(0);
|
|
2
|
+
/** The lane the instance buffer packs: zeros where the caller sent none. */
|
|
3
|
+
export function rowLane(row, count) {
|
|
4
|
+
return row ?? (count === 0 ? NO_ROWS : new Uint32Array(count));
|
|
5
|
+
}
|
|
6
|
+
export function bandHeightPx(params, canvasHeight) {
|
|
7
|
+
return params.rowHeight ?? canvasHeight;
|
|
8
|
+
}
|
|
9
|
+
export function bandTopPx(row, i, band) {
|
|
10
|
+
return row === undefined ? 0 : band * row[i];
|
|
11
|
+
}
|
package/esm/marks/spanMark.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { RenderBlock } from '../renderBlock.ts';
|
|
2
1
|
import type { MarkShape } from './types.ts';
|
|
3
2
|
/**
|
|
4
3
|
* The `span` shape's channels: a coloured rectangle from `x` to `x2` on the
|
|
@@ -8,12 +7,7 @@ export interface SpanChannels {
|
|
|
8
7
|
x: Uint32Array;
|
|
9
8
|
x2: Uint32Array;
|
|
10
9
|
row: Uint32Array;
|
|
11
|
-
/**
|
|
12
|
-
* Packed ABGR, resolved in the worker — a span's colour has no ramp arm.
|
|
13
|
-
* `rowRect`'s uniform struct is shared with four other consumers, and a
|
|
14
|
-
* sampler on it would move all of them for a channel no config asks for
|
|
15
|
-
* on a span (ADR-113).
|
|
16
|
-
*/
|
|
10
|
+
/** Packed ABGR, resolved in the worker: a span has no ramp arm (ADR-113). */
|
|
17
11
|
color: Uint32Array;
|
|
18
12
|
count: number;
|
|
19
13
|
}
|
|
@@ -23,31 +17,17 @@ export interface SpanParams {
|
|
|
23
17
|
/** Fraction of the row the rect fills, leaving inter-row gaps. */
|
|
24
18
|
rowProportion: number;
|
|
25
19
|
/**
|
|
26
|
-
* Narrowest a span is painted, in CSS px.
|
|
27
|
-
*
|
|
28
|
-
* paints ink the data does not contain. A caller whose marks are sparse
|
|
29
|
-
* intervals floors, because at chromosome zoom the alternative is a smudge.
|
|
20
|
+
* Narrowest a span is painted, in CSS px. A caller whose marks tile writes
|
|
21
|
+
* zero; one whose marks are sparse intervals floors them.
|
|
30
22
|
*/
|
|
31
23
|
minWidthPx: number;
|
|
32
24
|
/**
|
|
33
|
-
* CSS px
|
|
34
|
-
*
|
|
35
|
-
* The GPU pass needs none
|
|
36
|
-
* the rasterizer fills it once. Canvas2D antialiases each `fillRect`
|
|
37
|
-
* independently, so two runs meeting on a fractional pixel each take partial
|
|
38
|
-
* coverage of it and a hairline of background shows through. Which callers
|
|
39
|
-
* want it splits on the same axis `minWidthPx` does — MAF's cells tile, so it
|
|
40
|
-
* pads; the multi-row painter's features are sparse intervals with background
|
|
41
|
-
* between them by right, so it writes 0.
|
|
42
|
-
*
|
|
43
|
-
* Padding the drawn width and not the anchor is what keeps it growing
|
|
44
|
-
* rightward on both orientations, so a reversed block's spans stay exact
|
|
45
|
-
* mirrors of a forward block's. Wiggle's `WIGGLE_FUDGE_FACTOR` is the same
|
|
46
|
-
* rule spelled per display; the shader must not grow a matching pad.
|
|
25
|
+
* CSS px the **painter alone** adds to each span's right edge, closing the
|
|
26
|
+
* hairline two antialiased `fillRect`s leave where tiling runs meet on a
|
|
27
|
+
* fractional pixel. The GPU pass needs none, and the ink excludes it.
|
|
47
28
|
*/
|
|
48
29
|
seamPx: number;
|
|
49
30
|
/** Rows-area scroll offset in CSS px; 0 for a canvas sized to its content. */
|
|
50
31
|
scrollTop: number;
|
|
51
32
|
}
|
|
52
|
-
export declare function blockPx(block: RenderBlock, bp: number): number;
|
|
53
33
|
export declare const spanMark: MarkShape<SpanChannels, SpanParams>;
|
package/esm/marks/spanMark.js
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
import { bpRangeXTuple } from '../blockClipUtils.js';
|
|
2
|
-
import {
|
|
2
|
+
import { bpProjection, projectBp, spanLeft } from '../canvas2dUtils.js';
|
|
3
3
|
import { drawnRowHeightPx, rowBandOffsetPx, } from '../shaders/rowRect.js.generated.js';
|
|
4
4
|
import * as shader from '../shaders/spanMark.generated.js';
|
|
5
5
|
import { slangPass } from '../slangPass.js';
|
|
6
6
|
import { makeAbgrFill } from './colorFill.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
function spanFrame(block, params) {
|
|
8
|
+
const { rowHeight, rowProportion } = params;
|
|
9
|
+
const { originPx, startBp, spanBp, signedSpanPx } = bpProjection(block);
|
|
10
|
+
return {
|
|
11
|
+
originPx,
|
|
12
|
+
startBp,
|
|
13
|
+
spanBp,
|
|
14
|
+
signedSpanPx,
|
|
15
|
+
rowHeight,
|
|
16
|
+
scrollTop: params.scrollTop,
|
|
17
|
+
bandOffsetPx: rowBandOffsetPx(rowHeight, rowProportion),
|
|
18
|
+
minWidthPx: params.minWidthPx,
|
|
19
|
+
left: 0,
|
|
20
|
+
top: 0,
|
|
21
|
+
width: 0,
|
|
22
|
+
height: drawnRowHeightPx(rowHeight, rowProportion),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function placeSpan(c, g, i) {
|
|
26
|
+
const xa = projectBp(g, c.x[i]);
|
|
27
|
+
const xb = projectBp(g, c.x2[i]);
|
|
28
|
+
const width = Math.max(g.minWidthPx, Math.abs(xb - xa));
|
|
29
|
+
g.left = spanLeft(xa, xb, width);
|
|
30
|
+
g.top = g.bandOffsetPx + g.rowHeight * c.row[i] - g.scrollTop;
|
|
31
|
+
g.width = width;
|
|
12
32
|
}
|
|
13
33
|
export const spanMark = {
|
|
14
34
|
id: 'span',
|
|
@@ -20,9 +40,6 @@ export const spanMark = {
|
|
|
20
40
|
shader.writeUniforms(scratch, {
|
|
21
41
|
bpRangeX: bpRangeXTuple(clip, block.reversed),
|
|
22
42
|
canvasHeight: frame.canvasHeight,
|
|
23
|
-
// CSS px, not physical: `extendToMinWidthX` divides `minCellPx` by this
|
|
24
|
-
// to reach clip space, so a CSS width is what makes the floor a CSS-pixel
|
|
25
|
-
// one and matches the painter's `Math.max` below.
|
|
26
43
|
minCellDenomPx: clip.scissorW,
|
|
27
44
|
minCellPx: params.minWidthPx,
|
|
28
45
|
zero: 0,
|
|
@@ -32,33 +49,19 @@ export const spanMark = {
|
|
|
32
49
|
});
|
|
33
50
|
},
|
|
34
51
|
paintBlock(ctx, channels, block, _frame, params) {
|
|
35
|
-
const {
|
|
36
|
-
const {
|
|
37
|
-
const
|
|
38
|
-
const offset = rowBandOffsetPx(rowHeight, rowProportion);
|
|
39
|
-
const bpToPx = makeBpMapper(block);
|
|
52
|
+
const { color, count } = channels;
|
|
53
|
+
const { seamPx } = params;
|
|
54
|
+
const g = spanFrame(block, params);
|
|
40
55
|
const setFill = makeAbgrFill(ctx);
|
|
41
56
|
for (let i = 0; i < count; i++) {
|
|
42
|
-
|
|
43
|
-
const xb = bpToPx(x2[i]);
|
|
44
|
-
const width = Math.max(minWidthPx, Math.abs(xb - xa));
|
|
57
|
+
placeSpan(channels, g, i);
|
|
45
58
|
setFill(color[i]);
|
|
46
|
-
ctx.fillRect(
|
|
59
|
+
ctx.fillRect(g.left, g.top, g.width + seamPx, g.height);
|
|
47
60
|
}
|
|
48
61
|
},
|
|
49
62
|
ink(channels, block, _frame, params, i) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const xb = blockPx(block, x2[i]);
|
|
54
|
-
const width = Math.max(minWidthPx, Math.abs(xb - xa));
|
|
55
|
-
return {
|
|
56
|
-
left: spanLeft(xa, xb, width),
|
|
57
|
-
top: rowBandOffsetPx(rowHeight, rowProportion) +
|
|
58
|
-
rowHeight * row[i] -
|
|
59
|
-
scrollTop,
|
|
60
|
-
width,
|
|
61
|
-
height: drawnRowHeightPx(rowHeight, rowProportion),
|
|
62
|
-
};
|
|
63
|
+
const g = spanFrame(block, params);
|
|
64
|
+
placeSpan(channels, g, i);
|
|
65
|
+
return { left: g.left, top: g.top, width: g.width, height: g.height };
|
|
63
66
|
},
|
|
64
67
|
};
|