@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.
Files changed (190) 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 +18 -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/barMark.js +4 -2
  29. package/esm/marks/colorFill.d.ts +4 -4
  30. package/esm/marks/colorFill.js +4 -4
  31. package/esm/marks/connectorMark.d.ts +48 -0
  32. package/esm/marks/connectorMark.js +285 -0
  33. package/esm/marks/drawAgainstHit.d.ts +1 -1
  34. package/esm/marks/drawAgainstHit.js +4 -4
  35. package/esm/marks/glyphPaint.d.ts +5 -4
  36. package/esm/marks/glyphPaint.js +17 -7
  37. package/esm/marks/index.d.ts +8 -5
  38. package/esm/marks/index.js +6 -4
  39. package/esm/marks/lineMark.d.ts +47 -7
  40. package/esm/marks/lineMark.js +53 -39
  41. package/esm/marks/linkMark.d.ts +43 -6
  42. package/esm/marks/linkMark.js +118 -21
  43. package/esm/marks/markBackend.d.ts +1 -1
  44. package/esm/marks/markBackend.js +1 -1
  45. package/esm/marks/markPlan.d.ts +4 -3
  46. package/esm/marks/markPlan.js +5 -4
  47. package/esm/marks/markRamp.d.ts +11 -11
  48. package/esm/marks/markRamp.js +9 -9
  49. package/esm/marks/pointMark.d.ts +11 -5
  50. package/esm/marks/pointMark.js +34 -9
  51. package/esm/marks/rowLane.d.ts +4 -2
  52. package/esm/marks/rowLane.js +7 -3
  53. package/esm/marks/rowTable.d.ts +5 -5
  54. package/esm/marks/rowTable.js +4 -4
  55. package/esm/marks/ruleMark.js +1 -0
  56. package/esm/marks/spanMark.d.ts +8 -7
  57. package/esm/marks/spanMark.js +3 -3
  58. package/esm/marks/types.d.ts +8 -5
  59. package/esm/marks/types.js +8 -1
  60. package/esm/perRegionRenderingBackend.d.ts +1 -1
  61. package/esm/perRegionRenderingBackend.js +1 -1
  62. package/esm/regionDataMap.d.ts +2 -2
  63. package/esm/regionDataMap.js +2 -2
  64. package/esm/shaders/barMark.glsl.generated.d.ts +1 -1
  65. package/esm/shaders/barMark.glsl.generated.js +1 -1
  66. package/esm/shaders/barMark.wgsl.generated.d.ts +1 -1
  67. package/esm/shaders/barMark.wgsl.generated.js +1 -1
  68. package/esm/shaders/capsule.js.generated.d.ts +1 -1
  69. package/esm/shaders/capsule.js.generated.js +2 -2
  70. package/esm/shaders/connectorMark.consts.generated.d.ts +11 -0
  71. package/esm/shaders/connectorMark.consts.generated.js +13 -0
  72. package/esm/shaders/{coverageIndicator.generated.d.ts → connectorMark.generated.d.ts} +2 -1
  73. package/esm/shaders/connectorMark.generated.js +8 -0
  74. package/esm/shaders/connectorMark.glsl.generated.d.ts +2 -0
  75. package/esm/shaders/connectorMark.glsl.generated.js +4 -0
  76. package/esm/shaders/connectorMark.iface.generated.d.ts +88 -0
  77. package/esm/shaders/connectorMark.iface.generated.js +1194 -0
  78. package/esm/shaders/connectorMark.js.generated.d.ts +5 -0
  79. package/esm/shaders/connectorMark.js.generated.js +47 -0
  80. package/esm/shaders/connectorMark.wgsl.generated.d.ts +1 -0
  81. package/esm/shaders/connectorMark.wgsl.generated.js +3 -0
  82. package/esm/shaders/coverageInterbase.glsl.generated.d.ts +1 -1
  83. package/esm/shaders/coverageInterbase.glsl.generated.js +1 -1
  84. package/esm/shaders/coverageInterbase.wgsl.generated.d.ts +1 -1
  85. package/esm/shaders/coverageInterbase.wgsl.generated.js +1 -1
  86. package/esm/shaders/curveDistance.js.generated.d.ts +4 -0
  87. package/esm/shaders/curveDistance.js.generated.js +113 -0
  88. package/esm/shaders/insertionGlyph.generated.d.ts +6 -0
  89. package/esm/shaders/insertionGlyph.generated.js +8 -0
  90. package/esm/shaders/lineCenterMark.glsl.generated.d.ts +2 -2
  91. package/esm/shaders/lineCenterMark.glsl.generated.js +2 -2
  92. package/esm/shaders/lineCenterMark.iface.generated.d.ts +11 -9
  93. package/esm/shaders/lineCenterMark.iface.generated.js +20 -18
  94. package/esm/shaders/lineCenterMark.wgsl.generated.d.ts +1 -1
  95. package/esm/shaders/lineCenterMark.wgsl.generated.js +1 -1
  96. package/esm/shaders/lineCommon.js.generated.d.ts +1 -0
  97. package/esm/shaders/lineCommon.js.generated.js +20 -0
  98. package/esm/shaders/lineStepMark.glsl.generated.d.ts +2 -2
  99. package/esm/shaders/lineStepMark.glsl.generated.js +2 -2
  100. package/esm/shaders/lineStepMark.iface.generated.d.ts +11 -9
  101. package/esm/shaders/lineStepMark.iface.generated.js +20 -18
  102. package/esm/shaders/lineStepMark.wgsl.generated.d.ts +1 -1
  103. package/esm/shaders/lineStepMark.wgsl.generated.js +1 -1
  104. package/esm/shaders/linkMark.consts.generated.d.ts +1 -0
  105. package/esm/shaders/linkMark.consts.generated.js +1 -0
  106. package/esm/shaders/linkMark.glsl.generated.d.ts +2 -2
  107. package/esm/shaders/linkMark.glsl.generated.js +2 -2
  108. package/esm/shaders/linkMark.js.generated.d.ts +4 -2
  109. package/esm/shaders/linkMark.js.generated.js +33 -4
  110. package/esm/shaders/linkMark.wgsl.generated.d.ts +1 -1
  111. package/esm/shaders/linkMark.wgsl.generated.js +1 -1
  112. package/esm/shaders/pointMark.glsl.generated.d.ts +1 -1
  113. package/esm/shaders/pointMark.glsl.generated.js +1 -1
  114. package/esm/shaders/pointMark.iface.generated.d.ts +10 -8
  115. package/esm/shaders/pointMark.iface.generated.js +18 -16
  116. package/esm/shaders/pointMark.wgsl.generated.d.ts +1 -1
  117. package/esm/shaders/pointMark.wgsl.generated.js +1 -1
  118. package/esm/shaders/spanMark.glsl.generated.d.ts +1 -1
  119. package/esm/shaders/spanMark.glsl.generated.js +1 -1
  120. package/esm/shaders/spanMark.wgsl.generated.d.ts +1 -1
  121. package/esm/shaders/spanMark.wgsl.generated.js +1 -1
  122. package/esm/slangPass.d.ts +5 -3
  123. package/esm/slangPass.js +2 -2
  124. package/package.json +7 -4
  125. package/src/shaders/arcHull.test.ts +87 -0
  126. package/src/shaders/barMark.glsl.generated.ts +1 -1
  127. package/src/shaders/barMark.slang +2 -2
  128. package/src/shaders/barMark.wgsl.generated.ts +1 -1
  129. package/src/shaders/capsule.js.generated.ts +2 -2
  130. package/src/shaders/capsule.slang +69 -24
  131. package/src/shaders/clipStrip.slang +1 -1
  132. package/src/shaders/colorPack.slang +2 -2
  133. package/src/shaders/colorRampLut.slang +7 -7
  134. package/src/shaders/connectorMark.consts.generated.ts +24 -0
  135. package/src/shaders/connectorMark.generated.ts +12 -0
  136. package/src/shaders/connectorMark.glsl.generated.ts +6 -0
  137. package/src/shaders/connectorMark.iface.generated.ts +1270 -0
  138. package/src/shaders/connectorMark.js.generated.ts +52 -0
  139. package/src/shaders/connectorMark.slang +287 -0
  140. package/src/shaders/connectorMark.wgsl.generated.ts +4 -0
  141. package/src/shaders/coverageBand.slang +10 -12
  142. package/src/shaders/coverageInterbase.glsl.generated.ts +1 -1
  143. package/src/shaders/coverageInterbase.slang +24 -21
  144. package/src/shaders/coverageInterbase.wgsl.generated.ts +1 -1
  145. package/src/shaders/coverageSnp.slang +1 -1
  146. package/src/shaders/curveDistance.js.generated.ts +112 -0
  147. package/src/shaders/curveDistance.slang +104 -1
  148. package/src/shaders/farDome.test.ts +117 -0
  149. package/src/shaders/hpClampFloor.test.ts +0 -4
  150. package/src/shaders/insertionGlyph.generated.ts +14 -0
  151. package/src/shaders/insertionGlyph.slang +59 -0
  152. package/src/shaders/lineCenterMark.glsl.generated.ts +2 -2
  153. package/src/shaders/lineCenterMark.iface.generated.ts +21 -18
  154. package/src/shaders/lineCenterMark.slang +19 -23
  155. package/src/shaders/lineCenterMark.wgsl.generated.ts +1 -1
  156. package/src/shaders/lineCommon.js.generated.ts +20 -0
  157. package/src/shaders/lineCommon.slang +18 -8
  158. package/src/shaders/lineStepMark.glsl.generated.ts +2 -2
  159. package/src/shaders/lineStepMark.iface.generated.ts +21 -18
  160. package/src/shaders/lineStepMark.slang +10 -6
  161. package/src/shaders/lineStepMark.wgsl.generated.ts +1 -1
  162. package/src/shaders/linkMark.consts.generated.ts +2 -0
  163. package/src/shaders/linkMark.glsl.generated.ts +2 -2
  164. package/src/shaders/linkMark.js.generated.ts +30 -4
  165. package/src/shaders/linkMark.slang +67 -24
  166. package/src/shaders/linkMark.wgsl.generated.ts +1 -1
  167. package/src/shaders/markColor.slang +10 -10
  168. package/src/shaders/pointMark.glsl.generated.ts +1 -1
  169. package/src/shaders/pointMark.iface.generated.ts +19 -16
  170. package/src/shaders/pointMark.slang +12 -8
  171. package/src/shaders/pointMark.wgsl.generated.ts +1 -1
  172. package/src/shaders/rowRect.slang +3 -3
  173. package/src/shaders/rowTable.slang +12 -8
  174. package/src/shaders/scoreScale.slang +1 -1
  175. package/src/shaders/spanMark.glsl.generated.ts +1 -1
  176. package/src/shaders/spanMark.slang +5 -5
  177. package/src/shaders/spanMark.wgsl.generated.ts +1 -1
  178. package/src/shaders/viewRegion.slang +13 -0
  179. package/esm/shaders/coverageIndicator.generated.js +0 -7
  180. package/esm/shaders/coverageIndicator.glsl.generated.d.ts +0 -2
  181. package/esm/shaders/coverageIndicator.glsl.generated.js +0 -4
  182. package/esm/shaders/coverageIndicator.iface.generated.d.ts +0 -85
  183. package/esm/shaders/coverageIndicator.iface.generated.js +0 -114
  184. package/esm/shaders/coverageIndicator.wgsl.generated.d.ts +0 -1
  185. package/esm/shaders/coverageIndicator.wgsl.generated.js +0 -3
  186. package/src/shaders/coverageIndicator.generated.ts +0 -11
  187. package/src/shaders/coverageIndicator.glsl.generated.ts +0 -6
  188. package/src/shaders/coverageIndicator.iface.generated.ts +0 -172
  189. package/src/shaders/coverageIndicator.slang +0 -61
  190. package/src/shaders/coverageIndicator.wgsl.generated.ts +0 -4
