@jbrowse/bandage-core 6.0.0 → 6.0.2
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/README.md +23 -7
- package/dist/alleleProjection/projectAlleles.js +10 -0
- package/dist/anchoredNodes.js +24 -0
- package/dist/bubbleSpreads.js +11 -0
- package/dist/bubbles/bubbleHalos.js +11 -0
- package/dist/bubbles/bubbleLine.js +14 -0
- package/dist/bubbles/bubblesFromGraph.js +10 -0
- package/dist/bubbles/classifyBubble.js +15 -0
- package/dist/bubbles/popBubble.js +9 -0
- package/dist/chunks/chunk-3C2LGJKY.js +108 -0
- package/dist/chunks/chunk-3PLYZLYV.js +513 -0
- package/dist/chunks/chunk-3S3YVOJ7.js +69 -0
- package/dist/chunks/chunk-3WHKR5TY.js +726 -0
- package/dist/chunks/chunk-47DYCZOK.js +115 -0
- package/dist/chunks/chunk-4S47XRYA.js +149 -0
- package/dist/chunks/chunk-527AI7CP.js +122 -0
- package/dist/chunks/chunk-57ZIXHZA.js +90 -0
- package/dist/chunks/chunk-5QT6NPSO.js +40 -0
- package/dist/chunks/chunk-6JEWNXCQ.js +90 -0
- package/dist/chunks/chunk-6Q6HFBJT.js +170 -0
- package/dist/chunks/chunk-6SBUFFTB.js +97 -0
- package/dist/chunks/chunk-6YRRNX2P.js +61 -0
- package/dist/chunks/chunk-72N5DJHZ.js +155 -0
- package/dist/chunks/chunk-73RSLGZT.js +73 -0
- package/dist/chunks/chunk-7CHJKABZ.js +82 -0
- package/dist/chunks/chunk-7XHHVYG7.js +13 -0
- package/dist/chunks/chunk-AB35TJST.js +67 -0
- package/dist/chunks/chunk-AE3UQZY3.js +43 -0
- package/dist/chunks/chunk-BJV2W4KZ.js +257 -0
- package/dist/chunks/chunk-BPLIXMH7.js +52 -0
- package/dist/chunks/chunk-BXEPA4KS.js +179 -0
- package/dist/chunks/chunk-C44YRP5F.js +85 -0
- package/dist/chunks/chunk-CDLPYR5O.js +135 -0
- package/dist/chunks/chunk-CPMHA4W5.js +6 -0
- package/dist/chunks/chunk-CR7EGAFE.js +241 -0
- package/dist/chunks/chunk-CZ54VG2H.js +431 -0
- package/dist/chunks/chunk-D3M7SBKL.js +210 -0
- package/dist/chunks/chunk-EJ5OZTCK.js +52 -0
- package/dist/chunks/chunk-ES2N6V56.js +83 -0
- package/dist/chunks/chunk-EZPBJPK6.js +54 -0
- package/dist/chunks/chunk-FYQWPEEY.js +65 -0
- package/dist/chunks/chunk-GQ2AOHU4.js +112 -0
- package/dist/chunks/chunk-HB6PDCOU.js +177 -0
- package/dist/chunks/chunk-HCANBJDD.js +326 -0
- package/dist/chunks/chunk-HIJVRRYE.js +174 -0
- package/dist/chunks/chunk-HNQPEMYE.js +297 -0
- package/dist/chunks/chunk-IGVV37VI.js +60 -0
- package/dist/chunks/chunk-IMW3AK27.js +102 -0
- package/dist/chunks/chunk-JKJWSY4V.js +32 -0
- package/dist/chunks/chunk-KCW7L3NV.js +6 -0
- package/dist/chunks/chunk-KQFOCDFY.js +73 -0
- package/dist/chunks/chunk-L3O7AWLI.js +52 -0
- package/dist/chunks/chunk-L5RYUPZI.js +53 -0
- package/dist/chunks/chunk-LOEA5IHZ.js +71 -0
- package/dist/chunks/chunk-LTUWJKHD.js +112 -0
- package/dist/chunks/chunk-LVZZH2NI.js +181 -0
- package/dist/chunks/chunk-MFKORG3F.js +44 -0
- package/dist/chunks/chunk-MHGHYHTL.js +205 -0
- package/dist/chunks/chunk-MI35EUIE.js +76 -0
- package/dist/chunks/chunk-MNUSQCLG.js +29 -0
- package/dist/chunks/chunk-NNODIW4U.js +26 -0
- package/dist/chunks/chunk-P7B3GDAY.js +232 -0
- package/dist/chunks/chunk-PBENCR5L.js +60 -0
- package/dist/chunks/chunk-PUG7LXDF.js +70 -0
- package/dist/chunks/chunk-Q3KCTA4N.js +202 -0
- package/dist/chunks/chunk-QCYM44UL.js +476 -0
- package/dist/chunks/chunk-QYBZZI5H.js +0 -0
- package/dist/chunks/chunk-RAKOB6IV.js +169 -0
- package/dist/chunks/chunk-RVS2QCOX.js +135 -0
- package/dist/chunks/chunk-SDGM243E.js +190 -0
- package/dist/chunks/chunk-ST2IBUAN.js +119 -0
- package/dist/chunks/chunk-SV27AJCC.js +101 -0
- package/dist/chunks/chunk-SZNZ2T6D.js +198 -0
- package/dist/chunks/chunk-TDQLINVI.js +16 -0
- package/dist/chunks/chunk-TEDQJPMI.js +131 -0
- package/dist/chunks/chunk-TZFWUT2C.js +66 -0
- package/dist/chunks/chunk-UVBGRJQX.js +50 -0
- package/dist/chunks/chunk-VHW7FDHU.js +42 -0
- package/dist/chunks/chunk-VKB57ZYE.js +218 -0
- package/dist/chunks/chunk-VZQ4FKLR.js +38 -0
- package/dist/chunks/chunk-WOO7QYKB.js +121 -0
- package/dist/chunks/chunk-WSIFAZU7.js +132 -0
- package/dist/chunks/chunk-X32PABB7.js +117 -0
- package/dist/chunks/chunk-YJQ7CT7F.js +23 -0
- package/dist/cli/figure.js +84 -17
- package/dist/colorSchemes.js +10 -0
- package/dist/deletionEdges.js +13 -0
- package/dist/el.js +8 -0
- package/dist/facetGrid.js +16 -0
- package/dist/figure.js +51 -0
- package/dist/gaf/parseGaf.js +12 -0
- package/dist/gbzCut.js +19 -0
- package/dist/gbzJoin.js +6 -0
- package/dist/gbzWindow.js +28 -0
- package/dist/genes/exonOutline.js +11 -0
- package/dist/genes/geneFiles.js +14 -0
- package/dist/genes/genePins.js +10 -0
- package/dist/gfa/gfaConverter.js +11 -0
- package/dist/gfa-core/gfaParser.js +8 -0
- package/dist/gfa-core/index.js +9 -0
- package/dist/graphLabels.js +24 -0
- package/dist/index.js +262 -1407
- package/dist/labelLayout.js +24 -0
- package/dist/layout/anchoredLayout.js +11 -0
- package/dist/layout/deletionRoutes.js +10 -0
- package/dist/layout/drawnScale.js +22 -0
- package/dist/layout/mergeRuns.js +15 -0
- package/dist/layout/orderedLayout.js +12 -0
- package/dist/layout/orientToReference.js +7 -0
- package/dist/layout/placeOffReference.js +9 -0
- package/dist/layout/referenceSeeds.js +11 -0
- package/dist/layout/rowSpacing.js +6 -0
- package/dist/layout/sampleRowLayout.js +11 -0
- package/dist/layout/trimToWindow.js +99 -0
- package/dist/layout/tubeMapLayout.js +28 -0
- package/dist/layout/walkRowDraw.js +65 -0
- package/dist/layout/walkRowLayout.js +13 -0
- package/dist/layout/walkRows.js +11 -0
- package/dist/layout/walkStrip.js +40 -0
- package/dist/layoutModes.js +35 -0
- package/dist/loadBandage.js +6 -0
- package/dist/nodeWidths.js +18 -0
- package/dist/overlayLabels.js +18 -0
- package/dist/pansn.js +18 -0
- package/dist/pathAnchoring.js +18 -0
- package/dist/pathColors.js +31 -0
- package/dist/pipeline.js +40 -0
- package/dist/reference.js +24 -0
- package/dist/referenceRampCss.js +29 -0
- package/dist/referenceSpan.js +11 -0
- package/dist/referenceStrip.js +35 -0
- package/dist/renderer/Canvas2DRenderer.js +9 -0
- package/dist/renderer/GeometryBuilder.js +42 -0
- package/dist/renderer/colorBits.js +8 -0
- package/dist/renderer/recordingCanvas.js +44 -0
- package/dist/renderer/svgCanvas.js +6 -0
- package/dist/renderer/types.js +0 -0
- package/dist/tubeMap/axis.js +25 -0
- package/dist/tubeMap/coarsen.js +361 -0
- package/dist/tubeMap/connectors.js +13 -0
- package/dist/tubeMap/deviations.js +85 -0
- package/dist/tubeMap/draw.js +15 -0
- package/dist/tubeMap/frame.js +9 -0
- package/dist/tubeMap/genes.js +20 -0
- package/dist/tubeMap/mismatches.js +6 -0
- package/dist/tubeMap/nodeColors.js +21 -0
- package/dist/tubeMap/reads.js +17 -0
- package/dist/tubeMap/warp.js +8 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.js +0 -0
- package/dist/util/SpatialIndex.js +13 -0
- package/dist/util/edgeCurves.js +10 -0
- package/dist/util/geometry.js +39 -0
- package/dist/util/hitDetection.js +20 -0
- package/dist/util/wheelZoom.js +6 -0
- package/dist/version.js +6 -0
- package/dist/viewport.js +33 -0
- package/dist/walkEncoding.js +39 -0
- package/dist/walkHighlight.js +26 -0
- package/dist/walkKey.js +17 -0
- package/package.json +38 -5
- package/dist/chunks/chunk-OHMWSW6U.js +0 -7400
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/overlayLabels.ts
|
|
2
|
+
var LABEL_PX = 11;
|
|
3
|
+
var LABEL_CHAR_PX = 6.2;
|
|
4
|
+
var LABEL_PAD = 4;
|
|
5
|
+
function labelWidth(text) {
|
|
6
|
+
return text.length * LABEL_CHAR_PX + LABEL_PAD * 2;
|
|
7
|
+
}
|
|
8
|
+
function overlaps(a, b) {
|
|
9
|
+
return a.x0 < b.x1 && a.x1 > b.x0 && a.y0 < b.y1 && a.y1 > b.y0;
|
|
10
|
+
}
|
|
11
|
+
function offFrame(box, frame) {
|
|
12
|
+
return box.x1 <= 0 || box.x0 >= frame.width || box.y1 <= 0 || box.y0 >= frame.height;
|
|
13
|
+
}
|
|
14
|
+
function insideFrame(box, frame) {
|
|
15
|
+
return box.x0 >= 0 && box.x1 <= frame.width && box.y0 >= 0 && box.y1 <= frame.height;
|
|
16
|
+
}
|
|
17
|
+
function occupancy(frame, reserved = []) {
|
|
18
|
+
const placed = [...reserved];
|
|
19
|
+
return (box) => {
|
|
20
|
+
if (offFrame(box, frame) || placed.some((p) => overlaps(p, box))) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
placed.push(box);
|
|
24
|
+
return true;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function placeLabels(candidates, frame, take) {
|
|
28
|
+
const out = [];
|
|
29
|
+
const row = LABEL_PX + LABEL_PAD * 2;
|
|
30
|
+
const place = (c, text) => {
|
|
31
|
+
const w = labelWidth(text);
|
|
32
|
+
const direction = c.y > frame.height / 2 ? -1 : 1;
|
|
33
|
+
for (let tries = 0; tries <= (c.stack ?? 0); tries++) {
|
|
34
|
+
const y = c.y + direction * tries * row;
|
|
35
|
+
const box = {
|
|
36
|
+
x0: c.x - w / 2,
|
|
37
|
+
x1: c.x + w / 2,
|
|
38
|
+
y0: y - LABEL_PX - LABEL_PAD,
|
|
39
|
+
y1: y + LABEL_PAD
|
|
40
|
+
};
|
|
41
|
+
if (offFrame(box, frame)) {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
if (insideFrame(box, frame) && (c.fits?.(box) ?? true) && take(box)) {
|
|
45
|
+
return { ...c, text, y, w };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
};
|
|
50
|
+
for (const c of candidates) {
|
|
51
|
+
const placed = place(c, c.text) ?? (c.fallback === void 0 ? void 0 : place(c, c.fallback));
|
|
52
|
+
if (placed) {
|
|
53
|
+
out.push(placed);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
LABEL_PX,
|
|
61
|
+
LABEL_CHAR_PX,
|
|
62
|
+
LABEL_PAD,
|
|
63
|
+
labelWidth,
|
|
64
|
+
overlaps,
|
|
65
|
+
occupancy,
|
|
66
|
+
placeLabels
|
|
67
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBp,
|
|
3
|
+
groupDigits
|
|
4
|
+
} from "./chunk-HCANBJDD.js";
|
|
5
|
+
|
|
6
|
+
// src/walkKey.ts
|
|
7
|
+
function rangeText(contig, start, end) {
|
|
8
|
+
const s = Math.round(start);
|
|
9
|
+
const e = Math.round(end);
|
|
10
|
+
return `${contig ? `${contig}:` : ""}${groupDigits(s)}-${groupDigits(e)} (${formatBp(e - s)})`;
|
|
11
|
+
}
|
|
12
|
+
function walkKey(walk, reference) {
|
|
13
|
+
const { field } = walk.encoding;
|
|
14
|
+
const own = walk.range ? rangeText(walk.range.contig, walk.range.start, walk.range.end) : void 0;
|
|
15
|
+
const scale = field === "progress" ? own : field === "reference" && reference ? rangeText(reference.name, reference.start, reference.end) : void 0;
|
|
16
|
+
const spanned = walk.range ? walk.range.end - walk.range.start : walk.bp;
|
|
17
|
+
const outside = spanned - walk.bp;
|
|
18
|
+
return {
|
|
19
|
+
delta: walk.referenceBp === void 0 || spanned === walk.referenceBp ? "" : ` ${spanned > walk.referenceBp ? "+" : "\u2212"}${formatBp(Math.abs(spanned - walk.referenceBp))}`,
|
|
20
|
+
outside: outside > 0 ? `, ${formatBp(outside)} outside the cut` : "",
|
|
21
|
+
reversed: walk.reversedBp > 0 ? `, ${formatBp(walk.reversedBp)} reversed` : "",
|
|
22
|
+
shades: field !== "walk",
|
|
23
|
+
scale,
|
|
24
|
+
hover: scale === own ? void 0 : own
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function walkPosition(walk, nodeId, length) {
|
|
28
|
+
const progress = walk.progress.get(nodeId);
|
|
29
|
+
const range = walk.range;
|
|
30
|
+
if (progress === void 0 || !range) {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
const start = Math.round(
|
|
34
|
+
range.start + progress * (range.end - range.start) - length / 2
|
|
35
|
+
);
|
|
36
|
+
return `${range.contig}:${groupDigits(start)}-${groupDigits(start + length)}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
rangeText,
|
|
41
|
+
walkKey,
|
|
42
|
+
walkPosition
|
|
43
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EdgeSpatialIndex,
|
|
3
|
+
SpatialIndex
|
|
4
|
+
} from "./chunk-LVZZH2NI.js";
|
|
5
|
+
import {
|
|
6
|
+
pathRibbonOffsets,
|
|
7
|
+
translateCurves,
|
|
8
|
+
yToXOf
|
|
9
|
+
} from "./chunk-3PLYZLYV.js";
|
|
10
|
+
|
|
11
|
+
// src/util/hitDetection.ts
|
|
12
|
+
function distanceToSegment(px, py, x1, y1, x2, y2) {
|
|
13
|
+
const dx = x2 - x1;
|
|
14
|
+
const dy = y2 - y1;
|
|
15
|
+
const lenSq = dx * dx + dy * dy;
|
|
16
|
+
if (lenSq === 0) {
|
|
17
|
+
return Math.hypot(px - x1, py - y1);
|
|
18
|
+
}
|
|
19
|
+
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
20
|
+
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
21
|
+
}
|
|
22
|
+
function distanceToCubicBezier(px, py, x1, y1, cx1, cy1, cx2, cy2, x2, y2) {
|
|
23
|
+
let minDist = Infinity;
|
|
24
|
+
const samples = 20;
|
|
25
|
+
let lastX = x1;
|
|
26
|
+
let lastY = y1;
|
|
27
|
+
for (let i = 1; i <= samples; i++) {
|
|
28
|
+
const t = i / samples;
|
|
29
|
+
const u = 1 - t;
|
|
30
|
+
const bx = u * u * u * x1 + 3 * u * u * t * cx1 + 3 * u * t * t * cx2 + t * t * t * x2;
|
|
31
|
+
const by = u * u * u * y1 + 3 * u * u * t * cy1 + 3 * u * t * t * cy2 + t * t * t * y2;
|
|
32
|
+
minDist = Math.min(minDist, distanceToSegment(px, py, lastX, lastY, bx, by));
|
|
33
|
+
lastX = bx;
|
|
34
|
+
lastY = by;
|
|
35
|
+
}
|
|
36
|
+
return minDist;
|
|
37
|
+
}
|
|
38
|
+
function distanceToBezierCurve(px, py, c) {
|
|
39
|
+
return distanceToCubicBezier(
|
|
40
|
+
px,
|
|
41
|
+
py,
|
|
42
|
+
c.x0,
|
|
43
|
+
c.y0,
|
|
44
|
+
c.cx0,
|
|
45
|
+
c.cy0,
|
|
46
|
+
c.cx1,
|
|
47
|
+
c.cy1,
|
|
48
|
+
c.x1,
|
|
49
|
+
c.y1
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function distanceToEdgeCurves(px, py, curves, yToX = 1) {
|
|
53
|
+
let minDist = Infinity;
|
|
54
|
+
for (const c of curves) {
|
|
55
|
+
const d = distanceToBezierCurve(
|
|
56
|
+
px,
|
|
57
|
+
py,
|
|
58
|
+
yToX === 1 ? c : {
|
|
59
|
+
x0: c.x0,
|
|
60
|
+
y0: c.y0 * yToX,
|
|
61
|
+
cx0: c.cx0,
|
|
62
|
+
cy0: c.cy0 * yToX,
|
|
63
|
+
cx1: c.cx1,
|
|
64
|
+
cy1: c.cy1 * yToX,
|
|
65
|
+
x1: c.x1,
|
|
66
|
+
y1: c.y1 * yToX
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
if (d < minDist) {
|
|
70
|
+
minDist = d;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return minDist;
|
|
74
|
+
}
|
|
75
|
+
var nodeCache = /* @__PURE__ */ new WeakMap();
|
|
76
|
+
function getSpatialIndex(nodePositions, version) {
|
|
77
|
+
const cached = nodeCache.get(nodePositions);
|
|
78
|
+
if (cached?.version === version) {
|
|
79
|
+
return cached.index;
|
|
80
|
+
}
|
|
81
|
+
const index = new SpatialIndex(nodePositions);
|
|
82
|
+
nodeCache.set(nodePositions, { version, index });
|
|
83
|
+
return index;
|
|
84
|
+
}
|
|
85
|
+
var edgeCache = /* @__PURE__ */ new WeakMap();
|
|
86
|
+
function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis, version, deletions, routes, stranded = false) {
|
|
87
|
+
const cached = edgeCache.get(nodePositions);
|
|
88
|
+
if (cached?.graph === graph && cached.drawPaths === drawPaths && cached.scaleX === axis.scaleX && cached.scaleY === axis.scaleY && cached.version === version && cached.deletions === deletions && cached.routes === routes && cached.stranded === stranded) {
|
|
89
|
+
return cached.index;
|
|
90
|
+
}
|
|
91
|
+
const index = new EdgeSpatialIndex(
|
|
92
|
+
nodePositions,
|
|
93
|
+
graph,
|
|
94
|
+
drawPaths,
|
|
95
|
+
axis,
|
|
96
|
+
void 0,
|
|
97
|
+
deletions,
|
|
98
|
+
version,
|
|
99
|
+
routes,
|
|
100
|
+
stranded
|
|
101
|
+
);
|
|
102
|
+
edgeCache.set(nodePositions, {
|
|
103
|
+
graph,
|
|
104
|
+
drawPaths,
|
|
105
|
+
scaleX: axis.scaleX,
|
|
106
|
+
scaleY: axis.scaleY,
|
|
107
|
+
version,
|
|
108
|
+
index,
|
|
109
|
+
deletions,
|
|
110
|
+
routes,
|
|
111
|
+
stranded
|
|
112
|
+
});
|
|
113
|
+
return index;
|
|
114
|
+
}
|
|
115
|
+
var MIN_NODE_REACH_PX = 5;
|
|
116
|
+
function findHoveredNode(nodePositions, graphX, graphY, axis, version = 0, ink) {
|
|
117
|
+
const yToX = yToXOf(axis);
|
|
118
|
+
const index = getSpatialIndex(nodePositions, version);
|
|
119
|
+
const reach = (px) => Math.max(MIN_NODE_REACH_PX, px) / axis.scaleX;
|
|
120
|
+
const queryReach = reach(ink?.maxHalfWidthPx ?? 0);
|
|
121
|
+
const candidates = index.query(graphX, graphY, queryReach, queryReach / yToX);
|
|
122
|
+
let hovered = null;
|
|
123
|
+
let best = Infinity;
|
|
124
|
+
for (const { nodeId, segmentIdx } of candidates) {
|
|
125
|
+
const segments = nodePositions[nodeId];
|
|
126
|
+
const dist = distanceToSegment(
|
|
127
|
+
graphX,
|
|
128
|
+
graphY * yToX,
|
|
129
|
+
segments[segmentIdx].x,
|
|
130
|
+
segments[segmentIdx].y * yToX,
|
|
131
|
+
segments[segmentIdx + 1].x,
|
|
132
|
+
segments[segmentIdx + 1].y * yToX
|
|
133
|
+
);
|
|
134
|
+
if (dist < best && dist < reach(ink?.halfWidthPx(nodeId) ?? 0)) {
|
|
135
|
+
best = dist;
|
|
136
|
+
hovered = nodeId;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return hovered;
|
|
140
|
+
}
|
|
141
|
+
function segmentMeetsBox(a, b, box) {
|
|
142
|
+
const dx = b.x - a.x;
|
|
143
|
+
const dy = b.y - a.y;
|
|
144
|
+
let t0 = 0;
|
|
145
|
+
let t1 = 1;
|
|
146
|
+
for (const [p, q] of [
|
|
147
|
+
[-dx, a.x - box.x0],
|
|
148
|
+
[dx, box.x1 - a.x],
|
|
149
|
+
[-dy, a.y - box.y0],
|
|
150
|
+
[dy, box.y1 - a.y]
|
|
151
|
+
]) {
|
|
152
|
+
if (p === 0) {
|
|
153
|
+
if (q < 0) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
} else if (p < 0) {
|
|
157
|
+
t0 = Math.max(t0, q / p);
|
|
158
|
+
} else {
|
|
159
|
+
t1 = Math.min(t1, q / p);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return t0 <= t1;
|
|
163
|
+
}
|
|
164
|
+
function nodesUnderBox(nodePositions, t, version = 0) {
|
|
165
|
+
const index = getSpatialIndex(nodePositions, version);
|
|
166
|
+
const screen = (p) => ({
|
|
167
|
+
x: p.x * t.scaleX + t.translateX,
|
|
168
|
+
y: p.y * t.scaleY + t.translateY
|
|
169
|
+
});
|
|
170
|
+
return (box) => {
|
|
171
|
+
const hits = /* @__PURE__ */ new Set();
|
|
172
|
+
const candidates = index.query(
|
|
173
|
+
((box.x0 + box.x1) / 2 - t.translateX) / t.scaleX,
|
|
174
|
+
((box.y0 + box.y1) / 2 - t.translateY) / t.scaleY,
|
|
175
|
+
Math.abs((box.x1 - box.x0) / 2 / t.scaleX),
|
|
176
|
+
Math.abs((box.y1 - box.y0) / 2 / t.scaleY)
|
|
177
|
+
);
|
|
178
|
+
for (const { nodeId, segmentIdx } of candidates) {
|
|
179
|
+
const line = nodePositions[nodeId];
|
|
180
|
+
if (!hits.has(nodeId) && segmentMeetsBox(
|
|
181
|
+
screen(line[segmentIdx]),
|
|
182
|
+
screen(line[segmentIdx + 1]),
|
|
183
|
+
box
|
|
184
|
+
)) {
|
|
185
|
+
hits.add(nodeId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return hits;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths, version = 0, deletions, hiddenEdges, deletionRoutes, stranded = false) {
|
|
192
|
+
const yToX = yToXOf(axis);
|
|
193
|
+
const edgeThreshold = 10 / axis.scaleX;
|
|
194
|
+
const edgeIndex = getEdgeSpatialIndex(
|
|
195
|
+
nodePositions,
|
|
196
|
+
graph,
|
|
197
|
+
drawPaths,
|
|
198
|
+
axis,
|
|
199
|
+
version,
|
|
200
|
+
deletions,
|
|
201
|
+
deletionRoutes,
|
|
202
|
+
stranded
|
|
203
|
+
);
|
|
204
|
+
const candidates = edgeIndex.query(
|
|
205
|
+
graphX,
|
|
206
|
+
graphY,
|
|
207
|
+
edgeThreshold,
|
|
208
|
+
edgeThreshold / yToX
|
|
209
|
+
);
|
|
210
|
+
let hovered = null;
|
|
211
|
+
let best = edgeThreshold;
|
|
212
|
+
for (const edgeIdx of candidates) {
|
|
213
|
+
const edge = graph.edges[edgeIdx];
|
|
214
|
+
const fromSegments = nodePositions[edge.from];
|
|
215
|
+
const toSegments = nodePositions[edge.to];
|
|
216
|
+
if (!fromSegments?.length || !toSegments?.length || hiddenEdges?.has(edgeIdx)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const numPaths = edge.pathIds?.length ?? 0;
|
|
220
|
+
const baseCurves = edgeIndex.getCurves(edgeIdx);
|
|
221
|
+
if (!baseCurves) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
let dist;
|
|
225
|
+
if (!drawPaths || numPaths === 0) {
|
|
226
|
+
dist = distanceToEdgeCurves(graphX, graphY * yToX, baseCurves, yToX);
|
|
227
|
+
} else {
|
|
228
|
+
let minDist = Infinity;
|
|
229
|
+
for (const offset of pathRibbonOffsets(
|
|
230
|
+
fromSegments,
|
|
231
|
+
toSegments,
|
|
232
|
+
numPaths,
|
|
233
|
+
axis
|
|
234
|
+
)) {
|
|
235
|
+
const curves = offset.x === 0 && offset.y === 0 ? baseCurves : translateCurves(baseCurves, offset.x, offset.y);
|
|
236
|
+
const d = distanceToEdgeCurves(graphX, graphY * yToX, curves, yToX);
|
|
237
|
+
if (d < minDist) {
|
|
238
|
+
minDist = d;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
dist = minDist;
|
|
242
|
+
}
|
|
243
|
+
if (dist < best) {
|
|
244
|
+
best = dist;
|
|
245
|
+
hovered = edgeIdx;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return hovered;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export {
|
|
252
|
+
distanceToSegment,
|
|
253
|
+
distanceToCubicBezier,
|
|
254
|
+
findHoveredNode,
|
|
255
|
+
nodesUnderBox,
|
|
256
|
+
findHoveredEdge
|
|
257
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/pansn.ts
|
|
2
|
+
var SEP = "#";
|
|
3
|
+
function panSNSample(refName) {
|
|
4
|
+
return refName.split(SEP)[0];
|
|
5
|
+
}
|
|
6
|
+
var GENERIC_SAMPLES = /* @__PURE__ */ new Set(["_gbwt_ref"]);
|
|
7
|
+
function isGenericSample(sample) {
|
|
8
|
+
return GENERIC_SAMPLES.has(sample);
|
|
9
|
+
}
|
|
10
|
+
function panSNHaplotype(refName) {
|
|
11
|
+
const parts = refName.split(SEP);
|
|
12
|
+
return parts.length >= 3 ? `${parts[0]}${SEP}${parts[1]}` : void 0;
|
|
13
|
+
}
|
|
14
|
+
function panSNContig(refName) {
|
|
15
|
+
const parts = refName.split(SEP);
|
|
16
|
+
return parts.length >= 3 ? parts.slice(2).join(SEP) : parts.length === 2 ? parts[1] : refName;
|
|
17
|
+
}
|
|
18
|
+
function panSNMatchesPrefix(refName, prefix) {
|
|
19
|
+
return prefix !== void 0 && (refName === prefix || refName.startsWith(prefix + SEP));
|
|
20
|
+
}
|
|
21
|
+
function assemblyNameToPanSN(adapter) {
|
|
22
|
+
return adapter.getConf("assemblyNameToPanSN") ?? {};
|
|
23
|
+
}
|
|
24
|
+
function resolvePanSNPrefix(adapter, name) {
|
|
25
|
+
return name === void 0 ? void 0 : assemblyNameToPanSN(adapter)[name] ?? name;
|
|
26
|
+
}
|
|
27
|
+
var asmByPrefixCache = /* @__PURE__ */ new WeakMap();
|
|
28
|
+
function assemblyByPanSNPrefix(adapter) {
|
|
29
|
+
let out = asmByPrefixCache.get(adapter);
|
|
30
|
+
if (out === void 0) {
|
|
31
|
+
const map = assemblyNameToPanSN(adapter);
|
|
32
|
+
out = {};
|
|
33
|
+
for (const asm of /* @__PURE__ */ new Set([
|
|
34
|
+
...Object.keys(map),
|
|
35
|
+
...adapter.getConf("assemblyNames")
|
|
36
|
+
])) {
|
|
37
|
+
out[map[asm] ?? asm] = asm;
|
|
38
|
+
}
|
|
39
|
+
asmByPrefixCache.set(adapter, out);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
panSNSample,
|
|
46
|
+
isGenericSample,
|
|
47
|
+
panSNHaplotype,
|
|
48
|
+
panSNContig,
|
|
49
|
+
panSNMatchesPrefix,
|
|
50
|
+
resolvePanSNPrefix,
|
|
51
|
+
assemblyByPanSNPrefix
|
|
52
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// src/genes/geneFiles.ts
|
|
2
|
+
var GENE_TYPE = /(gene|gene_segment|RNA|transcript)$/i;
|
|
3
|
+
var isGene = (type) => GENE_TYPE.test(type) || type === "CDS" || type === "exon";
|
|
4
|
+
function mergedIntervals(intervals) {
|
|
5
|
+
const out = [];
|
|
6
|
+
for (const iv of [...intervals].sort((a, b) => a.start - b.start)) {
|
|
7
|
+
const last = out.at(-1);
|
|
8
|
+
if (last && iv.start <= last.end) {
|
|
9
|
+
last.end = Math.max(last.end, iv.end);
|
|
10
|
+
} else {
|
|
11
|
+
out.push({ ...iv });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
function strandOf(s) {
|
|
17
|
+
return s === "+" ? 1 : s === "-" ? -1 : 0;
|
|
18
|
+
}
|
|
19
|
+
function unescape(value) {
|
|
20
|
+
try {
|
|
21
|
+
return decodeURIComponent(value);
|
|
22
|
+
} catch {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function attributes(column) {
|
|
27
|
+
const out = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const pair of column.split(";")) {
|
|
29
|
+
const eq = pair.indexOf("=");
|
|
30
|
+
if (eq > 0) {
|
|
31
|
+
out.set(pair.slice(0, eq).trim(), unescape(pair.slice(eq + 1).trim()));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
function gff3Row(line) {
|
|
37
|
+
const cols = line.split(" ");
|
|
38
|
+
const start = Number(cols[3]);
|
|
39
|
+
const end = Number(cols[4]);
|
|
40
|
+
if (cols.length < 9 || !Number.isInteger(start) || !Number.isInteger(end)) {
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
refName: cols[0],
|
|
45
|
+
type: cols[2],
|
|
46
|
+
start: start - 1,
|
|
47
|
+
end,
|
|
48
|
+
strand: strandOf(cols[6]),
|
|
49
|
+
attrs: attributes(cols[8])
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function genesFromGff3Lines(lines) {
|
|
53
|
+
const rows = [];
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
if (line.startsWith("##FASTA")) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
const row = line.startsWith("#") ? void 0 : gff3Row(line);
|
|
59
|
+
if (row) {
|
|
60
|
+
rows.push(row);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
64
|
+
for (const { attrs } of rows) {
|
|
65
|
+
const id = attrs.get("ID");
|
|
66
|
+
const parent = attrs.get("Parent")?.split(",")[0];
|
|
67
|
+
if (id && parent) {
|
|
68
|
+
parentOf.set(id, parent);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const rootOf = (row) => {
|
|
72
|
+
let id = row.attrs.get("Parent")?.split(",")[0] ?? row.attrs.get("ID");
|
|
73
|
+
if (!id) {
|
|
74
|
+
return row.attrs.get("gene_id");
|
|
75
|
+
}
|
|
76
|
+
const seen = /* @__PURE__ */ new Set();
|
|
77
|
+
while (id && parentOf.has(id) && !seen.has(id)) {
|
|
78
|
+
seen.add(id);
|
|
79
|
+
id = parentOf.get(id);
|
|
80
|
+
}
|
|
81
|
+
return id;
|
|
82
|
+
};
|
|
83
|
+
const groups = /* @__PURE__ */ new Map();
|
|
84
|
+
rows.forEach((row, i) => {
|
|
85
|
+
const root = rootOf(row) ?? `#${i}`;
|
|
86
|
+
const key = `${row.refName} ${root}`;
|
|
87
|
+
const group = groups.get(key) ?? groups.set(key, { rows: [] }).get(key);
|
|
88
|
+
group.rows.push(row);
|
|
89
|
+
if (!row.attrs.has("Parent") && !group.top) {
|
|
90
|
+
group.top = row;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
const genes = [];
|
|
94
|
+
for (const [key, { top, rows: rows2 }] of groups) {
|
|
95
|
+
if (top && !isGene(top.type)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const first = top ?? rows2[0];
|
|
99
|
+
const start = top?.start ?? Math.min(...rows2.map((r) => r.start));
|
|
100
|
+
const end = top?.end ?? Math.max(...rows2.map((r) => r.end));
|
|
101
|
+
const a = first.attrs;
|
|
102
|
+
const spans = (type) => rows2.filter((r) => r.type === type).map((r) => ({ start: r.start, end: r.end }));
|
|
103
|
+
const exons = spans("exon");
|
|
104
|
+
const coding = spans("CDS");
|
|
105
|
+
const transcribed = rows2.some(
|
|
106
|
+
(r) => r !== top && r.type !== "exon" && r.type !== "CDS"
|
|
107
|
+
);
|
|
108
|
+
genes.push({
|
|
109
|
+
name: a.get("Name") ?? a.get("gene_name") ?? a.get("gene") ?? a.get("gene_id") ?? key.split(" ")[1],
|
|
110
|
+
refName: first.refName,
|
|
111
|
+
start,
|
|
112
|
+
end,
|
|
113
|
+
strand: first.strand,
|
|
114
|
+
// A range read holds only the rows over its window, so a gene with
|
|
115
|
+
// transcripts but no exon or CDS there is intronic in it. Only a gene
|
|
116
|
+
// with no transcripts, as a bacterium's gene and CDS, is one exon.
|
|
117
|
+
exons: exons.length ? mergedIntervals(exons) : transcribed ? mergedIntervals(coding) : [{ start, end }]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return genes;
|
|
121
|
+
}
|
|
122
|
+
function mergeOverlappingByName(genes) {
|
|
123
|
+
const out = [];
|
|
124
|
+
const open = /* @__PURE__ */ new Map();
|
|
125
|
+
for (const g of [...genes].sort((a, b) => a.start - b.start)) {
|
|
126
|
+
const key = `${g.refName} ${g.strand} ${g.name}`;
|
|
127
|
+
const last = open.get(key);
|
|
128
|
+
if (last && g.start < last.end) {
|
|
129
|
+
last.end = Math.max(last.end, g.end);
|
|
130
|
+
last.exons = mergedIntervals([...last.exons, ...g.exons]);
|
|
131
|
+
} else {
|
|
132
|
+
const gene = { ...g, exons: mergedIntervals(g.exons) };
|
|
133
|
+
out.push(gene);
|
|
134
|
+
open.set(key, gene);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
function genesFromBed(text) {
|
|
140
|
+
const rows = [];
|
|
141
|
+
for (const line of text.split("\n")) {
|
|
142
|
+
const cols = line.replace(/\r$/, "").split(" ");
|
|
143
|
+
const start = Number(cols[1]);
|
|
144
|
+
const end = Number(cols[2]);
|
|
145
|
+
if (/^(#|track|browser)/.test(line) || cols.length < 3 || !Number.isInteger(start) || !(end > start)) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const refName = cols[0];
|
|
149
|
+
const name = cols[3] && cols[3] !== "." ? cols[3] : `${refName}:${start + 1}-${end}`;
|
|
150
|
+
const sizes = cols[10]?.split(",").filter((s) => s !== "").map(Number);
|
|
151
|
+
const starts = cols[11]?.split(",").filter((s) => s !== "").map(Number);
|
|
152
|
+
const blocks = sizes?.length && sizes.length === starts?.length ? sizes.map((size, i) => ({
|
|
153
|
+
start: start + starts[i],
|
|
154
|
+
end: start + starts[i] + size
|
|
155
|
+
})) : [{ start, end }];
|
|
156
|
+
rows.push({
|
|
157
|
+
name,
|
|
158
|
+
refName,
|
|
159
|
+
start,
|
|
160
|
+
end,
|
|
161
|
+
strand: strandOf(cols[5]),
|
|
162
|
+
exons: blocks
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return mergeOverlappingByName(rows);
|
|
166
|
+
}
|
|
167
|
+
function genesFromText(text) {
|
|
168
|
+
const first = text.split("\n", 1e3).find((l) => l.trim() !== "" && !/^(#|track|browser)/.test(l));
|
|
169
|
+
const cols = first?.split(" ") ?? [];
|
|
170
|
+
return /^\d+$/.test(cols[1] ?? "") && /^\d+$/.test(cols[2] ?? "") ? genesFromBed(text) : genesFromGff3Lines(text.split(/\r?\n/));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export {
|
|
174
|
+
mergedIntervals,
|
|
175
|
+
genesFromGff3Lines,
|
|
176
|
+
mergeOverlappingByName,
|
|
177
|
+
genesFromBed,
|
|
178
|
+
genesFromText
|
|
179
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// src/gaf/parseGaf.ts
|
|
2
|
+
var STEP = /([><])([^><]+)/g;
|
|
3
|
+
function parseGafPath(column) {
|
|
4
|
+
if (!column.startsWith(">") && !column.startsWith("<")) {
|
|
5
|
+
return void 0;
|
|
6
|
+
}
|
|
7
|
+
const steps = [];
|
|
8
|
+
for (const [, arrow, name] of column.matchAll(STEP)) {
|
|
9
|
+
steps.push({ name, strand: arrow === ">" ? "+" : "-" });
|
|
10
|
+
}
|
|
11
|
+
return steps;
|
|
12
|
+
}
|
|
13
|
+
function parseGafLine(line) {
|
|
14
|
+
const columns = line.split(" ");
|
|
15
|
+
if (columns.length < 12 || line.startsWith("#")) {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
const path = parseGafPath(columns[5]);
|
|
19
|
+
if (!path) {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
let cs;
|
|
23
|
+
let secondary = false;
|
|
24
|
+
for (let i = 12; i < columns.length; i++) {
|
|
25
|
+
const tag = columns[i];
|
|
26
|
+
if (tag.startsWith("cs:Z:")) {
|
|
27
|
+
cs = tag.slice(5);
|
|
28
|
+
} else if (tag === "tp:A:S") {
|
|
29
|
+
secondary = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
name: columns[0],
|
|
34
|
+
queryLength: +columns[1],
|
|
35
|
+
queryStart: +columns[2],
|
|
36
|
+
queryEnd: +columns[3],
|
|
37
|
+
strand: columns[4] === "-" ? "-" : "+",
|
|
38
|
+
path,
|
|
39
|
+
pathLength: +columns[6],
|
|
40
|
+
pathStart: +columns[7],
|
|
41
|
+
pathEnd: +columns[8],
|
|
42
|
+
matches: +columns[9],
|
|
43
|
+
blockLength: +columns[10],
|
|
44
|
+
mappingQuality: +columns[11],
|
|
45
|
+
secondary,
|
|
46
|
+
...cs === void 0 ? {} : { cs }
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function parseGaf(text) {
|
|
50
|
+
const records = [];
|
|
51
|
+
for (const line of text.split("\n")) {
|
|
52
|
+
const record = parseGafLine(line.trimEnd());
|
|
53
|
+
if (record) {
|
|
54
|
+
records.push(record);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return records;
|
|
58
|
+
}
|
|
59
|
+
var CS_OP = /:(\d+)|=([A-Za-z]+)|\*[a-zA-Z]([a-zA-Z])|\+([A-Za-z]+)|-([A-Za-z]+)|~[a-z]{2}(\d+)[a-z]{2}/g;
|
|
60
|
+
function parseCs(cs) {
|
|
61
|
+
const ops = [];
|
|
62
|
+
for (const m of cs.matchAll(CS_OP)) {
|
|
63
|
+
if (m[1] !== void 0) {
|
|
64
|
+
ops.push({ op: "match", length: +m[1] });
|
|
65
|
+
} else if (m[2] !== void 0) {
|
|
66
|
+
ops.push({ op: "match", length: m[2].length });
|
|
67
|
+
} else if (m[3] !== void 0) {
|
|
68
|
+
ops.push({ op: "substitution", seq: m[3].toUpperCase() });
|
|
69
|
+
} else if (m[4] !== void 0) {
|
|
70
|
+
ops.push({ op: "insertion", seq: m[4].toUpperCase() });
|
|
71
|
+
} else if (m[5] !== void 0) {
|
|
72
|
+
ops.push({ op: "deletion", length: m[5].length });
|
|
73
|
+
} else if (m[6] !== void 0) {
|
|
74
|
+
ops.push({ op: "match", length: +m[6] });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return ops;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
parseGafPath,
|
|
82
|
+
parseGafLine,
|
|
83
|
+
parseGaf,
|
|
84
|
+
parseCs
|
|
85
|
+
};
|