@jbrowse/bandage-core 6.0.1 → 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-EXSFRX2F.js +0 -7400
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// src/renderer/svgCanvas.ts
|
|
2
|
+
var n = (v) => +v.toFixed(1);
|
|
3
|
+
function paint(css) {
|
|
4
|
+
const m = /^rgba?\(([^,]+),([^,]+),([^,)]+)(?:,([^)]+))?\)$/.exec(
|
|
5
|
+
css.replaceAll(" ", "")
|
|
6
|
+
);
|
|
7
|
+
if (!m) {
|
|
8
|
+
return { color: css, opacity: 1 };
|
|
9
|
+
}
|
|
10
|
+
const opacity = m[4] === void 0 ? 1 : Number(m[4]);
|
|
11
|
+
return { color: `rgb(${m[1]},${m[2]},${m[3]})`, opacity };
|
|
12
|
+
}
|
|
13
|
+
function paintAttrs(kind, css) {
|
|
14
|
+
const { color, opacity } = paint(css);
|
|
15
|
+
return `${kind}="${color}"${opacity < 1 ? ` ${kind}-opacity="${n(opacity)}"` : ""}`;
|
|
16
|
+
}
|
|
17
|
+
var DOT_PX = 0.5;
|
|
18
|
+
var DOT_CELL_PX = 0.25;
|
|
19
|
+
function svgCanvas(width, height) {
|
|
20
|
+
const out = [];
|
|
21
|
+
let subpaths = [];
|
|
22
|
+
const start = (x, y) => {
|
|
23
|
+
const s = { d: `M${n(x)} ${n(y)}`, segments: 0, x0: x, x1: x, y0: y, y1: y };
|
|
24
|
+
subpaths.push(s);
|
|
25
|
+
return s;
|
|
26
|
+
};
|
|
27
|
+
const reach = (points) => {
|
|
28
|
+
const s = subpaths.at(-1) ?? start(points[0], points[1]);
|
|
29
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
30
|
+
s.x0 = Math.min(s.x0, points[i]);
|
|
31
|
+
s.x1 = Math.max(s.x1, points[i]);
|
|
32
|
+
s.y0 = Math.min(s.y0, points[i + 1]);
|
|
33
|
+
s.y1 = Math.max(s.y1, points[i + 1]);
|
|
34
|
+
}
|
|
35
|
+
s.segments++;
|
|
36
|
+
return s;
|
|
37
|
+
};
|
|
38
|
+
const pathData = (dots) => {
|
|
39
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40
|
+
return subpaths.map((s) => {
|
|
41
|
+
if (!dots || s.x1 - s.x0 >= DOT_PX || s.y1 - s.y0 >= DOT_PX) {
|
|
42
|
+
return s.d;
|
|
43
|
+
}
|
|
44
|
+
const x = (s.x0 + s.x1) / 2;
|
|
45
|
+
const y = (s.y0 + s.y1) / 2;
|
|
46
|
+
const cell = `${Math.round(x / DOT_CELL_PX)} ${Math.round(y / DOT_CELL_PX)}`;
|
|
47
|
+
if (s.segments === 0 || seen.has(cell)) {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
seen.add(cell);
|
|
51
|
+
return `M${n(x)} ${n(y)}h0`;
|
|
52
|
+
}).join("");
|
|
53
|
+
};
|
|
54
|
+
const ctx = {
|
|
55
|
+
canvas: void 0,
|
|
56
|
+
fillStyle: "#000",
|
|
57
|
+
strokeStyle: "#000",
|
|
58
|
+
lineWidth: 1,
|
|
59
|
+
lineCap: "butt",
|
|
60
|
+
lineJoin: "miter",
|
|
61
|
+
beginPath() {
|
|
62
|
+
subpaths = [];
|
|
63
|
+
},
|
|
64
|
+
moveTo(x, y) {
|
|
65
|
+
start(x, y);
|
|
66
|
+
},
|
|
67
|
+
lineTo(x, y) {
|
|
68
|
+
reach([x, y]).d += `L${n(x)} ${n(y)}`;
|
|
69
|
+
},
|
|
70
|
+
bezierCurveTo(cx0, cy0, cx1, cy1, x, y) {
|
|
71
|
+
reach([cx0, cy0, cx1, cy1, x, y]).d += `C${n(cx0)} ${n(cy0)} ${n(cx1)} ${n(cy1)} ${n(x)} ${n(y)}`;
|
|
72
|
+
},
|
|
73
|
+
closePath() {
|
|
74
|
+
const s = subpaths.at(-1);
|
|
75
|
+
if (s) {
|
|
76
|
+
s.d += "Z";
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
stroke() {
|
|
80
|
+
const d = pathData(ctx.lineCap !== "butt");
|
|
81
|
+
if (d) {
|
|
82
|
+
out.push(
|
|
83
|
+
`<path d="${d}" fill="none" ${paintAttrs("stroke", ctx.strokeStyle)} stroke-width="${n(ctx.lineWidth)}" stroke-linecap="${ctx.lineCap}" stroke-linejoin="${ctx.lineJoin}"/>`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
fill() {
|
|
88
|
+
const d = pathData(false);
|
|
89
|
+
if (d) {
|
|
90
|
+
out.push(`<path d="${d}" ${paintAttrs("fill", ctx.fillStyle)}/>`);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
fillRect(x, y, w, h) {
|
|
94
|
+
out.push(
|
|
95
|
+
`<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" ${paintAttrs("fill", ctx.fillStyle)}/>`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const canvas = {
|
|
100
|
+
width,
|
|
101
|
+
height,
|
|
102
|
+
style: {},
|
|
103
|
+
getContext: () => ctx
|
|
104
|
+
};
|
|
105
|
+
ctx.canvas = canvas;
|
|
106
|
+
return {
|
|
107
|
+
canvas,
|
|
108
|
+
// the drawing so far, as SVG elements in canvas pixels
|
|
109
|
+
markup: () => out.join("")
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
svgCanvas
|
|
115
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SATURATED_PATH_COUNT
|
|
3
|
+
} from "./chunk-MFKORG3F.js";
|
|
4
|
+
import {
|
|
5
|
+
referenceOrder
|
|
6
|
+
} from "./chunk-HNQPEMYE.js";
|
|
7
|
+
import {
|
|
8
|
+
isBackbone
|
|
9
|
+
} from "./chunk-FYQWPEEY.js";
|
|
10
|
+
|
|
11
|
+
// src/bubbles/bubblesFromGraph.ts
|
|
12
|
+
function bubblesFromGraph(graph) {
|
|
13
|
+
if (!graph.nodes.some(isBackbone)) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
17
|
+
const order = referenceOrder(graph);
|
|
18
|
+
const indexOf = new Map(order.map((id, i) => [id, i]));
|
|
19
|
+
const reach = new Int32Array(order.length + 1);
|
|
20
|
+
const succ = /* @__PURE__ */ new Map();
|
|
21
|
+
const linked = /* @__PURE__ */ new Set();
|
|
22
|
+
for (const e of graph.edges) {
|
|
23
|
+
const a = indexOf.get(e.from);
|
|
24
|
+
const b = indexOf.get(e.to);
|
|
25
|
+
if (a === void 0 || b === void 0 || a === b) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
linked.add(`${e.from}>${e.to}`).add(`${e.to}>${e.from}`);
|
|
29
|
+
const [lo, hi] = a < b ? [a, b] : [b, a];
|
|
30
|
+
reach[lo + 1]++;
|
|
31
|
+
reach[hi]--;
|
|
32
|
+
const [from, to] = a < b ? [e.from, e.to] : [e.to, e.from];
|
|
33
|
+
(succ.get(from) ?? succ.set(from, /* @__PURE__ */ new Set()).get(from)).add(to);
|
|
34
|
+
}
|
|
35
|
+
for (let i = 1; i < reach.length; i++) {
|
|
36
|
+
reach[i] += reach[i - 1];
|
|
37
|
+
}
|
|
38
|
+
const backbone = graph.nodes.filter(isBackbone).sort((a, b) => a.stable.start - b.stable.start);
|
|
39
|
+
const ends = /* @__PURE__ */ new Set([backbone[0].id, backbone.at(-1).id]);
|
|
40
|
+
const boundaries = backbone.map((node) => [node, indexOf.get(node.id)]).filter(([node, i]) => ends.has(node.id) || reach[i] === 0).sort(([a, ia], [b, ib]) => ia - ib || a.stable.start - b.stable.start);
|
|
41
|
+
const walkIndex = graph.paths?.map((p) => {
|
|
42
|
+
const at = /* @__PURE__ */ new Map();
|
|
43
|
+
p.nodeIds.forEach((id, i) => {
|
|
44
|
+
if (!at.has(id)) {
|
|
45
|
+
at.set(id, i);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return at;
|
|
49
|
+
});
|
|
50
|
+
const bubbles = [];
|
|
51
|
+
for (let i = 0; i + 1 < boundaries.length; i++) {
|
|
52
|
+
const [start, i0] = boundaries[i];
|
|
53
|
+
const [end, i1] = boundaries[i + 1];
|
|
54
|
+
const interior = order.slice(i0 + 1, i1);
|
|
55
|
+
if (interior.length === 0) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const best = /* @__PURE__ */ new Map([[start.id, { min: 0, max: 0, n: 1 }]]);
|
|
59
|
+
for (const id of [start.id, ...interior]) {
|
|
60
|
+
const cur = best.get(id);
|
|
61
|
+
if (!cur) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
for (const t of succ.get(id) ?? []) {
|
|
65
|
+
const ti = indexOf.get(t);
|
|
66
|
+
if (ti > i1) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const add = t === end.id ? 0 : byId.get(t).length;
|
|
70
|
+
const prev = best.get(t);
|
|
71
|
+
best.set(t, {
|
|
72
|
+
min: Math.min(prev?.min ?? Infinity, cur.min + add),
|
|
73
|
+
max: Math.max(prev?.max ?? -Infinity, cur.max + add),
|
|
74
|
+
n: Math.min(SATURATED_PATH_COUNT, cur.n + (prev?.n ?? 0))
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const walked = walkIndex && walkRoutes(graph, byId, walkIndex, start.id, end.id);
|
|
79
|
+
const crossed = walked?.n ? walked : void 0;
|
|
80
|
+
const routes = crossed ?? best.get(end.id) ?? { min: 0, max: 0, n: 0 };
|
|
81
|
+
const walksLeave = walked !== void 0 && walked.left > 0;
|
|
82
|
+
const refStart = start.stable.start + start.length;
|
|
83
|
+
const refEnd = end.stable.start;
|
|
84
|
+
const chain = [
|
|
85
|
+
start.id,
|
|
86
|
+
...interior.filter((id) => isBackbone(byId.get(id))),
|
|
87
|
+
end.id
|
|
88
|
+
];
|
|
89
|
+
const chainBroken = chain.some(
|
|
90
|
+
(id, k) => k > 0 && !linked.has(`${chain[k - 1]}>${id}`)
|
|
91
|
+
);
|
|
92
|
+
const partial = chainBroken || walksLeave;
|
|
93
|
+
const fallback = chainBroken && !crossed;
|
|
94
|
+
bubbles.push({
|
|
95
|
+
refName: start.stable.refName,
|
|
96
|
+
start: refStart,
|
|
97
|
+
end: refEnd,
|
|
98
|
+
segmentCount: interior.length + 2,
|
|
99
|
+
pathCount: routes.n,
|
|
100
|
+
// a real inversion test needs each walk's direction relative to the
|
|
101
|
+
// reference; the first visit's strand is just whichever path anchored the
|
|
102
|
+
// node
|
|
103
|
+
inversion: false,
|
|
104
|
+
shortestAlleleLength: fallback ? refEnd - refStart : routes.min,
|
|
105
|
+
longestAlleleLength: fallback ? refEnd - refStart : routes.max,
|
|
106
|
+
segments: [start, ...interior.map((id) => byId.get(id)), end].map((n) => n.name).join(","),
|
|
107
|
+
shortestAllele: void 0,
|
|
108
|
+
longestAllele: void 0,
|
|
109
|
+
partial,
|
|
110
|
+
routes: crossed?.routes
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return bubbles;
|
|
114
|
+
}
|
|
115
|
+
function walkRoutes(graph, byId, walkIndex, startId, endId) {
|
|
116
|
+
const seen = /* @__PURE__ */ new Map();
|
|
117
|
+
let min = Infinity;
|
|
118
|
+
let max = -Infinity;
|
|
119
|
+
let left = 0;
|
|
120
|
+
graph.paths.forEach((p, k) => {
|
|
121
|
+
const i0 = walkIndex[k].get(startId);
|
|
122
|
+
const i1 = walkIndex[k].get(endId);
|
|
123
|
+
if (i0 === void 0 || i1 === void 0) {
|
|
124
|
+
if (i0 !== void 0 || i1 !== void 0) {
|
|
125
|
+
left++;
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const steps = p.nodeIds.slice(Math.min(i0, i1) + 1, Math.max(i0, i1));
|
|
130
|
+
if (i1 < i0) {
|
|
131
|
+
steps.reverse();
|
|
132
|
+
}
|
|
133
|
+
let bp = 0;
|
|
134
|
+
for (const id of steps) {
|
|
135
|
+
bp += byId.get(id)?.length ?? 0;
|
|
136
|
+
}
|
|
137
|
+
const key = steps.join(",");
|
|
138
|
+
const route = seen.get(key) ?? { steps, bp, walks: [] };
|
|
139
|
+
route.walks.push(p.name);
|
|
140
|
+
seen.set(key, route);
|
|
141
|
+
min = Math.min(min, bp);
|
|
142
|
+
max = Math.max(max, bp);
|
|
143
|
+
});
|
|
144
|
+
return seen.size || left ? { min, max, n: seen.size, left, routes: [...seen.values()] } : void 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export {
|
|
148
|
+
bubblesFromGraph
|
|
149
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasTubeMapPaths,
|
|
3
|
+
tubeMapLayout,
|
|
4
|
+
tubeMapReferenceLayout
|
|
5
|
+
} from "./chunk-P7B3GDAY.js";
|
|
6
|
+
import {
|
|
7
|
+
sampleRowLayout
|
|
8
|
+
} from "./chunk-LOEA5IHZ.js";
|
|
9
|
+
import {
|
|
10
|
+
anchoredLayout
|
|
11
|
+
} from "./chunk-L3O7AWLI.js";
|
|
12
|
+
import {
|
|
13
|
+
orderedLayout
|
|
14
|
+
} from "./chunk-HNQPEMYE.js";
|
|
15
|
+
import {
|
|
16
|
+
walkRowLayout
|
|
17
|
+
} from "./chunk-EZPBJPK6.js";
|
|
18
|
+
import {
|
|
19
|
+
isBackbone,
|
|
20
|
+
isOffReference
|
|
21
|
+
} from "./chunk-FYQWPEEY.js";
|
|
22
|
+
|
|
23
|
+
// src/layoutModes.ts
|
|
24
|
+
var FORCE_LAYOUT_LABEL = "Force-directed layout";
|
|
25
|
+
var hasBackbone = (graph) => graph.nodes.some(isBackbone);
|
|
26
|
+
var hasWalks = (graph) => hasBackbone(graph) && (graph.paths?.length ?? 0) > 1;
|
|
27
|
+
var hasTubeMapBackbone = (graph) => hasBackbone(graph) && hasTubeMapPaths(graph);
|
|
28
|
+
var hasAlleles = (graph) => hasBackbone(graph) && graph.nodes.some(isOffReference);
|
|
29
|
+
var LAYOUT_MODES = [
|
|
30
|
+
{
|
|
31
|
+
value: "auto",
|
|
32
|
+
label: "Anchored",
|
|
33
|
+
description: "x is reference bp, one row per stable rank. Needs rGFA tags or a reference path.",
|
|
34
|
+
run: anchoredLayout,
|
|
35
|
+
cutMargins: true,
|
|
36
|
+
wholeWalks: false,
|
|
37
|
+
available: hasBackbone,
|
|
38
|
+
drawsLocally: hasBackbone,
|
|
39
|
+
drawsNodes: true
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
value: "samplerows",
|
|
43
|
+
label: "Sample rows",
|
|
44
|
+
description: "x is reference bp, one row per contributing assembly. Needs rGFA tags or a reference path.",
|
|
45
|
+
run: sampleRowLayout,
|
|
46
|
+
cutMargins: true,
|
|
47
|
+
wholeWalks: false,
|
|
48
|
+
available: hasAlleles,
|
|
49
|
+
drawsLocally: hasAlleles,
|
|
50
|
+
drawsNodes: true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
value: "walkrows",
|
|
54
|
+
label: "Walk rows",
|
|
55
|
+
description: "x is each walk's own bp: one bar per haplotype, blue on the reference's path and purple off it, so a repeat expansion reads as bar length, and each bar boxes its haplotype's own genes where the session has them. Needs W or P lines.",
|
|
56
|
+
run: walkRowLayout,
|
|
57
|
+
cutMargins: false,
|
|
58
|
+
wholeWalks: true,
|
|
59
|
+
available: hasWalks,
|
|
60
|
+
drawsLocally: hasWalks,
|
|
61
|
+
drawsNodes: false
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: "ordered",
|
|
65
|
+
label: "Ordered",
|
|
66
|
+
description: "x is reference order, not bp: every node gets room, bubbles read as lenses. Needs rGFA tags or a reference path.",
|
|
67
|
+
run: orderedLayout,
|
|
68
|
+
cutMargins: false,
|
|
69
|
+
wholeWalks: false,
|
|
70
|
+
available: hasBackbone,
|
|
71
|
+
drawsLocally: hasBackbone,
|
|
72
|
+
drawsNodes: true
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
value: "tubemap",
|
|
76
|
+
label: "Tube map",
|
|
77
|
+
description: "sequenceTubeMap: every path a coloured tube through boxed nodes, columns in node order and node widths log-scaled. Needs P or W lines.",
|
|
78
|
+
run: tubeMapLayout,
|
|
79
|
+
cutMargins: false,
|
|
80
|
+
wholeWalks: false,
|
|
81
|
+
available: hasTubeMapPaths,
|
|
82
|
+
drawsLocally: hasTubeMapPaths,
|
|
83
|
+
drawsNodes: false
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
value: "tubemapref",
|
|
87
|
+
label: "Tube map on reference",
|
|
88
|
+
description: "The tube map with each column at the reference bp it covers, so the tubes line up with the tracks around them. Needs P or W lines and a reference path.",
|
|
89
|
+
run: tubeMapReferenceLayout,
|
|
90
|
+
cutMargins: true,
|
|
91
|
+
wholeWalks: false,
|
|
92
|
+
available: hasTubeMapBackbone,
|
|
93
|
+
drawsLocally: hasTubeMapBackbone,
|
|
94
|
+
drawsNodes: false
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
value: "force",
|
|
98
|
+
label: FORCE_LAYOUT_LABEL,
|
|
99
|
+
description: "OGDF FMMM, via the external Bandage engine.",
|
|
100
|
+
run: () => void 0,
|
|
101
|
+
cutMargins: false,
|
|
102
|
+
wholeWalks: false,
|
|
103
|
+
available: () => true,
|
|
104
|
+
drawsLocally: () => false,
|
|
105
|
+
drawsNodes: true
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
var LAYOUT_MODE_VALUES = LAYOUT_MODES.map((m) => m.value);
|
|
109
|
+
function modeUsesLayoutEngine(value, graph) {
|
|
110
|
+
return !layoutModeByValue(value).drawsLocally(graph);
|
|
111
|
+
}
|
|
112
|
+
function layoutModeByValue(value) {
|
|
113
|
+
return LAYOUT_MODES.find((m) => m.value === value) ?? LAYOUT_MODES[0];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
FORCE_LAYOUT_LABEL,
|
|
118
|
+
LAYOUT_MODES,
|
|
119
|
+
LAYOUT_MODE_VALUES,
|
|
120
|
+
modeUsesLayoutEngine,
|
|
121
|
+
layoutModeByValue
|
|
122
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
tubeSpan
|
|
3
|
+
} from "./chunk-Q3KCTA4N.js";
|
|
4
|
+
|
|
5
|
+
// src/tubeMap/genes.ts
|
|
6
|
+
function tubeMapGenes(byRefName, genes) {
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const gene of genes) {
|
|
9
|
+
const boxes = byRefName.get(gene.refName);
|
|
10
|
+
const first = boxes?.[0];
|
|
11
|
+
const last = boxes?.at(-1);
|
|
12
|
+
if (!boxes || !first || !last) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const start = Math.max(gene.start, first.bp0);
|
|
16
|
+
const end = Math.min(gene.end, last.bp1);
|
|
17
|
+
if (end <= start) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
out.push({
|
|
21
|
+
gene,
|
|
22
|
+
...tubeSpan(boxes, start, end),
|
|
23
|
+
exons: gene.exons.filter((e) => e.end > start && e.start < end).map(
|
|
24
|
+
(e) => tubeSpan(boxes, Math.max(e.start, start), Math.min(e.end, end))
|
|
25
|
+
)
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
var GENE_ROW_PX = 22;
|
|
31
|
+
var MAX_GENE_ROWS = 4;
|
|
32
|
+
var EXON_PX = 8;
|
|
33
|
+
var NAME_FONT = "11px sans-serif";
|
|
34
|
+
var NAME_GAP_PX = 8;
|
|
35
|
+
function tubeMapGeneRows(genes) {
|
|
36
|
+
const edges = genes.flatMap((g) => [
|
|
37
|
+
{ x: g.x0, step: 1 },
|
|
38
|
+
{ x: g.x1, step: -1 }
|
|
39
|
+
]).sort((a, b) => a.x - b.x || a.step - b.step);
|
|
40
|
+
let depth = 0;
|
|
41
|
+
let most = 0;
|
|
42
|
+
for (const { step } of edges) {
|
|
43
|
+
depth += step;
|
|
44
|
+
most = Math.max(most, depth);
|
|
45
|
+
}
|
|
46
|
+
return Math.min(MAX_GENE_ROWS, most);
|
|
47
|
+
}
|
|
48
|
+
function drawTubeMapGenes(ctx, genes, frame, bottom, top = 0) {
|
|
49
|
+
const { x, width, darkMode } = frame;
|
|
50
|
+
const rows = Math.max(1, Math.floor((bottom - top) / GENE_ROW_PX));
|
|
51
|
+
const rowEnds = [];
|
|
52
|
+
const ink = darkMode ? "#e0e0e6" : "#1c1c22";
|
|
53
|
+
ctx.font = NAME_FONT;
|
|
54
|
+
ctx.textAlign = "center";
|
|
55
|
+
ctx.textBaseline = "alphabetic";
|
|
56
|
+
ctx.fillStyle = ink;
|
|
57
|
+
ctx.strokeStyle = ink;
|
|
58
|
+
ctx.lineWidth = 1;
|
|
59
|
+
const placed = genes.map((g) => ({ g, s0: x(g.x0), s1: x(g.x1) })).filter(({ s0, s1 }) => s1 >= 0 && s0 <= width).sort((a, b) => a.s0 - b.s0);
|
|
60
|
+
for (const { g, s0, s1 } of placed) {
|
|
61
|
+
const arrow = g.gene.strand > 0 ? " \u2192" : g.gene.strand < 0 ? "\u2190 " : "";
|
|
62
|
+
const label = g.gene.strand < 0 ? `${arrow}${g.gene.name}` : `${g.gene.name}${arrow}`;
|
|
63
|
+
const mid = (Math.max(s0, 0) + Math.min(s1, width)) / 2;
|
|
64
|
+
const half = ctx.measureText(label).width / 2;
|
|
65
|
+
const left = Math.min(s0, mid - half);
|
|
66
|
+
const right = Math.max(s1, mid + half);
|
|
67
|
+
let row = rowEnds.findIndex((end) => end + NAME_GAP_PX <= left);
|
|
68
|
+
if (row === -1) {
|
|
69
|
+
row = Math.min(rowEnds.length, rows - 1);
|
|
70
|
+
}
|
|
71
|
+
rowEnds[row] = Math.max(rowEnds[row] ?? -Infinity, right);
|
|
72
|
+
const glyphY = bottom - row * GENE_ROW_PX - EXON_PX / 2;
|
|
73
|
+
ctx.beginPath();
|
|
74
|
+
ctx.moveTo(s0, glyphY + 0.5);
|
|
75
|
+
ctx.lineTo(s1, glyphY + 0.5);
|
|
76
|
+
ctx.stroke();
|
|
77
|
+
for (const e of g.exons) {
|
|
78
|
+
const e0 = x(e.x0);
|
|
79
|
+
ctx.fillRect(e0, glyphY - EXON_PX / 2, Math.max(1, x(e.x1) - e0), EXON_PX);
|
|
80
|
+
}
|
|
81
|
+
ctx.fillText(label, mid, glyphY - EXON_PX / 2 - 2);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
tubeMapGenes,
|
|
87
|
+
GENE_ROW_PX,
|
|
88
|
+
tubeMapGeneRows,
|
|
89
|
+
drawTubeMapGenes
|
|
90
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REFERENCE_RAMP_MAX_HUE
|
|
3
|
+
} from "./chunk-3WHKR5TY.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
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/layout/drawnScale.ts
|
|
2
|
+
var MEAN_NODE_LENGTH = 40;
|
|
3
|
+
var MIN_TOTAL_GRAPH_LENGTH = 500;
|
|
4
|
+
var BANDAGE_MINIMUM_NODE_LENGTH = 5;
|
|
5
|
+
function drawnNodeLength(opts, length) {
|
|
6
|
+
return Math.max(
|
|
7
|
+
opts.nodeLengthPerMegabase * length / 1e6,
|
|
8
|
+
opts.minimumNodeLength
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
function bandageAutoScale(graph, minNodeLength = BANDAGE_MINIMUM_NODE_LENGTH) {
|
|
12
|
+
const totalLength = graph.nodes.reduce((sum, n) => sum + n.length, 0);
|
|
13
|
+
const megabases = totalLength / 1e6;
|
|
14
|
+
const target = Math.max(
|
|
15
|
+
graph.nodes.length * MEAN_NODE_LENGTH,
|
|
16
|
+
MIN_TOTAL_GRAPH_LENGTH
|
|
17
|
+
);
|
|
18
|
+
return {
|
|
19
|
+
nodeLengthPerMegabase: megabases > 0 ? target / megabases : 1e4,
|
|
20
|
+
minimumNodeLength: Math.max(BANDAGE_MINIMUM_NODE_LENGTH, minNodeLength),
|
|
21
|
+
edgeLength: 5,
|
|
22
|
+
nodeSegmentLength: 20
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
var PROPORTIONAL_LENGTH = { power: 1, effect: 1 };
|
|
26
|
+
function isProportional(law) {
|
|
27
|
+
return law.power === 1 && law.effect === 1;
|
|
28
|
+
}
|
|
29
|
+
function drawnLengthFor(graph, law) {
|
|
30
|
+
const count = graph.nodes.length;
|
|
31
|
+
const totalLength = graph.nodes.reduce((sum, n) => sum + n.length, 0);
|
|
32
|
+
const meanBp = count > 0 ? totalLength / count : 0;
|
|
33
|
+
const meanDrawn = count > 0 ? Math.max(count * MEAN_NODE_LENGTH, MIN_TOTAL_GRAPH_LENGTH) / count : MEAN_NODE_LENGTH;
|
|
34
|
+
return (bp) => {
|
|
35
|
+
if (meanBp <= 0) {
|
|
36
|
+
return meanDrawn;
|
|
37
|
+
}
|
|
38
|
+
const relative = (bp / meanBp) ** law.power;
|
|
39
|
+
return Math.max(
|
|
40
|
+
meanDrawn * ((relative - 1) * law.effect + 1),
|
|
41
|
+
BANDAGE_MINIMUM_NODE_LENGTH
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
var LAYOUT_LENGTH_PRECISION = 1e3;
|
|
46
|
+
var PROPORTIONAL_SPREAD = {
|
|
47
|
+
law: PROPORTIONAL_LENGTH,
|
|
48
|
+
minNodeLength: 0
|
|
49
|
+
};
|
|
50
|
+
function layoutScaling(graph, spread = PROPORTIONAL_SPREAD) {
|
|
51
|
+
const { law, minNodeLength } = spread;
|
|
52
|
+
const opts = bandageAutoScale(graph, minNodeLength);
|
|
53
|
+
if (isProportional(law)) {
|
|
54
|
+
return { nodes: graph.nodes, opts };
|
|
55
|
+
}
|
|
56
|
+
const drawn = drawnLengthFor(graph, law);
|
|
57
|
+
return {
|
|
58
|
+
nodes: graph.nodes.map((node) => ({
|
|
59
|
+
...node,
|
|
60
|
+
length: Math.round(drawn(node.length) * LAYOUT_LENGTH_PRECISION)
|
|
61
|
+
})),
|
|
62
|
+
opts: {
|
|
63
|
+
...opts,
|
|
64
|
+
nodeLengthPerMegabase: 1e6 / LAYOUT_LENGTH_PRECISION,
|
|
65
|
+
// Bandage's own floor only. A law and a raised floor are alternatives
|
|
66
|
+
// rather than a stack: the law has already lifted the small end, and a
|
|
67
|
+
// floor on top of it would flatten exactly what it just spread.
|
|
68
|
+
minimumNodeLength: BANDAGE_MINIMUM_NODE_LENGTH
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function drawnLengthOf(graph, spread = PROPORTIONAL_SPREAD) {
|
|
73
|
+
if (isProportional(spread.law)) {
|
|
74
|
+
const opts = bandageAutoScale(graph, spread.minNodeLength);
|
|
75
|
+
return (bp) => drawnNodeLength(opts, bp);
|
|
76
|
+
}
|
|
77
|
+
return drawnLengthFor(graph, spread.law);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
MEAN_NODE_LENGTH,
|
|
82
|
+
drawnNodeLength,
|
|
83
|
+
bandageAutoScale,
|
|
84
|
+
PROPORTIONAL_LENGTH,
|
|
85
|
+
isProportional,
|
|
86
|
+
drawnLengthFor,
|
|
87
|
+
PROPORTIONAL_SPREAD,
|
|
88
|
+
layoutScaling,
|
|
89
|
+
drawnLengthOf
|
|
90
|
+
};
|