@jbrowse/bandage-core 8.2.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/bubbles/bubbleHalos.js +9 -5
  2. package/dist/bubbles/bubbleLine.js +3 -1
  3. package/dist/bubbles/bubblesFromGraph.js +8 -4
  4. package/dist/bubbles/classifyBubble.js +2 -2
  5. package/dist/bubbles/superbubbles.js +21 -0
  6. package/dist/chunks/{chunk-ISEI2FH3.js → chunk-2AWUWGAD.js} +10 -4
  7. package/dist/chunks/{chunk-GQ2AOHU4.js → chunk-2ZT7ZF7A.js} +13 -31
  8. package/dist/chunks/{chunk-DE4UFDUZ.js → chunk-3TGUKQWQ.js} +4 -4
  9. package/dist/chunks/{chunk-KCRL5JB2.js → chunk-75HR3756.js} +55 -17
  10. package/dist/chunks/{chunk-DRA7733Q.js → chunk-BPRXQ2SS.js} +8 -3
  11. package/dist/chunks/{chunk-TEDQJPMI.js → chunk-CWFNEKST.js} +22 -7
  12. package/dist/chunks/chunk-FUVI4WQP.js +273 -0
  13. package/dist/chunks/{chunk-MFKORG3F.js → chunk-FX4RDMDN.js} +5 -1
  14. package/dist/chunks/{chunk-4S47XRYA.js → chunk-GOZYKNQ2.js} +16 -11
  15. package/dist/chunks/chunk-INJ2UVOT.js +370 -0
  16. package/dist/chunks/{chunk-TL4B342F.js → chunk-MXDQGA7A.js} +1 -1
  17. package/dist/chunks/{chunk-SKKYLBRA.js → chunk-RRZMMYBG.js} +1 -1
  18. package/dist/chunks/{chunk-3FXPFVKJ.js → chunk-UID3QBWH.js} +1 -1
  19. package/dist/chunks/{chunk-2WNBVB55.js → chunk-WBCOREDZ.js} +121 -74
  20. package/dist/chunks/{chunk-OWFPAIJI.js → chunk-WEDZ76AK.js} +1 -1
  21. package/dist/chunks/{chunk-CF62U3QS.js → chunk-WNUZP3WQ.js} +2 -2
  22. package/dist/chunks/{chunk-WOO7QYKB.js → chunk-XKWD2RY3.js} +8 -2
  23. package/dist/chunks/{chunk-6VBDMZBZ.js → chunk-XLCTQHBE.js} +1 -1
  24. package/dist/chunks/{chunk-733L2TS6.js → chunk-XXT2JCAC.js} +10 -7
  25. package/dist/cli/figure.js +9 -9
  26. package/dist/figure.js +6 -6
  27. package/dist/figureSettings.js +3 -3
  28. package/dist/index.js +45 -33
  29. package/dist/layout/forceCache.js +3 -3
  30. package/dist/layout/tubeMapLayout.js +12 -6
  31. package/dist/layoutModes.js +3 -3
  32. package/dist/pipeline.js +2 -2
  33. package/dist/renderer/Canvas2DRenderer.js +1 -1
  34. package/dist/renderer/recordingCanvas.js +12 -1
  35. package/dist/tubeMap/axis.js +1 -1
  36. package/dist/tubeMap/coarsen.js +11 -23
  37. package/dist/tubeMap/connectors.js +3 -4
  38. package/dist/tubeMap/deviations.js +15 -19
  39. package/dist/tubeMap/draw.js +11 -5
  40. package/dist/tubeMap/genes.js +2 -2
  41. package/dist/tubeMap/mismatches.js +1 -1
  42. package/dist/tubeMap/reads.js +1 -1
  43. package/dist/types/bubbles/bubbleHalos.d.ts +2 -0
  44. package/dist/types/bubbles/bubbleLine.d.ts +3 -0
  45. package/dist/types/bubbles/bubblesFromGraph.d.ts +12 -2
  46. package/dist/types/bubbles/superbubbles.d.ts +5 -0
  47. package/dist/types/index.d.ts +5 -4
  48. package/dist/types/layout/tubeMapLayout.d.ts +15 -2
  49. package/dist/types/layoutModes.d.ts +5 -5
  50. package/dist/types/renderer/Canvas2DRenderer.d.ts +6 -3
  51. package/dist/types/tubeMap/axis.d.ts +6 -3
  52. package/dist/types/tubeMap/connectors.d.ts +3 -11
  53. package/dist/types/tubeMap/deviations.d.ts +2 -3
  54. package/dist/types/tubeMap/draw.d.ts +5 -1
  55. package/dist/types/tubeMap/mismatches.d.ts +1 -1
  56. package/dist/types/version.d.ts +1 -1
  57. package/dist/version.js +1 -1
  58. package/dist/viewport.js +3 -3
  59. package/package.json +1 -1
  60. package/dist/chunks/chunk-W2I4DOL4.js +0 -230
  61. package/dist/chunks/{chunk-5OIVTEF7.js → chunk-OOXIMURX.js} +3 -3
