@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.
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-2CVHXFK7.js +530 -0
  11. package/dist/chunks/chunk-3S3YVOJ7.js +69 -0
  12. package/dist/chunks/chunk-47DYCZOK.js +115 -0
  13. package/dist/chunks/chunk-4EMPBO7S.js +117 -0
  14. package/dist/chunks/chunk-4S47XRYA.js +149 -0
  15. package/dist/chunks/chunk-5XQDMQXU.js +431 -0
  16. package/dist/chunks/chunk-6JEWNXCQ.js +90 -0
  17. package/dist/chunks/chunk-6SBUFFTB.js +97 -0
  18. package/dist/chunks/chunk-6SXMCK6I.js +476 -0
  19. package/dist/chunks/chunk-72N5DJHZ.js +155 -0
  20. package/dist/chunks/chunk-73RSLGZT.js +73 -0
  21. package/dist/chunks/chunk-7XHHVYG7.js +13 -0
  22. package/dist/chunks/chunk-AB35TJST.js +67 -0
  23. package/dist/chunks/chunk-BPLIXMH7.js +52 -0
  24. package/dist/chunks/chunk-BUBZ32IC.js +101 -0
  25. package/dist/chunks/chunk-BXEPA4KS.js +179 -0
  26. package/dist/chunks/chunk-C44YRP5F.js +85 -0
  27. package/dist/chunks/chunk-CDLPYR5O.js +135 -0
  28. package/dist/chunks/chunk-CJ2EE4AF.js +40 -0
  29. package/dist/chunks/chunk-CPMHA4W5.js +6 -0
  30. package/dist/chunks/chunk-CR7EGAFE.js +241 -0
  31. package/dist/chunks/chunk-CS3E4ETM.js +202 -0
  32. package/dist/chunks/chunk-CTSBC2O6.js +90 -0
  33. package/dist/chunks/chunk-D3M7SBKL.js +210 -0
  34. package/dist/chunks/chunk-DKOB2OPC.js +177 -0
  35. package/dist/chunks/chunk-EJ5OZTCK.js +52 -0
  36. package/dist/chunks/chunk-EK4HGYO7.js +174 -0
  37. package/dist/chunks/chunk-EPZORFTI.js +23 -0
  38. package/dist/chunks/chunk-ES2N6V56.js +83 -0
  39. package/dist/chunks/chunk-EZPBJPK6.js +54 -0
  40. package/dist/chunks/chunk-FYQWPEEY.js +65 -0
  41. package/dist/chunks/chunk-GGQRADFS.js +170 -0
  42. package/dist/chunks/chunk-GOY6REMT.js +43 -0
  43. package/dist/chunks/chunk-GQ2AOHU4.js +112 -0
  44. package/dist/chunks/chunk-HNQPEMYE.js +297 -0
  45. package/dist/chunks/chunk-IGVV37VI.js +60 -0
  46. package/dist/chunks/chunk-IO2PCPZI.js +218 -0
  47. package/dist/chunks/chunk-ISEI2FH3.js +108 -0
  48. package/dist/chunks/chunk-JKJWSY4V.js +32 -0
  49. package/dist/chunks/chunk-KQFOCDFY.js +73 -0
  50. package/dist/chunks/chunk-L3O7AWLI.js +52 -0
  51. package/dist/chunks/chunk-LLJUG2H7.js +6 -0
  52. package/dist/chunks/chunk-LOEA5IHZ.js +71 -0
  53. package/dist/chunks/chunk-LTUWJKHD.js +112 -0
  54. package/dist/chunks/chunk-MFKORG3F.js +44 -0
  55. package/dist/chunks/chunk-MI35EUIE.js +76 -0
  56. package/dist/chunks/chunk-MNUSQCLG.js +29 -0
  57. package/dist/chunks/chunk-MQJUG2D2.js +53 -0
  58. package/dist/chunks/chunk-MREQ7GIA.js +181 -0
  59. package/dist/chunks/chunk-N62KBLFC.js +326 -0
  60. package/dist/chunks/chunk-NNODIW4U.js +26 -0
  61. package/dist/chunks/chunk-PBENCR5L.js +60 -0
  62. package/dist/chunks/chunk-PCZ4PTYC.js +726 -0
  63. package/dist/chunks/chunk-PUG7LXDF.js +70 -0
  64. package/dist/chunks/chunk-QYBZZI5H.js +0 -0
  65. package/dist/chunks/chunk-R6ZUK4Y3.js +232 -0
  66. package/dist/chunks/chunk-RAKOB6IV.js +169 -0
  67. package/dist/chunks/chunk-ROF2R3RF.js +205 -0
  68. package/dist/chunks/chunk-RVS2QCOX.js +135 -0
  69. package/dist/chunks/chunk-ST2IBUAN.js +119 -0
  70. package/dist/chunks/chunk-SZNZ2T6D.js +198 -0
  71. package/dist/chunks/chunk-T43W7NKK.js +122 -0
  72. package/dist/chunks/chunk-TAMCZHLW.js +190 -0
  73. package/dist/chunks/chunk-TDQLINVI.js +16 -0
  74. package/dist/chunks/chunk-TEDQJPMI.js +131 -0
  75. package/dist/chunks/chunk-TZFWUT2C.js +66 -0
  76. package/dist/chunks/chunk-UVBGRJQX.js +50 -0
  77. package/dist/chunks/chunk-VHW7FDHU.js +42 -0
  78. package/dist/chunks/chunk-VZQ4FKLR.js +38 -0
  79. package/dist/chunks/chunk-WFX3MAKA.js +102 -0
  80. package/dist/chunks/chunk-WOO7QYKB.js +121 -0
  81. package/dist/chunks/chunk-WSIFAZU7.js +132 -0
  82. package/dist/chunks/chunk-X5L37W4J.js +82 -0
  83. package/dist/chunks/chunk-Y3IOTZJA.js +61 -0
  84. package/dist/chunks/chunk-YMSSF2RK.js +257 -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-EXSFRX2F.js +0 -7400
