@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,297 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ROW_HEIGHT_PX
|
|
3
|
+
} from "./chunk-CPMHA4W5.js";
|
|
4
|
+
import {
|
|
5
|
+
isBackbone
|
|
6
|
+
} from "./chunk-FYQWPEEY.js";
|
|
7
|
+
|
|
8
|
+
// src/layout/orderedLayout.ts
|
|
9
|
+
var GUTTER = 12;
|
|
10
|
+
var LANE_PX = ROW_HEIGHT_PX;
|
|
11
|
+
function nodeWidth(node) {
|
|
12
|
+
return 10 + 8 * Math.log2(1 + node.length);
|
|
13
|
+
}
|
|
14
|
+
function compare(a, b) {
|
|
15
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
16
|
+
}
|
|
17
|
+
function compareKeys(a, b) {
|
|
18
|
+
return compare(a.at, b.at) || compare(a.side, b.side) || compare(a.hops, b.hops);
|
|
19
|
+
}
|
|
20
|
+
var UNREACHED = { at: Infinity, side: 0, hops: 0 };
|
|
21
|
+
function referenceOrder(graph) {
|
|
22
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
23
|
+
const key = orderKeys(graph);
|
|
24
|
+
return graph.nodes.map((n) => n.id).sort(
|
|
25
|
+
(a, b) => compareKeys(key.get(a) ?? UNREACHED, key.get(b) ?? UNREACHED) || compare(byId.get(a).name, byId.get(b).name) || compare(a, b)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function placeByWalks(graph, backbone) {
|
|
29
|
+
const isNode = new Set(graph.nodes.map((n) => n.id));
|
|
30
|
+
const startOf = new Map(backbone.map((n) => [n.id, n.stable.start]));
|
|
31
|
+
const next = /* @__PURE__ */ new Map();
|
|
32
|
+
const prev = /* @__PURE__ */ new Map();
|
|
33
|
+
backbone.forEach((n, i) => {
|
|
34
|
+
prev.set(n.id, backbone[i - 1]?.id);
|
|
35
|
+
next.set(n.id, backbone[i + 1]?.id);
|
|
36
|
+
});
|
|
37
|
+
let head = backbone[0]?.id;
|
|
38
|
+
const insertAfter = (at, id) => {
|
|
39
|
+
const after = next.get(at);
|
|
40
|
+
next.set(at, id);
|
|
41
|
+
prev.set(id, at);
|
|
42
|
+
next.set(id, after);
|
|
43
|
+
if (after !== void 0) {
|
|
44
|
+
prev.set(after, id);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const insertBefore = (at, id) => {
|
|
48
|
+
const before = prev.get(at);
|
|
49
|
+
if (before === void 0) {
|
|
50
|
+
head = id;
|
|
51
|
+
prev.set(id, void 0);
|
|
52
|
+
next.set(id, at);
|
|
53
|
+
prev.set(at, id);
|
|
54
|
+
} else {
|
|
55
|
+
insertAfter(before, id);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
for (const path of graph.paths ?? []) {
|
|
59
|
+
let rising = 0;
|
|
60
|
+
let falling = 0;
|
|
61
|
+
let last;
|
|
62
|
+
for (const id of path.nodeIds) {
|
|
63
|
+
const start = startOf.get(id);
|
|
64
|
+
if (start !== void 0) {
|
|
65
|
+
if (last !== void 0 && start !== last) {
|
|
66
|
+
if (start > last) {
|
|
67
|
+
rising++;
|
|
68
|
+
} else {
|
|
69
|
+
falling++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
last = start;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const ids = falling > rising ? [...path.nodeIds].reverse() : path.nodeIds;
|
|
76
|
+
let from;
|
|
77
|
+
const leading = /* @__PURE__ */ new Set();
|
|
78
|
+
for (const id of ids) {
|
|
79
|
+
if (next.has(id)) {
|
|
80
|
+
if (from === void 0) {
|
|
81
|
+
for (const lead of leading) {
|
|
82
|
+
insertBefore(id, lead);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
from = id;
|
|
86
|
+
} else if (isNode.has(id)) {
|
|
87
|
+
if (from === void 0) {
|
|
88
|
+
leading.add(id);
|
|
89
|
+
} else {
|
|
90
|
+
insertAfter(from, id);
|
|
91
|
+
from = id;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const placed = [];
|
|
97
|
+
for (let id = head; id !== void 0; id = next.get(id)) {
|
|
98
|
+
placed.push(id);
|
|
99
|
+
}
|
|
100
|
+
return placed;
|
|
101
|
+
}
|
|
102
|
+
function placeBySearch(adjacent, key) {
|
|
103
|
+
const queue = [...key.entries()].sort(([a, ka], [b, kb]) => compareKeys(ka, kb) || compare(a, b)).map(([id]) => id);
|
|
104
|
+
const placed = queue.length;
|
|
105
|
+
for (const id of queue) {
|
|
106
|
+
const from = key.get(id);
|
|
107
|
+
for (const nb of adjacent.get(id) ?? []) {
|
|
108
|
+
if (!key.has(nb)) {
|
|
109
|
+
key.set(nb, { at: from.at, side: 1, hops: from.hops + 1 });
|
|
110
|
+
queue.push(nb);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const searched = new Set(queue.slice(placed));
|
|
115
|
+
const sided = /* @__PURE__ */ new Set();
|
|
116
|
+
for (const seed of searched) {
|
|
117
|
+
if (sided.has(seed)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const { at } = key.get(seed);
|
|
121
|
+
const run = [seed];
|
|
122
|
+
sided.add(seed);
|
|
123
|
+
let farHalf = true;
|
|
124
|
+
for (const id of run) {
|
|
125
|
+
const { hops } = key.get(id);
|
|
126
|
+
let deadEnd = true;
|
|
127
|
+
let touchesLeft = false;
|
|
128
|
+
for (const nb of adjacent.get(id) ?? []) {
|
|
129
|
+
const other = key.get(nb);
|
|
130
|
+
if (other.at === at && searched.has(nb)) {
|
|
131
|
+
deadEnd &&= other.hops <= hops;
|
|
132
|
+
if (!sided.has(nb)) {
|
|
133
|
+
sided.add(nb);
|
|
134
|
+
run.push(nb);
|
|
135
|
+
}
|
|
136
|
+
} else if (other.at !== at) {
|
|
137
|
+
touchesLeft ||= other.at < at;
|
|
138
|
+
farHalf &&= other.at < at;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
farHalf &&= !deadEnd || touchesLeft;
|
|
142
|
+
}
|
|
143
|
+
if (farHalf) {
|
|
144
|
+
for (const id of run) {
|
|
145
|
+
const k = key.get(id);
|
|
146
|
+
key.set(id, { ...k, side: -1, hops: -k.hops });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function orderKeys(graph) {
|
|
152
|
+
const ids = new Set(graph.nodes.map((n) => n.id));
|
|
153
|
+
const adjacent = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const e of graph.edges) {
|
|
155
|
+
if (!ids.has(e.from) || !ids.has(e.to)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
;
|
|
159
|
+
(adjacent.get(e.from) ?? adjacent.set(e.from, []).get(e.from)).push(e.to);
|
|
160
|
+
(adjacent.get(e.to) ?? adjacent.set(e.to, []).get(e.to)).push(e.from);
|
|
161
|
+
}
|
|
162
|
+
const backbone = graph.nodes.filter(isBackbone).sort(
|
|
163
|
+
(a, b) => compare(a.stable.start, b.stable.start) || compare(a.name, b.name)
|
|
164
|
+
);
|
|
165
|
+
const key = /* @__PURE__ */ new Map();
|
|
166
|
+
placeByWalks(graph, backbone).forEach((id, rank) => {
|
|
167
|
+
key.set(id, { at: rank, side: 0, hops: 0 });
|
|
168
|
+
});
|
|
169
|
+
placeBySearch(adjacent, key);
|
|
170
|
+
return key;
|
|
171
|
+
}
|
|
172
|
+
function layerGraph(graph) {
|
|
173
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
174
|
+
const order = referenceOrder(graph);
|
|
175
|
+
const key = new Map(order.map((id, i) => [id, i]));
|
|
176
|
+
const isRef = (id) => isBackbone(byId.get(id));
|
|
177
|
+
const preds = new Map(order.map((id) => [id, []]));
|
|
178
|
+
const succs = new Map(order.map((id) => [id, []]));
|
|
179
|
+
const link = (a, b) => {
|
|
180
|
+
preds.get(b).push(a);
|
|
181
|
+
succs.get(a).push(b);
|
|
182
|
+
};
|
|
183
|
+
for (const e of graph.edges) {
|
|
184
|
+
if (e.from === e.to || !key.has(e.from) || !key.has(e.to)) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (key.get(e.from) < key.get(e.to)) {
|
|
188
|
+
link(e.from, e.to);
|
|
189
|
+
} else {
|
|
190
|
+
link(e.to, e.from);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const backbone = order.filter(isRef);
|
|
194
|
+
for (let i = 1; i < backbone.length; i++) {
|
|
195
|
+
link(backbone[i - 1], backbone[i]);
|
|
196
|
+
}
|
|
197
|
+
const layerOf = /* @__PURE__ */ new Map();
|
|
198
|
+
for (const id of order) {
|
|
199
|
+
let l = 0;
|
|
200
|
+
for (const p of preds.get(id)) {
|
|
201
|
+
l = Math.max(l, layerOf.get(p) + 1);
|
|
202
|
+
}
|
|
203
|
+
layerOf.set(id, l);
|
|
204
|
+
}
|
|
205
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
206
|
+
const id = order[i];
|
|
207
|
+
const s = succs.get(id);
|
|
208
|
+
if (isRef(id) || s.length === 0) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
let minSucc = Infinity;
|
|
212
|
+
for (const t of s) {
|
|
213
|
+
minSucc = Math.min(minSucc, layerOf.get(t));
|
|
214
|
+
}
|
|
215
|
+
const lo = layerOf.get(id);
|
|
216
|
+
const hi = Math.max(lo, minSucc - 1);
|
|
217
|
+
layerOf.set(id, Math.floor((lo + hi) / 2));
|
|
218
|
+
}
|
|
219
|
+
const layers = [];
|
|
220
|
+
for (const id of order) {
|
|
221
|
+
;
|
|
222
|
+
(layers[layerOf.get(id)] ??= []).push(id);
|
|
223
|
+
}
|
|
224
|
+
return { layers, layerOf, preds };
|
|
225
|
+
}
|
|
226
|
+
function orderedLayout(graph) {
|
|
227
|
+
if (!graph.nodes.some(isBackbone)) {
|
|
228
|
+
return void 0;
|
|
229
|
+
}
|
|
230
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
231
|
+
const isRef = (id) => isBackbone(byId.get(id));
|
|
232
|
+
const { layers, layerOf: layer, preds } = layerGraph(graph);
|
|
233
|
+
const layerWidth = layers.map((ids) => {
|
|
234
|
+
let w = 0;
|
|
235
|
+
for (const id of ids) {
|
|
236
|
+
w = Math.max(w, nodeWidth(byId.get(id)));
|
|
237
|
+
}
|
|
238
|
+
return w;
|
|
239
|
+
});
|
|
240
|
+
const layerX = [];
|
|
241
|
+
let x = 0;
|
|
242
|
+
for (let l = 0; l < layers.length; l++) {
|
|
243
|
+
layerX[l] = x;
|
|
244
|
+
x += layerWidth[l] + GUTTER;
|
|
245
|
+
}
|
|
246
|
+
const y = /* @__PURE__ */ new Map();
|
|
247
|
+
for (const ids of layers) {
|
|
248
|
+
const free = [];
|
|
249
|
+
for (const id of ids) {
|
|
250
|
+
if (isRef(id)) {
|
|
251
|
+
y.set(id, 0);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const placed = preds.get(id).filter((p) => y.has(p));
|
|
255
|
+
let ideal = 0;
|
|
256
|
+
for (const p of placed) {
|
|
257
|
+
ideal += y.get(p) / placed.length;
|
|
258
|
+
}
|
|
259
|
+
free.push({ id, ideal: ideal === 0 ? LANE_PX : ideal });
|
|
260
|
+
}
|
|
261
|
+
free.sort((a, b) => a.ideal - b.ideal);
|
|
262
|
+
const taken = /* @__PURE__ */ new Set([0]);
|
|
263
|
+
for (const { id, ideal } of free) {
|
|
264
|
+
let lane = Math.round(ideal / LANE_PX);
|
|
265
|
+
if (lane === 0) {
|
|
266
|
+
lane = ideal >= 0 ? 1 : -1;
|
|
267
|
+
}
|
|
268
|
+
for (let d = 1; taken.has(lane); d++) {
|
|
269
|
+
if (!taken.has(lane + d)) {
|
|
270
|
+
lane += d;
|
|
271
|
+
} else if (!taken.has(lane - d)) {
|
|
272
|
+
lane -= d;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
taken.add(lane);
|
|
276
|
+
y.set(id, lane * LANE_PX);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const nodePositions = {};
|
|
280
|
+
for (const n of graph.nodes) {
|
|
281
|
+
const l = layer.get(n.id);
|
|
282
|
+
const w = nodeWidth(n);
|
|
283
|
+
const cx = layerX[l] + layerWidth[l] / 2;
|
|
284
|
+
const ny = y.get(n.id);
|
|
285
|
+
nodePositions[n.id] = [
|
|
286
|
+
{ x: cx - w / 2, y: ny },
|
|
287
|
+
{ x: cx + w / 2, y: ny }
|
|
288
|
+
];
|
|
289
|
+
}
|
|
290
|
+
return { nodePositions, referenceAxis: false, pixelRows: true };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export {
|
|
294
|
+
referenceOrder,
|
|
295
|
+
layerGraph,
|
|
296
|
+
orderedLayout
|
|
297
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildNeighbors
|
|
3
|
+
} from "./chunk-ES2N6V56.js";
|
|
4
|
+
import {
|
|
5
|
+
drawnNodeLength
|
|
6
|
+
} from "./chunk-6JEWNXCQ.js";
|
|
7
|
+
import {
|
|
8
|
+
isBackbone
|
|
9
|
+
} from "./chunk-FYQWPEEY.js";
|
|
10
|
+
|
|
11
|
+
// src/layout/referenceSeeds.ts
|
|
12
|
+
var LANE_GAP = 30;
|
|
13
|
+
function referenceSeeds(graph, scaling) {
|
|
14
|
+
const { opts } = scaling;
|
|
15
|
+
const drawn = new Map(
|
|
16
|
+
scaling.nodes.map((node) => [node.id, drawnNodeLength(opts, node.length)])
|
|
17
|
+
);
|
|
18
|
+
const drawnOf = (id) => drawn.get(id) ?? opts.minimumNodeLength;
|
|
19
|
+
const backbone = graph.nodes.filter(isBackbone).sort((a, b) => a.stable.start - b.stable.start);
|
|
20
|
+
const seeds = /* @__PURE__ */ new Map();
|
|
21
|
+
let x = 0;
|
|
22
|
+
for (const node of backbone) {
|
|
23
|
+
seeds.set(node.id, { x, y: 0 });
|
|
24
|
+
x += drawnOf(node.id) + opts.edgeLength;
|
|
25
|
+
}
|
|
26
|
+
const adjacent = buildNeighbors(graph);
|
|
27
|
+
const depth = new Map(backbone.map((node) => [node.id, 0]));
|
|
28
|
+
const queue = backbone.map((node) => node.id);
|
|
29
|
+
for (const id of queue) {
|
|
30
|
+
const at = seeds.get(id);
|
|
31
|
+
const next = depth.get(id) + 1;
|
|
32
|
+
for (const neighbour of adjacent.get(id) ?? []) {
|
|
33
|
+
if (depth.has(neighbour)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
depth.set(neighbour, next);
|
|
37
|
+
seeds.set(neighbour, { x: at.x + drawnOf(id) / 2, y: LANE_GAP * next });
|
|
38
|
+
queue.push(neighbour);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let unreached = 0;
|
|
42
|
+
for (const node of graph.nodes) {
|
|
43
|
+
if (!seeds.has(node.id)) {
|
|
44
|
+
seeds.set(node.id, {
|
|
45
|
+
x: unreached++ * x / graph.nodes.length,
|
|
46
|
+
y: LANE_GAP * 3
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return seeds;
|
|
51
|
+
}
|
|
52
|
+
function seededNodes(graph, scaling) {
|
|
53
|
+
const seeds = referenceSeeds(graph, scaling);
|
|
54
|
+
return scaling.nodes.map((node) => ({ ...node, ...seeds.get(node.id) }));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
referenceSeeds,
|
|
59
|
+
seededNodes
|
|
60
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import {
|
|
2
|
+
orientToReference
|
|
3
|
+
} from "./chunk-PUG7LXDF.js";
|
|
4
|
+
import {
|
|
5
|
+
seededNodes
|
|
6
|
+
} from "./chunk-IGVV37VI.js";
|
|
7
|
+
import {
|
|
8
|
+
convertGFAToGraph
|
|
9
|
+
} from "./chunk-72N5DJHZ.js";
|
|
10
|
+
import {
|
|
11
|
+
parseGFA
|
|
12
|
+
} from "./chunk-CDLPYR5O.js";
|
|
13
|
+
import {
|
|
14
|
+
routeNodes,
|
|
15
|
+
takeRoutes,
|
|
16
|
+
withDeletionRoutes
|
|
17
|
+
} from "./chunk-MI35EUIE.js";
|
|
18
|
+
import {
|
|
19
|
+
anchorGraph
|
|
20
|
+
} from "./chunk-ST2IBUAN.js";
|
|
21
|
+
import {
|
|
22
|
+
mergeRuns,
|
|
23
|
+
splitRuns
|
|
24
|
+
} from "./chunk-TAMCZHLW.js";
|
|
25
|
+
import {
|
|
26
|
+
spreadFor
|
|
27
|
+
} from "./chunk-UVBGRJQX.js";
|
|
28
|
+
import {
|
|
29
|
+
drawnLengthOf,
|
|
30
|
+
drawnNodeLength,
|
|
31
|
+
layoutScaling
|
|
32
|
+
} from "./chunk-6JEWNXCQ.js";
|
|
33
|
+
import {
|
|
34
|
+
deletionEdges
|
|
35
|
+
} from "./chunk-4EMPBO7S.js";
|
|
36
|
+
import {
|
|
37
|
+
isBackbone
|
|
38
|
+
} from "./chunk-FYQWPEEY.js";
|
|
39
|
+
|
|
40
|
+
// src/pipeline.ts
|
|
41
|
+
function loadGraph(text, name, opts = {}) {
|
|
42
|
+
const graph = anchorGraph(
|
|
43
|
+
convertGFAToGraph(parseGFA(text), name),
|
|
44
|
+
opts.referencePath
|
|
45
|
+
);
|
|
46
|
+
if (graph.nodes.length === 0) {
|
|
47
|
+
throw new Error(`No graph segments in ${name}`);
|
|
48
|
+
}
|
|
49
|
+
if (opts.maxNodes !== void 0 && graph.nodes.length > opts.maxNodes) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Graph too large to draw: ${graph.nodes.length.toLocaleString()} nodes (limit ${opts.maxNodes.toLocaleString()}). Zoom in to a smaller region, or raise maxGraphNodes on this view.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return graph;
|
|
55
|
+
}
|
|
56
|
+
var COMPONENT_SEPARATION = 15;
|
|
57
|
+
function engineRequest(graph, scaling, settings) {
|
|
58
|
+
const anchored = graph.nodes.some(isBackbone);
|
|
59
|
+
const spaced = {
|
|
60
|
+
...scaling,
|
|
61
|
+
opts: {
|
|
62
|
+
...scaling.opts,
|
|
63
|
+
edgeLength: scaling.opts.edgeLength * (settings.spacing ?? 1)
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
graph: {
|
|
68
|
+
nodes: anchored ? seededNodes(graph, spaced) : spaced.nodes,
|
|
69
|
+
edges: graph.edges
|
|
70
|
+
},
|
|
71
|
+
options: {
|
|
72
|
+
quality: settings.quality,
|
|
73
|
+
linearLayout: settings.linearLayout,
|
|
74
|
+
...spaced.opts,
|
|
75
|
+
componentSeparation: COMPONENT_SEPARATION * (settings.componentSeparation ?? 1),
|
|
76
|
+
...anchored ? { rotateComponents: false } : {}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function engineKey(graph, settings) {
|
|
81
|
+
const anchored = graph.nodes.some(isBackbone);
|
|
82
|
+
return `${settings.quality}|${settings.linearLayout}|${settings.bubbleSpread}|${settings.spacing ?? 1}|${settings.componentSeparation ?? 1}|${anchored}|${graph.referencePath ?? ""}`;
|
|
83
|
+
}
|
|
84
|
+
async function forceLayout(graph, settings, engine) {
|
|
85
|
+
const spread = spreadFor(settings.bubbleSpread);
|
|
86
|
+
const deletions = deletionEdges(graph);
|
|
87
|
+
const routed = withDeletionRoutes(graph, deletions);
|
|
88
|
+
const merged = mergeRuns(routed.graph, routed.ids);
|
|
89
|
+
const runs = {
|
|
90
|
+
...merged.graph,
|
|
91
|
+
nodes: merged.graph.nodes.filter((n) => !routed.ids.has(n.id))
|
|
92
|
+
};
|
|
93
|
+
const engineScaling = layoutScaling(runs, spread);
|
|
94
|
+
const { result, duration } = await engine(
|
|
95
|
+
engineRequest(
|
|
96
|
+
merged.graph,
|
|
97
|
+
{
|
|
98
|
+
...engineScaling,
|
|
99
|
+
nodes: [
|
|
100
|
+
...engineScaling.nodes,
|
|
101
|
+
...routeNodes(
|
|
102
|
+
graph,
|
|
103
|
+
deletions,
|
|
104
|
+
routed.routes,
|
|
105
|
+
drawnLengthOf(runs, spread),
|
|
106
|
+
engineScaling.opts
|
|
107
|
+
)
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
settings
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
const scaling = layoutScaling(graph, spread);
|
|
114
|
+
const drawn = new Map(
|
|
115
|
+
scaling.nodes.map((n) => [n.id, drawnNodeLength(scaling.opts, n.length)])
|
|
116
|
+
);
|
|
117
|
+
const positions = splitRuns(
|
|
118
|
+
result.nodePositions,
|
|
119
|
+
merged.runs,
|
|
120
|
+
(id) => drawn.get(id) ?? 0
|
|
121
|
+
);
|
|
122
|
+
const { nodePositions, deletionRoutes } = takeRoutes(
|
|
123
|
+
graph.nodes.some(isBackbone) ? orientToReference(graph, positions) : positions,
|
|
124
|
+
routed.routes
|
|
125
|
+
);
|
|
126
|
+
return {
|
|
127
|
+
result: {
|
|
128
|
+
...result,
|
|
129
|
+
nodePositions,
|
|
130
|
+
stranded: true,
|
|
131
|
+
...deletions.length ? { deletionRoutes, extent: layoutExtent(deletionRoutes) } : {}
|
|
132
|
+
},
|
|
133
|
+
duration
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function layoutExtent(nodePositions) {
|
|
137
|
+
let minX = Infinity;
|
|
138
|
+
let minY = Infinity;
|
|
139
|
+
let maxX = -Infinity;
|
|
140
|
+
let maxY = -Infinity;
|
|
141
|
+
for (const segments of Object.values(nodePositions)) {
|
|
142
|
+
for (const seg of segments) {
|
|
143
|
+
minX = Math.min(minX, seg.x);
|
|
144
|
+
minY = Math.min(minY, seg.y);
|
|
145
|
+
maxX = Math.max(maxX, seg.x);
|
|
146
|
+
maxY = Math.max(maxY, seg.y);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { minX, minY, maxX, maxY };
|
|
150
|
+
}
|
|
151
|
+
var FIT_PADDING = 40;
|
|
152
|
+
var MIN_ZOOM = 1e-6;
|
|
153
|
+
var MAX_ZOOM = 100;
|
|
154
|
+
function clampZoom(zoom) {
|
|
155
|
+
return Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom));
|
|
156
|
+
}
|
|
157
|
+
function fittedTranslateY(bounds, usableHeight, yScale, padTop = FIT_PADDING) {
|
|
158
|
+
const leftoverY = usableHeight - bounds.h * yScale;
|
|
159
|
+
return padTop - bounds.minY * yScale + Math.max(0, leftoverY) / 2;
|
|
160
|
+
}
|
|
161
|
+
function fitTransform(bounds, paneWidth, paneHeight, pixelRows, {
|
|
162
|
+
minScale = 0,
|
|
163
|
+
padLeft = FIT_PADDING,
|
|
164
|
+
padTop = FIT_PADDING,
|
|
165
|
+
padRight = FIT_PADDING,
|
|
166
|
+
padBottom = FIT_PADDING
|
|
167
|
+
} = {}) {
|
|
168
|
+
const usableWidth = paneWidth - padLeft - padRight;
|
|
169
|
+
const usableHeight = paneHeight - padTop - padBottom;
|
|
170
|
+
if (usableWidth <= 0 || usableHeight <= 0 || !(bounds.w > 0 || !pixelRows && bounds.h > 0)) {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
const fitX = bounds.w > 0 ? usableWidth / bounds.w : Infinity;
|
|
174
|
+
const fitY = bounds.h > 0 ? usableHeight / bounds.h : Infinity;
|
|
175
|
+
const scale = clampZoom(
|
|
176
|
+
Math.max(minScale, pixelRows ? fitX : Math.min(fitX, fitY))
|
|
177
|
+
);
|
|
178
|
+
const leftoverX = usableWidth - bounds.w * scale;
|
|
179
|
+
return {
|
|
180
|
+
scale,
|
|
181
|
+
translateX: padLeft - bounds.minX * scale + Math.max(0, leftoverX) / 2,
|
|
182
|
+
translateY: fittedTranslateY(
|
|
183
|
+
bounds,
|
|
184
|
+
usableHeight,
|
|
185
|
+
pixelRows ? 1 : scale,
|
|
186
|
+
padTop
|
|
187
|
+
)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function drawingBounds(layout, opts = {}) {
|
|
191
|
+
let { minX, minY, maxX, maxY } = layoutExtent(layout.nodePositions);
|
|
192
|
+
const { region } = opts;
|
|
193
|
+
if (layout.referenceAxis && region && region.end > region.start) {
|
|
194
|
+
minX = region.start;
|
|
195
|
+
maxX = region.end;
|
|
196
|
+
}
|
|
197
|
+
const extent = opts.extent ?? layout.extent;
|
|
198
|
+
if (extent) {
|
|
199
|
+
minX = Math.min(minX, extent.minX ?? minX);
|
|
200
|
+
minY = Math.min(minY, extent.minY ?? minY);
|
|
201
|
+
maxX = Math.max(maxX, extent.maxX ?? maxX);
|
|
202
|
+
maxY = Math.max(maxY, extent.maxY ?? maxY);
|
|
203
|
+
}
|
|
204
|
+
return { minX, minY, w: maxX - minX, h: maxY - minY };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export {
|
|
208
|
+
loadGraph,
|
|
209
|
+
engineRequest,
|
|
210
|
+
engineKey,
|
|
211
|
+
forceLayout,
|
|
212
|
+
layoutExtent,
|
|
213
|
+
FIT_PADDING,
|
|
214
|
+
clampZoom,
|
|
215
|
+
fittedTranslateY,
|
|
216
|
+
fitTransform,
|
|
217
|
+
drawingBounds
|
|
218
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bubbleSegmentIds,
|
|
3
|
+
classifyBubble,
|
|
4
|
+
formatBp
|
|
5
|
+
} from "./chunk-WOO7QYKB.js";
|
|
6
|
+
import {
|
|
7
|
+
svgPath
|
|
8
|
+
} from "./chunk-2CVHXFK7.js";
|
|
9
|
+
import {
|
|
10
|
+
isBackbone
|
|
11
|
+
} from "./chunk-FYQWPEEY.js";
|
|
12
|
+
|
|
13
|
+
// src/bubbles/bubbleHalos.ts
|
|
14
|
+
var WHOLE_FRACTION = 0.9;
|
|
15
|
+
var NAMED_WALKS = 2;
|
|
16
|
+
var MIN_CHIPPED_BP = 1e3;
|
|
17
|
+
function bubbleHalos(graph, bubbles, positions, walkLabel = (name) => name, repeats = []) {
|
|
18
|
+
const byName = new Map(graph.nodes.map((n) => [n.name, n]));
|
|
19
|
+
const halos = [];
|
|
20
|
+
for (const bubble of bubbles) {
|
|
21
|
+
const parts = [];
|
|
22
|
+
let minX = Infinity;
|
|
23
|
+
let maxX = -Infinity;
|
|
24
|
+
let minY = Infinity;
|
|
25
|
+
const nodeIds = [];
|
|
26
|
+
const ends = [];
|
|
27
|
+
for (const name of bubbleSegmentIds(bubble)) {
|
|
28
|
+
const node = byName.get(name);
|
|
29
|
+
const line = node && positions[node.id];
|
|
30
|
+
if (!node || !line?.length) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (isBackbone(node) && (node.stable.start < bubble.start || node.stable.start >= bubble.end)) {
|
|
34
|
+
ends.push(line[Math.floor(line.length / 2)]);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
nodeIds.push(node.id);
|
|
38
|
+
parts.push(svgPath(line.length === 1 ? [line[0], line[0]] : line));
|
|
39
|
+
for (const p of line) {
|
|
40
|
+
minX = Math.min(minX, p.x);
|
|
41
|
+
maxX = Math.max(maxX, p.x);
|
|
42
|
+
minY = Math.min(minY, p.y);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (nodeIds.length === 0) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const labelAt = { x: (minX + maxX) / 2, y: minY };
|
|
49
|
+
const anchor = ends.length ? {
|
|
50
|
+
x: ends.reduce((s, p) => s + p.x, 0) / ends.length,
|
|
51
|
+
y: ends.reduce((s, p) => s + p.y, 0) / ends.length
|
|
52
|
+
} : labelAt;
|
|
53
|
+
const sharing = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const route of bubble.routes ?? []) {
|
|
55
|
+
for (const id of new Set(route.steps)) {
|
|
56
|
+
sharing.set(id, (sharing.get(id) ?? 0) + 1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const routes = [];
|
|
60
|
+
for (const route of bubble.routes ?? []) {
|
|
61
|
+
if (bubble.longestAlleleLength < MIN_CHIPPED_BP) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (route.steps.length === 0) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const rarest = Math.min(...route.steps.map((id) => sharing.get(id)));
|
|
68
|
+
let at;
|
|
69
|
+
let far = -1;
|
|
70
|
+
for (const id of route.steps) {
|
|
71
|
+
if (sharing.get(id) !== rarest) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
for (const p of positions[id] ?? []) {
|
|
75
|
+
const d = Math.hypot(p.x - anchor.x, p.y - anchor.y);
|
|
76
|
+
if (d > far) {
|
|
77
|
+
far = d;
|
|
78
|
+
at = p;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (at) {
|
|
83
|
+
routes.push({ route, at, text: routeText(route, walkLabel) });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
halos.push({
|
|
87
|
+
bubble,
|
|
88
|
+
...classifyBubble(bubble, repeats),
|
|
89
|
+
path: parts.join(""),
|
|
90
|
+
labelAt,
|
|
91
|
+
members: nodeIds.length,
|
|
92
|
+
nodeIds,
|
|
93
|
+
whole: nodeIds.length >= WHOLE_FRACTION * graph.nodes.length,
|
|
94
|
+
routes
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return halos;
|
|
98
|
+
}
|
|
99
|
+
function routeText(route, walkLabel) {
|
|
100
|
+
const names = [...new Set(route.walks.map(walkLabel))].sort();
|
|
101
|
+
const shown = names.slice(0, NAMED_WALKS).join(", ");
|
|
102
|
+
const more = names.length > NAMED_WALKS ? ` +${names.length - NAMED_WALKS}` : "";
|
|
103
|
+
return `${shown}${more} \xB7 ${formatBp(route.bp)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
bubbleHalos
|
|
108
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// src/colorSchemes.ts
|
|
2
|
+
var COLOR_SCHEMES = [
|
|
3
|
+
// Mirrors `layoutModes`' 'auto': a value the model resolves rather than a
|
|
4
|
+
// colour of its own. It exists because 'uniform' was the default, so a
|
|
5
|
+
// launched graph opened flat grey and both tutorials spent a step saying "now
|
|
6
|
+
// pick a colour" — one figure literally drives that click. A graph with
|
|
7
|
+
// reference coordinates resolves to 'reference-position', which is the one
|
|
8
|
+
// ramp a linear track beside it can be painted with too; a graph with none
|
|
9
|
+
// keeps 'uniform', because there the ramp would be a hue nothing earned.
|
|
10
|
+
//
|
|
11
|
+
// Resolve through `effectiveColorScheme`; the dropdown reads the raw prop, so
|
|
12
|
+
// "Auto" stays selectable and visible as what it is.
|
|
13
|
+
{ value: "auto", label: "Auto" },
|
|
14
|
+
{ value: "uniform", label: "Uniform" },
|
|
15
|
+
{ value: "random", label: "Random" },
|
|
16
|
+
{ value: "rainbow", label: "Rainbow" },
|
|
17
|
+
{ value: "depth", label: "Depth" },
|
|
18
|
+
{ value: "node-length", label: "Node length" },
|
|
19
|
+
{ value: "stable-rank", label: "Stable rank" },
|
|
20
|
+
{ value: "reference-position", label: "Reference position" },
|
|
21
|
+
{ value: "grey", label: "Grey" }
|
|
22
|
+
];
|
|
23
|
+
var COLOR_SCHEME_VALUES = COLOR_SCHEMES.map((s) => s.value);
|
|
24
|
+
function resolveColorScheme(scheme, graph) {
|
|
25
|
+
return scheme !== "auto" ? scheme : graph?.anchoredBy ? "reference-position" : "uniform";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
COLOR_SCHEMES,
|
|
30
|
+
COLOR_SCHEME_VALUES,
|
|
31
|
+
resolveColorScheme
|
|
32
|
+
};
|