@@ -0,0 +1,273 @@
1
+ import {
2
+ bubbleSubgraph
3
+ } from "./chunk-VHW7FDHU.js";
4
+ import {
5
+ coversGraph
6
+ } from "./chunk-2AWUWGAD.js";
7
+ import {
8
+ bubblesFromGraph,
9
+ walkIndexOf,
10
+ walkRoutes
11
+ } from "./chunk-GOZYKNQ2.js";
12
+ import {
13
+ bubbleSegmentIds
14
+ } from "./chunk-XKWD2RY3.js";
15
+ import {
16
+ SATURATED_PATH_COUNT
17
+ } from "./chunk-FX4RDMDN.js";
18
+ import {
19
+ strandSides
20
+ } from "./chunk-2CVHXFK7.js";
21
+ import {
22
+ isBackbone
23
+ } from "./chunk-FYQWPEEY.js";
24
+
25
+ // src/bubbles/superbubbles.ts
26
+ var MAX_SUPERBUBBLES = 50;
27
+ function slotGraph(graph, index) {
28
+ const slots = graph.nodes.length * 2;
29
+ const seen = /* @__PURE__ */ new Set();
30
+ const pairs = [];
31
+ const degree = new Int32Array(slots);
32
+ for (const e of graph.edges) {
33
+ const a = index.get(e.from);
34
+ const b = index.get(e.to);
35
+ if (a === void 0 || b === void 0) {
36
+ continue;
37
+ }
38
+ const sides = strandSides(e);
39
+ const ka = a * 2 + (sides.from === "end" ? 1 : 0);
40
+ const kb = b * 2 + (sides.to === "end" ? 1 : 0);
41
+ const id = Math.min(ka, kb) * slots + Math.max(ka, kb);
42
+ if (seen.has(id)) {
43
+ continue;
44
+ }
45
+ seen.add(id);
46
+ pairs.push(ka, kb);
47
+ degree[ka]++;
48
+ if (ka !== kb) {
49
+ degree[kb]++;
50
+ }
51
+ }
52
+ const offset = new Int32Array(slots + 1);
53
+ for (let k = 0; k < slots; k++) {
54
+ offset[k + 1] = offset[k] + degree[k];
55
+ }
56
+ const next = new Int32Array(offset[slots]);
57
+ const fill = offset.slice(0, slots);
58
+ for (let i = 0; i < pairs.length; i += 2) {
59
+ const ka = pairs[i];
60
+ const kb = pairs[i + 1];
61
+ next[fill[ka]++] = kb;
62
+ if (ka !== kb) {
63
+ next[fill[kb]++] = ka;
64
+ }
65
+ }
66
+ return { offset, next };
67
+ }
68
+ function searcher(nodes, offset, next) {
69
+ const visited = new Int32Array(nodes);
70
+ const pending = new Uint8Array(nodes * 2);
71
+ const stack = new Int32Array(nodes * 2);
72
+ const inside = new Int32Array(nodes);
73
+ const touched = [];
74
+ let stamp = 0;
75
+ return (start) => {
76
+ const s = start >> 1;
77
+ stamp++;
78
+ touched.length = 0;
79
+ let open = 0;
80
+ let top = 0;
81
+ let count = 0;
82
+ let found;
83
+ const expect = (k) => {
84
+ if (!pending[k]) {
85
+ pending[k] = 1;
86
+ open++;
87
+ touched.push(k);
88
+ }
89
+ };
90
+ expect(start);
91
+ stack[top++] = start;
92
+ search: while (top > 0) {
93
+ const k = stack[--top];
94
+ const v = k >> 1;
95
+ visited[v] = stamp;
96
+ if (v !== s) {
97
+ inside[count++] = k;
98
+ }
99
+ if (pending[k]) {
100
+ pending[k] = 0;
101
+ open--;
102
+ }
103
+ if (offset[k] === offset[k + 1]) {
104
+ break;
105
+ }
106
+ for (let p = offset[k]; p < offset[k + 1]; p++) {
107
+ const entry = next[p];
108
+ const u = entry >> 1;
109
+ if (u === s) {
110
+ break search;
111
+ }
112
+ expect(entry ^ 1);
113
+ if (visited[u] === stamp) {
114
+ continue;
115
+ }
116
+ let ready = true;
117
+ for (let q = offset[entry]; q < offset[entry + 1]; q++) {
118
+ if (visited[next[q] >> 1] !== stamp) {
119
+ ready = false;
120
+ break;
121
+ }
122
+ }
123
+ if (ready) {
124
+ stack[top++] = entry ^ 1;
125
+ }
126
+ }
127
+ if (top === 1 && open === 1) {
128
+ if (count > 0) {
129
+ found = {
130
+ source: start,
131
+ sink: stack[0],
132
+ inside: inside.slice(0, count)
133
+ };
134
+ }
135
+ break;
136
+ }
137
+ }
138
+ for (const k of touched) {
139
+ pending[k] = 0;
140
+ }
141
+ return found;
142
+ };
143
+ }
144
+ function reversed(f) {
145
+ return {
146
+ source: f.sink ^ 1,
147
+ sink: f.source ^ 1,
148
+ inside: f.inside.map((k) => k ^ 1).reverse()
149
+ };
150
+ }
151
+ var collator = new Intl.Collator(void 0, { numeric: true });
152
+ function onReference(a, b) {
153
+ return isBackbone(a) && isBackbone(b) && a.stable.refName === b.stable.refName;
154
+ }
155
+ function forward(f, nodes) {
156
+ const a = nodes[f.source >> 1];
157
+ const b = nodes[f.sink >> 1];
158
+ const byRef = onReference(a, b) ? a.stable.start - b.stable.start : 0;
159
+ const bySide = ((f.sink ^ 1) & 1) - (f.source & 1);
160
+ return (byRef || bySide || collator.compare(a.name, b.name)) < 0;
161
+ }
162
+ function nodesOf(f) {
163
+ return [f.source >> 1, ...Array.from(f.inside, (k) => k >> 1), f.sink >> 1];
164
+ }
165
+ function allSuperbubbles(graph) {
166
+ const { nodes } = graph;
167
+ const index = new Map(nodes.map((n, i) => [n.id, i]));
168
+ const { offset, next } = slotGraph(graph, index);
169
+ const find = searcher(nodes.length, offset, next);
170
+ const byEnds = /* @__PURE__ */ new Map();
171
+ for (let k = 0; k < nodes.length * 2; k++) {
172
+ const hit = find(k);
173
+ if (!hit) {
174
+ continue;
175
+ }
176
+ const a = hit.source >> 1;
177
+ const b = hit.sink >> 1;
178
+ const id = Math.min(a, b) * nodes.length + Math.max(a, b);
179
+ if (forward(hit, nodes) || !byEnds.has(id)) {
180
+ byEnds.set(id, hit);
181
+ }
182
+ }
183
+ const found = [...byEnds.values()].map(
184
+ (f) => forward(f, nodes) ? f : reversed(f)
185
+ );
186
+ return { found, offset, next };
187
+ }
188
+ function routesThrough(nodes, offset, next, f) {
189
+ const sink = f.sink >> 1;
190
+ const best = /* @__PURE__ */ new Map([
191
+ [f.source >> 1, { min: 0, max: 0, n: 1 }]
192
+ ]);
193
+ for (const k of [f.source, ...f.inside]) {
194
+ const cur = best.get(k >> 1);
195
+ if (!cur) {
196
+ continue;
197
+ }
198
+ for (let p = offset[k]; p < offset[k + 1]; p++) {
199
+ const u = next[p] >> 1;
200
+ const add = u === sink ? 0 : nodes[u].length;
201
+ const prev = best.get(u);
202
+ best.set(u, {
203
+ min: Math.min(prev?.min ?? Infinity, cur.min + add),
204
+ max: Math.max(prev?.max ?? -Infinity, cur.max + add),
205
+ n: Math.min(SATURATED_PATH_COUNT, cur.n + (prev?.n ?? 0))
206
+ });
207
+ }
208
+ }
209
+ return best.get(sink) ?? { min: 0, max: 0, n: 0 };
210
+ }
211
+ function superbubblesFromGraph(graph, span) {
212
+ const { nodes } = graph;
213
+ const { found, offset, next } = allSuperbubbles(graph);
214
+ const covers = found.map((f) => coversGraph(f.inside.length, nodes.length));
215
+ const members = found.map(nodesOf);
216
+ const holders = /* @__PURE__ */ new Map();
217
+ members.forEach((vs, i) => {
218
+ for (const v of vs) {
219
+ ;
220
+ (holders.get(v) ?? holders.set(v, []).get(v)).push(i);
221
+ }
222
+ });
223
+ const nested = (f, i) => holders.get(f.inside[0] >> 1).some(
224
+ (j) => j !== i && !covers[j] && members[j].length > members[i].length && members[i].every((v) => holders.get(v).includes(j))
225
+ );
226
+ const placed = (f) => span !== void 0 || onReference(nodes[f.source >> 1], nodes[f.sink >> 1]);
227
+ const outermost = found.filter((f, i) => !covers[i] && placed(f) && !nested(f, i)).sort((a, b) => b.inside.length - a.inside.length).slice(0, MAX_SUPERBUBBLES);
228
+ const byId = new Map(nodes.map((n) => [n.id, n]));
229
+ const walkIndex = walkIndexOf(graph);
230
+ return outermost.map((f) => {
231
+ const source = nodes[f.source >> 1];
232
+ const sink = nodes[f.sink >> 1];
233
+ const walked = walkIndex && walkRoutes(graph, byId, walkIndex, source.id, sink.id);
234
+ const crossed = walked?.n ? walked : void 0;
235
+ const routes = crossed ?? routesThrough(nodes, offset, next, f);
236
+ const placedOn = onReference(source, sink);
237
+ return {
238
+ refName: placedOn ? source.stable.refName : span.refName,
239
+ start: placedOn ? source.stable.start + source.length : span.start,
240
+ end: placedOn ? sink.stable.start : span.end,
241
+ segmentCount: f.inside.length + 2,
242
+ pathCount: routes.n,
243
+ inversion: false,
244
+ shortestAlleleLength: routes.min,
245
+ longestAlleleLength: routes.max,
246
+ segments: nodesOf(f).map((v) => nodes[v].name).join(","),
247
+ shortestAllele: void 0,
248
+ longestAllele: void 0,
249
+ partial: walked !== void 0 && walked.left > 0,
250
+ routes: crossed?.routes,
251
+ key: `${source.name}>${sink.name}`,
252
+ ...placedOn ? {} : { offReference: true }
253
+ };
254
+ }).sort(
255
+ (a, b) => a.start - b.start || a.end - b.end || collator.compare(a.key, b.key)
256
+ );
257
+ }
258
+ function graphBubbles(graph, span) {
259
+ const n = graph.nodes.length;
260
+ const reach = bubblesFromGraph(graph);
261
+ return reach.length === 0 ? superbubblesFromGraph(graph, span) : reach.flatMap(
262
+ (b) => coversGraph(b.segmentCount - 2, n) ? superbubblesFromGraph(
263
+ b.segmentCount === n ? graph : bubbleSubgraph(graph, bubbleSegmentIds(b)),
264
+ b
265
+ ) : [b]
266
+ );
267
+ }
268
+
269
+ export {
270
+ MAX_SUPERBUBBLES,
271
+ superbubblesFromGraph,
272
+ graphBubbles
273
+ };
@@ -19,7 +19,10 @@ function parseBubbleLine(line) {
19
19
  };
