@jbrowse/render-core 5.0.0-beta.13 → 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.
Files changed (153) hide show
  1. package/esm/OverlayCanvas.d.ts +3 -2
  2. package/esm/OverlayCanvas.js +4 -2
  3. package/esm/RenderCanvas.d.ts +4 -3
  4. package/esm/RenderCanvas.js +4 -2
  5. package/esm/ScrollLockedOverlay.d.ts +2 -2
  6. package/esm/ScrollLockedOverlay.js +3 -2
  7. package/esm/blockClipUtils.d.ts +5 -5
  8. package/esm/blockClipUtils.js +4 -3
  9. package/esm/canvas2dUtils.d.ts +15 -21
  10. package/esm/canvas2dUtils.js +25 -55
  11. package/esm/colorRampLut.d.ts +2 -2
  12. package/esm/colorRampLut.js +2 -2
  13. package/esm/coverageBand.d.ts +0 -1
  14. package/esm/coverageBand.js +0 -5
  15. package/esm/coverageBandBuffers.d.ts +8 -5
  16. package/esm/createRenderingBackend.d.ts +1 -1
  17. package/esm/displayPhase.d.ts +5 -9
  18. package/esm/displayPhase.js +2 -2
  19. package/esm/hal/mockHal.d.ts +3 -4
  20. package/esm/hal/mockHal.js +5 -4
  21. package/esm/hal/types.d.ts +5 -5
  22. package/esm/hal/webgl2Hal.d.ts +4 -1
  23. package/esm/hal/webgl2Hal.js +15 -6
  24. package/esm/hal/webgpuHal.js +2 -2
  25. package/esm/linkFeet.d.ts +4 -0
  26. package/esm/linkFeet.js +7 -0
  27. package/esm/marks/barMark.d.ts +1 -1
  28. package/esm/marks/colorFill.d.ts +4 -4
  29. package/esm/marks/colorFill.js +4 -4
  30. package/esm/marks/connectorMark.d.ts +48 -0
  31. package/esm/marks/connectorMark.js +285 -0
  32. package/esm/marks/drawAgainstHit.d.ts +1 -1
  33. package/esm/marks/drawAgainstHit.js +4 -4
  34. package/esm/marks/glyphPaint.d.ts +5 -4
  35. package/esm/marks/glyphPaint.js +17 -7
  36. package/esm/marks/index.d.ts +6 -3
  37. package/esm/marks/index.js +5 -3
  38. package/esm/marks/lineMark.d.ts +44 -10
  39. package/esm/marks/lineMark.js +20 -19
  40. package/esm/marks/linkMark.d.ts +22 -5
  41. package/esm/marks/linkMark.js +103 -25
  42. package/esm/marks/markBackend.d.ts +1 -1
  43. package/esm/marks/markBackend.js +1 -1
  44. package/esm/marks/markRamp.d.ts +11 -11
  45. package/esm/marks/markRamp.js +9 -9
  46. package/esm/marks/pointMark.d.ts +11 -5
  47. package/esm/marks/pointMark.js +34 -9
  48. package/esm/marks/rowLane.d.ts +3 -3
  49. package/esm/marks/rowLane.js +2 -2
  50. package/esm/marks/rowTable.d.ts +5 -5
  51. package/esm/marks/rowTable.js +4 -4
  52. package/esm/marks/ruleMark.js +1 -0
  53. package/esm/marks/spanMark.d.ts +4 -4
  54. package/esm/marks/types.d.ts +4 -4
  55. package/esm/marks/types.js +5 -0
  56. package/esm/perRegionRenderingBackend.d.ts +1 -1
  57. package/esm/perRegionRenderingBackend.js +1 -1
  58. package/esm/regionDataMap.d.ts +2 -2
  59. package/esm/regionDataMap.js +2 -2
  60. package/esm/shaders/capsule.js.generated.d.ts +1 -1
  61. package/esm/shaders/capsule.js.generated.js +2 -2
  62. package/esm/shaders/connectorMark.consts.generated.d.ts +11 -0
  63. package/esm/shaders/connectorMark.consts.generated.js +13 -0
  64. package/esm/shaders/{coverageIndicator.generated.d.ts → connectorMark.generated.d.ts} +2 -1
  65. package/esm/shaders/connectorMark.generated.js +8 -0
  66. package/esm/shaders/connectorMark.glsl.generated.d.ts +2 -0
  67. package/esm/shaders/connectorMark.glsl.generated.js +4 -0
  68. package/esm/shaders/connectorMark.iface.generated.d.ts +88 -0
  69. package/esm/shaders/connectorMark.iface.generated.js +1194 -0
  70. package/esm/shaders/connectorMark.js.generated.d.ts +5 -0
  71. package/esm/shaders/connectorMark.js.generated.js +47 -0
  72. package/esm/shaders/connectorMark.wgsl.generated.d.ts +1 -0
  73. package/esm/shaders/connectorMark.wgsl.generated.js +3 -0
  74. package/esm/shaders/coverageInterbase.glsl.generated.d.ts +1 -1
  75. package/esm/shaders/coverageInterbase.glsl.generated.js +1 -1
  76. package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -1
  77. package/esm/shaders/coverageInterbase.wgsl.generated.js +1 -1
  78. package/esm/shaders/curveDistance.js.generated.d.ts +3 -0
  79. package/esm/shaders/curveDistance.js.generated.js +104 -0
  80. package/esm/shaders/lineCenterMark.glsl.generated.d.ts +2 -2
  81. package/esm/shaders/lineCenterMark.glsl.generated.js +2 -2
  82. package/esm/shaders/lineCenterMark.wgsl.generated.d.ts +1 -1
  83. package/esm/shaders/lineCenterMark.wgsl.generated.js +1 -1
  84. package/esm/shaders/linkMark.consts.generated.d.ts +1 -0
  85. package/esm/shaders/linkMark.consts.generated.js +1 -0
  86. package/esm/shaders/linkMark.glsl.generated.d.ts +2 -2
  87. package/esm/shaders/linkMark.glsl.generated.js +2 -2
  88. package/esm/shaders/linkMark.js.generated.d.ts +2 -1
  89. package/esm/shaders/linkMark.js.generated.js +30 -1
  90. package/esm/shaders/linkMark.wgsl.generated.d.ts +1 -1
  91. package/esm/shaders/linkMark.wgsl.generated.js +1 -1
  92. package/esm/shaders/pointMark.glsl.generated.d.ts +1 -1
  93. package/esm/shaders/pointMark.glsl.generated.js +1 -1
  94. package/esm/shaders/pointMark.iface.generated.d.ts +10 -8
  95. package/esm/shaders/pointMark.iface.generated.js +18 -16
  96. package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -1
  97. package/esm/shaders/pointMark.wgsl.generated.js +1 -1
  98. package/package.json +7 -4
  99. package/src/shaders/arcHull.test.ts +87 -0
  100. package/src/shaders/barMark.slang +2 -2
  101. package/src/shaders/capsule.js.generated.ts +2 -2
  102. package/src/shaders/capsule.slang +69 -24
  103. package/src/shaders/clipStrip.slang +1 -1
  104. package/src/shaders/colorPack.slang +2 -2
  105. package/src/shaders/colorRampLut.slang +7 -7
  106. package/src/shaders/connectorMark.consts.generated.ts +24 -0
  107. package/src/shaders/connectorMark.generated.ts +12 -0
  108. package/src/shaders/connectorMark.glsl.generated.ts +6 -0
  109. package/src/shaders/connectorMark.iface.generated.ts +1270 -0
  110. package/src/shaders/connectorMark.js.generated.ts +52 -0
  111. package/src/shaders/connectorMark.slang +287 -0
  112. package/src/shaders/connectorMark.wgsl.generated.ts +4 -0
  113. package/src/shaders/coverageBand.slang +10 -12
  114. package/src/shaders/coverageInterbase.glsl.generated.ts +1 -1
  115. package/src/shaders/coverageInterbase.slang +24 -21
  116. package/src/shaders/coverageInterbase.wgsl.generated.ts +1 -1
  117. package/src/shaders/coverageSnp.slang +1 -1
  118. package/src/shaders/curveDistance.js.generated.ts +100 -0
  119. package/src/shaders/curveDistance.slang +104 -1
  120. package/src/shaders/farDome.test.ts +117 -0
  121. package/src/shaders/hpClampFloor.test.ts +0 -4
  122. package/src/shaders/lineCenterMark.glsl.generated.ts +2 -2
  123. package/src/shaders/lineCenterMark.slang +14 -22
  124. package/src/shaders/lineCenterMark.wgsl.generated.ts +1 -1
  125. package/src/shaders/lineCommon.slang +10 -10
  126. package/src/shaders/lineStepMark.slang +5 -5
  127. package/src/shaders/linkMark.consts.generated.ts +2 -0
  128. package/src/shaders/linkMark.glsl.generated.ts +2 -2
  129. package/src/shaders/linkMark.js.generated.ts +27 -1
  130. package/src/shaders/linkMark.slang +58 -18
  131. package/src/shaders/linkMark.wgsl.generated.ts +1 -1
  132. package/src/shaders/markColor.slang +10 -10
  133. package/src/shaders/pointMark.glsl.generated.ts +1 -1
  134. package/src/shaders/pointMark.iface.generated.ts +19 -16
  135. package/src/shaders/pointMark.slang +12 -8
  136. package/src/shaders/pointMark.wgsl.generated.ts +1 -1
  137. package/src/shaders/rowRect.slang +3 -3
  138. package/src/shaders/rowTable.slang +7 -7
  139. package/src/shaders/scoreScale.slang +1 -1
  140. package/src/shaders/spanMark.slang +5 -5
  141. package/src/shaders/viewRegion.slang +13 -0
  142. package/esm/shaders/coverageIndicator.generated.js +0 -7
  143. package/esm/shaders/coverageIndicator.glsl.generated.d.ts +0 -2
  144. package/esm/shaders/coverageIndicator.glsl.generated.js +0 -4
  145. package/esm/shaders/coverageIndicator.iface.generated.d.ts +0 -85
  146. package/esm/shaders/coverageIndicator.iface.generated.js +0 -114
  147. package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +0 -1
  148. package/esm/shaders/coverageIndicator.wgsl.generated.js +0 -3
  149. package/src/shaders/coverageIndicator.generated.ts +0 -11
  150. package/src/shaders/coverageIndicator.glsl.generated.ts +0 -6
  151. package/src/shaders/coverageIndicator.iface.generated.ts +0 -172
  152. package/src/shaders/coverageIndicator.slang +0 -61
  153. package/src/shaders/coverageIndicator.wgsl.generated.ts +0 -4
