@jbrowse/render-core 5.0.0-beta.11 → 5.0.0-beta.14
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/OverlayCanvas.d.ts +3 -2
- package/esm/OverlayCanvas.js +4 -2
- package/esm/RenderCanvas.d.ts +4 -3
- package/esm/RenderCanvas.js +4 -2
- package/esm/ScrollLockedOverlay.d.ts +2 -2
- package/esm/ScrollLockedOverlay.js +3 -2
- package/esm/blockClipUtils.d.ts +5 -5
- package/esm/blockClipUtils.js +4 -3
- package/esm/canvas2dUtils.d.ts +15 -21
- package/esm/canvas2dUtils.js +25 -55
- package/esm/colorRampLut.d.ts +2 -2
- package/esm/colorRampLut.js +2 -2
- package/esm/coverageBand.d.ts +0 -1
- package/esm/coverageBand.js +0 -5
- package/esm/coverageBandBuffers.d.ts +8 -5
- package/esm/createRenderingBackend.d.ts +1 -1
- package/esm/displayPhase.d.ts +5 -9
- package/esm/displayPhase.js +2 -2
- package/esm/hal/mockHal.d.ts +3 -4
- package/esm/hal/mockHal.js +5 -4
- package/esm/hal/types.d.ts +5 -5
- package/esm/hal/webgl2Hal.d.ts +4 -1
- package/esm/hal/webgl2Hal.js +18 -6
- package/esm/hal/webgpuHal.js +2 -2
- package/esm/linkFeet.d.ts +4 -0
- package/esm/linkFeet.js +7 -0
- package/esm/marks/barMark.d.ts +1 -1
- package/esm/marks/barMark.js +4 -2
- package/esm/marks/colorFill.d.ts +4 -4
- package/esm/marks/colorFill.js +4 -4
- package/esm/marks/connectorMark.d.ts +48 -0
- package/esm/marks/connectorMark.js +285 -0
- package/esm/marks/drawAgainstHit.d.ts +1 -1
- package/esm/marks/drawAgainstHit.js +4 -4
- package/esm/marks/glyphPaint.d.ts +5 -4
- package/esm/marks/glyphPaint.js +17 -7
- package/esm/marks/index.d.ts +8 -5
- package/esm/marks/index.js +6 -4
- package/esm/marks/lineMark.d.ts +47 -7
- package/esm/marks/lineMark.js +53 -39
- package/esm/marks/linkMark.d.ts +43 -6
- package/esm/marks/linkMark.js +118 -21
- package/esm/marks/markBackend.d.ts +1 -1
- package/esm/marks/markBackend.js +1 -1
- package/esm/marks/markPlan.d.ts +4 -3
- package/esm/marks/markPlan.js +5 -4
- package/esm/marks/markRamp.d.ts +11 -11
- package/esm/marks/markRamp.js +9 -9
- package/esm/marks/pointMark.d.ts +11 -5
- package/esm/marks/pointMark.js +34 -9
- package/esm/marks/rowLane.d.ts +4 -2
- package/esm/marks/rowLane.js +7 -3
- package/esm/marks/rowTable.d.ts +5 -5
- package/esm/marks/rowTable.js +4 -4
- package/esm/marks/ruleMark.js +1 -0
- package/esm/marks/spanMark.d.ts +8 -7
- package/esm/marks/spanMark.js +3 -3
- package/esm/marks/types.d.ts +8 -5
- package/esm/marks/types.js +8 -1
- package/esm/perRegionRenderingBackend.d.ts +1 -1
- package/esm/perRegionRenderingBackend.js +1 -1
- package/esm/regionDataMap.d.ts +2 -2
- package/esm/regionDataMap.js +2 -2
- 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/capsule.js.generated.d.ts +1 -1
- package/esm/shaders/capsule.js.generated.js +2 -2
- package/esm/shaders/connectorMark.consts.generated.d.ts +11 -0
- package/esm/shaders/connectorMark.consts.generated.js +13 -0
- package/esm/shaders/{coverageIndicator.generated.d.ts → connectorMark.generated.d.ts} +2 -1
- package/esm/shaders/connectorMark.generated.js +8 -0
- package/esm/shaders/connectorMark.glsl.generated.d.ts +2 -0
- package/esm/shaders/connectorMark.glsl.generated.js +4 -0
- package/esm/shaders/connectorMark.iface.generated.d.ts +88 -0
- package/esm/shaders/connectorMark.iface.generated.js +1194 -0
- package/esm/shaders/connectorMark.js.generated.d.ts +5 -0
- package/esm/shaders/connectorMark.js.generated.js +47 -0
- package/esm/shaders/connectorMark.wgsl.generated.d.ts +1 -0
- package/esm/shaders/connectorMark.wgsl.generated.js +3 -0
- package/esm/shaders/coverageInterbase.glsl.generated.d.ts +1 -1
- package/esm/shaders/coverageInterbase.glsl.generated.js +1 -1
- package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -1
- package/esm/shaders/coverageInterbase.wgsl.generated.js +1 -1
- package/esm/shaders/curveDistance.js.generated.d.ts +4 -0
- package/esm/shaders/curveDistance.js.generated.js +113 -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.consts.generated.d.ts +1 -0
- package/esm/shaders/linkMark.consts.generated.js +1 -0
- package/esm/shaders/linkMark.glsl.generated.d.ts +2 -2
- package/esm/shaders/linkMark.glsl.generated.js +2 -2
- package/esm/shaders/linkMark.js.generated.d.ts +4 -2
- package/esm/shaders/linkMark.js.generated.js +33 -4
- 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.iface.generated.d.ts +10 -8
- package/esm/shaders/pointMark.iface.generated.js +18 -16
- 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 +5 -3
- package/esm/slangPass.js +2 -2
- package/package.json +7 -4
- package/src/shaders/arcHull.test.ts +87 -0
- package/src/shaders/barMark.glsl.generated.ts +1 -1
- package/src/shaders/barMark.slang +2 -2
- package/src/shaders/barMark.wgsl.generated.ts +1 -1
- package/src/shaders/capsule.js.generated.ts +2 -2
- package/src/shaders/capsule.slang +69 -24
- package/src/shaders/clipStrip.slang +1 -1
- package/src/shaders/colorPack.slang +2 -2
- package/src/shaders/colorRampLut.slang +7 -7
- package/src/shaders/connectorMark.consts.generated.ts +24 -0
- package/src/shaders/connectorMark.generated.ts +12 -0
- package/src/shaders/connectorMark.glsl.generated.ts +6 -0
- package/src/shaders/connectorMark.iface.generated.ts +1270 -0
- package/src/shaders/connectorMark.js.generated.ts +52 -0
- package/src/shaders/connectorMark.slang +287 -0
- package/src/shaders/connectorMark.wgsl.generated.ts +4 -0
- package/src/shaders/coverageBand.slang +10 -12
- package/src/shaders/coverageInterbase.glsl.generated.ts +1 -1
- package/src/shaders/coverageInterbase.slang +24 -21
- package/src/shaders/coverageInterbase.wgsl.generated.ts +1 -1
- package/src/shaders/coverageSnp.slang +1 -1
- package/src/shaders/curveDistance.js.generated.ts +112 -0
- package/src/shaders/curveDistance.slang +104 -1
- package/src/shaders/farDome.test.ts +117 -0
- package/src/shaders/hpClampFloor.test.ts +0 -4
- 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 +19 -23
- package/src/shaders/lineCenterMark.wgsl.generated.ts +1 -1
- package/src/shaders/lineCommon.js.generated.ts +20 -0
- package/src/shaders/lineCommon.slang +18 -8
- package/src/shaders/lineStepMark.glsl.generated.ts +2 -2
- package/src/shaders/lineStepMark.iface.generated.ts +21 -18
- package/src/shaders/lineStepMark.slang +10 -6
- package/src/shaders/lineStepMark.wgsl.generated.ts +1 -1
- package/src/shaders/linkMark.consts.generated.ts +2 -0
- package/src/shaders/linkMark.glsl.generated.ts +2 -2
- package/src/shaders/linkMark.js.generated.ts +30 -4
- package/src/shaders/linkMark.slang +67 -24
- package/src/shaders/linkMark.wgsl.generated.ts +1 -1
- package/src/shaders/markColor.slang +10 -10
- package/src/shaders/pointMark.glsl.generated.ts +1 -1
- package/src/shaders/pointMark.iface.generated.ts +19 -16
- package/src/shaders/pointMark.slang +12 -8
- package/src/shaders/pointMark.wgsl.generated.ts +1 -1
- package/src/shaders/rowRect.slang +3 -3
- package/src/shaders/rowTable.slang +12 -8
- package/src/shaders/scoreScale.slang +1 -1
- package/src/shaders/spanMark.glsl.generated.ts +1 -1
- package/src/shaders/spanMark.slang +5 -5
- package/src/shaders/spanMark.wgsl.generated.ts +1 -1
- package/src/shaders/viewRegion.slang +13 -0
- package/esm/shaders/coverageIndicator.generated.js +0 -7
- package/esm/shaders/coverageIndicator.glsl.generated.d.ts +0 -2
- package/esm/shaders/coverageIndicator.glsl.generated.js +0 -4
- package/esm/shaders/coverageIndicator.iface.generated.d.ts +0 -85
- package/esm/shaders/coverageIndicator.iface.generated.js +0 -114
- package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +0 -1
- package/esm/shaders/coverageIndicator.wgsl.generated.js +0 -3
- package/src/shaders/coverageIndicator.generated.ts +0 -11
- package/src/shaders/coverageIndicator.glsl.generated.ts +0 -6
- package/src/shaders/coverageIndicator.iface.generated.ts +0 -172
- package/src/shaders/coverageIndicator.slang +0 -61
- package/src/shaders/coverageIndicator.wgsl.generated.ts +0 -4
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, {
|
|
@@ -301,7 +304,15 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
301
304
|
if (this.disposed) {
|
|
302
305
|
return { x: getDpr(), y: getDpr() };
|
|
303
306
|
}
|
|
304
|
-
|
|
307
|
+
// Resized past 7680x4320 px of area, Chrome shrinks the drawing buffer and
|
|
308
|
+
// leaves canvas.width/height at the requested size, so the scale comes from
|
|
309
|
+
// the buffer. A lost context reads 0x0 and falls back to syncCanvasSize's.
|
|
310
|
+
const { scale } = syncCanvasSize(this.canvas, width, height);
|
|
311
|
+
const { drawingBufferWidth: bufferW, drawingBufferHeight: bufferH } = this.gl;
|
|
312
|
+
return {
|
|
313
|
+
x: width > 0 && bufferW > 0 ? bufferW / width : scale.x,
|
|
314
|
+
y: height > 0 && bufferH > 0 ? bufferH / height : scale.y,
|
|
315
|
+
};
|
|
305
316
|
}
|
|
306
317
|
/**
|
|
307
318
|
* Nothing to give back. WebGL2 multisamples in the drawing buffer the context
|
|
@@ -351,23 +362,24 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
351
362
|
const gl = this.gl;
|
|
352
363
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
353
364
|
gl.disable(gl.SCISSOR_TEST);
|
|
354
|
-
gl.viewport(0, 0,
|
|
365
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
355
366
|
gl.clearColor(clearR, clearG, clearB, clearA);
|
|
356
367
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
357
368
|
}
|
|
358
369
|
setScissor(x, y, w, h) {
|
|
359
370
|
const gl = this.gl;
|
|
360
371
|
gl.enable(gl.SCISSOR_TEST);
|
|
361
|
-
gl.scissor(x,
|
|
372
|
+
gl.scissor(x, gl.drawingBufferHeight - y - h, w, h);
|
|
362
373
|
}
|
|
363
374
|
clearScissor() {
|
|
364
375
|
this.gl.disable(this.gl.SCISSOR_TEST);
|
|
365
376
|
}
|
|
366
377
|
setViewport(x, y, w, h) {
|
|
367
|
-
this.gl.viewport(x, this.
|
|
378
|
+
this.gl.viewport(x, this.gl.drawingBufferHeight - y - h, w, h);
|
|
368
379
|
}
|
|
369
380
|
clearViewport() {
|
|
370
|
-
|
|
381
|
+
const gl = this.gl;
|
|
382
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
371
383
|
}
|
|
372
384
|
drawPass(passId, regionKey, bufferPassId, verticesPerInstance) {
|
|
373
385
|
const gl = this.gl;
|
|
@@ -410,7 +422,7 @@ export class WebGL2Hal extends GpuHalBase {
|
|
|
410
422
|
endFrame() {
|
|
411
423
|
const gl = this.gl;
|
|
412
424
|
gl.disable(gl.SCISSOR_TEST);
|
|
413
|
-
gl.viewport(0, 0,
|
|
425
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
414
426
|
}
|
|
415
427
|
releaseResources() {
|
|
416
428
|
const gl = this.gl;
|
package/esm/hal/webgpuHal.js
CHANGED
|
@@ -113,7 +113,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
113
113
|
// at 1 there is no MSAA texture at all rather than a smaller one, which is
|
|
114
114
|
// the whole point of the knob.
|
|
115
115
|
sampleCount;
|
|
116
|
-
// The multisampled
|
|
116
|
+
// The multisampled color attachment, resolved into the canvas texture at the
|
|
117
117
|
// end of the frame. Null for the life of the HAL when `sampleCount` is 1.
|
|
118
118
|
msaaTexture = null;
|
|
119
119
|
msaaView = null;
|
|
@@ -395,7 +395,7 @@ export class WebGPUHal extends GpuHalBase {
|
|
|
395
395
|
const existing = byName.get(binding.name);
|
|
396
396
|
if (existing) {
|
|
397
397
|
// Same hazard as a buffer, and no longer a hypothetical one: every
|
|
398
|
-
// `uploadTexture` in tree is a
|
|
398
|
+
// `uploadTexture` in tree is a color ramp, and since the ramp became a
|
|
399
399
|
// `defineMark` `texture` they are all written by `GpuMarkBackend`'s
|
|
400
400
|
// `bindRamp` from inside `drawRegion` — mid-frame, between an encoded
|
|
401
401
|
// draw and its submit. The deferral is what makes replacing one there
|
package/esm/linkFeet.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const LINK_FOOT_FORWARD = 1;
|
|
2
|
+
export const LINK_FOOT_REVERSE = 2;
|
|
3
|
+
/** The `feet` lane's value for a tick at each foot, by genomic direction. */
|
|
4
|
+
export function linkFeet(xDir, x2Dir) {
|
|
5
|
+
const bits = (d) => d > 0 ? LINK_FOOT_FORWARD : d < 0 ? LINK_FOOT_REVERSE : 0;
|
|
6
|
+
return bits(xDir) | (bits(x2Dir) << 2);
|
|
7
|
+
}
|
package/esm/marks/barMark.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface BarChannels extends ColorChannel, RowChannel {
|
|
|
13
13
|
count: number;
|
|
14
14
|
}
|
|
15
15
|
export interface BarParams extends RowParams, MarkValueScale {
|
|
16
|
-
/** The quantitative
|
|
16
|
+
/** The quantitative color scale, for a bar whose color is a ramp or a threshold. */
|
|
17
17
|
colorScale?: MarkColorScale;
|
|
18
18
|
/** The value bars grow from; a bar below it hangs down. */
|
|
19
19
|
origin: number;
|
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/colorFill.d.ts
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `@jbrowse/core/util/colorBits` re-exports `abgrToCssRgba` from here: core
|
|
5
5
|
* depends on render-core, never the reverse. The direction that genuinely
|
|
6
|
-
* needs core is resolving a CSS
|
|
7
|
-
* shape takes packed
|
|
6
|
+
* needs core is resolving a CSS color to a packed number, which is why a
|
|
7
|
+
* shape takes packed colors and the display resolves them.
|
|
8
8
|
*/
|
|
9
9
|
export declare function abgrToCssRgba(c: number): string;
|
|
10
10
|
/**
|
|
11
|
-
* A fill-style setter that only touches `ctx.fillStyle` when the
|
|
12
|
-
* changes. Most of a painting is runs of one
|
|
11
|
+
* A fill-style setter that only touches `ctx.fillStyle` when the color
|
|
12
|
+
* changes. Most of a painting is runs of one color, and both the string
|
|
13
13
|
* allocation and the context write cost more than the comparison.
|
|
14
14
|
*/
|
|
15
15
|
export declare function makeAbgrFill(ctx: {
|
package/esm/marks/colorFill.js
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `@jbrowse/core/util/colorBits` re-exports `abgrToCssRgba` from here: core
|
|
5
5
|
* depends on render-core, never the reverse. The direction that genuinely
|
|
6
|
-
* needs core is resolving a CSS
|
|
7
|
-
* shape takes packed
|
|
6
|
+
* needs core is resolving a CSS color to a packed number, which is why a
|
|
7
|
+
* shape takes packed colors and the display resolves them.
|
|
8
8
|
*/
|
|
9
9
|
export function abgrToCssRgba(c) {
|
|
10
10
|
const a = ((c >>> 24) & 255) / 255;
|
|
11
11
|
return `rgba(${c & 255},${(c >>> 8) & 255},${(c >>> 16) & 255},${a})`;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
* A fill-style setter that only touches `ctx.fillStyle` when the
|
|
15
|
-
* changes. Most of a painting is runs of one
|
|
14
|
+
* A fill-style setter that only touches `ctx.fillStyle` when the color
|
|
15
|
+
* changes. Most of a painting is runs of one color, and both the string
|
|
16
16
|
* allocation and the context write cost more than the comparison.
|
|
17
17
|
*/
|
|
18
18
|
export function makeAbgrFill(ctx) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { RenderBlock } from '../renderBlock.ts';
|
|
2
|
+
import type { LinkRegion } from './linkMark.ts';
|
|
3
|
+
import type { MarkShape } from './types.ts';
|
|
4
|
+
/**
|
|
5
|
+
* The `connector` shape's channels: a stroked cubic from `x` on the displayed
|
|
6
|
+
* region whose payload holds it, at row `row`'s centre, to `x2` on the
|
|
7
|
+
* displayed region `x2Region` names, at row `row2`'s. `bend` is the depth in
|
|
8
|
+
* px a dipping connector reaches below its ends, or `CONNECTOR_BOW` for one
|
|
9
|
+
* that bows up over its row; `width` the stroke in CSS px; `color` ABGR with
|
|
10
|
+
* its alpha; `bits` the `CONNECTOR_*` flags: each end's strand, a split
|
|
11
|
+
* junction's leading second end, a straight segment, a dash, an arrowhead.
|
|
12
|
+
*/
|
|
13
|
+
export interface ConnectorChannels {
|
|
14
|
+
x: Uint32Array;
|
|
15
|
+
x2: Uint32Array;
|
|
16
|
+
x2Region: Uint32Array;
|
|
17
|
+
row: Uint32Array;
|
|
18
|
+
row2: Uint32Array;
|
|
19
|
+
bend: Float32Array;
|
|
20
|
+
width: Float32Array;
|
|
21
|
+
color: Uint32Array;
|
|
22
|
+
bits: Uint8Array;
|
|
23
|
+
count: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ConnectorParams {
|
|
26
|
+
/** The view's displayed regions, indexed as `x2Region` and the block's own index are. */
|
|
27
|
+
regions: readonly LinkRegion[];
|
|
28
|
+
/** Canvas y of row 0's centre. */
|
|
29
|
+
rowOffsetPx: number;
|
|
30
|
+
/** Canvas px between two rows' centres. */
|
|
31
|
+
rowPitchPx: number;
|
|
32
|
+
/** How far an arrowhead's arms reach back and off the row. */
|
|
33
|
+
arrowPx: number;
|
|
34
|
+
/** A dashed connector's `[dash, gap]` in CSS px, starting on a dash. */
|
|
35
|
+
strokeDash?: readonly [number, number];
|
|
36
|
+
maxHandlePx?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A connector's two ends in canvas px, for a gesture that acts on the nearer
|
|
40
|
+
* one; undefined where the instance places nowhere.
|
|
41
|
+
*/
|
|
42
|
+
export declare function connectorEnds(c: ConnectorChannels, block: RenderBlock, params: ConnectorParams, i: number): {
|
|
43
|
+
x: number;
|
|
44
|
+
y: number;
|
|
45
|
+
x2: number;
|
|
46
|
+
y2: number;
|
|
47
|
+
} | undefined;
|
|
48
|
+
export declare const connectorMark: MarkShape<ConnectorChannels, ConnectorParams>;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { getDpr } from '../canvas2dUtils.js';
|
|
2
|
+
import { CONNECTOR_ARROW, CONNECTOR_CURVE_SEGMENTS, CONNECTOR_DASHED, CONNECTOR_LEADING_2, CONNECTOR_MAX_HANDLE_PX, CONNECTOR_MAX_REACH_PX, CONNECTOR_MINUS_1, CONNECTOR_MINUS_2, CONNECTOR_STRAIGHT, } from '../shaders/connectorMark.consts.generated.js';
|
|
3
|
+
import * as shader from '../shaders/connectorMark.generated.js';
|
|
4
|
+
import { connectorBudgetPx, connectorHandlePx, connectorLiftPx, connectorStrokePx, connectorTangent, } from '../shaders/connectorMark.js.generated.js';
|
|
5
|
+
import { slangPass } from '../slangPass.js';
|
|
6
|
+
import { abgrToCssRgba } from './colorFill.js';
|
|
7
|
+
import { regionPx, viewRegionUniforms } from './linkMark.js';
|
|
8
|
+
import { nearestInk } from './markHit.js';
|
|
9
|
+
function newPlaced() {
|
|
10
|
+
return {
|
|
11
|
+
x0: 0,
|
|
12
|
+
y0: 0,
|
|
13
|
+
x1: 0,
|
|
14
|
+
y1: 0,
|
|
15
|
+
x2: 0,
|
|
16
|
+
y2: 0,
|
|
17
|
+
x3: 0,
|
|
18
|
+
y3: 0,
|
|
19
|
+
strokePx: 0,
|
|
20
|
+
arrow: false,
|
|
21
|
+
back: 0,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const strandOf = (bits, bit) => ((bits & bit) !== 0 ? -1 : 1);
|
|
25
|
+
// Places instance `i` into `p` as the shader does, or answers false where its
|
|
26
|
+
// far end lies on no displayed region the table holds.
|
|
27
|
+
function place(c, params, own, i, p, dpr) {
|
|
28
|
+
const { regions } = params;
|
|
29
|
+
const far = c.x2Region[i];
|
|
30
|
+
const ownRegion = regions[own];
|
|
31
|
+
const farRegion = regions[far];
|
|
32
|
+
if (!ownRegion || !farRegion) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const bits = c.bits[i];
|
|
36
|
+
p.x0 = regionPx(regions, own, c.x[i]);
|
|
37
|
+
p.y0 = params.rowOffsetPx + c.row[i] * params.rowPitchPx;
|
|
38
|
+
p.x3 = regionPx(regions, far, c.x2[i]);
|
|
39
|
+
p.y3 = params.rowOffsetPx + c.row2[i] * params.rowPitchPx;
|
|
40
|
+
p.strokePx = connectorStrokePx(c.width[i], dpr);
|
|
41
|
+
const reversed2 = farRegion.signedPxPerBp < 0;
|
|
42
|
+
const strand2 = strandOf(bits, CONNECTOR_MINUS_2);
|
|
43
|
+
p.arrow = (bits & CONNECTOR_ARROW) !== 0;
|
|
44
|
+
p.back = (reversed2 ? -strand2 : strand2) * -params.arrowPx;
|
|
45
|
+
if ((bits & CONNECTOR_STRAIGHT) !== 0) {
|
|
46
|
+
p.x1 = p.x0;
|
|
47
|
+
p.y1 = p.y0;
|
|
48
|
+
p.x2 = p.x3;
|
|
49
|
+
p.y2 = p.y3;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
const dx = p.x3 - p.x0;
|
|
53
|
+
const dy = p.y3 - p.y0;
|
|
54
|
+
const budget = connectorBudgetPx(dx, dy);
|
|
55
|
+
const handle = connectorHandlePx(budget, params.maxHandlePx ?? CONNECTOR_MAX_HANDLE_PX);
|
|
56
|
+
const lift = connectorLiftPx(budget, dy, c.bend[i]);
|
|
57
|
+
const t1 = connectorTangent(strandOf(bits, CONNECTOR_MINUS_1), 0, ownRegion.signedPxPerBp < 0 ? 1 : 0);
|
|
58
|
+
const t2 = connectorTangent(strand2, (bits & CONNECTOR_LEADING_2) !== 0 ? 1 : 0, reversed2 ? 1 : 0);
|
|
59
|
+
p.x1 = p.x0 + handle * t1;
|
|
60
|
+
p.y1 = p.y0 - lift;
|
|
61
|
+
p.x2 = p.x3 + handle * t2;
|
|
62
|
+
p.y2 = p.y3 - lift;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
// The curve's point at `t`, written into `out`.
|
|
66
|
+
function cubicAt(p, t, out) {
|
|
67
|
+
const s = 1 - t;
|
|
68
|
+
const a = s * s * s;
|
|
69
|
+
const b = 3 * s * s * t;
|
|
70
|
+
const d = 3 * s * t * t;
|
|
71
|
+
const e = t * t * t;
|
|
72
|
+
out.x = a * p.x0 + b * p.x1 + d * p.x2 + e * p.x3;
|
|
73
|
+
out.y = a * p.y0 + b * p.y1 + d * p.y2 + e * p.y3;
|
|
74
|
+
}
|
|
75
|
+
// A connector whose ends both lie further off the canvas than any curve can
|
|
76
|
+
// reach back onto it draws nothing there.
|
|
77
|
+
function offCanvas(p, frame) {
|
|
78
|
+
const reach = CONNECTOR_MAX_REACH_PX + p.strokePx;
|
|
79
|
+
return (Math.max(p.y0, p.y3) + reach < 0 ||
|
|
80
|
+
Math.min(p.y0, p.y3) - reach > frame.canvasHeight);
|
|
81
|
+
}
|
|
82
|
+
function traceCurve(ctx, p) {
|
|
83
|
+
ctx.moveTo(p.x0, p.y0);
|
|
84
|
+
ctx.bezierCurveTo(p.x1, p.y1, p.x2, p.y2, p.x3, p.y3);
|
|
85
|
+
}
|
|
86
|
+
function traceArrow(ctx, p, arrowPx) {
|
|
87
|
+
ctx.moveTo(p.x3 + p.back, p.y3 - arrowPx);
|
|
88
|
+
ctx.lineTo(p.x3, p.y3);
|
|
89
|
+
ctx.moveTo(p.x3 + p.back, p.y3 + arrowPx);
|
|
90
|
+
ctx.lineTo(p.x3, p.y3);
|
|
91
|
+
}
|
|
92
|
+
const svgNumber = (n) => Math.round(n * 100) / 100;
|
|
93
|
+
function svgPathOf(p, arrowPx) {
|
|
94
|
+
const parts = [];
|
|
95
|
+
const pt = (x, y) => `${svgNumber(x)} ${svgNumber(y)}`;
|
|
96
|
+
const sink = {
|
|
97
|
+
moveTo: (x, y) => parts.push(`M${pt(x, y)}`),
|
|
98
|
+
lineTo: (x, y) => parts.push(`L${pt(x, y)}`),
|
|
99
|
+
bezierCurveTo: (x1, y1, x2, y2, x, y) => parts.push(`C${pt(x1, y1)} ${pt(x2, y2)} ${pt(x, y)}`),
|
|
100
|
+
};
|
|
101
|
+
traceCurve(sink, p);
|
|
102
|
+
if (p.arrow) {
|
|
103
|
+
traceArrow(sink, p, arrowPx);
|
|
104
|
+
}
|
|
105
|
+
return parts.join('');
|
|
106
|
+
}
|
|
107
|
+
const POINT = { x: 0, y: 0 };
|
|
108
|
+
// The box the stroke lies in: the hull of the curve's chords, its arrow, and
|
|
109
|
+
// half the stroke.
|
|
110
|
+
function inkBox(p, arrowPx) {
|
|
111
|
+
let left = Math.min(p.x0, p.x3);
|
|
112
|
+
let right = Math.max(p.x0, p.x3);
|
|
113
|
+
let top = Math.min(p.y0, p.y3);
|
|
114
|
+
let bottom = Math.max(p.y0, p.y3);
|
|
115
|
+
for (let k = 1; k < CONNECTOR_CURVE_SEGMENTS; k++) {
|
|
116
|
+
cubicAt(p, k / CONNECTOR_CURVE_SEGMENTS, POINT);
|
|
117
|
+
left = Math.min(left, POINT.x);
|
|
118
|
+
right = Math.max(right, POINT.x);
|
|
119
|
+
top = Math.min(top, POINT.y);
|
|
120
|
+
bottom = Math.max(bottom, POINT.y);
|
|
121
|
+
}
|
|
122
|
+
if (p.arrow) {
|
|
123
|
+
left = Math.min(left, p.x3 + p.back);
|
|
124
|
+
right = Math.max(right, p.x3 + p.back);
|
|
125
|
+
top = Math.min(top, p.y3 - arrowPx);
|
|
126
|
+
bottom = Math.max(bottom, p.y3 + arrowPx);
|
|
127
|
+
}
|
|
128
|
+
const half = p.strokePx / 2;
|
|
129
|
+
return {
|
|
130
|
+
left: left - half,
|
|
131
|
+
top: top - half,
|
|
132
|
+
width: right - left + p.strokePx,
|
|
133
|
+
height: bottom - top + p.strokePx,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// The nearest point to (px, py) on segment (ax, ay)-(bx, by), into `best`
|
|
137
|
+
// where it is nearer than what `best` holds.
|
|
138
|
+
function nearSegment(ax, ay, bx, by, px, py, best) {
|
|
139
|
+
const dx = bx - ax;
|
|
140
|
+
const dy = by - ay;
|
|
141
|
+
const lenSq = dx * dx + dy * dy;
|
|
142
|
+
const t = lenSq > 0
|
|
143
|
+
? Math.min(1, Math.max(0, ((px - ax) * dx + (py - ay) * dy) / lenSq))
|
|
144
|
+
: 0;
|
|
145
|
+
const x = ax + t * dx;
|
|
146
|
+
const y = ay + t * dy;
|
|
147
|
+
const distSq = (px - x) ** 2 + (py - y) ** 2;
|
|
148
|
+
if (distSq < best.distSq) {
|
|
149
|
+
best.x = x;
|
|
150
|
+
best.y = y;
|
|
151
|
+
best.distSq = distSq;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// The nearest point of the drawn ink's centreline, over the chords the GPU
|
|
155
|
+
// strip is built from.
|
|
156
|
+
function nearestOnConnector(p, arrowPx, px, py) {
|
|
157
|
+
const best = { x: p.x0, y: p.y0, distSq: Infinity };
|
|
158
|
+
let ax = p.x0;
|
|
159
|
+
let ay = p.y0;
|
|
160
|
+
for (let k = 1; k <= CONNECTOR_CURVE_SEGMENTS; k++) {
|
|
161
|
+
cubicAt(p, k / CONNECTOR_CURVE_SEGMENTS, POINT);
|
|
162
|
+
nearSegment(ax, ay, POINT.x, POINT.y, px, py, best);
|
|
163
|
+
ax = POINT.x;
|
|
164
|
+
ay = POINT.y;
|
|
165
|
+
}
|
|
166
|
+
if (p.arrow) {
|
|
167
|
+
for (const sign of [-1, 1]) {
|
|
168
|
+
nearSegment(p.x3 + p.back, p.y3 + sign * arrowPx, p.x3, p.y3, px, py, best);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return best;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* A connector's two ends in canvas px, for a gesture that acts on the nearer
|
|
175
|
+
* one; undefined where the instance places nowhere.
|
|
176
|
+
*/
|
|
177
|
+
export function connectorEnds(c, block, params, i) {
|
|
178
|
+
const p = newPlaced();
|
|
179
|
+
return place(c, params, block.displayedRegionIndex, i, p, getDpr())
|
|
180
|
+
? { x: p.x0, y: p.y0, x2: p.x3, y2: p.y3 }
|
|
181
|
+
: undefined;
|
|
182
|
+
}
|
|
183
|
+
const PLACED = newPlaced();
|
|
184
|
+
export const connectorMark = {
|
|
185
|
+
id: 'connector',
|
|
186
|
+
spansView: true,
|
|
187
|
+
pass: {
|
|
188
|
+
...slangPass({ id: 'connector', mod: shader }),
|
|
189
|
+
pack: c => shader.packInstances({
|
|
190
|
+
x: c.x,
|
|
191
|
+
x2: c.x2,
|
|
192
|
+
x2Region: c.x2Region,
|
|
193
|
+
row: c.row,
|
|
194
|
+
row2: c.row2,
|
|
195
|
+
bend: c.bend,
|
|
196
|
+
width: c.width,
|
|
197
|
+
color: c.color,
|
|
198
|
+
bits: c.bits,
|
|
199
|
+
}, c.count),
|
|
200
|
+
},
|
|
201
|
+
writeUniforms(scratch, _clip, block, frame, params) {
|
|
202
|
+
shader.writeUniforms(scratch, {
|
|
203
|
+
canvasHeight: frame.canvasHeight,
|
|
204
|
+
canvasWidth: frame.canvasWidth,
|
|
205
|
+
devicePixelRatio: getDpr(),
|
|
206
|
+
rowOffsetPx: params.rowOffsetPx,
|
|
207
|
+
rowPitchPx: params.rowPitchPx,
|
|
208
|
+
maxHandlePx: params.maxHandlePx ?? CONNECTOR_MAX_HANDLE_PX,
|
|
209
|
+
dashPx: params.strokeDash?.[0] ?? 0,
|
|
210
|
+
gapPx: params.strokeDash?.[1] ?? 0,
|
|
211
|
+
arrowPx: params.arrowPx,
|
|
212
|
+
zero: 0,
|
|
213
|
+
...viewRegionUniforms(params.regions, block.displayedRegionIndex),
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
paintsBlock(block, _frame, params) {
|
|
217
|
+
return params.regions[block.displayedRegionIndex] !== undefined;
|
|
218
|
+
},
|
|
219
|
+
paintBlock(ctx, c, block, frame, params) {
|
|
220
|
+
const own = block.displayedRegionIndex;
|
|
221
|
+
const dpr = getDpr();
|
|
222
|
+
const dash = params.strokeDash ? [...params.strokeDash] : [];
|
|
223
|
+
ctx.lineCap = 'butt';
|
|
224
|
+
for (let i = 0; i < c.count; i++) {
|
|
225
|
+
if (!place(c, params, own, i, PLACED, dpr) || offCanvas(PLACED, frame)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
ctx.lineWidth = PLACED.strokePx;
|
|
229
|
+
ctx.strokeStyle = abgrToCssRgba(c.color[i]);
|
|
230
|
+
ctx.setLineDash((c.bits[i] & CONNECTOR_DASHED) !== 0 ? dash : []);
|
|
231
|
+
ctx.beginPath();
|
|
232
|
+
traceCurve(ctx, PLACED);
|
|
233
|
+
ctx.stroke();
|
|
234
|
+
if (PLACED.arrow) {
|
|
235
|
+
ctx.setLineDash([]);
|
|
236
|
+
ctx.beginPath();
|
|
237
|
+
traceArrow(ctx, PLACED, params.arrowPx);
|
|
238
|
+
ctx.stroke();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
ctx.setLineDash([]);
|
|
242
|
+
},
|
|
243
|
+
ink(c, block, _frame, params, i) {
|
|
244
|
+
const p = newPlaced();
|
|
245
|
+
if (!place(c, params, block.displayedRegionIndex, i, p, getDpr())) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
...inkBox(p, params.arrowPx),
|
|
250
|
+
stroke: {
|
|
251
|
+
d: svgPathOf(p, params.arrowPx),
|
|
252
|
+
widthPx: p.strokePx,
|
|
253
|
+
originX: 0,
|
|
254
|
+
originY: 0,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
},
|
|
258
|
+
hitNearest(c, block, _frame, params, xPx, yPx, candidates, maxDistSq) {
|
|
259
|
+
const own = block.displayedRegionIndex;
|
|
260
|
+
const dpr = getDpr();
|
|
261
|
+
const reach = Math.sqrt(maxDistSq);
|
|
262
|
+
return nearestInk(candidates, maxDistSq, i => {
|
|
263
|
+
if (!place(c, params, own, i, PLACED, dpr)) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
const box = inkBox(PLACED, params.arrowPx);
|
|
267
|
+
if (xPx < box.left - reach ||
|
|
268
|
+
xPx > box.left + box.width + reach ||
|
|
269
|
+
yPx < box.top - reach ||
|
|
270
|
+
yPx > box.top + box.height + reach) {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
const near = nearestOnConnector(PLACED, params.arrowPx, xPx, yPx);
|
|
274
|
+
const dist = Math.sqrt(near.distSq);
|
|
275
|
+
const half = PLACED.strokePx / 2;
|
|
276
|
+
if (dist <= half) {
|
|
277
|
+
return { x: xPx, y: yPx, distSq: 0 };
|
|
278
|
+
}
|
|
279
|
+
const t = half / dist;
|
|
280
|
+
const x = near.x + (xPx - near.x) * t;
|
|
281
|
+
const y = near.y + (yPx - near.y) * t;
|
|
282
|
+
return { x, y, distSq: (xPx - x) ** 2 + (yPx - y) ** 2 };
|
|
283
|
+
});
|
|
284
|
+
},
|
|
285
|
+
};
|
|
@@ -65,7 +65,7 @@ export declare function inkViolations<C, S extends MarkFrame>(mark: Mark<C, S>,
|
|
|
65
65
|
* **What the painter drew for instance `i` has to be attributed to `i`,** and
|
|
66
66
|
* there are two ways to get that. A mark whose every instance is one `fillRect`
|
|
67
67
|
* gets it positionally, which is the default and needs nothing. A mark that
|
|
68
|
-
* batches a
|
|
68
|
+
* batches a color run into one path (`point`'s glyphs), or that skips an
|
|
69
69
|
* instance the view has scrolled past (`cell`'s off-canvas rows), cannot be read
|
|
70
70
|
* that way at all — the batch has fewer rects than instances, or the wrong ones.
|
|
71
71
|
* `sliceOne` is the way in for those: hand back one instance's channels and the
|
|
@@ -3,7 +3,7 @@ const IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
|
3
3
|
/**
|
|
4
4
|
* A `MarkContext2D` that records every rect a painter fills, in paint order and
|
|
5
5
|
* in CANVAS coordinates — `fillRect` and path `rect` alike, so a shape that
|
|
6
|
-
* batches by
|
|
6
|
+
* batches by color records the same list as one that fills per instance. A
|
|
7
7
|
* path traced with `moveTo`/`lineTo`/`bezierCurveTo` and filled records its
|
|
8
8
|
* bounding box, the extent a shape whose hit test answers a polygon's box
|
|
9
9
|
* (the variant inversion triangle) is checked against; a curve contributes its control
|
|
@@ -12,9 +12,9 @@ const IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
|
12
12
|
* A **stroked** path records one rect per `moveTo`/`lineTo` edge, each the
|
|
13
13
|
* edge's bounding box widened by half the current `lineWidth` — the extent a
|
|
14
14
|
* round cap reaches on all four sides. Per edge rather than per `stroke()`,
|
|
15
|
-
* because a painter that batches a
|
|
15
|
+
* because a painter that batches a color run into one path (dotplot's) would
|
|
16
16
|
* otherwise record the run's hull as a single instance. `fillStyle` on such a
|
|
17
|
-
* record is the `strokeStyle` it went down in; the field is the
|
|
17
|
+
* record is the `strokeStyle` it went down in; the field is the color of the
|
|
18
18
|
* ink either way.
|
|
19
19
|
*
|
|
20
20
|
* `save`/`restore` bracket the transform and the styles the way a real context
|
|
@@ -281,7 +281,7 @@ function hitViolations(hit, boxes, x, y) {
|
|
|
281
281
|
* **What the painter drew for instance `i` has to be attributed to `i`,** and
|
|
282
282
|
* there are two ways to get that. A mark whose every instance is one `fillRect`
|
|
283
283
|
* gets it positionally, which is the default and needs nothing. A mark that
|
|
284
|
-
* batches a
|
|
284
|
+
* batches a color run into one path (`point`'s glyphs), or that skips an
|
|
285
285
|
* instance the view has scrolled past (`cell`'s off-canvas rows), cannot be read
|
|
286
286
|
* that way at all — the batch has fewer rects than instances, or the wrong ones.
|
|
287
287
|
* `sliceOne` is the way in for those: hand back one instance's channels and the
|
|
@@ -3,11 +3,12 @@ import type { InkRect, MarkContext2D } from './types.ts';
|
|
|
3
3
|
export type GlyphPath = Pick<MarkContext2D, 'moveTo' | 'lineTo' | 'closePath' | 'rect' | 'arc'>;
|
|
4
4
|
/**
|
|
5
5
|
* Append one of the `point` shape's glyphs to the current path, centred on
|
|
6
|
-
* `(cx, y)`.
|
|
6
|
+
* `(cx, y)`. The triangle is `triangleHeight` tall, `diameter` unless the
|
|
7
|
+
* mark draws one wider than it is tall.
|
|
7
8
|
*
|
|
8
9
|
* The disc/crisp-square split and the square's snap are pointGlyph.slang's
|
|
9
10
|
* (adr-051), so the painter cannot drift from the shader about where the split
|
|
10
|
-
* is. The caller owns `beginPath`/`fill`, so a run of one
|
|
11
|
+
* is. The caller owns `beginPath`/`fill`, so a run of one color can batch into
|
|
11
12
|
* a single fill.
|
|
12
13
|
*
|
|
13
14
|
* Its own module, not `pointMark.ts`'s: this is the half of the shape a
|
|
@@ -15,7 +16,7 @@ export type GlyphPath = Pick<MarkContext2D, 'moveTo' | 'lineTo' | 'closePath' |
|
|
|
15
16
|
* hand that consumer the shader's WGSL and GLSL strings. Wiggle's scatter mode
|
|
16
17
|
* is that consumer.
|
|
17
18
|
*/
|
|
18
|
-
export declare function appendGlyph(ctx: GlyphPath, glyph: number, cx: number, y: number, diameter: number): void;
|
|
19
|
+
export declare function appendGlyph(ctx: GlyphPath, glyph: number, cx: number, y: number, diameter: number, triangleHeight?: number): void;
|
|
19
20
|
/**
|
|
20
21
|
* Room a glyph needs at each end of a plot whose display wants its topmost and
|
|
21
22
|
* bottommost points drawn whole: a bar's datum is its top edge and never
|
|
@@ -33,4 +34,4 @@ export declare function pointInsetPx(diameter: number): number;
|
|
|
33
34
|
* diamond is scaled by `DIAMOND_GLYPH_SCALE` and the small square snaps to
|
|
34
35
|
* the pixel grid, so neither is the diameter about the centre.
|
|
35
36
|
*/
|
|
36
|
-
export declare function glyphBox(glyph: number, cx: number, y: number, diameter: number): InkRect;
|
|
37
|
+
export declare function glyphBox(glyph: number, cx: number, y: number, diameter: number, triangleHeight?: number): InkRect;
|
package/esm/marks/glyphPaint.js
CHANGED
|
@@ -3,11 +3,12 @@ import { crispSquareTopLeftPx } from '../shaders/pointGlyph.js.generated.js';
|
|
|
3
3
|
import { DIAMOND_GLYPH_SCALE, GLYPH_DIAMOND, GLYPH_SQUARE, GLYPH_TRIANGLE, } from '../shaders/pointMark.consts.generated.js';
|
|
4
4
|
/**
|
|
5
5
|
* Append one of the `point` shape's glyphs to the current path, centred on
|
|
6
|
-
* `(cx, y)`.
|
|
6
|
+
* `(cx, y)`. The triangle is `triangleHeight` tall, `diameter` unless the
|
|
7
|
+
* mark draws one wider than it is tall.
|
|
7
8
|
*
|
|
8
9
|
* The disc/crisp-square split and the square's snap are pointGlyph.slang's
|
|
9
10
|
* (adr-051), so the painter cannot drift from the shader about where the split
|
|
10
|
-
* is. The caller owns `beginPath`/`fill`, so a run of one
|
|
11
|
+
* is. The caller owns `beginPath`/`fill`, so a run of one color can batch into
|
|
11
12
|
* a single fill.
|
|
12
13
|
*
|
|
13
14
|
* Its own module, not `pointMark.ts`'s: this is the half of the shape a
|
|
@@ -15,12 +16,13 @@ import { DIAMOND_GLYPH_SCALE, GLYPH_DIAMOND, GLYPH_SQUARE, GLYPH_TRIANGLE, } fro
|
|
|
15
16
|
* hand that consumer the shader's WGSL and GLSL strings. Wiggle's scatter mode
|
|
16
17
|
* is that consumer.
|
|
17
18
|
*/
|
|
18
|
-
export function appendGlyph(ctx, glyph, cx, y, diameter) {
|
|
19
|
+
export function appendGlyph(ctx, glyph, cx, y, diameter, triangleHeight = diameter) {
|
|
19
20
|
const r = diameter / 2;
|
|
20
21
|
if (glyph === GLYPH_TRIANGLE) {
|
|
21
|
-
|
|
22
|
-
ctx.
|
|
23
|
-
ctx.lineTo(cx, y
|
|
22
|
+
const ry = triangleHeight / 2;
|
|
23
|
+
ctx.moveTo(cx - r, y - ry);
|
|
24
|
+
ctx.lineTo(cx + r, y - ry);
|
|
25
|
+
ctx.lineTo(cx, y + ry);
|
|
24
26
|
ctx.closePath();
|
|
25
27
|
}
|
|
26
28
|
else if (glyph === GLYPH_DIAMOND) {
|
|
@@ -58,8 +60,16 @@ export function pointInsetPx(diameter) {
|
|
|
58
60
|
* diamond is scaled by `DIAMOND_GLYPH_SCALE` and the small square snaps to
|
|
59
61
|
* the pixel grid, so neither is the diameter about the centre.
|
|
60
62
|
*/
|
|
61
|
-
export function glyphBox(glyph, cx, y, diameter) {
|
|
63
|
+
export function glyphBox(glyph, cx, y, diameter, triangleHeight = diameter) {
|
|
62
64
|
const r = diameter / 2;
|
|
65
|
+
if (glyph === GLYPH_TRIANGLE) {
|
|
66
|
+
return {
|
|
67
|
+
left: cx - r,
|
|
68
|
+
top: y - triangleHeight / 2,
|
|
69
|
+
width: diameter,
|
|
70
|
+
height: triangleHeight,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
63
73
|
if (glyph === GLYPH_DIAMOND) {
|
|
64
74
|
const ri = r * DIAMOND_GLYPH_SCALE;
|
|
65
75
|
return { left: cx - ri, top: y - ri, width: 2 * ri, height: 2 * ri };
|