20
20
  }
21
21
  function sameBubble(a, b) {
22
- return a.refName === b.refName && a.start === b.start && a.end === b.end;
22
+ return a.key !== void 0 || b.key !== void 0 ? a.key === b.key : a.refName === b.refName && a.start === b.start && a.end === b.end;
23
+ }
24
+ function bubbleKey(b) {
25
+ return b.key ?? `${b.refName}:${b.start}-${b.end}`;
23
26
  }
24
27
  function bubbleLabel(bubble) {
25
28
  const { shortestAlleleLength: min, longestAlleleLength: max } = bubble;
@@ -38,6 +41,7 @@ function bubbleDescription(bubble) {
38
41
  export {
39
42
  parseBubbleLine,
40
43
  sameBubble,
44
+ bubbleKey,
41
45
  bubbleLabel,
42
46
  SATURATED_PATH_COUNT,
43
47
  bubbleDescription
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SATURATED_PATH_COUNT
3
- } from "./chunk-MFKORG3F.js";
3
+ } from "./chunk-FX4RDMDN.js";
4
4
  import {
5
5
  referenceOrder
6
6
  } from "./chunk-HNQPEMYE.js";
@@ -38,15 +38,7 @@ function bubblesFromGraph(graph) {
38
38
  const backbone = graph.nodes.filter(isBackbone).sort((a, b) => a.stable.start - b.stable.start);
39
39
  const ends = /* @__PURE__ */ new Set([backbone[0].id, backbone.at(-1).id]);
40
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
- });
41
+ const walkIndex = walkIndexOf(graph);
50
42
  const bubbles = [];