@@ -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 { 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';
@@ -16,7 +18,7 @@ export { abgrToCssRgba, makeAbgrFill } from './colorFill.ts';
16
18
  export { RAMP_NOT_A_NUMBER_COLOR, RAMP_NO_VALUE_BITS, RAMP_NO_VALUE_COLOR, } from '../shaders/markColor.generated.ts';
17
19
  export { instanceColor, isThreshold, keepRampValues, paintColors, rampUniforms, rampValueBits, thresholdBandOf, } from './markRamp.ts';
18
20
  export { HIDDEN_ROW, NO_ROW_COLOR, RowKeys, buildRowTable } from './rowTable.ts';
19
- export { keySlot, rowColor, rowSlot } from './rowLane.ts';
21
+ export { keySlot, rowColor, rowSlot, rowTableKeys, rowTableTextures, } from './rowLane.ts';
20
22
  export { rowSpanIndex, spansInRow } from './rowSpanIndex.ts';
21
23
  export type { MarkPlan } from './markPlan.ts';
22
24
  export type { MarkInstance } from './markInk.ts';
@@ -27,7 +29,8 @@ export type { PointChannels, PointParams } from './pointMark.ts';
27
29
  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
- export type { LinkChannels, LinkParams, LinkRegion, LinkSizeScale, } from './linkMark.ts';
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 { 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';
@@ -16,5 +18,5 @@ export { abgrToCssRgba, makeAbgrFill } from './colorFill.js';
16
18
  export { RAMP_NOT_A_NUMBER_COLOR, RAMP_NO_VALUE_BITS, RAMP_NO_VALUE_COLOR, } from '../shaders/markColor.generated.js';
17
19
  export { instanceColor, isThreshold, keepRampValues, paintColors, rampUniforms, rampValueBits, thresholdBandOf, } from './markRamp.js';
18
20
  export { HIDDEN_ROW, NO_ROW_COLOR, RowKeys, buildRowTable } from './rowTable.js';
19
- export { keySlot, rowColor, rowSlot } from './rowLane.js';
21
+ export { keySlot, rowColor, rowSlot, rowTableKeys, rowTableTextures, } from './rowLane.js';
20
22
  export { rowSpanIndex, spansInRow } from './rowSpanIndex.js';
@@ -1,25 +1,34 @@
1
1
  import type { ColorChannel } from './markRamp.ts';
2
2
  import type { RowChannel, RowParams } from './rowLane.ts';
3
- import type { MarkColorScale, MarkShape } from './types.ts';
3
+ import type { MarkColorScale, MarkContext2D, MarkShape } from './types.ts';
4
4
  import type { MarkValueScale } from './valueScale.ts';
5
5
  /**
6
6
  * The `line` shape's channels: a stroke through consecutive instances of one
7
- * row, in the order given, each instance a span from `x` to `x2` at `y` on the
8
- * value scale. `gapBp` is the linear variant's break: a midpoint further than
9
- * that from the previous one on its row starts a new run. It rides with the
10
- * data, as the neighbour fields the packer derives from it do, so both
11
- * backends break in the same place; absent, a row is one run.
7
+ * row and one `series`, in the order given, each instance a span from `x` to
8
+ * `x2` at `y` on the value scale. The caller hands each series' instances
9
+ * together; a change of row or series starts a new run, and absent, a row is
10
+ * one series. `gapBp` is the linear variant's break: a midpoint further than
11
+ * that from the previous one starts a new run. Both ride with the data, as
12
+ * the neighbour fields the packer derives from them do, so both backends
13
+ * break in the same place.
12
14
  */
13
15
  export interface LineChannels extends ColorChannel, RowChannel {
14
16
  x: Uint32Array;
15
17
  x2: Uint32Array;
16
18
  y: Float32Array;
19
+ series?: Uint32Array;
17
20
  gapBp?: number;
18
21
  count: number;
19
22
  }
20
23
  export interface LineParams extends RowParams, MarkValueScale {
21
- /** The quantitative colour scale, for a line whose colour is a ramp or a threshold. */
24
+ /** The quantitative color scale, for a line whose color is a ramp or a threshold. */
22
25
  colorScale?: MarkColorScale;
26
+ /**
27
+ * Whether the color field is the plotted `y`. Only then does a threshold's
28
+ * cut sit on the y scale, so the line changes color where it crosses one;
29
+ * otherwise each instance takes its own value's color.
30
+ */
31
+ colorFromY?: boolean;
23
32
  /** The value a step drops to across a gap. */
24
33
  origin: number;
25
34
  /** Stroke width in CSS px. */
@@ -29,6 +38,37 @@ export interface LineParams extends RowParams, MarkValueScale {
29
38
  }
30
39
  /** `step` holds each value across its span; `linear` joins the spans' midpoints. */
31
40
  export type LineInterpolate = 'step' | 'linear';
41
+ /**
42
+ * What the two tracers hand a pen: the polyline's points, each with the y its
43
+ * color is read at where that differs from the y it is drawn at, and the
44
+ * instance the next points belong to.
45
+ */
46
+ export interface LinePen {
47
+ instance(i: number): void;
48
+ moveTo(x: number, y: number, colorY?: number): void;
49
+ lineTo(x: number, y: number, colorY?: number): void;
50
+ /** Reads the next segment's color from `colorY` without moving the pen. */
51
+ recolor(colorY: number): void;
52
+ endRun(): void;
53
+ end(): void;
54
+ }
55
+ export declare class BandPen implements LinePen {
56
+ private readonly ctx;
57
+ private readonly top;
58
+ private readonly bottom;
59
+ private x;
60
+ private y;
61
+ private colorY;
62
+ private drawing;
63
+ private readonly path;
64
+ constructor(ctx: MarkContext2D, top: number, bottom: number);
65
+ instance(): void;
66
+ moveTo(x: number, y: number, colorY?: number): void;
67
+ recolor(colorY: number): void;
68
+ lineTo(x: number, y: number, colorY?: number): void;
69
+ endRun(): void;
70
+ end(): void;
71
+ }
32
72
  /** The step variant: three square-capped quads an instance, flat filled. */
33
73
  export declare const lineStepMark: MarkShape<LineChannels, LineParams>;
34
74
  /** The linear variant: one round-capped capsule an instance, max blended. */
@@ -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,21 +10,22 @@ 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
- function sameRow(row, a, b) {
16
- return row[a] === row[b];
16
+ // Whether instances `a` and `b` stand on one row and in one series.
17
+ function sameRun(c, row, a, b) {
18
+ return row[a] === row[b] && (!c.series || c.series[a] === c.series[b]);
17
19
  }
18
- // Whether instance `i` abuts the one before it on its row, so the step joins
20
+ // Whether instance `i` abuts the one before it in its run, so the step joins
19
21
  // them at a shared corner.
20
22
  function abutsBefore(c, row, i) {
21
- return i > 0 && sameRow(row, i - 1, i) && c.x2[i - 1] === c.x[i];
23
+ return i > 0 && sameRun(c, row, i - 1, i) && c.x2[i - 1] === c.x[i];
22
24
  }
23
25
  // Whether the linear variant links instance `i` to the one before it: the same
24
- // row, within `gapBp` midpoint to midpoint.
26
+ // row and series, within `gapBp` midpoint to midpoint.
25
27
  function linksBefore(c, row, i) {
26
- if (i === 0 || !sameRow(row, i - 1, i)) {
28
+ if (i === 0 || !sameRun(c, row, i - 1, i)) {
27
29
  return false;
28
30
  }
29
31
  const gapBp = c.gapBp ?? NO_GAP;
@@ -66,6 +68,7 @@ function writeLineUniforms(scratch, clip, block, frame, params) {
66
68
  domainMax: params.domain[1],
67
69
  ...valueScaleUniforms(params),
68
70
  ...rampUniforms(params.colorScale),
71
+ colorFromY: params.colorFromY ? 1 : 0,
69
72
  rowHeight,
70
73
  rowBandPx: params.rowBandPx ?? rowHeight,
71
74
  rowOffsetPx: params.rowOffsetPx ?? 0,
@@ -96,16 +99,16 @@ function valueYPx(g, value, slot) {
96
99
  return (rowBandTopPx(g.rowOffsetPx, g.pitch, slot) +
97
100
  valueToYPxScaled(value, g.domainMin, g.domainMax, g.band, g.scaleType, g.symlogConstant));
98
101
  }
99
- // A value's y in the band for the colour test, placed UNCLAMPED where
102
+ // A value's y in the band for the color test, placed UNCLAMPED where
100
103
  // `valueYPx` clamps it to draw: the cuts go through it, and so does the line a
101
- // threshold colours along, as the shader's `lineColorYPx` does.
104
+ // threshold colors along, as the shader's `lineColorYPx` does.
102
105
  function colorYPx(g, value, slot) {
103
106
  const norm = normalizeScoreUnclamped(value, g.domainMin, g.domainMax, g.scaleType, g.symlogConstant);
104
107
  return rowBandTopPx(g.rowOffsetPx, g.pitch, slot) + (1 - norm) * g.band;
105
108
  }
106
- // One colour per instance: a change ends the stroke batch and reopens it from
107
- // the pen, so a segment carries the colour of the instance it belongs to, as
108
- // the shader's per-instance colour does.
109
+ // One color per instance: a change ends the stroke batch and reopens it from
110
+ // the pen, so a segment carries the color of the instance it belongs to, as
111
+ // the shader's per-instance color does.
109
112
  class InstancePen {
110
113
  ctx;
111
114
  colorOf;
@@ -152,7 +155,7 @@ class InstancePen {
152
155
  }
153
156
  // The part of the segment from y0 to y1, as a fraction of it, inside the band
154
157
  // top < y <= bottom; undefined where it misses. A band owns its lower edge,
155
- // so a line lying on a cut takes the colour above it, as the shader does.
158
+ // so a line lying on a cut takes the color above it, as the shader does.
156
159
  function bandSpan(y0, y1, top, bottom) {
157
160
  if (y0 === y1) {
158
161
  return top < y0 && y0 <= bottom ? [0, 1] : undefined;
@@ -166,10 +169,10 @@ function bandSpan(y0, y1, top, bottom) {
166
169
  : undefined;
167
170
  }
168
171
  // Keeps the part of each segment inside one threshold band, cut where it
169
- // crosses the band's edges, so a line stroked once per band changes colour
170
- // where the shader's fragments do. A point's colour y is where the band test
172
+ // crosses the band's edges, so a line stroked once per band changes color
173
+ // where the shader's fragments do. A point's color y is where the band test
171
174
  // reads it and its y where it draws, which part where the drawn y is clamped.
172
- class BandPen {
175
+ export class BandPen {
173
176
  ctx;
174
177
  top;
175
178
  bottom;
@@ -225,8 +228,8 @@ class BandPen {
225
228
  }
226
229
  // The step polyline over `indices`, in order: a rise from the origin, the top
227
230
  // across the span, the step at each joint and a drop to the origin at a gap.
228
- // A rise is coloured along the y it is drawn at, a level run by its value's
229
- // colour y, as the shader reads them.
231
+ // A rise is colored along the y it is drawn at, a level run by its value's
232
+ // color y, as the shader reads them.
230
233
  function traceStep(pen, c, g, rows, indices, origin) {
231
234
  const { x, x2, y, row, count } = c;
232
235
  let inRun = false;
@@ -281,29 +284,39 @@ function traceCenter(pen, c, g, rows, indices) {
281
284
  pen.lineTo(cx, cy, colorY);
282
285
  }
283
286
  }
284
- // Under a threshold the line is stroked once per band per row, each pass
285
- // keeping the parts of the polyline inside its band; otherwise once, in each
286
- // instance's own colour.
287
+ // Along y, the line is stroked once per band per row, each pass keeping the
288
+ // parts of the polyline inside its band; otherwise once, in each instance's
289
+ // own color. A row the table recolors takes its override either way.
287
290
  function paintLine(ctx, c, g, params, trace) {
288
291
  const { row, count } = c;
289
292
  const scale = params.colorScale;
290
- if (scale && isThreshold(scale) && c.colorValue) {
291
- const bySlot = new Map();
292
- for (let i = 0; i < count; i++) {
293
- const slot = rowSlot(row, i, g.table);
294
- if (slot !== undefined) {
295
- const held = bySlot.get(slot);
296
- if (held) {
297
- held.push(i);
298
- }
299
- else {
300
- bySlot.set(slot, [i]);
301
- }
293
+ const bySlot = new Map();
294
+ const own = [];
295
+ const { rampMode } = rampUniforms(scale);
296
+ const colorFromY = params.colorFromY ? 1 : 0;
297
+ for (let i = 0; i < count; i++) {
298
+ const slot = rowSlot(row, i, g.table);
299
+ if (slot === undefined) {
300
+ continue;
301
+ }
302
+ const overridden = rowColorOverride(row, i, g.table) !== undefined;
303
+ if (lineColorAlongY(rampMode, colorFromY, overridden)) {
304
+ const held = bySlot.get(slot);
305
+ if (held) {
306
+ held.push(i);
307
+ }
308
+ else {
309
+ bySlot.set(slot, [i]);
302
310
  }
303
311
  }
312
+ else {
313
+ own.push(i);
314
+ }
315
+ }
316
+ if (scale && isThreshold(scale)) {
304
317
  const { cuts, colors } = scale;
305
318
  for (const [slot, indices] of bySlot) {
306
- const cutYs = cuts.map(cut => colorYPx(g, cut, slot));
319
+ const cutYs = cuts.map(cut => colorYPx(g, Math.fround(cut), slot));
307
320
  for (let k = 0; k <= cuts.length; k++) {
308
321
  ctx.strokeStyle = abgrToCssRgba(colors[k] ?? 0);
309
322
  const pen = new BandPen(ctx, k < cuts.length ? cutYs[k] : Number.NEGATIVE_INFINITY, k > 0 ? cutYs[k - 1] : Number.POSITIVE_INFINITY);
@@ -311,12 +324,13 @@ function paintLine(ctx, c, g, params, trace) {
311
324
  pen.end();
312
325
  }
313
326
  }
314
- return;
315
327
  }
316
- const color = paintColors(c, count, scale);
317
- const pen = new InstancePen(ctx, i => rowColor(color[i], row, i, g.table));
318
- trace(pen, Array.from({ length: count }, (_, i) => i));
319
- pen.end();
328
+ if (own.length > 0) {
329
+ const color = paintColors(c, count, scale);
330
+ const pen = new InstancePen(ctx, i => rowColor(color[i], row, i, g.table));
331
+ trace(pen, own);
332
+ pen.end();
333
+ }
320
334
  }
321
335
  function paintStep(ctx, c, block, frame, params) {
322
336
  if (c.count === 0) {
@@ -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
@@ -24,10 +25,9 @@ export interface LinkChannels extends ColorChannel, RowChannel {
24
25
  feet?: Uint8Array;
25
26
  count: number;
26
27
  }
27
- export declare const LINK_FOOT_FORWARD = 1;
28
- export declare const LINK_FOOT_REVERSE = 2;
29
- /** The `feet` lane's value for a tick at each foot, by genomic direction. */
30
- export declare function linkFeet(xDir: number, x2Dir: number): number;
28
+ export { LINK_FOOT_FORWARD, LINK_FOOT_REVERSE, linkFeet } from '../linkFeet.ts';
29
+ /** The channels cut down to instance `i`, for its own painter to trace alone. */
30
+ export declare function linkInstance(c: LinkChannels, i: number): LinkChannels;
31
31
  /**
32
32
  * One displayed region as a foot places through it: the canvas px of an
33
33
  * anchor bp chosen near the view, so a foot's offset from it stays inside
@@ -49,7 +49,7 @@ export interface LinkSizeScale {
49
49
  }
50
50
  export type LinkShape = 'dome' | 'arc' | 'line';
51
51
  export interface LinkParams extends RowParams, MarkValueScale {
52
- /** The quantitative colour scale, for a link whose colour is a ramp or a threshold. */
52
+ /** The quantitative color scale, for a link whose color is a ramp or a threshold. */
53
53
  colorScale?: MarkColorScale;
54
54
  /** The view's displayed regions, indexed as `x2Region` and the block's own index are. */
55
55
  regions: readonly LinkRegion[];
@@ -72,5 +72,42 @@ export interface LinkParams extends RowParams, MarkValueScale {
72
72
  /** How far inside the band the value range ends, the axis's own. */
73
73
  insetPx?: number;
74
74
  }
75
+ export declare function regionPx(regions: readonly LinkRegion[], index: number, bp: number): number;
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;
95
+ /**
96
+ * Where a label of instance `i` stands: its apex, or for a far dome whose
97
+ * apex is off the canvas, the dome's point over the middle of the stretch the
98
+ * canvas shows, so a view inside the pair still carries the label. `rise` is
99
+ * the curve's height there.
100
+ */
101
+ export declare function linkLabelAnchor(c: LinkChannels, block: RenderBlock, frame: MarkFrame, params: LinkParams, i: number): LinkApex | undefined;
102
+ /**
103
+ * The view's region table as a view-spanning shader's uniforms take it: the
104
+ * entries in use, their count, and the drawing region's own entry. Shared
105
+ * arrays, rewritten per call, so a frame allocates nothing per block.
106
+ */
107
+ export declare function viewRegionUniforms(regions: readonly LinkRegion[], own: number): {
108
+ regionCount: number;
109
+ ownEntry: [number, number, number, number];
110
+ regionTable: [[number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number]];
111
+ };
75
112
  export declare const linkMark: MarkShape<LinkChannels, LinkParams>;
76
113
  export { LINK_ELSEWHERE, LINK_NO_REGION };
@@ -1,22 +1,30 @@
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, LINK_FAR_SCREEN_WIDTHS, 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';
3
+ import { distToWideCirclePx, farDomeParam, 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 { linkApexHeld, 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';
10
10
  import { nearestInk } from './markHit.js';
11
- import { colorBits, paintColors, rampUniforms } from './markRamp.js';
11
+ import { colorBits, instanceColor, paintColors, rampUniforms, } from './markRamp.js';
12
12
  import { bandHeightPx, rowColor, rowLane, rowSlot, rowTableKeys, rowTableTextures, } from './rowLane.js';
13
13
  import { valueScaleUniforms } from './valueScale.js';
14
- export const LINK_FOOT_FORWARD = 1;
15
- export const LINK_FOOT_REVERSE = 2;
16
- /** The `feet` lane's value for a tick at each foot, by genomic direction. */
17
- export function linkFeet(xDir, x2Dir) {
18
- const bits = (d) => d > 0 ? LINK_FOOT_FORWARD : d < 0 ? LINK_FOOT_REVERSE : 0;
19
- return bits(xDir) | (bits(x2Dir) << 2);
14
+ export { LINK_FOOT_FORWARD, LINK_FOOT_REVERSE, linkFeet } from '../linkFeet.js';
15
+ /** The channels cut down to instance `i`, for its own painter to trace alone. */
16
+ export function linkInstance(c, i) {
17
+ return {
18
+ x: c.x.subarray(i, i + 1),
19
+ x2: c.x2.subarray(i, i + 1),
20
+ x2Region: c.x2Region.subarray(i, i + 1),
21
+ y: c.y?.subarray(i, i + 1),
22
+ size: c.size?.subarray(i, i + 1),
23
+ feet: c.feet?.subarray(i, i + 1),
24
+ row: c.row?.subarray(i, i + 1),
25
+ color: instanceColor(c.color, i),
26
+ count: 1,
27
+ };
20
28
  }
21
29
  const KIND_ELLIPSE = 0;
22
30
  const KIND_CIRCLE = 1;
@@ -26,6 +34,7 @@ const KIND_LINE = 4;
26
34
  // The painter's leg polyline: enough segments that the chord sagitta on any
27
35
  // leg the band can show is under a pixel.
28
36
  const LEG_SEGMENTS = 32;
37
+ const FAR_DOME_COARSE = 16;
29
38
  function linkFrame(block, frame, params) {
30
39
  const band = bandHeightPx(params, frame.canvasHeight);
31
40
  const { valueScaleType, valueSymlogConstant } = valueScaleUniforms(params);
@@ -88,7 +97,7 @@ function linkShapeCode(shape) {
88
97
  ? LINK_SHAPE_LINE
89
98
  : LINK_SHAPE_DOME;
90
99
  }
91
- function regionPx(regions, index, bp) {
100
+ export function regionPx(regions, index, bp) {
92
101
  const r = regions[index];
93
102
  return r.anchorPx + (bp - r.anchorBp) * r.signedPxPerBp;
94
103
  }
@@ -164,25 +173,71 @@ function placeCurve(c, g, i) {
164
173
  g.x2Px = regionPx(regions, region, c.x2[i]);
165
174
  const pairHalf = Math.abs(g.x2Px - g.xPx) / 2;
166
175
  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);
176
+ const apex = linkApexPx(pairHalf, g.reach, g.strokePx / 2, g.shape, g.valued, valuePx);
168
177
  if (g.shape === LINK_SHAPE_LINE) {
169
178
  g.kind = KIND_LINE;
170
179
  g.rx = Math.max(2 * pairHalf, LINK_LINE_MIN_PX);
171
180
  g.ry = apex;
172
181
  return;
173
182
  }
174
- const [rx, ry] = linkRadiiPx(pairHalf, apex, g.screenW);
183
+ const [rx, ry] = linkRadiiPx(pairHalf, apex, g.screenW, linkApexHeld(g.valued, c.y ? c.y[i] : 0, g.domainMax));
175
184
  g.rx = rx;
176
185
  g.ry = ry;
177
- if (2 * pairHalf > LINK_FAR_SCREEN_WIDTHS * g.screenW) {
186
+ // A far pair with a low apex keeps its apex (`linkRadiiPx`) and is an
187
+ // ellipse still, traced as chords (`farDomePoints`).
188
+ if (linkIsFar(pairHalf, g.screenW) && ry >= rx) {
178
189
  g.kind = KIND_CIRCLE;
179
- g.legSweep = Math.asin(Math.min(1, Math.max(0, (g.reach + g.strokePx / 2) / Math.max(rx, 1e-6))));
190
+ g.legSweep = legSweepAngle(rx, g.reach + g.strokePx / 2);
180
191
  g.legHeight = rx * Math.sin(g.legSweep);
181
192
  }
182
193
  else {
183
194
  g.kind = KIND_ELLIPSE;
184
195
  }
185
196
  }
197
+ function apexOf(g) {
198
+ if (g.kind === KIND_NONE || g.kind === KIND_CIRCLE) {
199
+ return undefined;
200
+ }
201
+ return {
202
+ x: (g.xPx + g.x2Px) / 2,
203
+ y: yAt(g, g.ry),
204
+ rise: g.ry,
205
+ halfWidth: g.kind === KIND_ELLIPSE ? g.rx : 0,
206
+ strokePx: g.strokePx,
207
+ inward: g.reverse ? -1 : 1,
208
+ };
209
+ }
210
+ /**
211
+ * Instance `i`'s apex, undefined where it draws none on the band: no curve
212
+ * at all, or a far pair's circle, which the band clips to its legs.
213
+ */
214
+ export function linkApex(c, block, frame, params, i) {
215
+ const g = linkFrame(block, frame, params);
216
+ placeCurve(c, g, i);
217
+ return apexOf(g);
218
+ }
219
+ /**
220
+ * Where a label of instance `i` stands: its apex, or for a far dome whose
221
+ * apex is off the canvas, the dome's point over the middle of the stretch the
222
+ * canvas shows, so a view inside the pair still carries the label. `rise` is
223
+ * the curve's height there.
224
+ */
225
+ export function linkLabelAnchor(c, block, frame, params, i) {
226
+ const g = linkFrame(block, frame, params);
227
+ placeCurve(c, g, i);
228
+ const apex = apexOf(g);
229
+ if (!apex || g.kind !== KIND_ELLIPSE || !linkIsFar(g.rx, g.screenW)) {
230
+ return apex;
231
+ }
232
+ const from = Math.max(apex.x - g.rx, 0);
233
+ const to = Math.min(apex.x + g.rx, g.screenW);
234
+ if (to <= from || (apex.x >= 0 && apex.x <= g.screenW)) {
235
+ return apex;
236
+ }
237
+ const x = (from + to) / 2;
238
+ const rise = g.ry * Math.sqrt(Math.max(1 - ((x - apex.x) / g.rx) ** 2, 0));
239
+ return { ...apex, x, y: yAt(g, rise), rise };
240
+ }
186
241
  function sizeLane(c) {
187
242
  const lane = new Float32Array(c.count);
188
243
  for (let i = 0; i < c.count; i++) {
@@ -224,6 +279,18 @@ function fillTable(regions) {
224
279
  }
225
280
  return n;
226
281
  }
282
+ /**
283
+ * The view's region table as a view-spanning shader's uniforms take it: the
284
+ * entries in use, their count, and the drawing region's own entry. Shared
285
+ * arrays, rewritten per call, so a frame allocates nothing per block.
286
+ */
287
+ export function viewRegionUniforms(regions, own) {
288
+ return {
289
+ regionCount: fillTable(regions),
290
+ ownEntry: tableEntry(regions[own]),
291
+ regionTable: TABLE,
292
+ };
293
+ }
227
294
  // Canvas y of a point `up` px from the baseline on the drawn side.
228
295
  function yAt(g, up) {
229
296
  return g.baseY - g.ySign * up;
@@ -246,6 +313,34 @@ function legPoints(g, footX, legDir) {
246
313
  }
247
314
  return points;
248
315
  }
316
+ // A far dome as chords from the foot nearer the canvas, stepped as the shader
317
+ // steps it: fine over the stretch the canvas shows, coarse over the rest. A
318
+ // canvas flattens an ellipse tens of thousands of px wide into too few curves
319
+ // to hold its feet.
320
+ function farDomePoints(g, left, right) {
321
+ const fromRight = g.screenW / 2 - left > g.rx;
322
+ const footX = fromRight ? right : left;
323
+ const dir = fromRight ? -1 : 1;
324
+ const margin = g.strokePx;
325
+ const u0 = farDomeParam(fromRight ? footX - g.screenW - margin : -margin - footX, g.rx);
326
+ const u1 = Math.max(u0, farDomeParam(fromRight ? footX + margin : g.screenW + margin - footX, g.rx));
327
+ const coarse = Array.from({ length: FAR_DOME_COARSE + 1 }, (_, k) => Math.sin(((k / FAR_DOME_COARSE) * Math.PI) / 2));
328
+ const fine = Array.from({ length: LINK_CURVE_SEGMENTS + 1 }, (_, k) => u0 + ((u1 - u0) * k) / LINK_CURVE_SEGMENTS);
329
+ return [
330
+ ...coarse.filter(u => u < u0),
331
+ ...fine,
332
+ ...coarse.filter(u => u > u1),
333
+ ].map(u => ({
334
+ x: footX + dir * 2 * g.rx * u * u,
335
+ y: yAt(g, 2 * g.ry * u * Math.sqrt(1 - u * u)),
336
+ }));
337
+ }
338
+ function traceChords(ctx, points) {
339
+ ctx.moveTo(points[0].x, points[0].y);
340
+ for (let k = 1; k < points.length; k++) {
341
+ ctx.lineTo(points[k].x, points[k].y);
342
+ }
343
+ }
249
344
  function tracePath(ctx, g) {
250
345
  const { baseY } = g;
251
346
  if (g.kind === KIND_STEM) {
@@ -267,14 +362,14 @@ function tracePath(ctx, g) {
267
362
  [right, 1],
268
363
  [left, -1],
269
364
  ]) {
270
- const points = legPoints(g, footX, legDir);
271
- ctx.moveTo(points[0].x, points[0].y);
272
- for (let k = 1; k < points.length; k++) {
273
- ctx.lineTo(points[k].x, points[k].y);
274
- }
365
+ traceChords(ctx, legPoints(g, footX, legDir));
275
366
  }
276
367
  return;
277
368
  }
369
+ if (linkIsFar(g.rx, g.screenW)) {
370
+ traceChords(ctx, farDomePoints(g, left, right));
371
+ return;
372
+ }
278
373
  const mid = (left + right) / 2;
279
374
  if (g.reverse) {
280
375
  ctx.moveTo(mid + g.rx, baseY);
@@ -439,7 +534,9 @@ function nearestRising(g, px, py) {
439
534
  return { x, y: baseY, dist: Math.hypot(px - x, py - baseY) };
440
535
  }
441
536
  const near = ellipseNearest(px - mid, py - baseY, g.rx, g.ry);
442
- return { x: mid + near.x, y: baseY + near.y, dist: near.dist };
537
+ // The drawn half rises from the baseline. A cursor on the baseline is as
538
+ // near the mirrored half, and the solve may name that one.
539
+ return { x: mid + near.x, y: baseY - Math.abs(near.y), dist: near.dist };
443
540
  }
444
541
  export const linkMark = {
445
542
  id: 'link',
@@ -110,7 +110,7 @@ export declare function createMarkBackend<TRegion, TState extends FrameDimension
110
110
  /**
111
111
  * What the frame is cleared to, when transparent is the wrong answer. The
112
112
  * GPU side hands it to `beginFrame` and the Canvas2D side fills it after
113
- * `prepareCanvas`, so both backends composite over the same colour — which
113
+ * `prepareCanvas`, so both backends composite over the same color — which
114
114
  * synteny needs, its indel wedges being pre-blended against the band's
115
115
  * ground rather than composited over it.
116
116
  */
@@ -63,7 +63,7 @@ export function drawPlannedPasses(hal, plan, regionKey) {
63
63
  // its sampler unconditionally, and a textured pass missing a texture draws
64
64
  // nothing on either GPU HAL. A mark declaring no `texture` at all is the same case —
65
65
  // `pointMark` carries a sampler for the shapes that ramp, and a point of a
66
- // constant colour draws it without one.
66
+ // constant color draws it without one.
67
67
  const INERT_RAMP = new Uint8Array(COLOR_RAMP_LUT_ENTRIES * 4);
68
68
  /**
69
69
  * The texture each textured pass holds, by identity — the mirror of the one