@jbrowse/render-core 5.0.0-beta.10 → 5.0.0-beta.13
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 +1 -1
- package/esm/RenderCanvas.js +1 -1
- package/esm/canvas2dUtils.js +1 -1
- package/esm/createRenderingBackend.d.ts +2 -2
- package/esm/createRenderingBackend.js +2 -2
- package/esm/graphicsCapabilities.js +1 -1
- package/esm/hal/createHal.js +1 -1
- package/esm/hal/deviceGpuCache.js +8 -11
- package/esm/hal/types.d.ts +0 -2
- package/esm/hal/webgl2Hal.d.ts +1 -1
- package/esm/hal/webgl2Hal.js +4 -4
- package/esm/hal/webgpuHal.js +2 -3
- package/esm/marks/barMark.js +4 -2
- package/esm/marks/index.d.ts +4 -4
- package/esm/marks/index.js +3 -3
- package/esm/marks/lineMark.d.ts +6 -0
- package/esm/marks/lineMark.js +34 -21
- package/esm/marks/linkMark.d.ts +21 -1
- package/esm/marks/linkMark.js +25 -6
- package/esm/marks/markPlan.d.ts +4 -3
- package/esm/marks/markPlan.js +5 -4
- package/esm/marks/markRamp.d.ts +4 -2
- package/esm/marks/markRamp.js +14 -6
- package/esm/marks/rowLane.d.ts +2 -0
- package/esm/marks/rowLane.js +7 -3
- package/esm/marks/rowSpanIndex.d.ts +3 -2
- package/esm/marks/rowSpanIndex.js +18 -8
- package/esm/marks/spanMark.d.ts +4 -3
- package/esm/marks/spanMark.js +3 -3
- package/esm/marks/types.d.ts +4 -1
- package/esm/marks/types.js +3 -1
- package/esm/shaders/barMark.glsl.generated.d.ts +1 -1
- package/esm/shaders/barMark.glsl.generated.js +1 -1
- package/esm/shaders/barMark.wgsl.generated.d.ts +1 -1
- package/esm/shaders/barMark.wgsl.generated.js +1 -1
- package/esm/shaders/curveDistance.js.generated.d.ts +1 -0
- package/esm/shaders/curveDistance.js.generated.js +9 -0
- package/esm/shaders/insertionGlyph.generated.d.ts +6 -0
- package/esm/shaders/insertionGlyph.generated.js +8 -0
- package/esm/shaders/lineCenterMark.glsl.generated.d.ts +2 -2
- package/esm/shaders/lineCenterMark.glsl.generated.js +2 -2
- package/esm/shaders/lineCenterMark.iface.generated.d.ts +11 -9
- package/esm/shaders/lineCenterMark.iface.generated.js +20 -18
- package/esm/shaders/lineCenterMark.wgsl.generated.d.ts +1 -1
- package/esm/shaders/lineCenterMark.wgsl.generated.js +1 -1
- package/esm/shaders/lineCommon.js.generated.d.ts +1 -0
- package/esm/shaders/lineCommon.js.generated.js +20 -0
- package/esm/shaders/lineStepMark.glsl.generated.d.ts +2 -2
- package/esm/shaders/lineStepMark.glsl.generated.js +2 -2
- package/esm/shaders/lineStepMark.iface.generated.d.ts +11 -9
- package/esm/shaders/lineStepMark.iface.generated.js +20 -18
- package/esm/shaders/lineStepMark.wgsl.generated.d.ts +1 -1
- package/esm/shaders/lineStepMark.wgsl.generated.js +1 -1
- package/esm/shaders/linkMark.glsl.generated.d.ts +1 -1
- package/esm/shaders/linkMark.glsl.generated.js +1 -1
- package/esm/shaders/linkMark.js.generated.d.ts +2 -1
- package/esm/shaders/linkMark.js.generated.js +3 -3
- package/esm/shaders/linkMark.wgsl.generated.d.ts +1 -1
- package/esm/shaders/linkMark.wgsl.generated.js +1 -1
- package/esm/shaders/pointMark.glsl.generated.d.ts +1 -1
- package/esm/shaders/pointMark.glsl.generated.js +1 -1
- package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -1
- package/esm/shaders/pointMark.wgsl.generated.js +1 -1
- package/esm/shaders/spanMark.glsl.generated.d.ts +1 -1
- package/esm/shaders/spanMark.glsl.generated.js +1 -1
- package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -1
- package/esm/shaders/spanMark.wgsl.generated.js +1 -1
- package/esm/slangPass.d.ts +10 -9
- package/esm/slangPass.js +2 -2
- package/package.json +3 -3
- package/src/shaders/barMark.glsl.generated.ts +1 -1
- package/src/shaders/barMark.wgsl.generated.ts +1 -1
- package/src/shaders/capsule.slang +1 -1
- package/src/shaders/curveDistance.js.generated.ts +12 -0
- package/src/shaders/curveDistance.slang +1 -1
- package/src/shaders/insertionGlyph.generated.ts +14 -0
- package/src/shaders/insertionGlyph.slang +59 -0
- package/src/shaders/lineCenterMark.glsl.generated.ts +2 -2
- package/src/shaders/lineCenterMark.iface.generated.ts +21 -18
- package/src/shaders/lineCenterMark.slang +5 -1
- package/src/shaders/lineCenterMark.wgsl.generated.ts +1 -1
- package/src/shaders/lineCommon.js.generated.ts +20 -0
- package/src/shaders/lineCommon.slang +14 -4
- package/src/shaders/lineStepMark.glsl.generated.ts +2 -2
- package/src/shaders/lineStepMark.iface.generated.ts +21 -18
- package/src/shaders/lineStepMark.slang +5 -1
- package/src/shaders/lineStepMark.wgsl.generated.ts +1 -1
- package/src/shaders/linkMark.glsl.generated.ts +1 -1
- package/src/shaders/linkMark.js.generated.ts +3 -3
- package/src/shaders/linkMark.slang +10 -7
- package/src/shaders/linkMark.wgsl.generated.ts +1 -1
- package/src/shaders/pointMark.glsl.generated.ts +1 -1
- package/src/shaders/pointMark.wgsl.generated.ts +1 -1
- package/src/shaders/rowRect.slang +2 -2
- package/src/shaders/rowTable.slang +5 -1
- package/src/shaders/spanMark.glsl.generated.ts +1 -1
- package/src/shaders/spanMark.wgsl.generated.ts +1 -1
package/esm/RenderCanvas.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface RenderCanvasHandle {
|
|
|
25
25
|
* render-prop body on the same value for the displays on the chrome, which it
|
|
26
26
|
* does *not* get for free from the `renderError` unmount (that covers only a
|
|
27
27
|
* reported loss — see the `canvasKey` note in useRenderingBackend.ts).
|
|
28
|
-
*
|
|
28
|
+
* GPU_DISPLAY_LIFECYCLE.md said so and ended with "any new consumer rendering its own
|
|
29
29
|
* banner must too" — a rule enforced by remembering to read the doc. Rendering
|
|
30
30
|
* the element here makes it structural instead: there is no way to mount this
|
|
31
31
|
* canvas without the key.
|
package/esm/RenderCanvas.js
CHANGED
|
@@ -16,7 +16,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
16
16
|
* render-prop body on the same value for the displays on the chrome, which it
|
|
17
17
|
* does *not* get for free from the `renderError` unmount (that covers only a
|
|
18
18
|
* reported loss — see the `canvasKey` note in useRenderingBackend.ts).
|
|
19
|
-
*
|
|
19
|
+
* GPU_DISPLAY_LIFECYCLE.md said so and ended with "any new consumer rendering its own
|
|
20
20
|
* banner must too" — a rule enforced by remembering to read the doc. Rendering
|
|
21
21
|
* the element here makes it structural instead: there is no way to mount this
|
|
22
22
|
* canvas without the key.
|
package/esm/canvas2dUtils.js
CHANGED
|
@@ -139,7 +139,7 @@ export function prepareCanvas(canvas, ctx, canvasWidth, canvasHeight) {
|
|
|
139
139
|
// `getContext('2d')` + null-guard + `prepareCanvas` ritual that every
|
|
140
140
|
// standalone 2D overlay otherwise hand-rolls in its draw effect — and the
|
|
141
141
|
// `prepareCanvas` DPR step is easy to omit, which renders blurry on Retina
|
|
142
|
-
// (see agent-docs/reference/
|
|
142
|
+
// (see agent-docs/reference/GPU_SHADERS.md §"Canvas scaling & hi-DPI").
|
|
143
143
|
// Returning the
|
|
144
144
|
// prepared ctx structurally prevents that omission.
|
|
145
145
|
export function getPreparedCanvas2D(canvas, width, height) {
|
|
@@ -40,9 +40,9 @@ export declare function createRenderingBackend<TRenderingBackend>(canvas: HTMLCa
|
|
|
40
40
|
* `DisplayChrome` machinery as a GPU display — the lifecycle is backend-
|
|
41
41
|
* agnostic, so nothing downstream knows or cares there's no HAL.
|
|
42
42
|
*
|
|
43
|
-
*
|
|
43
|
+
* No in-tree display uses it. Promote to the dual-path
|
|
44
44
|
* `createRenderingBackend` only once a profile shows Canvas2D can't keep 60fps
|
|
45
45
|
* at the display's real feature counts (≳100K features/frame — see
|
|
46
|
-
*
|
|
46
|
+
* agent-docs/reference/PLUGIN_ABI_STABILITY.md §Canvas2D or GPU).
|
|
47
47
|
*/
|
|
48
48
|
export declare function createCanvas2DBackend<TRenderingBackend>(canvas: HTMLCanvasElement, createCanvas2DRenderingBackend: (canvas: HTMLCanvasElement) => TRenderingBackend): Promise<TRenderingBackend>;
|
|
@@ -50,10 +50,10 @@ export async function createRenderingBackend(canvas, { passes, sampleCount = der
|
|
|
50
50
|
* `DisplayChrome` machinery as a GPU display — the lifecycle is backend-
|
|
51
51
|
* agnostic, so nothing downstream knows or cares there's no HAL.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
53
|
+
* No in-tree display uses it. Promote to the dual-path
|
|
54
54
|
* `createRenderingBackend` only once a profile shows Canvas2D can't keep 60fps
|
|
55
55
|
* at the display's real feature counts (≳100K features/frame — see
|
|
56
|
-
*
|
|
56
|
+
* agent-docs/reference/PLUGIN_ABI_STABILITY.md §Canvas2D or GPU).
|
|
57
57
|
*/
|
|
58
58
|
export function createCanvas2DBackend(canvas, createCanvas2DRenderingBackend) {
|
|
59
59
|
return Promise.resolve(createCanvas2DRenderingBackend(canvas));
|
|
@@ -54,7 +54,7 @@ async function probeWebgpu() {
|
|
|
54
54
|
* moment this returns and the browser reclaims the context on GC, the same
|
|
55
55
|
* release the HAL relies on. A page that then reaches the 16-context ceiling
|
|
56
56
|
* evicts this one first (it is the oldest, and nothing draws to it or
|
|
57
|
-
* re-acquires it), so the eviction cascade in
|
|
57
|
+
* re-acquires it), so the eviction cascade in GPU_PORTABILITY.md cannot
|
|
58
58
|
* start here.
|
|
59
59
|
*
|
|
60
60
|
* The driver string comes off the same context, so it is free where it matters:
|
package/esm/hal/createHal.js
CHANGED
|
@@ -64,7 +64,7 @@ async function climbLadder(canvas, { passes, sampleCount, failures }) {
|
|
|
64
64
|
// volvox tracks, and *no scroll churn at all* — headless Chrome (SwiftShader),
|
|
65
65
|
// three runs each: WebGL2 blocks the main thread for 1.3-5.5 s in a single
|
|
66
66
|
// task and 2.1-8.8 s in total, while Canvas2D never exceeds 339 ms and never
|
|
67
|
-
// once produces a task over 500 ms.
|
|
67
|
+
// once produces a task over 500 ms. GPU_PORTABILITY.md has the churn case
|
|
68
68
|
// at ~25x; this is the floor, and it is the load-time pipeline build rather
|
|
69
69
|
// than the per-pass rebuild the churn number measures.
|
|
70
70
|
//
|
|
@@ -28,21 +28,18 @@ export function bindGroupLayoutEntries(bindings) {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
function gpuBlendState(bs) {
|
|
31
|
-
// WebGPU rejects any factor but 'one' under max
|
|
32
|
-
//
|
|
31
|
+
// WebGPU rejects any factor but 'one' under max, which ignores them. Alpha
|
|
32
|
+
// accumulates as webgl2Hal's applyBlendState does.
|
|
33
33
|
const max = { srcFactor: 'one', dstFactor: 'one', operation: 'max' };
|
|
34
|
-
const min = { srcFactor: 'one', dstFactor: 'one', operation: 'min' };
|
|
35
34
|
const behind = { srcFactor: 'one-minus-dst-alpha', dstFactor: 'one' };
|
|
36
35
|
return bs.op === 'max'
|
|
37
36
|
? { color: max, alpha: max }
|
|
38
|
-
: bs.op === '
|
|
39
|
-
? { color:
|
|
40
|
-
:
|
|
41
|
-
|
|
42
|
-
: {
|
|
43
|
-
|
|
44
|
-
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },
|
|
45
|
-
};
|
|
37
|
+
: bs.op === 'behind'
|
|
38
|
+
? { color: behind, alpha: behind }
|
|
39
|
+
: {
|
|
40
|
+
color: { srcFactor: bs.srcFactor, dstFactor: bs.dstFactor },
|
|
41
|
+
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },
|
|
42
|
+
};
|
|
46
43
|
}
|
|
47
44
|
export function pipelineRecipe(desc, wgslSource, sampleCount) {
|
|
48
45
|
return {
|
package/esm/hal/types.d.ts
CHANGED
package/esm/hal/webgl2Hal.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export declare class WebGL2Hal extends GpuHalBase<RegionPassBuffer> implements G
|
|
|
43
43
|
* owns (`antialias` above one sample), so this HAL holds no separate target —
|
|
44
44
|
* releasing its samples means dropping the context, which costs a re-acquire
|
|
45
45
|
* against a hard 16-context ceiling and a full shader recompile per display.
|
|
46
|
-
* `agent-docs/reference/
|
|
46
|
+
* `agent-docs/reference/GPU_PORTABILITY.md` measured that trade and
|
|
47
47
|
* declined it; what makes the WebGPU side worth doing is that it pays
|
|
48
48
|
* neither.
|
|
49
49
|
*/
|
package/esm/hal/webgl2Hal.js
CHANGED
|
@@ -181,6 +181,9 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
181
181
|
// Programs link on first draw (`getPass`). The first links here as a
|
|
182
182
|
// canary, so a GL stack that cannot compile our shaders throws while
|
|
183
183
|
// `createGpuHal` can still fall back to Canvas2D.
|
|
184
|
+
// Starting every pass's compile here was a loss (headed Chrome, Intel UHD
|
|
185
|
+
// 630): the GPU process ran all 17 programs before the 10 a first BAM draw
|
|
186
|
+
// needs, and a first visit drew at 1094 ms against 907.
|
|
184
187
|
const canary = descriptors[0];
|
|
185
188
|
if (canary) {
|
|
186
189
|
this.passes.set(canary.id, {
|
|
@@ -308,7 +311,7 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
308
311
|
* owns (`antialias` above one sample), so this HAL holds no separate target —
|
|
309
312
|
* releasing its samples means dropping the context, which costs a re-acquire
|
|
310
313
|
* against a hard 16-context ceiling and a full shader recompile per display.
|
|
311
|
-
* `agent-docs/reference/
|
|
314
|
+
* `agent-docs/reference/GPU_PORTABILITY.md` measured that trade and
|
|
312
315
|
* declined it; what makes the WebGPU side worth doing is that it pays
|
|
313
316
|
* neither.
|
|
314
317
|
*/
|
|
@@ -458,9 +461,6 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
458
461
|
if (bs?.op === 'max') {
|
|
459
462
|
gl.blendEquation(gl.MAX);
|
|
460
463
|
}
|
|
461
|
-
else if (bs?.op === 'min') {
|
|
462
|
-
gl.blendEquation(gl.MIN);
|
|
463
|
-
}
|
|
464
464
|
else if (bs?.op === 'behind') {
|
|
465
465
|
gl.blendEquation(gl.FUNC_ADD);
|
|
466
466
|
gl.blendFuncSeparate(gl.ONE_MINUS_DST_ALPHA, gl.ONE, gl.ONE_MINUS_DST_ALPHA, gl.ONE);
|
package/esm/hal/webgpuHal.js
CHANGED
|
@@ -60,9 +60,8 @@ async function buildPipeline(device, recipe, passId) {
|
|
|
60
60
|
multisample: recipe.sampleCount === 1 ? undefined : { count: recipe.sampleCount },
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
-
// Every declared pass up front, unlike WebGL2's first-draw link
|
|
64
|
-
//
|
|
65
|
-
// before it can paint". Each pass compiles as soon as its own WGSL arrives.
|
|
63
|
+
// Every declared pass up front, unlike WebGL2's first-draw link. Each pass
|
|
64
|
+
// compiles as soon as its own WGSL arrives.
|
|
66
65
|
async function resolvePipelines(device, descriptors, sampleCount) {
|
|
67
66
|
const built = await Promise.all(descriptors.map(async (desc) => {
|
|
68
67
|
const { WGSL_SOURCE } = await desc.source.wgsl();
|
package/esm/marks/barMark.js
CHANGED
|
@@ -94,6 +94,8 @@ export const barMark = {
|
|
|
94
94
|
const yScale = valueScaleUniforms(params);
|
|
95
95
|
const table = params.rowTable;
|
|
96
96
|
const [domainMin, domainMax] = params.domain;
|
|
97
|
+
const { seamPx } = params;
|
|
98
|
+
const seamLeft = block.reversed ? seamPx : 0;
|
|
97
99
|
let clipped = false;
|
|
98
100
|
for (let i = 0; i < count; i++) {
|
|
99
101
|
const slot = rowSlot(row, i, table);
|
|
@@ -104,7 +106,7 @@ export const barMark = {
|
|
|
104
106
|
const r = barRect(bpToPx, x[i], x2[i], y[i], top, band, params, yScale);
|
|
105
107
|
if (r) {
|
|
106
108
|
setFill(rowColor(color[i], row, i, table));
|
|
107
|
-
ctx.fillRect(r.left, r.top, r.width +
|
|
109
|
+
ctx.fillRect(r.left - seamLeft, r.top, r.width + seamPx, r.height);
|
|
108
110
|
}
|
|
109
111
|
clipped ||=
|
|
110
112
|
clipSide(y[i], domainMin, domainMax, yScale.valueScaleType) !== 0;
|
|
@@ -123,7 +125,7 @@ export const barMark = {
|
|
|
123
125
|
const { top, band } = barBand(params, frame.canvasHeight, slot);
|
|
124
126
|
const s = stripRect(bpToPx, x[i], x2[i], y[i], top, band, params, yScale);
|
|
125
127
|
if (s) {
|
|
126
|
-
ctx.fillRect(s.left, s.top, s.width +
|
|
128
|
+
ctx.fillRect(s.left - seamLeft, s.top, s.width + seamPx, s.height);
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
},
|
package/esm/marks/index.d.ts
CHANGED
|
@@ -8,15 +8,15 @@ export { pointMark } from './pointMark.ts';
|
|
|
8
8
|
export { ruleMark } from './ruleMark.ts';
|
|
9
9
|
export { spanMark } from './spanMark.ts';
|
|
10
10
|
export { lineCenterMark, lineMarkOf, lineStepMark } from './lineMark.ts';
|
|
11
|
-
export { linkFeet, linkMark, LINK_ELSEWHERE, LINK_NO_REGION, } from './linkMark.ts';
|
|
11
|
+
export { linkApex, linkFeet, linkMark, LINK_ELSEWHERE, LINK_NO_REGION, } from './linkMark.ts';
|
|
12
12
|
export { ellipseDistance, ellipseNearest } from './ellipseDistance.ts';
|
|
13
13
|
export { appendGlyph, pointInsetPx } from './glyphPaint.ts';
|
|
14
14
|
export { recordPath } from './pathRecorder.ts';
|
|
15
15
|
export { abgrToCssRgba, makeAbgrFill } from './colorFill.ts';
|
|
16
16
|
export { RAMP_NOT_A_NUMBER_COLOR, RAMP_NO_VALUE_BITS, RAMP_NO_VALUE_COLOR, } from '../shaders/markColor.generated.ts';
|
|
17
|
-
export { instanceColor, isThreshold, keepRampValues, rampUniforms, rampValueBits, thresholdBandOf, } from './markRamp.ts';
|
|
17
|
+
export { instanceColor, isThreshold, keepRampValues, paintColors, rampUniforms, rampValueBits, thresholdBandOf, } from './markRamp.ts';
|
|
18
18
|
export { HIDDEN_ROW, NO_ROW_COLOR, RowKeys, buildRowTable } from './rowTable.ts';
|
|
19
|
-
export { keySlot, rowColor, rowSlot } from './rowLane.ts';
|
|
19
|
+
export { keySlot, rowColor, rowSlot, rowTableKeys, rowTableTextures, } from './rowLane.ts';
|
|
20
20
|
export { rowSpanIndex, spansInRow } from './rowSpanIndex.ts';
|
|
21
21
|
export type { MarkPlan } from './markPlan.ts';
|
|
22
22
|
export type { MarkInstance } from './markInk.ts';
|
|
@@ -27,7 +27,7 @@ export type { PointChannels, PointParams } from './pointMark.ts';
|
|
|
27
27
|
export type { RuleChannels, RuleParams } from './ruleMark.ts';
|
|
28
28
|
export type { SpanChannels, SpanParams } from './spanMark.ts';
|
|
29
29
|
export type { LineChannels, LineInterpolate, LineParams } from './lineMark.ts';
|
|
30
|
-
export type { LinkChannels, LinkParams, LinkRegion, LinkSizeScale, } from './linkMark.ts';
|
|
30
|
+
export type { LinkApex, LinkChannels, LinkParams, LinkRegion, LinkSizeScale, } from './linkMark.ts';
|
|
31
31
|
export type { RowTable } from './rowTable.ts';
|
|
32
32
|
export type { RowSpanIndex } from './rowSpanIndex.ts';
|
|
33
33
|
export type { ColorChannel } from './markRamp.ts';
|
package/esm/marks/index.js
CHANGED
|
@@ -8,13 +8,13 @@ export { pointMark } from './pointMark.js';
|
|
|
8
8
|
export { ruleMark } from './ruleMark.js';
|
|
9
9
|
export { spanMark } from './spanMark.js';
|
|
10
10
|
export { lineCenterMark, lineMarkOf, lineStepMark } from './lineMark.js';
|
|
11
|
-
export { linkFeet, linkMark, LINK_ELSEWHERE, LINK_NO_REGION, } from './linkMark.js';
|
|
11
|
+
export { linkApex, linkFeet, linkMark, LINK_ELSEWHERE, LINK_NO_REGION, } from './linkMark.js';
|
|
12
12
|
export { ellipseDistance, ellipseNearest } from './ellipseDistance.js';
|
|
13
13
|
export { appendGlyph, pointInsetPx } from './glyphPaint.js';
|
|
14
14
|
export { recordPath } from './pathRecorder.js';
|
|
15
15
|
export { abgrToCssRgba, makeAbgrFill } from './colorFill.js';
|
|
16
16
|
export { RAMP_NOT_A_NUMBER_COLOR, RAMP_NO_VALUE_BITS, RAMP_NO_VALUE_COLOR, } from '../shaders/markColor.generated.js';
|
|
17
|
-
export { instanceColor, isThreshold, keepRampValues, rampUniforms, rampValueBits, thresholdBandOf, } from './markRamp.js';
|
|
17
|
+
export { instanceColor, isThreshold, keepRampValues, paintColors, rampUniforms, rampValueBits, thresholdBandOf, } from './markRamp.js';
|
|
18
18
|
export { HIDDEN_ROW, NO_ROW_COLOR, RowKeys, buildRowTable } from './rowTable.js';
|
|
19
|
-
export { keySlot, rowColor, rowSlot } from './rowLane.js';
|
|
19
|
+
export { keySlot, rowColor, rowSlot, rowTableKeys, rowTableTextures, } from './rowLane.js';
|
|
20
20
|
export { rowSpanIndex, spansInRow } from './rowSpanIndex.js';
|
package/esm/marks/lineMark.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export interface LineChannels extends ColorChannel, RowChannel {
|
|
|
20
20
|
export interface LineParams extends RowParams, MarkValueScale {
|
|
21
21
|
/** The quantitative colour scale, for a line whose colour is a ramp or a threshold. */
|
|
22
22
|
colorScale?: MarkColorScale;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the colour field is the plotted `y`. Only then does a threshold's
|
|
25
|
+
* cut sit on the y scale, so the line changes colour where it crosses one;
|
|
26
|
+
* otherwise each instance takes its own value's colour.
|
|
27
|
+
*/
|
|
28
|
+
colorFromY?: boolean;
|
|
23
29
|
/** The value a step drops to across a gap. */
|
|
24
30
|
origin: number;
|
|
25
31
|
/** Stroke width in CSS px. */
|
package/esm/marks/lineMark.js
CHANGED
|
@@ -2,6 +2,7 @@ import { bpRangeXTuple } from '../blockClipUtils.js';
|
|
|
2
2
|
import { CappedPath, getDpr, makeBpMapper } from '../canvas2dUtils.js';
|
|
3
3
|
import * as centerShader from '../shaders/lineCenterMark.generated.js';
|
|
4
4
|
import { GAP_Y, NO_PREV_X } from '../shaders/lineCommon.generated.js';
|
|
5
|
+
import { lineColorAlongY } from '../shaders/lineCommon.js.generated.js';
|
|
5
6
|
import * as stepShader from '../shaders/lineStepMark.generated.js';
|
|
6
7
|
import { valueToYPxScaled } from '../shaders/pointMark.js.generated.js';
|
|
7
8
|
import { rowBandTopPx } from '../shaders/rowTable.js.generated.js';
|
|
@@ -9,7 +10,7 @@ import { normalizeScoreUnclamped } from '../shaders/scoreScale.js.generated.js';
|
|
|
9
10
|
import { slangPass } from '../slangPass.js';
|
|
10
11
|
import { abgrToCssRgba } from './colorFill.js';
|
|
11
12
|
import { colorBits, isThreshold, paintColors, rampUniforms, } from './markRamp.js';
|
|
12
|
-
import { bandHeightPx, rowColor, rowLane, rowSlot, rowTableKeys, rowTableTextures, } from './rowLane.js';
|
|
13
|
+
import { bandHeightPx, rowColor, rowColorOverride, rowLane, rowSlot, rowTableKeys, rowTableTextures, } from './rowLane.js';
|
|
13
14
|
import { valueScaleUniforms } from './valueScale.js';
|
|
14
15
|
const NO_GAP = Number.POSITIVE_INFINITY;
|
|
15
16
|
function sameRow(row, a, b) {
|
|
@@ -66,6 +67,7 @@ function writeLineUniforms(scratch, clip, block, frame, params) {
|
|
|
66
67
|
domainMax: params.domain[1],
|
|
67
68
|
...valueScaleUniforms(params),
|
|
68
69
|
...rampUniforms(params.colorScale),
|
|
70
|
+
colorFromY: params.colorFromY ? 1 : 0,
|
|
69
71
|
rowHeight,
|
|
70
72
|
rowBandPx: params.rowBandPx ?? rowHeight,
|
|
71
73
|
rowOffsetPx: params.rowOffsetPx ?? 0,
|
|
@@ -281,26 +283,36 @@ function traceCenter(pen, c, g, rows, indices) {
|
|
|
281
283
|
pen.lineTo(cx, cy, colorY);
|
|
282
284
|
}
|
|
283
285
|
}
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
286
|
+
// Along y, the line is stroked once per band per row, each pass keeping the
|
|
287
|
+
// parts of the polyline inside its band; otherwise once, in each instance's
|
|
288
|
+
// own colour. A row the table recolours takes its override either way.
|
|
287
289
|
function paintLine(ctx, c, g, params, trace) {
|
|
288
290
|
const { row, count } = c;
|
|
289
291
|
const scale = params.colorScale;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
292
|
+
const bySlot = new Map();
|
|
293
|
+
const own = [];
|
|
294
|
+
const { rampMode } = rampUniforms(scale);
|
|
295
|
+
const colorFromY = params.colorFromY ? 1 : 0;
|
|
296
|
+
for (let i = 0; i < count; i++) {
|
|
297
|
+
const slot = rowSlot(row, i, g.table);
|
|
298
|
+
if (slot === undefined) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const overridden = rowColorOverride(row, i, g.table) !== undefined;
|
|
302
|
+
if (lineColorAlongY(rampMode, colorFromY, overridden)) {
|
|
303
|
+
const held = bySlot.get(slot);
|
|
304
|
+
if (held) {
|
|
305
|
+
held.push(i);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
bySlot.set(slot, [i]);
|
|
302
309
|
}
|
|
303
310
|
}
|
|
311
|
+
else {
|
|
312
|
+
own.push(i);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (scale && isThreshold(scale)) {
|
|
304
316
|
const { cuts, colors } = scale;
|
|
305
317
|
for (const [slot, indices] of bySlot) {
|
|
306
318
|
const cutYs = cuts.map(cut => colorYPx(g, cut, slot));
|
|
@@ -311,12 +323,13 @@ function paintLine(ctx, c, g, params, trace) {
|
|
|
311
323
|
pen.end();
|
|
312
324
|
}
|
|
313
325
|
}
|
|
314
|
-
return;
|
|
315
326
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
327
|
+
if (own.length > 0) {
|
|
328
|
+
const color = paintColors(c, count, scale);
|
|
329
|
+
const pen = new InstancePen(ctx, i => rowColor(color[i], row, i, g.table));
|
|
330
|
+
trace(pen, own);
|
|
331
|
+
pen.end();
|
|
332
|
+
}
|
|
320
333
|
}
|
|
321
334
|
function paintStep(ctx, c, block, frame, params) {
|
|
322
335
|
if (c.count === 0) {
|
package/esm/marks/linkMark.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { LINK_ELSEWHERE, LINK_NO_REGION } from '../shaders/linkMark.consts.generated.ts';
|
|
2
|
+
import type { RenderBlock } from '../renderBlock.ts';
|
|
2
3
|
import type { ColorChannel } from './markRamp.ts';
|
|
3
4
|
import type { RowChannel, RowParams } from './rowLane.ts';
|
|
4
|
-
import type { MarkColorScale, MarkShape } from './types.ts';
|
|
5
|
+
import type { MarkFrame, MarkColorScale, MarkShape } from './types.ts';
|
|
5
6
|
import type { MarkValueScale } from './valueScale.ts';
|
|
6
7
|
/**
|
|
7
8
|
* The `link` shape's channels: a stroked curve from `x`, on the displayed
|
|
@@ -72,5 +73,24 @@ export interface LinkParams extends RowParams, MarkValueScale {
|
|
|
72
73
|
/** How far inside the band the value range ends, the axis's own. */
|
|
73
74
|
insetPx?: number;
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Where an instance's curve peaks as the painter and the shader draw it: the
|
|
78
|
+
* centreline's apex in canvas px and how far it stands off its baseline.
|
|
79
|
+
*/
|
|
80
|
+
export interface LinkApex {
|
|
81
|
+
x: number;
|
|
82
|
+
y: number;
|
|
83
|
+
rise: number;
|
|
84
|
+
/** The curve's half-span at its baseline; 0 for a line or a stem, which enclose nothing. */
|
|
85
|
+
halfWidth: number;
|
|
86
|
+
strokePx: number;
|
|
87
|
+
/** Canvas y per px toward the baseline: 1 standing on the band's foot, -1 hanging from its top. */
|
|
88
|
+
inward: 1 | -1;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Instance `i`'s apex, undefined where it draws none on the band: no curve
|
|
92
|
+
* at all, or a far pair's circle, which the band clips to its legs.
|
|
93
|
+
*/
|
|
94
|
+
export declare function linkApex(c: LinkChannels, block: RenderBlock, frame: MarkFrame, params: LinkParams, i: number): LinkApex | undefined;
|
|
75
95
|
export declare const linkMark: MarkShape<LinkChannels, LinkParams>;
|
|
76
96
|
export { LINK_ELSEWHERE, LINK_NO_REGION };
|
package/esm/marks/linkMark.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getDpr } from '../canvas2dUtils.js';
|
|
2
2
|
import { SCALE_TYPE_LOG } from '../scoreScale.js';
|
|
3
|
-
import { distToWideCirclePx } from '../shaders/curveDistance.js.generated.js';
|
|
4
|
-
import { LINK_CURVE_SEGMENTS, LINK_ELSEWHERE,
|
|
3
|
+
import { distToWideCirclePx, legSweepAngle, } from '../shaders/curveDistance.js.generated.js';
|
|
4
|
+
import { LINK_CURVE_SEGMENTS, LINK_ELSEWHERE, LINK_FOOT_PX, LINK_MAX_REGIONS, LINK_NO_REGION, LINK_NO_SIZE, LINK_LINE_MIN_PX, LINK_SHAPE_ARC, LINK_SHAPE_DOME, LINK_SHAPE_LINE, LINK_SIZE_CONSTANT, LINK_SIZE_SCALED, LINK_STEM_PX, } from '../shaders/linkMark.consts.generated.js';
|
|
5
5
|
import * as shader from '../shaders/linkMark.generated.js';
|
|
6
|
-
import { linkApexPx, linkBaseYPx, linkFootDir, linkFootLenPx, linkRadiiPx, linkStrokeWidthPx, linkValuePx, } from '../shaders/linkMark.js.generated.js';
|
|
6
|
+
import { linkApexPx, linkBaseYPx, linkFootDir, linkFootLenPx, linkIsFar, linkRadiiPx, linkStrokeWidthPx, linkValuePx, } from '../shaders/linkMark.js.generated.js';
|
|
7
7
|
import { slangPass } from '../slangPass.js';
|
|
8
8
|
import { abgrToCssRgba } from './colorFill.js';
|
|
9
9
|
import { ellipseNearest } from './ellipseDistance.js';
|
|
@@ -164,7 +164,7 @@ function placeCurve(c, g, i) {
|
|
|
164
164
|
g.x2Px = regionPx(regions, region, c.x2[i]);
|
|
165
165
|
const pairHalf = Math.abs(g.x2Px - g.xPx) / 2;
|
|
166
166
|
const valuePx = linkValuePx(c.y ? c.y[i] : 0, g.domainMin, g.domainMax, g.band, g.scaleType, g.insetPx, g.symlogConstant);
|
|
167
|
-
const apex = linkApexPx(pairHalf, g.reach, g.shape, g.valued, valuePx);
|
|
167
|
+
const apex = linkApexPx(pairHalf, g.reach, g.strokePx / 2, g.shape, g.valued, valuePx);
|
|
168
168
|
if (g.shape === LINK_SHAPE_LINE) {
|
|
169
169
|
g.kind = KIND_LINE;
|
|
170
170
|
g.rx = Math.max(2 * pairHalf, LINK_LINE_MIN_PX);
|
|
@@ -174,15 +174,34 @@ function placeCurve(c, g, i) {
|
|
|
174
174
|
const [rx, ry] = linkRadiiPx(pairHalf, apex, g.screenW);
|
|
175
175
|
g.rx = rx;
|
|
176
176
|
g.ry = ry;
|
|
177
|
-
if (
|
|
177
|
+
if (linkIsFar(pairHalf, g.screenW)) {
|
|
178
178
|
g.kind = KIND_CIRCLE;
|
|
179
|
-
g.legSweep =
|
|
179
|
+
g.legSweep = legSweepAngle(rx, g.reach + g.strokePx / 2);
|
|
180
180
|
g.legHeight = rx * Math.sin(g.legSweep);
|
|
181
181
|
}
|
|
182
182
|
else {
|
|
183
183
|
g.kind = KIND_ELLIPSE;
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Instance `i`'s apex, undefined where it draws none on the band: no curve
|
|
188
|
+
* at all, or a far pair's circle, which the band clips to its legs.
|
|
189
|
+
*/
|
|
190
|
+
export function linkApex(c, block, frame, params, i) {
|
|
191
|
+
const g = linkFrame(block, frame, params);
|
|
192
|
+
placeCurve(c, g, i);
|
|
193
|
+
if (g.kind === KIND_NONE || g.kind === KIND_CIRCLE) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
x: (g.xPx + g.x2Px) / 2,
|
|
198
|
+
y: yAt(g, g.ry),
|
|
199
|
+
rise: g.ry,
|
|
200
|
+
halfWidth: g.kind === KIND_ELLIPSE ? g.rx : 0,
|
|
201
|
+
strokePx: g.strokePx,
|
|
202
|
+
inward: g.reverse ? -1 : 1,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
186
205
|
function sizeLane(c) {
|
|
187
206
|
const lane = new Float32Array(c.count);
|
|
188
207
|
for (let i = 0; i < c.count; i++) {
|
package/esm/marks/markPlan.d.ts
CHANGED
|
@@ -18,8 +18,9 @@ export interface MarkPlan<TRegion, TState extends MarkFrame> {
|
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Resolve which marks draw this frame. Throws for an enabled mark the plan
|
|
21
|
-
* form cannot carry — one with a `band
|
|
22
|
-
*
|
|
23
|
-
* would draw what
|
|
21
|
+
* form cannot carry — one with a `band`, a `paintsBlock` or a per-draw
|
|
22
|
+
* `verticesPerInstance` — since a plan neither scissors, asks the block
|
|
23
|
+
* anything nor sizes a draw, and without the throw the GPU would draw what
|
|
24
|
+
* Canvas2D declines, or a chevron a line.
|
|
24
25
|
*/
|
|
25
26
|
export declare function planMarks<TRegion, TState extends MarkFrame>(marks: readonly Mark<TRegion, TState>[], state: TState): MarkPlan<TRegion, TState>;
|
package/esm/marks/markPlan.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve which marks draw this frame. Throws for an enabled mark the plan
|
|
3
|
-
* form cannot carry — one with a `band
|
|
4
|
-
*
|
|
5
|
-
* would draw what
|
|
3
|
+
* form cannot carry — one with a `band`, a `paintsBlock` or a per-draw
|
|
4
|
+
* `verticesPerInstance` — since a plan neither scissors, asks the block
|
|
5
|
+
* anything nor sizes a draw, and without the throw the GPU would draw what
|
|
6
|
+
* Canvas2D declines, or a chevron a line.
|
|
6
7
|
*/
|
|
7
8
|
export function planMarks(marks, state) {
|
|
8
9
|
const enabled = [];
|
|
@@ -10,7 +11,7 @@ export function planMarks(marks, state) {
|
|
|
10
11
|
for (const mark of marks) {
|
|
11
12
|
if (!mark.enabled || mark.enabled(state)) {
|
|
12
13
|
if (!mark.planned) {
|
|
13
|
-
throw new Error(`mark ${mark.pass.id} declares a band or a
|
|
14
|
+
throw new Error(`mark ${mark.pass.id} declares a band, a block gate or a per-draw vertex count, which a frame plan does not carry; draw it through drawMarks`);
|
|
14
15
|
}
|
|
15
16
|
enabled.push(mark);
|
|
16
17
|
passes.push(mark.planned);
|
package/esm/marks/markRamp.d.ts
CHANGED
|
@@ -56,8 +56,10 @@ export declare function rampValueBits(values: Float32Array): Uint32Array<ArrayBu
|
|
|
56
56
|
*/
|
|
57
57
|
export declare function keepRampValues(values: Float32Array, keep: (value: number, index: number) => boolean): Float32Array<ArrayBuffer>;
|
|
58
58
|
/**
|
|
59
|
-
* The interval a value is in on a threshold: how many of the ascending
|
|
60
|
-
* is at or past, `thresholdIndex`'s rule and the shader's
|
|
59
|
+
* The interval a lane value is in on a threshold: how many of the ascending
|
|
60
|
+
* cuts it is at or past, `thresholdIndex`'s rule and the shader's
|
|
61
|
+
* `thresholdBand`. The value is a float32 and so is each cut in the shader's
|
|
62
|
+
* uniform, so a cut compares as one here: 0.7 off a lane is at a cut of 0.7.
|
|
61
63
|
*/
|
|
62
64
|
export declare function thresholdBandOf(value: number, cuts: readonly number[]): number;
|
|
63
65
|
/**
|
package/esm/marks/markRamp.js
CHANGED
|
@@ -99,13 +99,15 @@ export function keepRampValues(values, keep) {
|
|
|
99
99
|
return new Float32Array(kept.buffer, kept.byteOffset, kept.length);
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
|
-
* The interval a value is in on a threshold: how many of the ascending
|
|
103
|
-
* is at or past, `thresholdIndex`'s rule and the shader's
|
|
102
|
+
* The interval a lane value is in on a threshold: how many of the ascending
|
|
103
|
+
* cuts it is at or past, `thresholdIndex`'s rule and the shader's
|
|
104
|
+
* `thresholdBand`. The value is a float32 and so is each cut in the shader's
|
|
105
|
+
* uniform, so a cut compares as one here: 0.7 off a lane is at a cut of 0.7.
|
|
104
106
|
*/
|
|
105
107
|
export function thresholdBandOf(value, cuts) {
|
|
106
108
|
let band = 0;
|
|
107
109
|
for (const cut of cuts) {
|
|
108
|
-
if (value >= cut) {
|
|
110
|
+
if (value >= Math.fround(cut)) {
|
|
109
111
|
band++;
|
|
110
112
|
}
|
|
111
113
|
}
|
|
@@ -150,9 +152,7 @@ export function paintColors(c, count, scale) {
|
|
|
150
152
|
const colors = new Uint32Array(count);
|
|
151
153
|
const bits = rampValueBits(colorValue);
|
|
152
154
|
const colorOf = isThreshold(scale)
|
|
153
|
-
? (
|
|
154
|
-
? scale.colors[thresholdBandOf(value, scale.cuts)]
|
|
155
|
-
: scale.colors[value > 0 ? scale.cuts.length : 0]
|
|
155
|
+
? thresholdColorOf(scale)
|
|
156
156
|
: rampColorOf(scale);
|
|
157
157
|
for (let i = 0; i < count; i++) {
|
|
158
158
|
const value = colorValue[i];
|
|
@@ -175,6 +175,14 @@ function constantColors(c, color, count) {
|
|
|
175
175
|
c.constantBake = colors;
|
|
176
176
|
return colors;
|
|
177
177
|
}
|
|
178
|
+
// The cuts the shader's uniform block holds, so Canvas2D paints a value past
|
|
179
|
+
// the last of them in the band the GPU does.
|
|
180
|
+
function thresholdColorOf(scale) {
|
|
181
|
+
const cuts = scale.cuts.slice(0, MAX_COLOR_CUTS);
|
|
182
|
+
return (value) => Number.isFinite(value)
|
|
183
|
+
? scale.colors[thresholdBandOf(value, cuts)]
|
|
184
|
+
: scale.colors[value > 0 ? cuts.length : 0];
|
|
185
|
+
}
|
|
178
186
|
function rampColorOf(ramp) {
|
|
179
187
|
const [min, max] = ramp.domain;
|
|
180
188
|
const log = ramp.scale === 'log';
|
package/esm/marks/rowLane.d.ts
CHANGED
|
@@ -35,5 +35,7 @@ export declare function bandHeightPx(params: RowParams, canvasHeight: number): n
|
|
|
35
35
|
export declare function keySlot(key: number, table: RowTable | undefined): number | undefined;
|
|
36
36
|
/** The slot instance `i` is drawn on, undefined where the table hides its key. */
|
|
37
37
|
export declare function rowSlot(row: Uint32Array | undefined, i: number, table: RowTable | undefined): number | undefined;
|
|
38
|
+
/** Instance `i`'s key's colour override, undefined where it keeps its own. */
|
|
39
|
+
export declare function rowColorOverride(row: Uint32Array | undefined, i: number, table: RowTable | undefined): number | undefined;
|
|
38
40
|
/** The colour instance `i` is drawn in: its key's override, else `color`. */
|
|
39
41
|
export declare function rowColor(color: number, row: Uint32Array | undefined, i: number, table: RowTable | undefined): number;
|
package/esm/marks/rowLane.js
CHANGED
|
@@ -33,9 +33,13 @@ export function keySlot(key, table) {
|
|
|
33
33
|
export function rowSlot(row, i, table) {
|
|
34
34
|
return keySlot(rowKey(row, i), table);
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
37
|
-
export function
|
|
36
|
+
/** Instance `i`'s key's colour override, undefined where it keeps its own. */
|
|
37
|
+
export function rowColorOverride(row, i, table) {
|
|
38
38
|
const key = rowKey(row, i);
|
|
39
39
|
const override = table && key < table.keys ? table.color[key] : NO_ROW_COLOR;
|
|
40
|
-
return override >>> 24 === 0 ?
|
|
40
|
+
return override >>> 24 === 0 ? undefined : override;
|
|
41
|
+
}
|
|
42
|
+
/** The colour instance `i` is drawn in: its key's override, else `color`. */
|
|
43
|
+
export function rowColor(color, row, i, table) {
|
|
44
|
+
return rowColorOverride(row, i, table) ?? color;
|
|
41
45
|
}
|
|
@@ -16,8 +16,9 @@ export interface RowSpanIndex {
|
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* The index over `count` spans from `x` to `x2`, each in the row `row` names
|
|
19
|
-
* (row 0 where there is no lane)
|
|
20
|
-
*
|
|
19
|
+
* (row 0 where there is no lane), less those on `HIDDEN_ROW`, which no row
|
|
20
|
+
* holds. A counting sort by row, a sort of each row the input left out of
|
|
21
|
+
* start order, and one pass for the reach.
|
|
21
22
|
*/
|
|
22
23
|
export declare function rowSpanIndex(x: Uint32Array, x2: Uint32Array, row: Uint32Array | undefined, count?: number): RowSpanIndex;
|
|
23
24
|
/**
|