@@ -0,0 +1,241 @@
1
+ import {
2
+ tubeMapMismatches
3
+ } from "./chunk-EJ5OZTCK.js";
4
+
5
+ // src/tubeMap/draw.ts
6
+ import { curvePaths, nodeOutlinePath } from "@gmod/tubemap-core";
7
+ function parsePath(d) {
8
+ const tokens = d.trim().split(/[\s,]+/);
9
+ const commands = [];
10
+ let i = 0;
11
+ const num = () => Number(tokens[i++]);
12
+ while (i < tokens.length) {
13
+ const op = tokens[i++];
14
+ if (op === "M" || op === "L") {
15
+ commands.push({ op, x: num(), y: num() });
16
+ } else if (op === "H") {
17
+ commands.push({ op, x: num() });
18
+ } else if (op === "V") {
19
+ commands.push({ op, y: num() });
20
+ } else if (op === "Q") {
21
+ commands.push({ op, x1: num(), y1: num(), x: num(), y: num() });
22
+ } else if (op === "C") {
23
+ commands.push({
24
+ op,
25
+ x1: num(),
26
+ y1: num(),
27
+ x2: num(),
28
+ y2: num(),
29
+ x: num(),
30
+ y: num()
31
+ });
32
+ } else if (op === "Z") {
33
+ commands.push({ op });
34
+ }
35
+ }
36
+ return commands;
37
+ }
38
+ function extentOf(commands) {
39
+ let x0 = Infinity;
40
+ let x1 = -Infinity;
41
+ for (const c of commands) {
42
+ if ("x" in c) {
43
+ x0 = Math.min(x0, c.x);
44
+ x1 = Math.max(x1, c.x);
45
+ }
46
+ if ("x1" in c) {
47
+ x0 = Math.min(x0, c.x1);
48
+ x1 = Math.max(x1, c.x1);
49
+ }
50
+ }
51
+ return { x0, x1 };
52
+ }
53
+ function shapeOf(d, color, alpha, track) {
54
+ const commands = parsePath(d);
55
+ return { commands, color, track, alpha, ...extentOf(commands) };
56
+ }
57
+ function layerOf(layout, type) {
58
+ const { shapes } = layout;
59
+ const trackOf = (s) => type === "haplotype" ? s.id : void 0;
60
+ const rects = [...shapes.rectangles, ...shapes.verticalRectangles].filter((r) => r.type === type).map((r) => ({
61
+ color: r.color,
62
+ track: trackOf(r),
63
+ alpha: r.alpha ?? 1,
64
+ x0: r.xStart,
65
+ x1: r.xEnd + 1,
66
+ y0: r.yStart,
67
+ y1: r.yEnd + 1
68
+ }));
69
+ const curves = curvePaths(shapes.curves, type).map(
70
+ (c) => shapeOf(c.path, c.color, c.alpha ?? 1, trackOf(c))
71
+ );
72
+ const corners = shapes.corners.filter((c) => c.type === type).map((c) => shapeOf(c.path, c.color, 1, trackOf(c)));
73
+ return { rects, shapes: [...curves, ...corners] };
74
+ }
75
+ function tubeMapPicture(layout) {
76
+ const nodes = [];
77
+ layout.nodes.forEach((node) => {
78
+ if (node.order >= 0) {
79
+ const commands = parsePath(nodeOutlinePath(node));
80
+ nodes.push({ name: node.name, commands, ...extentOf(commands) });
81
+ }
82
+ });
83
+ return {
84
+ bounds: layout.bounds,
85
+ layers: [layerOf(layout, "haplotype"), layerOf(layout, "read")],
86
+ nodes,
87
+ mismatches: tubeMapMismatches(layout).filter(
88
+ (m) => m.kind !== "insertion" || !m.softClip
89
+ )
90
+ };
91
+ }
92
+ function trace(ctx, commands, x, y) {
93
+ let cx = 0;
94
+ let cy = 0;
95
+ for (const c of commands) {
96
+ switch (c.op) {
97
+ case "M":
98
+ cx = c.x;
99
+ cy = c.y;
100
+ ctx.moveTo(x(cx), y(cy));
101
+ break;
102
+ case "L":
103
+ cx = c.x;
104
+ cy = c.y;
105
+ ctx.lineTo(x(cx), y(cy));
106
+ break;
107
+ case "H":
108
+ cx = c.x;
109
+ ctx.lineTo(x(cx), y(cy));
110
+ break;
111
+ case "V":
112
+ cy = c.y;
113
+ ctx.lineTo(x(cx), y(cy));
114
+ break;
115
+ case "Q":
116
+ cx = c.x;
117
+ cy = c.y;
118
+ ctx.quadraticCurveTo(x(c.x1), y(c.y1), x(cx), y(cy));
119
+ break;
120
+ case "C":
121
+ cx = c.x;
122
+ cy = c.y;
123
+ ctx.bezierCurveTo(x(c.x1), y(c.y1), x(c.x2), y(c.y2), x(cx), y(cy));
124
+ break;
125
+ case "Z":
126
+ ctx.closePath();
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ function fillByColor(ctx, items, visible, colorOf, addTo) {
132
+ const byColor = /* @__PURE__ */ new Map();
133
+ for (const item of items) {
134
+ if (visible(item)) {
135
+ const key = `${colorOf(item)}|${item.alpha}`;
136
+ const list = byColor.get(key);
137
+ if (list) {
138
+ list.push(item);
139
+ } else {
140
+ byColor.set(key, [item]);
141
+ }
142
+ }
143
+ }
144
+ for (const list of byColor.values()) {
145
+ ctx.fillStyle = colorOf(list[0]);
146
+ ctx.globalAlpha = list[0].alpha;
147
+ ctx.beginPath();
148
+ for (const item of list) {
149
+ addTo(item);
150
+ }
151
+ ctx.fill();
152
+ }
153
+ ctx.globalAlpha = 1;
154
+ }
155
+ var FULL_OUTLINE_PX = 12;
156
+ var MIN_OUTLINE_ALPHA = 0.15;
157
+ function outlineAlpha(screenWidth) {
158
+ return Math.max(MIN_OUTLINE_ALPHA, Math.min(1, screenWidth / FULL_OUTLINE_PX));
159
+ }
160
+ var NODE_TINT_ALPHA = 0.55;
161
+ function drawTubeMap(ctx, picture, frame) {
162
+ const { x, y, width } = frame;
163
+ const visible = (item) => x(item.x1) >= 0 && x(item.x0) <= width;
164
+ const colorOf = (item) => item.track !== void 0 && frame.tubeColors?.[item.track] || item.color;
165
+ for (const layer of picture.layers) {
166
+ fillByColor(ctx, layer.rects, visible, colorOf, (r) => {
167
+ const left = x(r.x0);
168
+ const top = y(r.y0);
169
+ ctx.rect(left, top, x(r.x1) - left, y(r.y1) - top);
170
+ });
171
+ fillByColor(ctx, layer.shapes, visible, colorOf, (s) => {
172
+ trace(ctx, s.commands, x, y);
173
+ });
174
+ }
175
+ const stroke = frame.darkMode ? "#d0d0d0" : "#000000";
176
+ const fill = frame.darkMode ? "rgba(40,40,40,0.4)" : "rgba(255,255,255,0.4)";
177
+ ctx.lineWidth = 2 * Math.max(0.25, Math.min(1, frame.yScale));
178
+ for (const node of picture.nodes) {
179
+ if (visible(node)) {
180
+ const lit = node.name === frame.highlightNode;
181
+ ctx.beginPath();
182
+ trace(ctx, node.commands, x, y);
183
+ const tint = lit ? void 0 : frame.nodeColors?.get(node.name);
184
+ ctx.fillStyle = lit ? "rgba(255,192,203,0.5)" : tint ?? fill;
185
+ ctx.strokeStyle = lit ? "#ff0000" : stroke;
186
+ ctx.globalAlpha = tint ? NODE_TINT_ALPHA : 1;
187
+ ctx.fill();
188
+ ctx.globalAlpha = lit ? 1 : outlineAlpha(x(node.x1) - x(node.x0));
189
+ ctx.stroke();
190
+ ctx.globalAlpha = 1;
191
+ }
192
+ }
193
+ drawMismatches(ctx, picture.mismatches, frame);
194
+ }
195
+ var MISMATCH_FONT_PX = 12;
196
+ var MIN_MISMATCH_FONT_PX = 6;
197
+ function mismatchesLegible(yScale) {
198
+ return MISMATCH_FONT_PX * yScale >= MIN_MISMATCH_FONT_PX;
199
+ }
200
+ function mismatchOnScreen(m, { x, width }) {
201
+ const [x0, x1] = m.kind === "insertion" ? [m.x, m.x] : [m.x0, m.x1];
202
+ return x(x1) >= 0 && x(x0) <= width;
203
+ }
204
+ function drawMismatches(ctx, marks, frame) {
205
+ const { x, y, yScale, darkMode } = frame;
206
+ if (!mismatchesLegible(yScale) || marks.length === 0) {
207
+ return;
208
+ }
209
+ const fontPx = MISMATCH_FONT_PX * yScale;
210
+ ctx.fillStyle = "grey";
211
+ ctx.beginPath();
212
+ for (const m of marks) {
213
+ if (m.kind === "deletion" && mismatchOnScreen(m, frame)) {
214
+ const left = x(m.x0);
215
+ const top = y(m.y);
216
+ ctx.rect(left, top, x(m.x1) - left, y(m.y + m.height) - top);
217
+ }
218
+ }
219
+ ctx.fill();
220
+ ctx.font = `${fontPx}px monospace`;
221
+ ctx.textBaseline = "alphabetic";
222
+ ctx.fillStyle = darkMode ? "#ffffff" : "#000000";
223
+ for (const m of marks) {
224
+ if (!mismatchOnScreen(m, frame)) {
225
+ continue;
226
+ }
227
+ if (m.kind === "substitution") {
228
+ ctx.fillText(m.seq, x(m.x0) + 1, y(m.y + m.height));
229
+ } else if (m.kind === "insertion") {
230
+ ctx.fillText("*", x(m.x) - 3, y(m.y + m.height));
231
+ }
232
+ }
233
+ }
234
+
235
+ export {
236
+ parsePath,
237
+ tubeMapPicture,
238
+ drawTubeMap,
239
+ mismatchesLegible,
240
+ mismatchOnScreen
241
+ };
@@ -0,0 +1,202 @@
1
+ import {
2
+ formatBp
3
+ } from "./chunk-N62KBLFC.js";
4
+ import {
5
+ isBackbone
6
+ } from "./chunk-FYQWPEEY.js";
7
+
8
+ // src/tubeMap/axis.ts
9
+ function referenceBoxes(graph, layout) {
10
+ const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
11
+ const byRefName = /* @__PURE__ */ new Map();
12
+ layout.nodes.forEach((node) => {
13
+ const graphNode = nodeById.get(node.name);
14
+ if (node.order >= 0 && graphNode && isBackbone(graphNode)) {
15
+ const { refName, start } = graphNode.stable;
16
+ const boxes = byRefName.get(refName) ?? byRefName.set(refName, []).get(refName);
17
+ boxes.push({
18
+ bp0: start,
19
+ bp1: start + node.sequenceLength,
20
+ x0: node.x,
21
+ x1: node.x + node.pixelWidth
22
+ });
23
+ }
24
+ });
25
+ for (const boxes of byRefName.values()) {
26
+ boxes.sort((a, b) => a.bp0 - b.bp0);
27
+ }
28
+ return byRefName;
29
+ }
30
+ function tubeX(boxes, bp, isEnd = false) {
31
+ let lo = 0;
32
+ let hi = boxes.length - 1;
33
+ while (lo < hi) {
34
+ const mid = lo + hi >> 1;
35
+ const bp1 = boxes[mid].bp1;
36
+ if (isEnd ? bp1 < bp : bp1 <= bp) {
37
+ lo = mid + 1;
38
+ } else {
39
+ hi = mid;
40
+ }
41
+ }
42
+ const box = boxes[lo];
43
+ if (bp <= box.bp0) {
44
+ return box.x0;
45
+ }
46
+ const span = box.bp1 - box.bp0;
47
+ return span > 0 ? box.x0 + (Math.min(bp, box.bp1) - box.bp0) / span * (box.x1 - box.x0) : box.x0;
48
+ }
49
+ function tubeSpan(boxes, start, end) {
50
+ const a = tubeX(boxes, start);
51
+ const b = tubeX(boxes, end, true);
52
+ return { x0: Math.min(a, b), x1: Math.max(a, b) };
53
+ }
54
+ function rulerBoxes(byRefName) {
55
+ let best;
56
+ for (const boxes of byRefName.values()) {
57
+ if (!best || boxes.length > best.length) {
58
+ best = boxes;
59
+ }
60
+ }
61
+ return best;
62
+ }
63
+ var TICK_PX = 5;
64
+ var LABEL_GAP_PX = 10;
65
+ var TARGET_TICK_PX = 110;
66
+ var MIN_BRACKET_PX = 3;
67
+ function rulerInk(darkMode) {
68
+ return darkMode ? "#b0b0b8" : "#55555c";
69
+ }
70
+ function niceStep(raw) {
71
+ const pow = 10 ** Math.floor(Math.log10(raw));
72
+ const m = raw / pow;
73
+ return (m <= 1 ? 1 : m <= 2 ? 2 : m <= 5 ? 5 : 10) * pow;
74
+ }
75
+ function placeLabels(candidates) {
76
+ const placed = [];
77
+ for (const a of candidates) {
78
+ if (placed.every(
79
+ (b) => Math.abs(a.at - b.at) >= (a.width + b.width) / 2 + LABEL_GAP_PX
80
+ )) {
81
+ placed.push(a);
82
+ }
83
+ }
84
+ return placed.map(({ text, at }) => ({ text, at }));
85
+ }
86
+ function scaleMarks(boxes, x, width, measure) {
87
+ const first = boxes[0];
88
+ const last = boxes.at(-1);
89
+ if (!first || !last) {
90
+ return void 0;
91
+ }
92
+ const from = x(first.x0);
93
+ const to = x(last.x1);
94
+ const bpSpan = last.bp1 - first.bp0;
95
+ if (to <= from || bpSpan <= 0) {
96
+ return void 0;
97
+ }
98
+ const step = niceStep(bpSpan / (to - from) * TARGET_TICK_PX);
99
+ const ticks = [];
100
+ const positions = [];
101
+ for (let bp = Math.ceil(first.bp0 / step) * step; bp <= last.bp1; bp += step) {
102
+ const sx = Math.round(x(tubeX(boxes, bp))) + 0.5;
103
+ if (sx >= 0 && sx <= width) {
104
+ ticks.push(sx);
105
+ const text = bp.toLocaleString("en-US");
106
+ positions.push({ text, at: sx, width: measure(text) });
107
+ }
108
+ }
109
+ return {
110
+ spans: [{ x0: from, x1: to }],
111
+ ticks,
112
+ labels: placeLabels(positions)
113
+ };
114
+ }
115
+ function boxMarks(boxes, x, width, measure) {
116
+ const shown = boxes.map((box) => ({ box, s0: x(box.x0), s1: x(box.x1) })).filter(({ s0, s1 }) => s1 >= 0 && s0 <= width);
117
+ const first = shown[0];
118
+ const last = shown.at(-1);
119
+ if (!first || !last) {
120
+ return void 0;
121
+ }
122
+ const label = (text, at) => ({
123
+ text,
124
+ at,
125
+ width: measure(text)
126
+ });
127
+ const position = (bp, at) => label(bp.toLocaleString("en-US"), at);
128
+ const spans = [];
129
+ const ticks = [];
130
+ const lengths = [];
131
+ const boundaries = [];
132
+ for (const { box, s0, s1 } of shown) {
133
+ spans.push({ x0: s0, x1: s1 });
134
+ ticks.push(Math.round(s0) + 0.5);
135
+ if (s1 - s0 >= MIN_BRACKET_PX) {
136
+ ticks.push(Math.round(s1) + 0.5);
137
+ }
138
+ const length = label(formatBp(box.bp1 - box.bp0), (s0 + s1) / 2);
139
+ if (length.width <= s1 - s0) {
140
+ lengths.push(length);
141
+ }
142
+ if (box !== first.box) {
143
+ boundaries.push(position(box.bp0, s0));
144
+ }
145
+ }
146
+ const ends = [
147
+ position(first.box.bp0, first.s0),
148
+ position(last.box.bp1, last.s1)
149
+ ];
150
+ return {
151
+ spans,
152
+ ticks,
153
+ labels: placeLabels([...ends, ...lengths, ...boundaries])
154
+ };
155
+ }
156
+ function rulerMarks(boxes, x, width, measure, referenceAxis = false) {
157
+ return (referenceAxis ? scaleMarks : boxMarks)(boxes, x, width, measure);
158
+ }
159
+ function drawTubeMapRuler(ctx, boxes, frame, top, referenceAxis = false) {
160
+ const { x, width, darkMode } = frame;
161
+ ctx.font = "10px sans-serif";
162
+ const marks = rulerMarks(
163
+ boxes,
164
+ x,
165
+ width,
166
+ (text) => ctx.measureText(text).width,
167
+ referenceAxis
168
+ );
169
+ if (!marks) {
170
+ return;
171
+ }
172
+ const ink = rulerInk(darkMode);
173
+ const y = top + 0.5;
174
+ ctx.strokeStyle = ink;
175
+ ctx.fillStyle = ink;
176
+ ctx.lineWidth = 1;
177
+ ctx.textAlign = "center";
178
+ ctx.textBaseline = "top";
179
+ ctx.beginPath();
180
+ for (const { x0, x1 } of marks.spans) {
181
+ ctx.moveTo(Math.max(0, x0), y);
182
+ ctx.lineTo(Math.min(width, x1), y);
183
+ }
184
+ for (const sx of marks.ticks) {
185
+ ctx.moveTo(sx, top);
186
+ ctx.lineTo(sx, top + TICK_PX);
187
+ }
188
+ ctx.stroke();
189
+ for (const { text, at } of marks.labels) {
190
+ ctx.fillText(text, at, top + TICK_PX + 2);
191
+ }
192
+ }
193
+
194
+ export {
195
+ referenceBoxes,
196
+ tubeX,
197
+ tubeSpan,
198
+ rulerBoxes,
199
+ rulerInk,
200
+ rulerMarks,
201
+ drawTubeMapRuler
202
+ };
@@ -0,0 +1,90 @@
1
+ import {
2
+ tubeSpan
3
+ } from "./chunk-CS3E4ETM.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
+ };