@jbrowse/bandage-core 6.0.0 → 6.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +23 -7
  2. package/dist/alleleProjection/projectAlleles.js +10 -0
  3. package/dist/anchoredNodes.js +24 -0
  4. package/dist/bubbleSpreads.js +11 -0
  5. package/dist/bubbles/bubbleHalos.js +11 -0
  6. package/dist/bubbles/bubbleLine.js +14 -0
  7. package/dist/bubbles/bubblesFromGraph.js +10 -0
  8. package/dist/bubbles/classifyBubble.js +15 -0
  9. package/dist/bubbles/popBubble.js +9 -0
  10. package/dist/chunks/chunk-3C2LGJKY.js +108 -0
  11. package/dist/chunks/chunk-3PLYZLYV.js +513 -0
  12. package/dist/chunks/chunk-3S3YVOJ7.js +69 -0
  13. package/dist/chunks/chunk-3WHKR5TY.js +726 -0
  14. package/dist/chunks/chunk-47DYCZOK.js +115 -0
  15. package/dist/chunks/chunk-4S47XRYA.js +149 -0
  16. package/dist/chunks/chunk-527AI7CP.js +122 -0
  17. package/dist/chunks/chunk-57ZIXHZA.js +90 -0
  18. package/dist/chunks/chunk-5QT6NPSO.js +40 -0
  19. package/dist/chunks/chunk-6JEWNXCQ.js +90 -0
  20. package/dist/chunks/chunk-6Q6HFBJT.js +170 -0
  21. package/dist/chunks/chunk-6SBUFFTB.js +97 -0
  22. package/dist/chunks/chunk-6YRRNX2P.js +61 -0
  23. package/dist/chunks/chunk-72N5DJHZ.js +155 -0
  24. package/dist/chunks/chunk-73RSLGZT.js +73 -0
  25. package/dist/chunks/chunk-7CHJKABZ.js +82 -0
  26. package/dist/chunks/chunk-7XHHVYG7.js +13 -0
  27. package/dist/chunks/chunk-AB35TJST.js +67 -0
  28. package/dist/chunks/chunk-AE3UQZY3.js +43 -0
  29. package/dist/chunks/chunk-BJV2W4KZ.js +257 -0
  30. package/dist/chunks/chunk-BPLIXMH7.js +52 -0
  31. package/dist/chunks/chunk-BXEPA4KS.js +179 -0
  32. package/dist/chunks/chunk-C44YRP5F.js +85 -0
  33. package/dist/chunks/chunk-CDLPYR5O.js +135 -0
  34. package/dist/chunks/chunk-CPMHA4W5.js +6 -0
  35. package/dist/chunks/chunk-CR7EGAFE.js +241 -0
  36. package/dist/chunks/chunk-CZ54VG2H.js +431 -0
  37. package/dist/chunks/chunk-D3M7SBKL.js +210 -0
  38. package/dist/chunks/chunk-EJ5OZTCK.js +52 -0
  39. package/dist/chunks/chunk-ES2N6V56.js +83 -0
  40. package/dist/chunks/chunk-EZPBJPK6.js +54 -0
  41. package/dist/chunks/chunk-FYQWPEEY.js +65 -0
  42. package/dist/chunks/chunk-GQ2AOHU4.js +112 -0
  43. package/dist/chunks/chunk-HB6PDCOU.js +177 -0
  44. package/dist/chunks/chunk-HCANBJDD.js +326 -0
  45. package/dist/chunks/chunk-HIJVRRYE.js +174 -0
  46. package/dist/chunks/chunk-HNQPEMYE.js +297 -0
  47. package/dist/chunks/chunk-IGVV37VI.js +60 -0
  48. package/dist/chunks/chunk-IMW3AK27.js +102 -0
  49. package/dist/chunks/chunk-JKJWSY4V.js +32 -0
  50. package/dist/chunks/chunk-KCW7L3NV.js +6 -0
  51. package/dist/chunks/chunk-KQFOCDFY.js +73 -0
  52. package/dist/chunks/chunk-L3O7AWLI.js +52 -0
  53. package/dist/chunks/chunk-L5RYUPZI.js +53 -0
  54. package/dist/chunks/chunk-LOEA5IHZ.js +71 -0
  55. package/dist/chunks/chunk-LTUWJKHD.js +112 -0
  56. package/dist/chunks/chunk-LVZZH2NI.js +181 -0
  57. package/dist/chunks/chunk-MFKORG3F.js +44 -0
  58. package/dist/chunks/chunk-MHGHYHTL.js +205 -0
  59. package/dist/chunks/chunk-MI35EUIE.js +76 -0
  60. package/dist/chunks/chunk-MNUSQCLG.js +29 -0
  61. package/dist/chunks/chunk-NNODIW4U.js +26 -0
  62. package/dist/chunks/chunk-P7B3GDAY.js +232 -0
  63. package/dist/chunks/chunk-PBENCR5L.js +60 -0
  64. package/dist/chunks/chunk-PUG7LXDF.js +70 -0
  65. package/dist/chunks/chunk-Q3KCTA4N.js +202 -0
  66. package/dist/chunks/chunk-QCYM44UL.js +476 -0
  67. package/dist/chunks/chunk-QYBZZI5H.js +0 -0
  68. package/dist/chunks/chunk-RAKOB6IV.js +169 -0
  69. package/dist/chunks/chunk-RVS2QCOX.js +135 -0
  70. package/dist/chunks/chunk-SDGM243E.js +190 -0
  71. package/dist/chunks/chunk-ST2IBUAN.js +119 -0
  72. package/dist/chunks/chunk-SV27AJCC.js +101 -0
  73. package/dist/chunks/chunk-SZNZ2T6D.js +198 -0
  74. package/dist/chunks/chunk-TDQLINVI.js +16 -0
  75. package/dist/chunks/chunk-TEDQJPMI.js +131 -0
  76. package/dist/chunks/chunk-TZFWUT2C.js +66 -0
  77. package/dist/chunks/chunk-UVBGRJQX.js +50 -0
  78. package/dist/chunks/chunk-VHW7FDHU.js +42 -0
  79. package/dist/chunks/chunk-VKB57ZYE.js +218 -0
  80. package/dist/chunks/chunk-VZQ4FKLR.js +38 -0
  81. package/dist/chunks/chunk-WOO7QYKB.js +121 -0
  82. package/dist/chunks/chunk-WSIFAZU7.js +132 -0
  83. package/dist/chunks/chunk-X32PABB7.js +117 -0
  84. package/dist/chunks/chunk-YJQ7CT7F.js +23 -0
  85. package/dist/cli/figure.js +84 -17
  86. package/dist/colorSchemes.js +10 -0
  87. package/dist/deletionEdges.js +13 -0
  88. package/dist/el.js +8 -0
  89. package/dist/facetGrid.js +16 -0
  90. package/dist/figure.js +51 -0
  91. package/dist/gaf/parseGaf.js +12 -0
  92. package/dist/gbzCut.js +19 -0
  93. package/dist/gbzJoin.js +6 -0
  94. package/dist/gbzWindow.js +28 -0
  95. package/dist/genes/exonOutline.js +11 -0
  96. package/dist/genes/geneFiles.js +14 -0
  97. package/dist/genes/genePins.js +10 -0
  98. package/dist/gfa/gfaConverter.js +11 -0
  99. package/dist/gfa-core/gfaParser.js +8 -0
  100. package/dist/gfa-core/index.js +9 -0
  101. package/dist/graphLabels.js +24 -0
  102. package/dist/index.js +262 -1407
  103. package/dist/labelLayout.js +24 -0
  104. package/dist/layout/anchoredLayout.js +11 -0
  105. package/dist/layout/deletionRoutes.js +10 -0
  106. package/dist/layout/drawnScale.js +22 -0
  107. package/dist/layout/mergeRuns.js +15 -0
  108. package/dist/layout/orderedLayout.js +12 -0
  109. package/dist/layout/orientToReference.js +7 -0
  110. package/dist/layout/placeOffReference.js +9 -0
  111. package/dist/layout/referenceSeeds.js +11 -0
  112. package/dist/layout/rowSpacing.js +6 -0
  113. package/dist/layout/sampleRowLayout.js +11 -0
  114. package/dist/layout/trimToWindow.js +99 -0
  115. package/dist/layout/tubeMapLayout.js +28 -0
  116. package/dist/layout/walkRowDraw.js +65 -0
  117. package/dist/layout/walkRowLayout.js +13 -0
  118. package/dist/layout/walkRows.js +11 -0
  119. package/dist/layout/walkStrip.js +40 -0
  120. package/dist/layoutModes.js +35 -0
  121. package/dist/loadBandage.js +6 -0
  122. package/dist/nodeWidths.js +18 -0
  123. package/dist/overlayLabels.js +18 -0
  124. package/dist/pansn.js +18 -0
  125. package/dist/pathAnchoring.js +18 -0
  126. package/dist/pathColors.js +31 -0
  127. package/dist/pipeline.js +40 -0
  128. package/dist/reference.js +24 -0
  129. package/dist/referenceRampCss.js +29 -0
  130. package/dist/referenceSpan.js +11 -0
  131. package/dist/referenceStrip.js +35 -0
  132. package/dist/renderer/Canvas2DRenderer.js +9 -0
  133. package/dist/renderer/GeometryBuilder.js +42 -0
  134. package/dist/renderer/colorBits.js +8 -0
  135. package/dist/renderer/recordingCanvas.js +44 -0
  136. package/dist/renderer/svgCanvas.js +6 -0
  137. package/dist/renderer/types.js +0 -0
  138. package/dist/tubeMap/axis.js +25 -0
  139. package/dist/tubeMap/coarsen.js +361 -0
  140. package/dist/tubeMap/connectors.js +13 -0
  141. package/dist/tubeMap/deviations.js +85 -0
  142. package/dist/tubeMap/draw.js +15 -0
  143. package/dist/tubeMap/frame.js +9 -0
  144. package/dist/tubeMap/genes.js +20 -0
  145. package/dist/tubeMap/mismatches.js +6 -0
  146. package/dist/tubeMap/nodeColors.js +21 -0
  147. package/dist/tubeMap/reads.js +17 -0
  148. package/dist/tubeMap/warp.js +8 -0
  149. package/dist/types/version.d.ts +1 -1
  150. package/dist/types.js +0 -0
  151. package/dist/util/SpatialIndex.js +13 -0
  152. package/dist/util/edgeCurves.js +10 -0
  153. package/dist/util/geometry.js +39 -0
  154. package/dist/util/hitDetection.js +20 -0
  155. package/dist/util/wheelZoom.js +6 -0
  156. package/dist/version.js +6 -0
  157. package/dist/viewport.js +33 -0
  158. package/dist/walkEncoding.js +39 -0
  159. package/dist/walkHighlight.js +26 -0
  160. package/dist/walkKey.js +17 -0
  161. package/package.json +38 -5
  162. package/dist/chunks/chunk-OHMWSW6U.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
+ };