@jbrowse/bandage-core 8.2.0 → 9.0.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 (35) hide show
  1. package/dist/chunks/{chunk-3FXPFVKJ.js → chunk-AMS3MAFP.js} +1 -1
  2. package/dist/chunks/{chunk-DRA7733Q.js → chunk-BPRXQ2SS.js} +8 -3
  3. package/dist/chunks/{chunk-TEDQJPMI.js → chunk-CWFNEKST.js} +22 -7
  4. package/dist/chunks/chunk-INJ2UVOT.js +370 -0
  5. package/dist/chunks/{chunk-KCRL5JB2.js → chunk-LLP3FXPM.js} +31 -10
  6. package/dist/chunks/{chunk-DE4UFDUZ.js → chunk-TLQ7F2ZT.js} +1 -1
  7. package/dist/chunks/{chunk-OWFPAIJI.js → chunk-WEDZ76AK.js} +1 -1
  8. package/dist/chunks/{chunk-CF62U3QS.js → chunk-WNUZP3WQ.js} +2 -2
  9. package/dist/chunks/{chunk-733L2TS6.js → chunk-XXT2JCAC.js} +10 -7
  10. package/dist/chunks/{chunk-GQ2AOHU4.js → chunk-YOYLCLIG.js} +7 -29
  11. package/dist/cli/figure.js +5 -5
  12. package/dist/figure.js +2 -2
  13. package/dist/index.js +15 -13
  14. package/dist/layout/tubeMapLayout.js +12 -6
  15. package/dist/layoutModes.js +3 -3
  16. package/dist/tubeMap/axis.js +1 -1
  17. package/dist/tubeMap/coarsen.js +11 -23
  18. package/dist/tubeMap/connectors.js +1 -4
  19. package/dist/tubeMap/deviations.js +15 -19
  20. package/dist/tubeMap/draw.js +9 -5
  21. package/dist/tubeMap/genes.js +2 -2
  22. package/dist/tubeMap/mismatches.js +1 -1
  23. package/dist/tubeMap/reads.js +1 -1
  24. package/dist/types/index.d.ts +4 -4
  25. package/dist/types/layout/tubeMapLayout.d.ts +15 -2
  26. package/dist/types/layoutModes.d.ts +5 -5
  27. package/dist/types/tubeMap/axis.d.ts +6 -3
  28. package/dist/types/tubeMap/connectors.d.ts +2 -11
  29. package/dist/types/tubeMap/deviations.d.ts +2 -3
  30. package/dist/types/tubeMap/draw.d.ts +4 -1
  31. package/dist/types/tubeMap/mismatches.d.ts +1 -1
  32. package/dist/types/version.d.ts +1 -1
  33. package/dist/version.js +1 -1
  34. package/package.json +1 -1
  35. package/dist/chunks/chunk-W2I4DOL4.js +0 -230
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var version = "8.2.0";
2
+ var version = "9.0.0";
3
3
 