51
43
  for (let i = 0; i + 1 < boundaries.length; i++) {
52
44
  const [start, i0] = boundaries[i];
@@ -112,6 +104,17 @@ function bubblesFromGraph(graph) {
112
104
  }
113
105
  return bubbles;
114
106
  }
107
+ function walkIndexOf(graph) {
108
+ return graph.paths?.map((p) => {
109
+ const at = /* @__PURE__ */ new Map();
110
+ p.nodeIds.forEach((id, i) => {
111
+ if (!at.has(id)) {
112
+ at.set(id, i);
113
+ }
114
+ });
115
+ return at;
116
+ });
117
+ }
115
118
  function walkRoutes(graph, byId, walkIndex, startId, endId) {
116
119
  const seen = /* @__PURE__ */ new Map();
117
120
  let min = Infinity;
@@ -145,5 +148,7 @@ function walkRoutes(graph, byId, walkIndex, startId, endId) {
145
148
  }
146
149
 
147
150
  export {
148
- bubblesFromGraph
151
+ bubblesFromGraph,
152
+ walkIndexOf,
153
+ walkRoutes
149
154
  };
@@ -0,0 +1,370 @@
1
+ import {
2
+ canonicalStrand,
3
+ stepName,
4
+ tubeMapReads
5
+ } from "./chunk-CWFNEKST.js";
6
+ import {
7
+ pathCssColor,
8
+ pathGreyCssColor
9
+ } from "./chunk-7GBL7SJY.js";
10
+ import {
11
+ pathOrigin
12
+ } from "./chunk-ST2IBUAN.js";
13
+ import {
14
+ ownStrand,
15
+ strandSides
16
+ } from "./chunk-2CVHXFK7.js";
17
+ import {
18
+ isBackbone
19
+ } from "./chunk-FYQWPEEY.js";
20
+
21
+ // src/layout/tubeMapLayout.ts
22
+ import { forward, isReverse, layoutTubeMap } from "@jbrowse/tubemap-core";
23
+ function pathSteps(graph) {
24
+ const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
25
+ const strands = /* @__PURE__ */ new Map();
26
+ for (const [segment, visits] of graph.pathVisits ?? []) {
27
+ for (const visit of visits) {
28
+ const key = `${visit.path} ${segment}`;
29
+ const list = strands.get(key);
30
+ if (list) {
31
+ list.push(visit.strand);
32
+ } else {
33
+ strands.set(key, [visit.strand]);
34
+ }
35
+ }
36
+ }
37
+ const seen = /* @__PURE__ */ new Map();
38
+ return (graph.paths ?? []).map((path) => {
39
+ const walk = pathOrigin(path.name).name;
40
+ return path.nodeIds.flatMap((id) => {
41
+ const node = nodeById.get(id);
42
+ if (!node) {
43
+ return [];
44
+ }
45
+ const key = `${walk} ${node.name}`;
46
+ const k = seen.get(key) ?? 0;
47
+ seen.set(key, k + 1);
48
+ return [{ node, strand: strands.get(key)?.[k] ?? canonicalStrand(node) }];
49
+ });
50
+ });
51
+ }
52
+ function tubeMapTracks(graph) {
53
+ const steps = pathSteps(graph);
54
+ const tracks = (graph.paths ?? []).flatMap((path, index) => {
55
+ const sequence = steps[index].map((s) => stepName(s.node, s.strand));
56
+ return sequence.length > 0 ? [
57
+ {
58
+ id: index,
59
+ name: path.name,
60
+ sequence,
61
+ sourceTrackID: 0,
62
+ type: "haplotype"
63
+ }
64
+ ] : [];
65
+ });
66
+ const reference = tracks.findIndex(
67
+ (t) => pathOrigin(t.name).name === graph.referencePath
68
+ );
69
+ if (reference > 0) {
70
+ tracks.unshift(...tracks.splice(reference, 1));
71
+ }
72
+ return tracks;
73
+ }
74
+ function tubeMapNodes(graph) {
75
+ return graph.nodes.map((n) => ({
76
+ name: n.id,
77
+ seq: "",
78
+ sequenceLength: Math.max(1, n.length)
79
+ }));
80
+ }
81
+ function hasTubeMapPaths(graph) {
82
+ return (graph.paths?.length ?? 0) > 0;
83
+ }
84
+ var STACK_PX = 600;
85
+ var MIN_TUBE_PX = 3;
86
+ var MAX_TUBE_PX = 15;
87
+ function tubeWidth(trackCount) {
88
+ return Math.max(
89
+ MIN_TUBE_PX,
90
+ Math.min(MAX_TUBE_PX, Math.floor(STACK_PX / Math.max(1, trackCount)))
91
+ );
92
+ }
93
+ function straightened(first) {
94
+ const flipped = /* @__PURE__ */ new Set();
95
+ const forwards = /* @__PURE__ */ new Set();
96
+ for (const step of first?.sequence ?? []) {
97
+ if (!isReverse(step)) {
98
+ forwards.add(step);
99
+ } else if (!forwards.has(forward(step))) {
100
+ flipped.add(forward(step));
101
+ }
102
+ }
103
+ return flipped;
104
+ }
105
+ function mergedRuns(graph, layout, walks) {
106
+ const flipped = straightened(walks[0]);
107
+ const before = /* @__PURE__ */ new Map();
108
+ for (const { sequence, is_secondary } of walks) {
109
+ if (!is_secondary) {
110
+ sequence.forEach((step, i) => {
111
+ const id = forward(step);
112
+ const against = isReverse(step) !== flipped.has(id);
113
+ const neighbour = against ? sequence[i + 1] : sequence[i - 1];
114
+ if (!layout.nodeMap.has(id) && neighbour !== void 0) {
115
+ before.set(id, forward(neighbour));
116
+ }
117
+ });
118
+ }
119
+ }
120
+ const lengthOf = new Map(graph.nodes.map((n) => [n.id, Math.max(1, n.length)]));
121
+ const after = new Map([...before].map(([node, prev]) => [prev, node]));
122
+ const members = /* @__PURE__ */ new Map();
123
+ for (const head of after.keys()) {
124
+ const index = layout.nodeMap.get(head);
125
+ if (index !== void 0) {
126
+ const run = [head];
127
+ for (let n = after.get(head); n !== void 0; n = after.get(n)) {
128
+ run.push(n);
129
+ }
130
+ const bp = run.reduce((sum, id) => sum + (lengthOf.get(id) ?? 0), 0);
131
+ if (bp === layout.nodes[index].sequenceLength) {
132
+ members.set(head, run);
133
+ }
134
+ }
135
+ }
136
+ return { members, flipped };
137
+ }
138
+ var opposite = { start: "end", end: "start" };
139
+ var otherStrand = { "+": "-", "-": "+" };
140
+ function boxGraph(graph, { members, flipped }) {
141
+ if (members.size === 0) {
142
+ return graph;
143
+ }
144
+ const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
145
+ const boxOf = /* @__PURE__ */ new Map();
146
+ const inner = /* @__PURE__ */ new Set();
147
+ const drawn = (id, side) => flipped.has(id) ? opposite[side] : side;
148
+ for (const [box, run] of members) {
149
+ run.forEach((id, i) => {
150
+ boxOf.set(id, box);
151
+ const next = run[i + 1];
152
+ if (next !== void 0) {
153
+ inner.add(`${id}:${drawn(id, "end")} ${next}:${drawn(next, "start")}`);
154
+ }
155
+ });
156
+ }
157
+ const absorbed = new Set(
158
+ [...boxOf].filter(([id, box]) => id !== box).map(([id]) => id)
159
+ );
160
+ const nodes = graph.nodes.flatMap((node) => {
161
+ const run = members.get(node.id);
162
+ if (!run) {
163
+ return absorbed.has(node.id) ? [] : [node];
164
+ }
165
+ const parts = run.map((id) => nodeById.get(id));
166
+ const starts = parts.flatMap((p) => p.stable ? [p.stable.start] : []);
167
+ return [
168
+ {
169
+ ...node,
170
+ length: parts.reduce((sum, p) => sum + p.length, 0),
171
+ stable: node.stable && { ...node.stable, start: Math.min(...starts) }
172
+ }
173
+ ];
174
+ });
175
+ const leaving = (id, box, side) => drawn(box, drawn(id, side)) === "end" ? ownStrand(box) : otherStrand[ownStrand(box)];
176
+ const edges = graph.edges.flatMap((edge) => {
177
+ const sides = strandSides(edge);
178
+ const from = `${edge.from}:${sides.from}`;
179
+ const to = `${edge.to}:${sides.to}`;
180
+ if (inner.has(`${from} ${to}`) || inner.has(`${to} ${from}`)) {
181
+ return [];
182
+ }
183
+ const fromBox = boxOf.get(edge.from);
184
+ const toBox = boxOf.get(edge.to);
185
+ return [
186
+ {
187
+ ...edge,
188
+ ...fromBox === void 0 ? {} : {
189
+ from: fromBox,
190
+ fromStrand: leaving(edge.from, fromBox, sides.from)
191
+ },
192
+ ...toBox === void 0 ? {} : {
193
+ to: toBox,
194
+ toStrand: otherStrand[leaving(edge.to, toBox, sides.to)]
195
+ }
196
+ }
197
+ ];
198
+ });
199
+ const absorbedNames = new Set(
200
+ [...absorbed].map((id) => nodeById.get(id)?.name ?? id)
201
+ );
202
+ return {
203
+ ...graph,
204
+ nodes,
205
+ edges,
206
+ paths: graph.paths?.map((path) => ({
207
+ ...path,
208
+ nodeIds: path.nodeIds.filter((id) => !absorbed.has(id))
209
+ })),
210
+ pathVisits: graph.pathVisits && new Map(
211
+ [...graph.pathVisits].filter(([name]) => !absorbedNames.has(name))
212
+ )
213
+ };
214
+ }
215
+ function runTubeMap(graph) {
216
+ const paths = graph.paths ?? [];
217
+ const tracks = tubeMapTracks(graph);
218
+ const reads = graph.reads ? tubeMapReads(graph, graph.reads, paths.length) : [];
219
+ const tubeColor = reads.length > 0 ? pathGreyCssColor : pathCssColor;
220
+ const pathColors = paths.map((_, i) => tubeColor(i, paths.length));
221
+ const layout = layoutTubeMap(tubeMapNodes(graph), tracks, reads, {
222
+ nodeWidthOption: "compressed",
223
+ trackWidth: tubeWidth(tracks.length)
224
+ });
225
+ if (!layout) {
226
+ return void 0;
227
+ }
228
+ const runs = mergedRuns(graph, layout, [...tracks, ...reads]);
229
+ return {
230
+ layout,
231
+ pathColors,
232
+ members: runs.members,
233
+ graph: boxGraph(graph, runs)
234
+ };
235
+ }
236
+ function drawnNodes(layout) {
237
+ const nodes = [];
238
+ layout.nodes.forEach((node) => {
239
+ if (node.order >= 0) {
240
+ nodes.push(node);
241
+ }
242
+ });
243
+ return nodes;
244
+ }
245
+ function centreline(node, yOffset, x0, x1) {
246
+ const y = node.y + node.contentHeight / 2 - yOffset;
247
+ return [
248
+ { x: x0, y },
249
+ { x: x1, y }
250
+ ];
251
+ }
252
+ function extentOf(layout, yOffset) {
253
+ return {
254
+ minY: layout.bounds.minY - yOffset,
255
+ maxY: layout.bounds.maxY - yOffset
256
+ };
257
+ }
258
+ function tubeMapLayout(graph) {
259
+ const run = hasTubeMapPaths(graph) ? runTubeMap(graph) : void 0;
260
+ if (!run) {
261
+ return void 0;
262
+ }
263
+ const { layout, ...drawing } = run;
264
+ const yOffset = layout.bounds.minY;
265
+ const nodePositions = {};
266
+ for (const node of drawnNodes(layout)) {
267
+ nodePositions[node.name] = centreline(
268
+ node,
269
+ yOffset,
270
+ node.x,
271
+ node.x + node.pixelWidth
272
+ );
273
+ }
274
+ const { minY, maxY } = extentOf(layout, yOffset);
275
+ return {
276
+ nodePositions,
277
+ tubeMap: { layout, yOffset, ...drawing },
278
+ extent: {
279
+ minX: layout.bounds.minX,
280
+ maxX: layout.bounds.maxX,
281
+ minY,
282
+ maxY
283
+ }
284
+ };
285
+ }
286
+ function tubeMapColumns(graph, layout) {
287
+ const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
288
+ const byOrder = /* @__PURE__ */ new Map();
289
+ for (const node of drawnNodes(layout)) {
290
+ const list = byOrder.get(node.order);
291
+ if (list) {
292
+ list.push(node);
293
+ } else {
294
+ byOrder.set(node.order, [node]);
295
+ }
296
+ }
297
+ const columns = [];
298
+ let end = -Infinity;
299
+ for (const order of [...byOrder.keys()].sort((a, b) => a - b)) {
300
+ const nodes = byOrder.get(order);
301
+ let bp0 = Infinity;
302
+ let bp1 = -Infinity;
303
+ for (const node of nodes) {
304
+ const graphNode = nodeById.get(node.name);
305
+ if (graphNode && isBackbone(graphNode)) {
306
+ bp0 = Math.min(bp0, graphNode.stable.start);
307
+ bp1 = Math.max(bp1, graphNode.stable.start + graphNode.length);
308
+ }
309
+ }
310
+ if (bp0 === Infinity) {
311
+ bp0 = bp1 = end;
312
+ }
313
+ if (bp0 < end) {
314
+ bp0 = end;
315
+ bp1 = Math.max(end, bp1);
316
+ }
317
+ end = Math.max(end, bp1);
318
+ columns.push({
319
+ order,
320
+ x0: Math.min(...nodes.map((n) => n.x)),
321
+ x1: Math.max(...nodes.map((n) => n.x + n.pixelWidth)),
322
+ bp0,
323
+ bp1
324
+ });
325
+ }
326
+ const first = columns.find((c) => c.bp0 !== -Infinity);
327
+ for (const column of columns) {
328
+ if (column.bp0 === -Infinity) {
329
+ column.bp0 = column.bp1 = first?.bp0 ?? 0;
330
+ }
331
+ }
332
+ return columns;
333
+ }
334
+ function hasTubeMapBackbone(graph) {
335
+ return hasTubeMapPaths(graph) && graph.nodes.some(isBackbone);
336
+ }
337
+ function tubeMapReferenceLayout(graph) {
338
+ const run = hasTubeMapBackbone(graph) ? runTubeMap(graph) : void 0;
339
+ if (!run) {
340
+ return void 0;
341
+ }
342
+ const { layout, ...drawing } = run;
343
+ const columns = tubeMapColumns(drawing.graph, layout);
344
+ const byOrder = new Map(columns.map((c) => [c.order, c]));
345
+ const yOffset = layout.bounds.minY;
346
+ const nodePositions = {};
347
+ for (const node of drawnNodes(layout)) {
348
+ const column = byOrder.get(node.order);
349
+ nodePositions[node.name] = centreline(node, yOffset, column.bp0, column.bp1);
350
+ }
351
+ return {
352
+ nodePositions,
353
+ tubeMap: { layout, yOffset, columns, ...drawing },
354
+ referenceAxis: true,
355
+ pixelRows: true,
356
+ extent: extentOf(layout, yOffset)
357
+ };
358
+ }
359
+
360
+ export {
361
+ pathSteps,
362
+ tubeMapTracks,
363
+ hasTubeMapPaths,
364
+ tubeWidth,
365
+ mergedRuns,
366
+ boxGraph,
367
+ tubeMapLayout,
368
+ hasTubeMapBackbone,
369
+ tubeMapReferenceLayout
370
+ };