@jbrowse/render-core 4.3.0
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/LICENSE +201 -0
- package/esm/OverlayCanvas.d.ts +29 -0
- package/esm/OverlayCanvas.js +43 -0
- package/esm/RenderCanvas.d.ts +76 -0
- package/esm/RenderCanvas.js +38 -0
- package/esm/RenderLifecycleMixin.d.ts +180 -0
- package/esm/RenderLifecycleMixin.js +279 -0
- package/esm/ScrollLockedOverlay.d.ts +28 -0
- package/esm/ScrollLockedOverlay.js +42 -0
- package/esm/blockClipUtils.d.ts +27 -0
- package/esm/blockClipUtils.js +101 -0
- package/esm/canvas2dUtils.d.ts +372 -0
- package/esm/canvas2dUtils.js +564 -0
- package/esm/canvasContext.d.ts +68 -0
- package/esm/canvasContext.js +74 -0
- package/esm/colorRampLut.d.ts +17 -0
- package/esm/colorRampLut.js +25 -0
- package/esm/coverageBand.d.ts +107 -0
- package/esm/coverageBand.js +109 -0
- package/esm/coverageBandBuffers.d.ts +30 -0
- package/esm/coverageBandBuffers.js +13 -0
- package/esm/createRenderingBackend.d.ts +52 -0
- package/esm/createRenderingBackend.js +58 -0
- package/esm/displayPhase.d.ts +155 -0
- package/esm/displayPhase.js +97 -0
- package/esm/globalRenderingBackend.d.ts +90 -0
- package/esm/globalRenderingBackend.js +53 -0
- package/esm/gpuDevice.d.ts +91 -0
- package/esm/gpuDevice.js +277 -0
- package/esm/graphicsCapabilities.d.ts +98 -0
- package/esm/graphicsCapabilities.js +165 -0
- package/esm/hal/createHal.d.ts +22 -0
- package/esm/hal/createHal.js +91 -0
- package/esm/hal/deviceGpuCache.d.ts +36 -0
- package/esm/hal/deviceGpuCache.js +120 -0
- package/esm/hal/gpuHalBase.d.ts +55 -0
- package/esm/hal/gpuHalBase.js +81 -0
- package/esm/hal/index.d.ts +7 -0
- package/esm/hal/index.js +5 -0
- package/esm/hal/mockHal.d.ts +153 -0
- package/esm/hal/mockHal.js +297 -0
- package/esm/hal/oomReporter.d.ts +15 -0
- package/esm/hal/oomReporter.js +22 -0
- package/esm/hal/passIds.d.ts +22 -0
- package/esm/hal/passIds.js +36 -0
- package/esm/hal/regionRegistry.d.ts +11 -0
- package/esm/hal/regionRegistry.js +66 -0
- package/esm/hal/types.d.ts +116 -0
- package/esm/hal/types.js +1 -0
- package/esm/hal/webgl2Hal.d.ts +47 -0
- package/esm/hal/webgl2Hal.js +490 -0
- package/esm/hal/webgpuHal.d.ts +169 -0
- package/esm/hal/webgpuHal.js +722 -0
- package/esm/installUpload.d.ts +91 -0
- package/esm/installUpload.js +94 -0
- package/esm/instanceCache.d.ts +43 -0
- package/esm/instanceCache.js +53 -0
- package/esm/instancePass.d.ts +50 -0
- package/esm/instancePass.js +34 -0
- package/esm/keyedRenderingBackend.d.ts +41 -0
- package/esm/keyedRenderingBackend.js +19 -0
- package/esm/mapUploadSync.d.ts +31 -0
- package/esm/mapUploadSync.js +45 -0
- package/esm/namedReactions.d.ts +29 -0
- package/esm/namedReactions.js +62 -0
- package/esm/perRegionRenderingBackend.d.ts +114 -0
- package/esm/perRegionRenderingBackend.js +93 -0
- package/esm/recoveryBudget.d.ts +40 -0
- package/esm/recoveryBudget.js +56 -0
- package/esm/regionDataMap.d.ts +60 -0
- package/esm/regionDataMap.js +98 -0
- package/esm/renderBlock.d.ts +25 -0
- package/esm/renderBlock.js +10 -0
- package/esm/renderingBackendBase.d.ts +71 -0
- package/esm/renderingBackendBase.js +50 -0
- package/esm/shaders/antialias.js.generated.d.ts +4 -0
- package/esm/shaders/antialias.js.generated.js +25 -0
- package/esm/shaders/capsule.generated.d.ts +1 -0
- package/esm/shaders/capsule.generated.js +3 -0
- package/esm/shaders/colorRampLut.generated.d.ts +1 -0
- package/esm/shaders/colorRampLut.generated.js +3 -0
- package/esm/shaders/coverageBand.generated.d.ts +2 -0
- package/esm/shaders/coverageBand.generated.js +4 -0
- package/esm/shaders/coverageBar.generated.d.ts +4 -0
- package/esm/shaders/coverageBar.generated.js +6 -0
- package/esm/shaders/coverageBar.iface.generated.d.ts +85 -0
- package/esm/shaders/coverageBar.iface.generated.js +114 -0
- package/esm/shaders/coverageIndicator.generated.d.ts +4 -0
- package/esm/shaders/coverageIndicator.generated.js +6 -0
- package/esm/shaders/coverageIndicator.iface.generated.d.ts +85 -0
- package/esm/shaders/coverageIndicator.iface.generated.js +114 -0
- package/esm/shaders/coverageInterbase.generated.d.ts +4 -0
- package/esm/shaders/coverageInterbase.generated.js +6 -0
- package/esm/shaders/coverageInterbase.iface.generated.d.ts +93 -0
- package/esm/shaders/coverageInterbase.iface.generated.js +134 -0
- package/esm/shaders/coverageMod.generated.d.ts +4 -0
- package/esm/shaders/coverageMod.generated.js +6 -0
- package/esm/shaders/coverageMod.iface.generated.d.ts +97 -0
- package/esm/shaders/coverageMod.iface.generated.js +144 -0
- package/esm/shaders/coverageSnp.generated.d.ts +4 -0
- package/esm/shaders/coverageSnp.generated.js +6 -0
- package/esm/shaders/coverageSnp.iface.generated.d.ts +97 -0
- package/esm/shaders/coverageSnp.iface.generated.js +144 -0
- package/esm/shaders/hpmath.js.generated.d.ts +3 -0
- package/esm/shaders/hpmath.js.generated.js +48 -0
- package/esm/shaders/pointGlyph.generated.d.ts +1 -0
- package/esm/shaders/pointGlyph.generated.js +3 -0
- package/esm/shaders/pointGlyph.js.generated.d.ts +1 -0
- package/esm/shaders/pointGlyph.js.generated.js +7 -0
- package/esm/shaders/rowRect.generated.d.ts +3 -0
- package/esm/shaders/rowRect.generated.js +5 -0
- package/esm/shaders/rowRect.js.generated.d.ts +2 -0
- package/esm/shaders/rowRect.js.generated.js +13 -0
- package/esm/shaders/scoreScale.js.generated.d.ts +1 -0
- package/esm/shaders/scoreScale.js.generated.js +46 -0
- package/esm/slangPass.d.ts +49 -0
- package/esm/slangPass.js +22 -0
- package/esm/useEventCallback.d.ts +1 -0
- package/esm/useEventCallback.js +19 -0
- package/esm/useGraphicsCapabilities.d.ts +17 -0
- package/esm/useGraphicsCapabilities.js +38 -0
- package/esm/useRenderingBackend.d.ts +83 -0
- package/esm/useRenderingBackend.js +422 -0
- package/esm/useTabVisibilityRerender.d.ts +13 -0
- package/esm/useTabVisibilityRerender.js +39 -0
- package/esm/webgpuUtils.d.ts +23 -0
- package/esm/webgpuUtils.js +76 -0
- package/package.json +91 -0
- package/src/shaders/antialias.js.generated.ts +33 -0
- package/src/shaders/antialias.slang +112 -0
- package/src/shaders/capsule.generated.ts +4 -0
- package/src/shaders/capsule.slang +62 -0
- package/src/shaders/colorPack.slang +26 -0
- package/src/shaders/colorRampLut.generated.ts +4 -0
- package/src/shaders/colorRampLut.slang +51 -0
- package/src/shaders/coverageBand.generated.ts +6 -0
- package/src/shaders/coverageBand.slang +276 -0
- package/src/shaders/coverageBar.generated.ts +10 -0
- package/src/shaders/coverageBar.iface.generated.ts +172 -0
- package/src/shaders/coverageBar.slang +37 -0
- package/src/shaders/coverageIndicator.generated.ts +10 -0
- package/src/shaders/coverageIndicator.iface.generated.ts +172 -0
- package/src/shaders/coverageIndicator.slang +61 -0
- package/src/shaders/coverageInterbase.generated.ts +10 -0
- package/src/shaders/coverageInterbase.iface.generated.ts +198 -0
- package/src/shaders/coverageInterbase.slang +63 -0
- package/src/shaders/coverageMod.generated.ts +10 -0
- package/src/shaders/coverageMod.iface.generated.ts +211 -0
- package/src/shaders/coverageMod.slang +36 -0
- package/src/shaders/coverageSnp.generated.ts +10 -0
- package/src/shaders/coverageSnp.iface.generated.ts +211 -0
- package/src/shaders/coverageSnp.slang +52 -0
- package/src/shaders/diagonalGrid.slang +25 -0
- package/src/shaders/glyphEdgeAlpha.test.ts +241 -0
- package/src/shaders/hpmath.js.generated.ts +47 -0
- package/src/shaders/hpmath.slang +226 -0
- package/src/shaders/pointGlyph.generated.ts +4 -0
- package/src/shaders/pointGlyph.js.generated.ts +9 -0
- package/src/shaders/pointGlyph.slang +93 -0
- package/src/shaders/rowRect.generated.ts +8 -0
- package/src/shaders/rowRect.js.generated.ts +17 -0
- package/src/shaders/rowRect.slang +160 -0
- package/src/shaders/scoreScale.js.generated.ts +51 -0
- package/src/shaders/scoreScale.slang +68 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import type { BpRegionBounds } from './renderBlock.ts';
|
|
2
|
+
export interface BlockClip {
|
|
3
|
+
scissorX: number;
|
|
4
|
+
scissorW: number;
|
|
5
|
+
fullBlockWidth: number;
|
|
6
|
+
bpLength: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const MAX_DPR = 2;
|
|
9
|
+
/**
|
|
10
|
+
* The ratio every canvas in the app renders at, capped at {@link MAX_DPR}.
|
|
11
|
+
*
|
|
12
|
+
* Use this, never a bare `devicePixelRatio` read — the value has to agree
|
|
13
|
+
* across the backing-store sizing (`syncCanvasSize` / `prepareCanvas`), the
|
|
14
|
+
* scissor/viewport rects derived from it (`clipBlock`, alignments'
|
|
15
|
+
* `computeBlockGeom`), and any shader uniform that reconstructs backing-store
|
|
16
|
+
* dimensions from CSS ones. A call site that reads the global directly renders
|
|
17
|
+
* geometry at a different scale than the canvas it lands on.
|
|
18
|
+
*
|
|
19
|
+
* Two places legitimately want something other than this and should keep saying
|
|
20
|
+
* so explicitly: `createSvgRasterCanvas` pins 2x because export goes to a file
|
|
21
|
+
* rather than a screen, and the analytics / error-report paths read the raw
|
|
22
|
+
* global because they are reporting the device, not drawing on it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getDpr(): number;
|
|
25
|
+
export declare const MAX_CANVAS_DIM_PX = 8192;
|
|
26
|
+
/**
|
|
27
|
+
* The largest CSS-px extent a canvas may take on one axis before its backing
|
|
28
|
+
* store (`× dpr`) crosses the ceiling above.
|
|
29
|
+
*
|
|
30
|
+
* For displays that size a canvas to their **content** rather than to a
|
|
31
|
+
* viewport — a row stack that grows with the row count, so nothing downstream
|
|
32
|
+
* bounds it. Past this the backing store stops tracking the CSS size
|
|
33
|
+
* (`syncCanvasSize` clamps it) while scissor/viewport rects are still derived
|
|
34
|
+
* as `cssPx * dpr`, so WebGPU rejects an out-of-bounds rect and blanks the
|
|
35
|
+
* frame, WebGL clamps and paints at the wrong scale, and the Canvas2D fallback
|
|
36
|
+
* stretches its top slice over the whole track.
|
|
37
|
+
*
|
|
38
|
+
* A function, not a constant: `getDpr()` follows the window, so a display
|
|
39
|
+
* reading this into a computed re-derives it when the window moves to another
|
|
40
|
+
* screen. Where the cap lands is the caller's — MAF scrolls its overflow into a
|
|
41
|
+
* viewport, the multi-row painting divides the cap across its rows so they thin
|
|
42
|
+
* out and all stay on screen.
|
|
43
|
+
*/
|
|
44
|
+
export declare function maxCanvasCssPx(): number;
|
|
45
|
+
/**
|
|
46
|
+
* The ratio a canvas's backing store ACTUALLY has to its CSS box, per axis.
|
|
47
|
+
*
|
|
48
|
+
* Equal to `getDpr()` on both axes until `backingPx` clamps, and below it after
|
|
49
|
+
* — which is the whole point: every device-px rect a renderer hands the GPU has
|
|
50
|
+
* to be derived from this rather than from the true dpr, or a clamped canvas
|
|
51
|
+
* gets a viewport taller than the target it draws into and the frame is
|
|
52
|
+
* rejected outright. See ARCHITECTURAL_LIMITS.md §"A canvas past
|
|
53
|
+
* MAX_CANVAS_DIM_PX renders wrong, not smaller".
|
|
54
|
+
*/
|
|
55
|
+
export interface CanvasScale {
|
|
56
|
+
x: number;
|
|
57
|
+
y: number;
|
|
58
|
+
}
|
|
59
|
+
export declare function syncCanvasSize(canvas: HTMLCanvasElement, width: number, height: number): {
|
|
60
|
+
changed: boolean;
|
|
61
|
+
scale: CanvasScale;
|
|
62
|
+
};
|
|
63
|
+
export declare function prepareCanvas(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, canvasWidth: number, canvasHeight: number): void;
|
|
64
|
+
export declare function getPreparedCanvas2D(canvas: HTMLCanvasElement | null, width: number, height: number): CanvasRenderingContext2D | null;
|
|
65
|
+
export declare function devicePxSpan(cssStart: number, cssEnd: number, dpr: number): {
|
|
66
|
+
start: number;
|
|
67
|
+
width: number;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* One horizontal band of a stacked canvas as a device-px scissor rect, clamped
|
|
71
|
+
* to the backing store: `top`/`height` in CSS px, `bufH` the backing store's
|
|
72
|
+
* height in device px.
|
|
73
|
+
*
|
|
74
|
+
* The clamp is the part that matters. A band scrolled past either edge — a
|
|
75
|
+
* grouped alignments section, a MAF rows viewport under a resized band — yields
|
|
76
|
+
* a rect the backing store does not contain, and WebGPU REJECTS an
|
|
77
|
+
* out-of-bounds scissor and blanks the whole frame rather than clipping it. A
|
|
78
|
+
* band clamped to zero height is the caller's cue to skip its passes.
|
|
79
|
+
*
|
|
80
|
+
* `dpr` is the scale the canvas ACTUALLY got (`hal.resize`'s return), not
|
|
81
|
+
* `getDpr()`: the two part company once the backing store clamps at
|
|
82
|
+
* `MAX_CANVAS_DIM_PX`, and past that a band built from the true ratio is taller
|
|
83
|
+
* than the target.
|
|
84
|
+
*/
|
|
85
|
+
export declare function devicePxBand(top: number, height: number, dpr: number, bufH: number): {
|
|
86
|
+
top: number;
|
|
87
|
+
height: number;
|
|
88
|
+
};
|
|
89
|
+
export declare function clampBlockScissor(screenStartPx: number, screenEndPx: number, canvasWidth: number): {
|
|
90
|
+
scissorX: number;
|
|
91
|
+
scissorEnd: number;
|
|
92
|
+
scissorW: number;
|
|
93
|
+
} | null;
|
|
94
|
+
export declare function clipBlockForCanvas(block: BpRegionBounds, canvasWidth: number): BlockClip | null;
|
|
95
|
+
/**
|
|
96
|
+
* The 2D-context subset the block scaffold below needs. Structural on purpose:
|
|
97
|
+
* render-core must not depend on `@jbrowse/core`, where the real `Ctx2D =
|
|
98
|
+
* CanvasRenderingContext2D | SvgCanvas` union lives. Both members satisfy it,
|
|
99
|
+
* so a plugin passes its `Ctx2D` straight in.
|
|
100
|
+
*/
|
|
101
|
+
export interface ClipContext2D {
|
|
102
|
+
save(): void;
|
|
103
|
+
restore(): void;
|
|
104
|
+
beginPath(): void;
|
|
105
|
+
rect(x: number, y: number, w: number, h: number): void;
|
|
106
|
+
clip(): void;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Clip to one rect, paint, unclip — the `save`/`clip`/`restore` triple with the
|
|
110
|
+
* `restore` in a `finally`.
|
|
111
|
+
*
|
|
112
|
+
* The `finally` is the whole point, and the cost of missing it is not local: an
|
|
113
|
+
* on-screen ctx outlives the frame and `prepareCanvas`'s setTransform/clearRect
|
|
114
|
+
* do **not** reset clip state, so one painter throwing once leaves every later
|
|
115
|
+
* frame drawing through a stale clip. A transient error becomes permanent
|
|
116
|
+
* corruption, and the visible symptom (content missing from a band that has no
|
|
117
|
+
* reason to be clipped) points nowhere near the painter that threw.
|
|
118
|
+
*
|
|
119
|
+
* `forEachClippedBlock` is the per-block form and covers the common case on its
|
|
120
|
+
* own. Reach for this one for the clips *nested inside* a block — a display
|
|
121
|
+
* whose bands each own a horizontal strip of the canvas (alignments clips its
|
|
122
|
+
* coverage, pileup and arc bands separately within one block clip), where the
|
|
123
|
+
* nesting is what makes a hand-rolled pairing easy to get wrong: an early
|
|
124
|
+
* `return` inside the band body unbalances the block's `save` too.
|
|
125
|
+
*/
|
|
126
|
+
export declare function withClip(ctx: ClipContext2D, x: number, y: number, w: number, h: number, paint: () => void): void;
|
|
127
|
+
/**
|
|
128
|
+
* The Canvas2D twin of `GpuPerRegionRenderingBackend.renderBlocks`' per-block
|
|
129
|
+
* scissor: skip blocks with nothing to draw or no on-screen span, clip to the
|
|
130
|
+
* block's columns, paint, unclip. Owning it here means a painter can't pair
|
|
131
|
+
* `save()` with a missed `restore()` (leaking the clip onto every later block),
|
|
132
|
+
* clip to a span other than `clipBlockForCanvas`', or forget the null check.
|
|
133
|
+
*
|
|
134
|
+
* `select` resolves a block to what it paints, or `undefined` to skip it — the
|
|
135
|
+
* one gate covering all three reasons a painter skips: no region in the map, a
|
|
136
|
+
* region with zero features, and a region whose relevant sub-field is absent
|
|
137
|
+
* (MAF's `?.coverage`). It runs *before* the clip so an empty block costs
|
|
138
|
+
* neither the path-and-clip round trip on a real context nor the queued group
|
|
139
|
+
* on `SvgCanvas` — that class now drops a clip nothing was drawn inside, so a
|
|
140
|
+
* skipped-late block no longer leaves dead `<clipPath>` markup in the export,
|
|
141
|
+
* but relying on it to clean up after the gate is backwards. Resolving here
|
|
142
|
+
* also lets `paint` take a non-optional value instead of re-narrowing inside
|
|
143
|
+
* the callback.
|
|
144
|
+
*
|
|
145
|
+
* `clipHeight` is a parameter rather than read off a render state because
|
|
146
|
+
* painters that own a sub-band of the canvas clip to that band, not the full
|
|
147
|
+
* height (MAF's coverage row).
|
|
148
|
+
*
|
|
149
|
+
* `paint` receives the block, so callers needing bp→px build their own mapper
|
|
150
|
+
* (`makeBpMapper` / `makeCellLeftMapper` — which one is a per-painter choice).
|
|
151
|
+
* Two closures per draw call, not per feature: per-feature loops live inside the
|
|
152
|
+
* `paint` body and stay allocation-free.
|
|
153
|
+
*/
|
|
154
|
+
export declare function forEachClippedBlock<T, B extends BpRegionBounds>(ctx: ClipContext2D, blocks: readonly B[], canvasWidth: number, clipHeight: number, select: (block: B) => T | undefined, paint: (data: T, block: B, clip: BlockClip) => void): void;
|
|
155
|
+
/**
|
|
156
|
+
* Stroke the outline of `[x, y, w, h]` so the line lands **inside** the rect
|
|
157
|
+
* rather than straddling its edge.
|
|
158
|
+
*
|
|
159
|
+
* Canvas2D centres a stroke on its path, so `strokeRect(x, y, w, h)` puts half
|
|
160
|
+
* the line width outside the rectangle you named. Every GPU pass in this repo
|
|
161
|
+
* draws an outline as an inner band — a fragment test on distance-to-edge, which
|
|
162
|
+
* has no way to paint outside the glyph — so the straddling spelling is a
|
|
163
|
+
* guaranteed half-pixel disagreement with the shader, and it eats whatever gap
|
|
164
|
+
* separates the glyph from its neighbour.
|
|
165
|
+
*
|
|
166
|
+
* **It matters more in the SVG export than on screen**, which is the reason this
|
|
167
|
+
* is a helper and not a house style. On a canvas the difference is half a pixel;
|
|
168
|
+
* in SVG a centred stroke means the emitted element's *declared geometry* and
|
|
169
|
+
* its painted extent differ by half the stroke width, so a 10px-tall read
|
|
170
|
+
* exports as an 11px-tall mark. Anything that measures the file, or opens it in
|
|
171
|
+
* a vector editor and snaps to the shape's bounds, is then reading a rectangle
|
|
172
|
+
* whose coordinates do not describe what is drawn.
|
|
173
|
+
*
|
|
174
|
+
* Degenerate rects are left to the caller: below `lineWidth` on an axis the
|
|
175
|
+
* inset would invert it, and what to do instead (skip the outline, or fill
|
|
176
|
+
* solid) is a per-glyph decision. The alignments read painter and the canvas
|
|
177
|
+
* feature painter both gate on a minimum size first.
|
|
178
|
+
*/
|
|
179
|
+
export declare function strokeRectInside(ctx: {
|
|
180
|
+
strokeRect: (x: number, y: number, w: number, h: number) => void;
|
|
181
|
+
}, x: number, y: number, w: number, h: number, lineWidth?: number): void;
|
|
182
|
+
/**
|
|
183
|
+
* `fillRect` x for a mark that spans `x1`→`x2` but is painted at `width` — the
|
|
184
|
+
* Canvas2D pivot for every painter that widens a too-narrow span to a floor (a
|
|
185
|
+
* 1bp feature, a zoomed-out coverage bin), and the twin of the pivot baked into
|
|
186
|
+
* the shaders' `extendToMinWidthX`.
|
|
187
|
+
*
|
|
188
|
+
* The mark is anchored at `x1` and grows *away* from it. That's the whole point:
|
|
189
|
+
* on a reversed block `makeBpMapper` flips, so `x2 < x1`, `x1` is the feature's
|
|
190
|
+
* **start** — its right edge — and the mark must grow leftward from it. Spelling
|
|
191
|
+
* the fill as `min(x1, x2)` + `max(floor, |dx|)` instead anchors whichever edge
|
|
192
|
+
* happens to be leftmost, which reversed is the feature's *end*, sliding the
|
|
193
|
+
* mark by up to the floor. Forward blocks are identical either way, so the error
|
|
194
|
+
* only ever shows on flipped regions and survives review. The canvas rect
|
|
195
|
+
* painter (2px floor), the multi-row painter (1px), and wiggle (1.5px) each had
|
|
196
|
+
* it independently.
|
|
197
|
+
*
|
|
198
|
+
* Callers own the *width*, like `makeCellLeftMapper` below — it's a per-plugin
|
|
199
|
+
* rule (canvas/multi-row take `max(floor, |dx|)`, matching `extendToMinWidthX`
|
|
200
|
+
* exactly; wiggle adds a seam-closing fudge the shader deliberately omits).
|
|
201
|
+
* Only the pivot is shared, and it's the part that keeps being got wrong.
|
|
202
|
+
*
|
|
203
|
+
* Scalar in, scalar out: runs per feature per frame, so it must not allocate.
|
|
204
|
+
*/
|
|
205
|
+
export declare function spanLeft(x1: number, x2: number, width: number): number;
|
|
206
|
+
export declare function makeRampFillStyleLut(ramp: Uint8Array): (t: number) => string;
|
|
207
|
+
export declare function bpToScreenPx(absBp: number, regionStart: number, regionEnd: number, screenStartPx: number, screenEndPx: number, reversed?: boolean): number;
|
|
208
|
+
export declare function makeBpMapper(bounds: BpRegionBounds): (bp: number) => number;
|
|
209
|
+
/**
|
|
210
|
+
* Left edge of the **1bp cell** covering `bp` — for painters that fill a rect
|
|
211
|
+
* per base (MAF alignment cells, the alignments pileup's mismatch /
|
|
212
|
+
* modification / per-base quality+letter / soft-clip base layers).
|
|
213
|
+
*
|
|
214
|
+
* Use this, not `makeBpMapper`, whenever the mark is a cell rather than a point
|
|
215
|
+
* or a two-edge span. `makeBpMapper(bp)` is the cell's left edge only on a
|
|
216
|
+
* forward block: a reversed block runs bp leftward, so it lands on the cell's
|
|
217
|
+
* *right* edge, and `fillRect(x, y, +w, h)` from there covers bp-1 instead. The
|
|
218
|
+
* error is one base wide — invisible zoomed out (cells floor to ~1px), a whole
|
|
219
|
+
* base off zoomed in, and it only reproduces on flipped regions, so it survives
|
|
220
|
+
* casual review. Every plugin that hand-rolled the pivot had to rediscover it;
|
|
221
|
+
* the alignments pileup got it wrong across all five of its cell layers.
|
|
222
|
+
*
|
|
223
|
+
* Two-edge spans (`min(toX(start), toX(end))`) and boundary marks (insertions,
|
|
224
|
+
* clip bars, centered on a bp edge) are orientation-safe already and don't need
|
|
225
|
+
* this. The GPU side has no equivalent bug — shaders span
|
|
226
|
+
* `bpToClipX(pos)`→`bpToClipX(pos+1)` unflipped and mirror via `flipX` — and
|
|
227
|
+
* gets its own pivot from `writeBpRangeUniforms`.
|
|
228
|
+
*
|
|
229
|
+
* Callers own the cell *width*: it's a per-plugin rule (MAF uses the raw
|
|
230
|
+
* bp-scale; the pileup floors at 1px and adds a seam fudge). Only the pivot is
|
|
231
|
+
* shared.
|
|
232
|
+
*/
|
|
233
|
+
/**
|
|
234
|
+
* Screen px one base occupies in this region — the scale factor, unsigned, with
|
|
235
|
+
* the block's direction deliberately left out (that lives in `makeBpMapper` and
|
|
236
|
+
* `spanLeft`, and folding it in here would make a *scale* carry an orientation).
|
|
237
|
+
*
|
|
238
|
+
* Shared because it is usually an input to something a second painter has to
|
|
239
|
+
* reproduce exactly: a mark sized against it in a draw pass and hit-tested
|
|
240
|
+
* against it in a component is two spellings of one number, and the hit target
|
|
241
|
+
* silently stops covering the mark if they ever drift. Trivial arithmetic, but
|
|
242
|
+
* the drift is not.
|
|
243
|
+
*/
|
|
244
|
+
export declare function pxPerBpOf(bounds: BpRegionBounds): number;
|
|
245
|
+
export declare function makeCellLeftMapper(bounds: BpRegionBounds): (bp: number) => number;
|
|
246
|
+
/**
|
|
247
|
+
* Fill the rect covering genomic `[startBp, endBp)`, given a bare
|
|
248
|
+
* `makeBpMapper`. Maps **both** edges and takes the leftmost, which is
|
|
249
|
+
* orientation-safe by construction: on a reversed block bp runs leftward, so
|
|
250
|
+
* the span's left edge is its *end*, and there is no pivot to get backwards.
|
|
251
|
+
*
|
|
252
|
+
* Prefer this to `makeCellLeftMapper` + a caller-computed width for anything
|
|
253
|
+
* wider than one base. The pivot in `makeCellLeftMapper` is exactly one base,
|
|
254
|
+
* which is right for a 1bp cell and silently wrong for an N-bp span — it
|
|
255
|
+
* anchors the rect one base from the correct edge and then grows it the wrong
|
|
256
|
+
* way. `fillBpSpan(bp, bp + 1)` is *identical* to the cell-mapper spelling, so
|
|
257
|
+
* a per-base painter can use this too and never think about orientation again.
|
|
258
|
+
*
|
|
259
|
+
* `seam` is the caller's sub-pixel overlap (MAF's `GAP_STROKE_OFFSET`), added
|
|
260
|
+
* to the width so adjacent cells don't show hairlines at ~1px/bp. Widening a
|
|
261
|
+
* sub-pixel *feature* to a visible floor is a different job — that's
|
|
262
|
+
* `spanLeft`, which must anchor the feature's start edge.
|
|
263
|
+
*/
|
|
264
|
+
export declare function fillBpSpan(ctx: {
|
|
265
|
+
fillRect: (x: number, y: number, w: number, h: number) => void;
|
|
266
|
+
}, toX: (bp: number) => number, startBp: number, endBp: number, top: number, height: number, seam?: number): void;
|
|
267
|
+
/**
|
|
268
|
+
* The **integer base** whose 1bp cell covers screen pixel `px` — the inverse of
|
|
269
|
+
* `makeCellLeftMapper`, for hit-testing (which base did the cursor land on) as
|
|
270
|
+
* opposed to painting.
|
|
271
|
+
*
|
|
272
|
+
* Rounding is not `Math.floor` on both orientations, which is the trap. The
|
|
273
|
+
* un-rounded inverse of `makeBpMapper` lands in `[b, b+1)` on a forward block
|
|
274
|
+
* but in `(b, b+1]` on a reversed one, because bp runs leftward there — so
|
|
275
|
+
* flooring reversed names `b+1` on base `b`'s leftmost pixel column, and names
|
|
276
|
+
* `end` (outside the block) on the block's first column. It is the same
|
|
277
|
+
* one-base pivot `makeCellLeftMapper` exists to get right, seen from the other
|
|
278
|
+
* direction, and it fails the same way: invisible zoomed out, a whole base off
|
|
279
|
+
* at base zoom, and only on flipped regions.
|
|
280
|
+
*
|
|
281
|
+
* Callers own the *range* check: a `px` outside `[screenStartPx, screenEndPx)`
|
|
282
|
+
* extrapolates rather than clamping, since which block owns a pixel is the
|
|
283
|
+
* caller's decision (adjacent blocks share an edge pixel).
|
|
284
|
+
*
|
|
285
|
+
* **Multiply before dividing, and never form the fraction.** That is the whole
|
|
286
|
+
* of the arithmetic below and it is load-bearing: `(px - screenStartPx) * span`
|
|
287
|
+
* is *exact* — both operands are dyadic and the product tops out around 3e12
|
|
288
|
+
* against a 2^53 budget — so the one division that follows is the only rounding
|
|
289
|
+
* in the expression, and its true quotient is either an exact integer (the
|
|
290
|
+
* division is then exact too) or at least `1 / blockWidth` from one, which is
|
|
291
|
+
* ~1e-3 against a relative error of 2^-53. So the floor cannot land on the
|
|
292
|
+
* wrong base. Spelling it `frac = (px - s) / w` and then `frac * span` rounds
|
|
293
|
+
* twice, and the second product can land arbitrarily close to an integer from
|
|
294
|
+
* either side.
|
|
295
|
+
*
|
|
296
|
+
* Not a micro-optimization — a correctness one, and measured. Against an exact
|
|
297
|
+
* rational oracle over 11.6M realistic samples (integer through eighth-pixel
|
|
298
|
+
* cursor positions, chr1-scale starts, spans from 1bp to 3Mb, both
|
|
299
|
+
* orientations, fractional block offsets) this form is exact on every one,
|
|
300
|
+
* where the `frac` spelling it replaces named the wrong base 4202 times and
|
|
301
|
+
* wiggle's independent `baseAtFraction` — which this now backs — 10992 times.
|
|
302
|
+
* Those are all boundary pixels, which is exactly where a per-base tooltip is
|
|
303
|
+
* read.
|
|
304
|
+
*
|
|
305
|
+
* **The anchor splits, because `bounds.start` is usually not a whole base.** A
|
|
306
|
+
* block's edges are laid out in PIXELS, so its bp edges land wherever that
|
|
307
|
+
* projection puts them — `visibleRegions` hands every hit test a start like
|
|
308
|
+
* `14468.9936` — and rounding the *offset* and then adding such an anchor
|
|
309
|
+
* returns the anchor's fraction untouched. The result is then not a base at
|
|
310
|
+
* all, and every caller that indexes one by equality (a mismatch column, a
|
|
311
|
+
* coverage bin, the sort-at-this-column anchor) misses silently: the pileup
|
|
312
|
+
* offered no SNP submenu over a mismatch it was drawing. Rounding
|
|
313
|
+
* `whole + fraction + offset` in one step instead fixes that and hands back the
|
|
314
|
+
* genome-scale addend the paragraph above exists to keep out, so the whole base
|
|
315
|
+
* comes off first and only the sub-base remainder enters the rounded
|
|
316
|
+
* expression. On a whole-base anchor that remainder is zero and the arithmetic
|
|
317
|
+
* is identical to what an integer start has always done.
|
|
318
|
+
*
|
|
319
|
+
* The same oracle, re-run over 3.7M samples on fractional anchors: this spelling
|
|
320
|
+
* misses 696 and the shipped one missed every single fractional-anchor sample,
|
|
321
|
+
* 3,076,480 of them. `Math.floor(start + offset)` misses 2,524. The residue is
|
|
322
|
+
* the boundary pixel itself, which no double-precision spelling resolves both
|
|
323
|
+
* ways once base boundaries stop landing on whole pixels.
|
|
324
|
+
*/
|
|
325
|
+
export declare function bpAtPx(px: number, bounds: BpRegionBounds): number;
|
|
326
|
+
/**
|
|
327
|
+
* The **fractional bp** at screen pixel `px` — the un-rounded inverse of
|
|
328
|
+
* `makeBpMapper`, for hit tests that measure a distance or open a search window
|
|
329
|
+
* rather than index a base (the alignments interbase triangles and modification
|
|
330
|
+
* centers, the variant cell's bp padding, the Manhattan point's ±radius query).
|
|
331
|
+
*
|
|
332
|
+
* Ask `bpAtPx` instead the moment the answer indexes per-base data, and **do
|
|
333
|
+
* not floor this** — flooring an already-divided fraction is the double
|
|
334
|
+
* rounding `bpAtPx`'s JSDoc argues against at length, and reversed it names the
|
|
335
|
+
* neighbouring base as well. `end - offset` reversed, not `start + offset`: bp
|
|
336
|
+
* runs leftward there, and each of the three plugins that spelled this out
|
|
337
|
+
* locally had to rediscover that.
|
|
338
|
+
*
|
|
339
|
+
* **Neither function is the other's implementation.** This multiplies before
|
|
340
|
+
* dividing for the same reason `bpAtPx` does — `(px - screenStartPx) * span` is
|
|
341
|
+
* exact, so the division is the only rounding — but it does not split the
|
|
342
|
+
* anchor into whole and remainder the way `bpAtPx` does. That split keeps the
|
|
343
|
+
* genome-scale addend out of a *rounded* expression, and there is no rounding
|
|
344
|
+
* here to protect; the caller wants the anchor's fraction kept, not floored
|
|
345
|
+
* away.
|
|
346
|
+
*
|
|
347
|
+
* Adopting it moved no consumer's answer, which is the point rather than a
|
|
348
|
+
* caveat. Over 300000 fractional-anchor samples against the exact-rational
|
|
349
|
+
* oracle `bpAtPx` was measured on, it and all three spellings it replaced land
|
|
350
|
+
* within 1 ulp of the exactly-rounded value; it returns a different double from
|
|
351
|
+
* the `frac` spelling on 114 and from gwas' `bpPerPx` spelling on 289, by at
|
|
352
|
+
* most 7.5e-9 bp against hit radii measured in whole pixels.
|
|
353
|
+
*/
|
|
354
|
+
export declare function bpAtPxExact(px: number, bounds: BpRegionBounds): number;
|
|
355
|
+
/**
|
|
356
|
+
* The region owning screen pixel `px`, or undefined past the last one — the
|
|
357
|
+
* range check `bpAtPx` and `bpAtPxExact` leave to their callers, answered once.
|
|
358
|
+
*
|
|
359
|
+
* **The exclusive upper bound is the whole content of this.** Adjacent regions
|
|
360
|
+
* share a pixel (`regionA.screenEndPx === regionB.screenStartPx`), so an
|
|
361
|
+
* inclusive bound matches both and `find` hands back the earlier one — which
|
|
362
|
+
* then steals every click meant for the later region's first column. Half-open,
|
|
363
|
+
* exactly one region can match, so this answers with a region rather than a
|
|
364
|
+
* list.
|
|
365
|
+
*
|
|
366
|
+
* Generic so a caller gets its own region type back, since what it wants from
|
|
367
|
+
* the match is a `displayedRegionIndex` or the bp/px edges for a mapper.
|
|
368
|
+
*/
|
|
369
|
+
export declare function regionAtPixel<R extends {
|
|
370
|
+
screenStartPx: number;
|
|
371
|
+
screenEndPx: number;
|
|
372
|
+
}>(regions: readonly R[], px: number): R | undefined;
|