@@ -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 colour attachment, resolved into the canvas texture at the
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 colour ramp, and since the ramp became 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
@@ -0,0 +1,4 @@
1
+ export declare const LINK_FOOT_FORWARD = 1;
2
+ export declare const LINK_FOOT_REVERSE = 2;
3
+ /** The `feet` lane's value for a tick at each foot, by genomic direction. */
4
+ export declare function linkFeet(xDir: number, x2Dir: number): number;
@@ -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
+ }
@@ -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 colour scale, for a bar whose colour is a ramp or a threshold. */
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;
@@ -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 colour to a packed number, which is why a
7
- * shape takes packed colours and the display resolves them.
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 colour
12
- * changes. Most of a painting is runs of one colour, and both the string
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: {
@@ -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 colour to a packed number, which is why a
7
- * shape takes packed colours and the display resolves them.
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 colour
15
- * changes. Most of a painting is runs of one colour, and both the string
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 colour run into one path (`point`'s glyphs), or that skips an
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 colour records the same list as one that fills per instance. A
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 colour run into one path (dotplot's) would
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 colour of 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 colour run into one path (`point`'s glyphs), or that skips an
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 colour can batch into
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;
@@ -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 colour can batch into
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
- ctx.moveTo(cx - r, y - r);
22
- ctx.lineTo(cx + r, y - r);
23
- ctx.lineTo(cx, y + r);
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 };
@@ -4,11 +4,13 @@ export { planMarks } from './markPlan.ts';
4
4
  export { inkOfInstances, shiftInk } from './markInk.ts';
5
5
  export { backToFront, nearestMarkHit, valueWindow } from './nearestMarkHit.ts';
6
6
  export { barMark } from './barMark.ts';
7
- export { pointMark } from './pointMark.ts';
7
+ export { pointInstance, pointMark } from './pointMark.ts';
8
8
  export { ruleMark } from './ruleMark.ts';
9
9
  export { spanMark } from './spanMark.ts';
10
- export { lineCenterMark, lineMarkOf, lineStepMark } from './lineMark.ts';
11
- export { linkApex, linkFeet, linkMark, LINK_ELSEWHERE, LINK_NO_REGION, } from './linkMark.ts';
10
+ export { BandPen, lineCenterMark, lineMarkOf, lineStepMark, } from './lineMark.ts';
11
+ export { linkApex, linkLabelAnchor, linkFeet, linkInstance, linkMark, LINK_ELSEWHERE, LINK_NO_REGION, } from './linkMark.ts';
12
+ export { connectorEnds, connectorMark } from './connectorMark.ts';
13
+ export { CONNECTOR_ARROW, CONNECTOR_BOW, CONNECTOR_DASHED, CONNECTOR_LEADING_2, CONNECTOR_MAX_REACH_PX, CONNECTOR_MINUS_1, CONNECTOR_MINUS_2, CONNECTOR_STRAIGHT, } from '../shaders/connectorMark.consts.generated.ts';
12
14
  export { ellipseDistance, ellipseNearest } from './ellipseDistance.ts';
13
15
  export { appendGlyph, pointInsetPx } from './glyphPaint.ts';
14
16
  export { recordPath } from './pathRecorder.ts';
@@ -28,6 +30,7 @@ export type { RuleChannels, RuleParams } from './ruleMark.ts';
28
30
  export type { SpanChannels, SpanParams } from './spanMark.ts';
29
31
  export type { LineChannels, LineInterpolate, LineParams } from './lineMark.ts';
30
32
  export type { LinkApex, LinkChannels, LinkParams, LinkRegion, LinkSizeScale, } from './linkMark.ts';
33
+ export type { ConnectorChannels, ConnectorParams } from './connectorMark.ts';
31
34
  export type { RowTable } from './rowTable.ts';
32
35
  export type { RowSpanIndex } from './rowSpanIndex.ts';
33
36
  export type { ColorChannel } from './markRamp.ts';
@@ -4,11 +4,13 @@ export { planMarks } from './markPlan.js';
4
4
  export { inkOfInstances, shiftInk } from './markInk.js';
5
5
  export { backToFront, nearestMarkHit, valueWindow } from './nearestMarkHit.js';
6
6
  export { barMark } from './barMark.js';
7
- export { pointMark } from './pointMark.js';
7
+ export { pointInstance, pointMark } from './pointMark.js';
8
8
  export { ruleMark } from './ruleMark.js';
9
9
  export { spanMark } from './spanMark.js';
10
- export { lineCenterMark, lineMarkOf, lineStepMark } from './lineMark.js';
11
- export { linkApex, linkFeet, linkMark, LINK_ELSEWHERE, LINK_NO_REGION, } from './linkMark.js';
10
+ export { BandPen, lineCenterMark, lineMarkOf, lineStepMark, } from './lineMark.js';
11
+ export { linkApex, linkLabelAnchor, linkFeet, linkInstance, linkMark, LINK_ELSEWHERE, LINK_NO_REGION, } from './linkMark.js';
12
+ export { connectorEnds, connectorMark } from './connectorMark.js';
13
+ export { CONNECTOR_ARROW, CONNECTOR_BOW, CONNECTOR_DASHED, CONNECTOR_LEADING_2, CONNECTOR_MAX_REACH_PX, CONNECTOR_MINUS_1, CONNECTOR_MINUS_2, CONNECTOR_STRAIGHT, } from '../shaders/connectorMark.consts.generated.js';
12
14
  export { ellipseDistance, ellipseNearest } from './ellipseDistance.js';
13
15
  export { appendGlyph, pointInsetPx } from './glyphPaint.js';
14
16
  export { recordPath } from './pathRecorder.js';