4
4
  export {
5
5
  version
@@ -6,17 +6,22 @@ import {
6
6
  } from "./chunk-FYQWPEEY.js";
7
7
 
8
8
  // src/tubeMap/axis.ts
9
- function referenceBoxes(graph, layout) {
9
+ function referenceBoxes({
10
+ graph,
11
+ layout
12
+ }) {
10
13
  const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
11
14
  const byRefName = /* @__PURE__ */ new Map();
12
- layout.nodes.forEach((node) => {
15
+ layout.nodes.forEach((node, index) => {
13
16
  const graphNode = nodeById.get(node.name);
14
17
  if (node.order >= 0 && graphNode && isBackbone(graphNode)) {
15
18
  const { refName, start } = graphNode.stable;
16
19
  const boxes = byRefName.get(refName) ?? byRefName.set(refName, []).get(refName);
17
20
  boxes.push({
21
+ name: node.name,
22
+ index,
18
23
  bp0: start,
19
- bp1: start + node.sequenceLength,
24
+ bp1: start + graphNode.length,
20
25
  x0: node.x,
21
26
  x1: node.x + node.pixelWidth
22
27
  });
@@ -30,16 +30,30 @@ function mismatchesByStep(record, lengths) {
30
30
  return byStep;
31
31
  }
32
32
  const starts = stepStarts(lengths, record.pathLength);
33
+ const known = starts.flatMap((start, s) => start === void 0 ? [] : [s]);
34
+ const startOf = (k) => starts[known[k]];
35
+ const endOf = (k) => startOf(k) + lengths[known[k]];
36
+ let first = 0;
33
37
  const stepAt = (pos2, atEnd = false) => {
34
- for (let s = 0; s < starts.length; s++) {
35
- const start = starts[s];
36
- const length = lengths[s];
37
- if (start !== void 0 && length !== void 0 && start <= pos2 && (pos2 < start + length || atEnd && pos2 === start + length)) {
38
- return { s, local: pos2 - start, room: start + length - pos2 };
38
+ while (first < known.length && endOf(first) < pos2) {
39
+ first++;
40
+ }
41
+ for (let k = first; k < known.length && startOf(k) <= pos2; k++) {
42
+ const end = endOf(k);
43
+ if (pos2 < end || atEnd && pos2 === end) {
44
+ return { s: known[k], local: pos2 - startOf(k), room: end - pos2 };
39
45
  }
40
46
  }
41
47
  return void 0;
42
48
  };
49
+ const unplacedFrom = (pos2) => {
50
+ for (let k = first; k < known.length; k++) {
51
+ if (startOf(k) > pos2) {
52
+ return startOf(k) - pos2;
53
+ }
54
+ }
55
+ return Infinity;
56
+ };
43
57
  const add = (s, mismatch) => {
44
58
  const list = byStep[s];
45
59
  const last = list.at(-1);
@@ -68,7 +82,7 @@ function mismatchesByStep(record, lengths) {
68
82
  let left = op.length;
69
83
  while (left > 0) {
70
84
  const at = stepAt(pos);
71
- const take = at ? Math.min(left, at.room) : left;
85
+ const take = Math.min(left, at ? at.room : unplacedFrom(pos));
72
86
  if (at) {
73
87
  add(at.s, { type: "deletion", pos: at.local, length: take });
74
88
  }
@@ -106,7 +120,8 @@ function tubeMapReads(graph, records, firstId) {
106
120
  })),
107
121
  firstNodeOffset: first === 0 ? record.pathStart : 0,
108
122
  finalNodeCoverLength: last === record.path.length - 1 ? lastLength - (record.pathLength - record.pathEnd) : lastLength,
109
- mapping_quality: record.mappingQuality === 255 ? 0 : record.mappingQuality,
123
+ // 255 is GAF's missing MAPQ
124
+ mapping_quality: record.mappingQuality === 255 ? void 0 : record.mappingQuality,
110
125
  is_secondary: record.secondary,
111
126
  is_reverse: record.strand === "-"
112
127
  });
@@ -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
+ };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  tubeMapMismatches
3
- } from "./chunk-733L2TS6.js";
3
+ } from "./chunk-XXT2JCAC.js";
4
4
  import {
5
5
  readColor
6
- } from "./chunk-TEDQJPMI.js";
6
+ } from "./chunk-CWFNEKST.js";
7
7
 
8
8
  // src/tubeMap/draw.ts
9
9
  import { curvePaths, nodeOutlinePath } from "@jbrowse/tubemap-core";
@@ -53,9 +53,9 @@ function extentOf(commands) {
53
53
  }
54
54
  return { x0, x1 };
55
55
  }
56
- function shapeOf(d, color, track) {
56
+ function shapeOf(d, color, id, haplotype) {
57
57
  const commands = parsePath(d);
58
- return { commands, color, track, ...extentOf(commands) };
58
+ return { commands, color, id, haplotype, ...extentOf(commands) };
59
59
  }
60
60
  function trackColors(layout, pathColors) {
61
61
  return new Map(
@@ -67,20 +67,21 @@ function trackColors(layout, pathColors) {
67
67
  }
68
68
  function layerOf(layout, type, colors) {
69
69
  const { shapes } = layout;
70
+ const haplotype = type === "haplotype";
70
71
  const colorOf = (s) => colors.get(s.id);
71
- const trackOf = (s) => type === "haplotype" ? s.id : void 0;
72
72
  const rects = [...shapes.rectangles, ...shapes.verticalRectangles].filter((r) => r.type === type).map((r) => ({
73
73
  color: colorOf(r),
74
- track: trackOf(r),
74
+ id: r.id,
75
+ haplotype,
75
76
  x0: r.xStart,
76
77
  x1: r.xEnd + 1,
77
78
  y0: r.yStart,
78
79
  y1: r.yEnd + 1
79
80
  }));
80
81
  const curves = curvePaths(shapes.curves, type).map(
81
- (c) => shapeOf(c.path, colorOf(c), trackOf(c))
82
+ (c) => shapeOf(c.path, colorOf(c), c.id, haplotype)
82
83
  );
83
- const corners = shapes.corners.filter((c) => c.type === type).map((c) => shapeOf(c.path, colorOf(c), trackOf(c)));
84
+ const corners = shapes.corners.filter((c) => c.type === type).map((c) => shapeOf(c.path, colorOf(c), c.id, haplotype));
84
85
  return { rects, shapes: [...curves, ...corners] };
85
86
  }
86
87
  function tubeMapPicture({
@@ -177,7 +178,7 @@ var NODE_TINT_ALPHA = 0.55;
177
178
  function drawTubeMap(ctx, picture, frame) {
178
179
  const { x, y, width } = frame;
179
180
  const visible = (item) => x(item.x1) >= 0 && x(item.x0) <= width;
180
- const colorOf = (item) => item.track !== void 0 && frame.tubeColors?.[item.track] || item.color;
181
+ const colorOf = (item) => item.haplotype && frame.tubeColors?.[item.id] || item.color;
181
182
  ctx.globalAlpha = 1;
182
183
  for (const layer of picture.layers) {
183
184
  fillByColor(ctx, layer.rects, visible, colorOf, (r) => {
@@ -235,6 +236,7 @@ function drawMismatches(ctx, marks, frame) {
235
236
  }
236
237
  ctx.fill();
237
238
  ctx.font = `${fontPx}px monospace`;
239
+ ctx.textAlign = "left";
238
240
  ctx.textBaseline = "alphabetic";
239
241
  ctx.fillStyle = darkMode ? "#ffffff" : "#000000";
240
242
  for (const m of marks) {
@@ -248,6 +250,23 @@ function drawMismatches(ctx, marks, frame) {
248
250
  }
249
251
  }
250
252
  }
253
+ function tubeMapTrackAt(picture, { x, y }, sx, sy) {
254
+ for (let l = picture.layers.length - 1; l >= 0; l--) {
255
+ for (const r of picture.layers[l].rects) {
256
+ if (x(r.x0) <= sx && sx <= x(r.x1) && y(r.y0) <= sy && sy <= y(r.y1)) {
257
+ return r.id;
258
+ }
259
+ }
260
+ }
261
+ return void 0;
262
+ }
263
+ var INSERTION_SLOP_PX = 4;
264
+ function tubeMapMismatchAt(picture, frame, sx, sy) {
265
+ const { x, y, yScale } = frame;
266
+ return mismatchesLegible(yScale) ? picture.mismatches.find(
267
+ (m) => y(m.y) <= sy && sy <= y(m.y + m.height) && (m.kind === "insertion" ? Math.abs(x(m.x) - sx) <= INSERTION_SLOP_PX : x(m.x0) <= sx && sx <= x(m.x1))
268
+ ) : void 0;
269
+ }
251
270
 
252
271
  export {
253
272
  parsePath,
@@ -255,5 +274,7 @@ export {
255
274
  tubeMapPicture,
256
275
  drawTubeMap,
257
276
  mismatchesLegible,
258
- mismatchOnScreen
277
+ mismatchOnScreen,
278
+ tubeMapTrackAt,
279
+ tubeMapMismatchAt
259
280
  };
@@ -42,7 +42,7 @@ import {
42
42
  } from "./chunk-HBEKUQO4.js";
43
43
  import {
44
44
  version
45
- } from "./chunk-3FXPFVKJ.js";
45
+ } from "./chunk-AMS3MAFP.js";
46
46
  import {
47
47
  axisScaleOf
48
48
  } from "./chunk-TL4B342F.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  tubeSpan
3
- } from "./chunk-DRA7733Q.js";
3
+ } from "./chunk-BPRXQ2SS.js";
4
4
 
5
5
  // src/tubeMap/genes.ts
6
6
  function tubeMapGenes(byRefName, genes) {
@@ -1,8 +1,9 @@
1
1
  import {
2
+ hasTubeMapBackbone,
2
3
  hasTubeMapPaths,
3
4
  tubeMapLayout,
4
5
  tubeMapReferenceLayout
5
- } from "./chunk-W2I4DOL4.js";
6
+ } from "./chunk-INJ2UVOT.js";
6
7
  import {
7
8
  sampleRowLayout
8
9
  } from "./chunk-LOEA5IHZ.js";
@@ -24,7 +25,6 @@ import {
24
25
  var FORCE_LAYOUT_LABEL = "Force-directed layout";
25
26
  var hasBackbone = (graph) => graph.nodes.some(isBackbone);
26
27
  var hasWalks = (graph) => hasBackbone(graph) && (graph.paths?.length ?? 0) > 1;
27
- var hasTubeMapBackbone = (graph) => hasBackbone(graph) && hasTubeMapPaths(graph);
28
28
  var hasAlleles = (graph) => hasBackbone(graph) && graph.nodes.some(isOffReference);
29
29
  var LAYOUT_MODES = [
30
30
  {
@@ -3,16 +3,13 @@ import { forward, getXCoordinateOfBaseWithinNode } from "@jbrowse/tubemap-core";
3
3
  function readMismatches(read, layout) {
4
4
  const marks = [];
5
5
  const entries = read.sequenceNew ?? [];
6
+ const visits = read.path.filter((segment) => segment.node !== null);
6
7
  entries.forEach((entry, i) => {
7
8
  const nodeIndex = layout.nodeMap.get(forward(entry.nodeName));
8
9
  const node = nodeIndex === void 0 ? void 0 : layout.nodes[nodeIndex];
9
- let pathIndex = i;
10
- while (pathIndex < read.path.length && read.path[pathIndex].node !== nodeIndex) {
11
- pathIndex += 1;
12
- }
13
- const y = read.path[pathIndex]?.y;
10
+ const y = visits[i]?.y;
14
11
  if (node && y !== void 0) {
15
- const base = { readId: read.id, y, height: read.width, nodeY: node.y };
12
+ const base = { readId: read.id, y, height: read.width };
16
13
  const at = (pos) => getXCoordinateOfBaseWithinNode(node, pos);
17
14
  for (const mm of entry.mismatches) {
18
15
  const x0 = at(mm.pos);
@@ -29,7 +26,13 @@ function readMismatches(read, layout) {
29
26
  } else if (mm.type === "deletion" && mm.length !== void 0) {
30
27
  const x1 = at(mm.pos + mm.length);
31
28
  if (x1 !== null) {
32
- marks.push({ ...base, kind: "deletion", x0, x1 });
29
+ marks.push({
30
+ ...base,
31
+ kind: "deletion",
32
+ x0,
33
+ x1,
34
+ length: mm.length
35
+ });
33
36
  }
34
37
  } else if (mm.type === "substitution" && mm.seq !== void 0) {
35
38
  const x1 = at(mm.pos + mm.seq.length);
@@ -1,35 +1,14 @@
1
- import {
2
- isBackbone
3
- } from "./chunk-FYQWPEEY.js";
4
-
5
1
  // src/tubeMap/connectors.ts
6
- function referenceNodes(graph, layout) {
7
- const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
8
- const out = [];
9
- layout.nodes.forEach((node) => {
10
- const graphNode = nodeById.get(node.name);
11
- if (node.order >= 0 && graphNode && isBackbone(graphNode)) {
12
- out.push({
13
- node: node.name,
14
- bp0: graphNode.stable.start,
15
- bp1: graphNode.stable.start + node.sequenceLength,
16
- x0: node.x,
17
- x1: node.x + node.pixelWidth
18
- });
19
- }
20
- });
21
- return out;
22
- }
23
- function tubeMapConnectors(nodes, bpToScreen, tubeToScreen, width) {
2
+ function tubeMapConnectors(boxes, bpToScreen, tubeToScreen, width) {
24
3
  const out = [];
25
- for (const n of nodes) {
26
- const bottom0 = tubeToScreen(n.x0);
27
- const bottom1 = tubeToScreen(n.x1);
4
+ for (const box of boxes) {
5
+ const bottom0 = tubeToScreen(box.x0);
6
+ const bottom1 = tubeToScreen(box.x1);
28
7
  if (bottom1 >= 0 && bottom0 <= width) {
29
8
  out.push({
30
- node: n.node,
31
- top0: bpToScreen(n.bp0),
32
- top1: bpToScreen(n.bp1),
9
+ node: box.name,
10
+ top0: bpToScreen(box.bp0),
11
+ top1: bpToScreen(box.bp1),
33
12
  bottom0,
34
13
  bottom1
35
14
  });
@@ -105,7 +84,6 @@ function drawTubeMapConnectors(ctx, connectors, bottom, {
105
84
  }
106
85
 
107
86
  export {
108
- referenceNodes,
109
87
  tubeMapConnectors,
110
88
  connectorAt,
111
89
  drawTubeMapConnectors