@jbrowse/bandage-core 6.0.1 → 6.0.3
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-2CVHXFK7.js +530 -0
- package/dist/chunks/chunk-3S3YVOJ7.js +69 -0
- package/dist/chunks/chunk-47DYCZOK.js +115 -0
- package/dist/chunks/chunk-4EMPBO7S.js +117 -0
- package/dist/chunks/chunk-4S47XRYA.js +149 -0
- package/dist/chunks/chunk-5XQDMQXU.js +431 -0
- package/dist/chunks/chunk-6JEWNXCQ.js +90 -0
- package/dist/chunks/chunk-6SBUFFTB.js +97 -0
- package/dist/chunks/chunk-6SXMCK6I.js +476 -0
- package/dist/chunks/chunk-72N5DJHZ.js +155 -0
- package/dist/chunks/chunk-73RSLGZT.js +73 -0
- package/dist/chunks/chunk-7XHHVYG7.js +13 -0
- package/dist/chunks/chunk-AB35TJST.js +67 -0
- package/dist/chunks/chunk-BPLIXMH7.js +52 -0
- package/dist/chunks/chunk-BUBZ32IC.js +101 -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-CJ2EE4AF.js +40 -0
- package/dist/chunks/chunk-CPMHA4W5.js +6 -0
- package/dist/chunks/chunk-CR7EGAFE.js +241 -0
- package/dist/chunks/chunk-CS3E4ETM.js +202 -0
- package/dist/chunks/chunk-CTSBC2O6.js +90 -0
- package/dist/chunks/chunk-D3M7SBKL.js +210 -0
- package/dist/chunks/chunk-DKOB2OPC.js +177 -0
- package/dist/chunks/chunk-EJ5OZTCK.js +52 -0
- package/dist/chunks/chunk-EK4HGYO7.js +174 -0
- package/dist/chunks/chunk-EPZORFTI.js +23 -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-GGQRADFS.js +170 -0
- package/dist/chunks/chunk-GOY6REMT.js +43 -0
- package/dist/chunks/chunk-GQ2AOHU4.js +112 -0
- package/dist/chunks/chunk-HNQPEMYE.js +297 -0
- package/dist/chunks/chunk-IGVV37VI.js +60 -0
- package/dist/chunks/chunk-IO2PCPZI.js +218 -0
- package/dist/chunks/chunk-ISEI2FH3.js +108 -0
- package/dist/chunks/chunk-JKJWSY4V.js +32 -0
- package/dist/chunks/chunk-KQFOCDFY.js +73 -0
- package/dist/chunks/chunk-L3O7AWLI.js +52 -0
- package/dist/chunks/chunk-LLJUG2H7.js +6 -0
- package/dist/chunks/chunk-LOEA5IHZ.js +71 -0
- package/dist/chunks/chunk-LTUWJKHD.js +112 -0
- package/dist/chunks/chunk-MFKORG3F.js +44 -0
- package/dist/chunks/chunk-MI35EUIE.js +76 -0
- package/dist/chunks/chunk-MNUSQCLG.js +29 -0
- package/dist/chunks/chunk-MQJUG2D2.js +53 -0
- package/dist/chunks/chunk-MREQ7GIA.js +181 -0
- package/dist/chunks/chunk-N62KBLFC.js +326 -0
- package/dist/chunks/chunk-NNODIW4U.js +26 -0
- package/dist/chunks/chunk-PBENCR5L.js +60 -0
- package/dist/chunks/chunk-PCZ4PTYC.js +726 -0
- package/dist/chunks/chunk-PUG7LXDF.js +70 -0
- package/dist/chunks/chunk-QYBZZI5H.js +0 -0
- package/dist/chunks/chunk-R6ZUK4Y3.js +232 -0
- package/dist/chunks/chunk-RAKOB6IV.js +169 -0
- package/dist/chunks/chunk-ROF2R3RF.js +205 -0
- package/dist/chunks/chunk-RVS2QCOX.js +135 -0
- package/dist/chunks/chunk-ST2IBUAN.js +119 -0
- package/dist/chunks/chunk-SZNZ2T6D.js +198 -0
- package/dist/chunks/chunk-T43W7NKK.js +122 -0
- package/dist/chunks/chunk-TAMCZHLW.js +190 -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-VZQ4FKLR.js +38 -0
- package/dist/chunks/chunk-WFX3MAKA.js +102 -0
- package/dist/chunks/chunk-WOO7QYKB.js +121 -0
- package/dist/chunks/chunk-WSIFAZU7.js +132 -0
- package/dist/chunks/chunk-X5L37W4J.js +82 -0
- package/dist/chunks/chunk-Y3IOTZJA.js +61 -0
- package/dist/chunks/chunk-YMSSF2RK.js +257 -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-EXSFRX2F.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,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,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
polylineSlice
|
|
3
|
+
} from "./chunk-TAMCZHLW.js";
|
|
4
|
+
import {
|
|
5
|
+
svgPath
|
|
6
|
+
} from "./chunk-2CVHXFK7.js";
|
|
7
|
+
import {
|
|
8
|
+
firstNodeAtOrAfter,
|
|
9
|
+
isAnchored
|
|
10
|
+
} from "./chunk-FYQWPEEY.js";
|
|
11
|
+
|
|
12
|
+
// src/genes/genePins.ts
|
|
13
|
+
function genePins(graph, genes, positions) {
|
|
14
|
+
const byRefName = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const node of graph.nodes) {
|
|
16
|
+
if (isAnchored(node)) {
|
|
17
|
+
const { refName } = node.stable;
|
|
18
|
+
if (positions[node.id]?.length) {
|
|
19
|
+
const entry = byRefName.get(refName) ?? byRefName.set(refName, { nodes: [], reach: 0 }).get(refName);
|
|
20
|
+
entry.nodes.push(node);
|
|
21
|
+
entry.reach = Math.max(entry.reach, node.length);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const { nodes } of byRefName.values()) {
|
|
26
|
+
nodes.sort((a, b) => a.stable.start - b.stable.start);
|
|
27
|
+
}
|
|
28
|
+
const pins = [];
|
|
29
|
+
for (const gene of genes) {
|
|
30
|
+
const onRefName = byRefName.get(gene.refName);
|
|
31
|
+
if (!onRefName) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const parts = [];
|
|
35
|
+
const exonsByNode = [];
|
|
36
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
37
|
+
let at;
|
|
38
|
+
let atDistance = Infinity;
|
|
39
|
+
let covered = 0;
|
|
40
|
+
const mid = (gene.start + gene.end) / 2;
|
|
41
|
+
const { nodes, reach } = onRefName;
|
|
42
|
+
for (let i = firstNodeAtOrAfter(nodes, gene.start - reach); i < nodes.length && nodes[i].stable.start < gene.end; i++) {
|
|
43
|
+
const node = nodes[i];
|
|
44
|
+
const nodeStart = node.stable.start;
|
|
45
|
+
const nodeEnd = nodeStart + node.length;
|
|
46
|
+
if (nodeEnd <= gene.start) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const line = positions[node.id];
|
|
50
|
+
nodeIds.add(node.id);
|
|
51
|
+
covered += Math.min(nodeEnd, gene.end) - Math.max(nodeStart, gene.start);
|
|
52
|
+
const nodeParts = [];
|
|
53
|
+
for (const exon of gene.exons) {
|
|
54
|
+
const a = Math.max(exon.start, nodeStart);
|
|
55
|
+
const b = Math.min(exon.end, nodeEnd);
|
|
56
|
+
if (b <= a) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const stretch = polylineSlice(
|
|
60
|
+
line,
|
|
61
|
+
(a - nodeStart) / node.length,
|
|
62
|
+
(b - nodeStart) / node.length
|
|
63
|
+
);
|
|
64
|
+
if (stretch.length === 1) {
|
|
65
|
+
stretch.push({ ...stretch[0] });
|
|
66
|
+
}
|
|
67
|
+
nodeParts.push(svgPath(stretch));
|
|
68
|
+
}
|
|
69
|
+
if (nodeParts.length > 0) {
|
|
70
|
+
parts.push(...nodeParts);
|
|
71
|
+
exonsByNode.push({ nodeId: node.id, d: nodeParts.join("") });
|
|
72
|
+
}
|
|
73
|
+
const pinBp = Math.min(Math.max(mid, nodeStart), nodeEnd);
|
|
74
|
+
const distance = Math.abs(pinBp - mid);
|
|
75
|
+
if (distance < atDistance) {
|
|
76
|
+
atDistance = distance;
|
|
77
|
+
const [p] = polylineSlice(
|
|
78
|
+
line,
|
|
79
|
+
(pinBp - nodeStart) / node.length,
|
|
80
|
+
(pinBp - nodeStart) / node.length
|
|
81
|
+
);
|
|
82
|
+
at = p;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (at) {
|
|
86
|
+
pins.push({
|
|
87
|
+
gene,
|
|
88
|
+
exons: parts.join(""),
|
|
89
|
+
exonsByNode,
|
|
90
|
+
nodeIds,
|
|
91
|
+
at,
|
|
92
|
+
covered: covered / (gene.end - gene.start)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return pins;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
genePins
|
|
101
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// src/gfa-core/gfaParser.ts
|
|
2
|
+
function parseTag(tag, tags) {
|
|
3
|
+
const nameEnd = tag.indexOf(":");
|
|
4
|
+
const typeEnd = tag.indexOf(":", nameEnd + 1);
|
|
5
|
+
if (nameEnd !== -1 && typeEnd !== -1) {
|
|
6
|
+
const name = tag.slice(0, nameEnd);
|
|
7
|
+
const type = tag.slice(nameEnd + 1, typeEnd);
|
|
8
|
+
const val = tag.slice(typeEnd + 1);
|
|
9
|
+
if (type === "i" || type === "f") {
|
|
10
|
+
tags[name] = +val;
|
|
11
|
+
} else if (type === "Z") {
|
|
12
|
+
tags[name] = val;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function parseTags(fields) {
|
|
17
|
+
const tags = {};
|
|
18
|
+
for (const field of fields) {
|
|
19
|
+
parseTag(field, tags);
|
|
20
|
+
}
|
|
21
|
+
return tags;
|
|
22
|
+
}
|
|
23
|
+
function stableCoordinate(node) {
|
|
24
|
+
const { SN, SO, SR } = node.tags;
|
|
25
|
+
return typeof SN === "string" && typeof SO === "number" && typeof SR === "number" ? { refName: SN, start: SO, rank: SR } : void 0;
|
|
26
|
+
}
|
|
27
|
+
var GT = 62;
|
|
28
|
+
var LT = 60;
|
|
29
|
+
function parseWalkBody(body) {
|
|
30
|
+
const segments = [];
|
|
31
|
+
let strand = "+";
|
|
32
|
+
let start = 0;
|
|
33
|
+
function push(end) {
|
|
34
|
+
if (end > start) {
|
|
35
|
+
segments.push({ id: body.slice(start, end), strand });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (let i = 0; i < body.length; i++) {
|
|
39
|
+
const ch = body.charCodeAt(i);
|
|
40
|
+
if (ch === GT || ch === LT) {
|
|
41
|
+
push(i);
|
|
42
|
+
strand = ch === GT ? "+" : "-";
|
|
43
|
+
start = i + 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
push(body.length);
|
|
47
|
+
return segments;
|
|
48
|
+
}
|
|
49
|
+
function parseGFA(file) {
|
|
50
|
+
const graph = {
|
|
51
|
+
nodes: [],
|
|
52
|
+
links: [],
|
|
53
|
+
paths: [],
|
|
54
|
+
walks: [],
|
|
55
|
+
header: [],
|
|
56
|
+
id: ""
|
|
57
|
+
};
|
|
58
|
+
for (const rawLine of file.split("\n")) {
|
|
59
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
60
|
+
if (line.startsWith("H")) {
|
|
61
|
+
const [, ...rest] = line.split(" ");
|
|
62
|
+
graph.header.push(parseTags(rest));
|
|
63
|
+
} else if (line.startsWith("S")) {
|
|
64
|
+
const [, name, ...rest] = line.split(" ");
|
|
65
|
+
let len;
|
|
66
|
+
let seq;
|
|
67
|
+
let tagfields;
|
|
68
|
+
let gfa1 = false;
|
|
69
|
+
const first = rest[0] ?? "*";
|
|
70
|
+
if (/^\d+$/.test(first)) {
|
|
71
|
+
len = +first;
|
|
72
|
+
seq = rest[1] ?? "*";
|
|
73
|
+
tagfields = rest.slice(2);
|
|
74
|
+
} else {
|
|
75
|
+
gfa1 = true;
|
|
76
|
+
seq = first;
|
|
77
|
+
len = seq === "*" ? 0 : seq.length;
|
|
78
|
+
tagfields = rest.slice(1);
|
|
79
|
+
}
|
|
80
|
+
const tags = parseTags(tagfields);
|
|
81
|
+
if (gfa1 && "LN" in tags) {
|
|
82
|
+
len = +tags.LN;
|
|
83
|
+
}
|
|
84
|
+
graph.nodes.push({ id: name, length: len, sequence: seq, tags });
|
|
85
|
+
} else if (line.startsWith("E")) {
|
|
86
|
+
const [, , source, target, , , , , cigar, ...rest] = line.split(" ");
|
|
87
|
+
if (source && target) {
|
|
88
|
+
graph.links.push({
|
|
89
|
+
source: source.slice(0, -1),
|
|
90
|
+
target: target.slice(0, -1),
|
|
91
|
+
strand1: source.at(-1),
|
|
92
|
+
strand2: target.at(-1),
|
|
93
|
+
cigar: cigar ?? "*",
|
|
94
|
+
tags: parseTags(rest)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
} else if (line.startsWith("L")) {
|
|
98
|
+
const [, source, strand1, target, strand2, cigar, ...rest] = line.split(" ");
|
|
99
|
+
if (source && target && strand2) {
|
|
100
|
+
graph.links.push({
|
|
101
|
+
source,
|
|
102
|
+
target,
|
|
103
|
+
strand1,
|
|
104
|
+
strand2,
|
|
105
|
+
cigar: cigar ?? "*",
|
|
106
|
+
tags: parseTags(rest)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
} else if (line.startsWith("W")) {
|
|
110
|
+
const [, sample, hap, contig, start, end, body, ...rest] = line.split(" ");
|
|
111
|
+
if (sample && hap && contig && start && end && body) {
|
|
112
|
+
graph.walks.push({
|
|
113
|
+
sample,
|
|
114
|
+
haplotype: +hap,
|
|
115
|
+
contig,
|
|
116
|
+
start: start === "*" ? -1 : +start,
|
|
117
|
+
end: end === "*" ? -1 : +end,
|
|
118
|
+
segments: parseWalkBody(body),
|
|
119
|
+
tags: parseTags(rest)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
} else if (line.startsWith("P")) {
|
|
123
|
+
const [, name, path, ...rest] = line.split(" ");
|
|
124
|
+
if (name && path) {
|
|
125
|
+
graph.paths.push({ name, path, rest });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return graph;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
stableCoordinate,
|
|
134
|
+
parseGFA
|
|
135
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REFERENCE_RAMP_MAX_HUE
|
|
3
|
+
} from "./chunk-PCZ4PTYC.js";
|
|
4
|
+
|
|
5
|
+
// src/referenceRampCss.ts
|
|
6
|
+
function rampHueCss(hue) {
|
|
7
|
+
return `hsl(${hue}, 70%, 50%)`;
|
|
8
|
+
}
|
|
9
|
+
function rampHue(bp, domain) {
|
|
10
|
+
const frac = (bp - domain.start) / (domain.end - domain.start);
|
|
11
|
+
return Math.max(0, Math.min(1, frac)) * REFERENCE_RAMP_MAX_HUE;
|
|
12
|
+
}
|
|
13
|
+
var HUE_STOP_DEG = 30;
|
|
14
|
+
function rampStops({ start, bp, reversed }, domain) {
|
|
15
|
+
const low = rampHue(start, domain);
|
|
16
|
+
const high = rampHue(start + bp, domain);
|
|
17
|
+
if (high - low < 1) {
|
|
18
|
+
return [(low + high) / 2];
|
|
19
|
+
}
|
|
20
|
+
const n = Math.ceil((high - low) / HUE_STOP_DEG);
|
|
21
|
+
return Array.from(
|
|
22
|
+
{ length: n + 1 },
|
|
23
|
+
(_, i) => rampHue(start + bp * (reversed ? n - i : i) / n, domain)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
var RAMP_GRADIENT_STOPS = Array.from({ length: 7 }, (_, i) => ({
|
|
27
|
+
offset: i / 6,
|
|
28
|
+
color: rampHueCss(i / 6 * REFERENCE_RAMP_MAX_HUE)
|
|
29
|
+
}));
|
|
30
|
+
var RAMP_GRADIENT_CSS = `linear-gradient(to right, ${RAMP_GRADIENT_STOPS.map(
|
|
31
|
+
(s) => `${s.color} ${s.offset * 100}%`
|
|
32
|
+
).join(", ")})`;
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
rampHueCss,
|
|
36
|
+
rampHue,
|
|
37
|
+
rampStops,
|
|
38
|
+
RAMP_GRADIENT_STOPS,
|
|
39
|
+
RAMP_GRADIENT_CSS
|
|
40
|
+
};
|