@jbrowse/bandage-core 11.7.3 → 11.8.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 (99) hide show
  1. package/README.md +59 -19
  2. package/dist/bubbles/bubbleHalos.js +6 -6
  3. package/dist/bubbles/classifyBubble.js +5 -5
  4. package/dist/bubbles/superbubbles.js +7 -7
  5. package/dist/chunks/{chunk-ZZZ6NVLW.js → chunk-22ZMVREZ.js} +1 -1
  6. package/dist/chunks/{chunk-VXKL2ZLR.js → chunk-2H3ALI6L.js} +223 -45
  7. package/dist/chunks/{chunk-F4CDC72I.js → chunk-2RCKHCF7.js} +1 -1
  8. package/dist/chunks/{chunk-P276TWPM.js → chunk-3CXZUC2Y.js} +1 -1
  9. package/dist/chunks/{chunk-VNF236GG.js → chunk-3XYLEK4H.js} +2 -2
  10. package/dist/chunks/{chunk-PKGPSTRW.js → chunk-4OVIUCSB.js} +2 -2
  11. package/dist/chunks/{chunk-BLDUBB7X.js → chunk-4YPCUVMC.js} +2 -2
  12. package/dist/chunks/{chunk-N73RA4DR.js → chunk-6CXLLJ2C.js} +3 -3
  13. package/dist/chunks/{chunk-4SN7R4UF.js → chunk-6MD4VS2F.js} +1 -1
  14. package/dist/chunks/{chunk-VACK24HF.js → chunk-735MPLUL.js} +35 -9
  15. package/dist/chunks/chunk-7GDNDQD7.js +504 -0
  16. package/dist/chunks/{chunk-2US43KNG.js → chunk-7PEYA3NA.js} +2 -2
  17. package/dist/chunks/{chunk-XTCIKBWM.js → chunk-AXE3RR7J.js} +1 -1
  18. package/dist/chunks/{chunk-BXE3SBBN.js → chunk-C3KAPREU.js} +1 -1
  19. package/dist/chunks/{chunk-QJC63NFR.js → chunk-G3GLGDKV.js} +2 -2
  20. package/dist/chunks/{chunk-63XWYNID.js → chunk-G4WQBRDS.js} +4 -4
  21. package/dist/chunks/{chunk-SDI6BVG7.js → chunk-J4PDHWP2.js} +1 -1
  22. package/dist/chunks/{chunk-GD6RUZU5.js → chunk-JKIAHYAW.js} +14 -8
  23. package/dist/chunks/{chunk-AJQRKN3X.js → chunk-L2RQV23D.js} +1 -1
  24. package/dist/chunks/{chunk-EPOPGV5G.js → chunk-LA6JHEVV.js} +1 -1
  25. package/dist/chunks/{chunk-G7SG4XMK.js → chunk-LODKC7PJ.js} +7 -0
  26. package/dist/chunks/{chunk-QXBQDHKL.js → chunk-MO4DIRHD.js} +80 -38
  27. package/dist/chunks/{chunk-IHM43Y7B.js → chunk-N2GAA7RB.js} +1 -1
  28. package/dist/chunks/{chunk-6RB5ZQ34.js → chunk-N4AJV4GH.js} +25 -14
  29. package/dist/chunks/{chunk-LH436HS2.js → chunk-NHOXLUIL.js} +2 -2
  30. package/dist/chunks/{chunk-QLLKALSH.js → chunk-NQWEYJDD.js} +1 -1
  31. package/dist/chunks/{chunk-AWJJSA7D.js → chunk-OIOAK7BS.js} +1 -1
  32. package/dist/chunks/{chunk-UTDMFVC7.js → chunk-R3NPBUKT.js} +8 -8
  33. package/dist/chunks/{chunk-4KOYS5WR.js → chunk-R4XCTRIU.js} +1 -1
  34. package/dist/chunks/{chunk-XKPXOIWD.js → chunk-S3ICPPXC.js} +3 -3
  35. package/dist/chunks/{chunk-K4KVAMEF.js → chunk-S4CH434Z.js} +1 -1
  36. package/dist/chunks/{chunk-HMEAGAGC.js → chunk-S7BCZVRK.js} +2 -2
  37. package/dist/chunks/{chunk-SG7VQB4F.js → chunk-SDDMV2MM.js} +1 -1
  38. package/dist/chunks/{chunk-KMLATBNT.js → chunk-SIXFXXRI.js} +3 -3
  39. package/dist/chunks/{chunk-T3MEG5D3.js → chunk-U6JPSXJ3.js} +90 -21
  40. package/dist/chunks/{chunk-SENPGW4O.js → chunk-VCUOYVLC.js} +1 -1
  41. package/dist/chunks/{chunk-33KUO34R.js → chunk-VSRRIFRF.js} +1 -1
  42. package/dist/chunks/{chunk-BFUS3ENH.js → chunk-X5ULFAMM.js} +2 -2
  43. package/dist/chunks/{chunk-W5YRBEWQ.js → chunk-XOIYBEU7.js} +1 -1
  44. package/dist/chunks/{chunk-CL6ELLVF.js → chunk-YEDZ37PH.js} +1 -1
  45. package/dist/chunks/{chunk-Z4HXW6ZH.js → chunk-YF6T33PU.js} +10 -2
  46. package/dist/chunks/{chunk-2VMKQKVO.js → chunk-YIWN32ZX.js} +1 -1
  47. package/dist/chunks/{chunk-2T3V6Z3M.js → chunk-ZKIH2USC.js} +1 -1
  48. package/dist/cli/figure.js +31 -30
  49. package/dist/cli/fold.js +6 -6
  50. package/dist/deletionEdges.js +2 -2
  51. package/dist/figure.js +27 -26
  52. package/dist/figureData.js +25 -24
  53. package/dist/figureScene.js +18 -17
  54. package/dist/figureSettings.js +6 -6
  55. package/dist/foldVariants.js +2 -2
  56. package/dist/genes/genePins.js +3 -3
  57. package/dist/graphLabels.js +4 -4
  58. package/dist/index.js +45 -44
  59. package/dist/labelLayout.js +8 -8
  60. package/dist/layout/forceCache.js +6 -6
  61. package/dist/layout/mergeRuns.js +2 -2
  62. package/dist/layout/orientToReference.js +2 -2
  63. package/dist/layout/tubeMapLayout.js +9 -9
  64. package/dist/layout/walkRowDraw.js +9 -8
  65. package/dist/layout/walkRowLayout.js +3 -3
  66. package/dist/layout/walkRowRuns.js +1 -1
  67. package/dist/layout/walkRows.js +2 -2
  68. package/dist/layout/walkStrip.js +13 -12
  69. package/dist/layoutModes.js +11 -11
  70. package/dist/pathColors.js +5 -5
  71. package/dist/pipeline.js +5 -5
  72. package/dist/referenceStrip.js +9 -8
  73. package/dist/renderer/GeometryBuilder.js +8 -7
  74. package/dist/renderer/walkLanes.js +44 -0
  75. package/dist/tubeMap/axis.js +5 -5
  76. package/dist/tubeMap/bundle.js +10 -10
  77. package/dist/tubeMap/coarsen.js +10 -10
  78. package/dist/tubeMap/deviations.js +11 -11
  79. package/dist/tubeMap/draw.js +5 -5
  80. package/dist/tubeMap/genes.js +6 -6
  81. package/dist/tubeMap/nodeColors.js +9 -8
  82. package/dist/tubeMap/reads.js +2 -2
  83. package/dist/types/figureData.d.ts +22 -0
  84. package/dist/types/renderer/types.d.ts +20 -0
  85. package/dist/types/renderer/walkLanes.d.ts +60 -0
  86. package/dist/types/util/geometry.d.ts +13 -1
  87. package/dist/types/version.d.ts +1 -1
  88. package/dist/types/walkEncoding.d.ts +7 -5
  89. package/dist/types/walkHighlight.d.ts +3 -0
  90. package/dist/util/SpatialIndex.js +4 -4
  91. package/dist/util/edgeCurves.js +3 -3
  92. package/dist/util/geometry.js +7 -1
  93. package/dist/util/hitDetection.js +5 -5
  94. package/dist/version.js +1 -1
  95. package/dist/viewport.js +6 -6
  96. package/dist/walkEncoding.js +9 -8
  97. package/dist/walkHighlight.js +10 -9
  98. package/dist/walkKey.js +5 -5
  99. package/package.json +1 -1
@@ -97,7 +97,8 @@ function visibleStretches(curves, lengths, bounds, yToX) {
97
97
  });
98
98
  return stretches;
99
99
  }
100
- function dashCurves(curves, dashWorld, yToX = 1, bounds) {
100
+ var dashCurves = (curves, dashWorld, yToX = 1, bounds) => dashSpans(curves, dashWorld, yToX, bounds).map((d) => d.curves);
101
+ function dashSpans(curves, dashWorld, yToX = 1, bounds) {
101
102
  const lengths = curves.map(
102
103
  (c) => Math.hypot(c.cx0 - c.x0, (c.cy0 - c.y0) * yToX) + Math.hypot(c.cx1 - c.cx0, (c.cy1 - c.cy0) * yToX) + Math.hypot(c.x1 - c.cx1, (c.y1 - c.cy1) * yToX)
103
104
  );
@@ -122,27 +123,49 @@ function dashCurves(curves, dashWorld, yToX = 1, bounds) {
122
123
  }
123
124
  }
124
125
  const dash = [];
126
+ const along = [];
125
127
  let start = 0;
126
128
  curves.forEach((c, k) => {
127
129
  const length = lengths[k];
128
130
  const end = start + length;
129
131
  if (length > 0 && end > from && start < to) {
130
- dash.push(
131
- subCurve(
132
- c,
133
- Math.max(0, (from - start) / length),
134
- Math.min(1, (to - start) / length)
135
- )
136
- );
132
+ const t0 = Math.max(0, (from - start) / length);
133
+ const t1 = Math.min(1, (to - start) / length);
134
+ dash.push(subCurve(c, t0, t1));
135
+ along.push((k + t0) / curves.length, (k + t1) / curves.length);
137
136
  }
138
137
  start = end;
139
138
  });
140
139
  if (dash.length) {
141
- out.push(dash);
140
+ out.push({
141
+ curves: dash,
142
+ startAlong: along[0],
143
+ endAlong: along.at(-1)
144
+ });
142
145
  }
143
146
  }
144
147
  return out;
145
148
  }
149
+ var slideAt = (from, to, along) => ({
150
+ dx: from.dx + (to.dx - from.dx) * along,
151
+ dy: from.dy + (to.dy - from.dy) * along
152
+ });
153
+ function slideCurves(curves, from, to) {
154
+ return curves.map((c, i) => {
155
+ const a = slideAt(from, to, i / curves.length);
156
+ const b = slideAt(from, to, (i + 1) / curves.length);
157
+ return {
158
+ x0: c.x0 + a.dx,
159
+ y0: c.y0 + a.dy,
160
+ cx0: c.cx0 + a.dx,
161
+ cy0: c.cy0 + a.dy,
162
+ cx1: c.cx1 + b.dx,
163
+ cy1: c.cy1 + b.dy,
164
+ x1: c.x1 + b.dx,
165
+ y1: c.y1 + b.dy
166
+ };
167
+ });
168
+ }
146
169
  function translateCurves(curves, dx, dy) {
147
170
  const out = new Array(curves.length);
148
171
  for (let i = 0; i < curves.length; i++) {
@@ -555,6 +578,9 @@ export {
555
578
  curveMidpoint,
556
579
  curveBounds,
557
580
  dashCurves,
581
+ dashSpans,
582
+ slideAt,
583
+ slideCurves,
558
584
  translateCurves,
559
585
  ownStrand,
560
586
  strandSides,
@@ -0,0 +1,504 @@
1
+ import {
2
+ slideAt
3
+ } from "./chunk-735MPLUL.js";
4
+
5
+ // src/renderer/walkLanes.ts
6
+ var THREAD_PX = 2;
7
+ var THREAD_GAP_PX = 1.5;
8
+ var SLOT_GAP_PX = 4;
9
+ var LANE_SMOOTHING_PX = 12;
10
+ var THREAD_REACH_PX = 64;
11
+ var THREAD_TAPER_PX = 24;
12
+ var MAX_BAND_PX = 48;
13
+ var PASSES_KEY = "a thread per pass through a node";
14
+ var threadsPasses = (walks) => walks.length > 1 && walks.some((w) => w.mostPasses > 1);
15
+ var threadsWidth = (threads) => threads * THREAD_PX + Math.max(0, threads - 1) * THREAD_GAP_PX;
16
+ var sum = (values) => values.reduce((a, b) => a + b, 0);
17
+ function slotLayout(spans, mostPasses, laneWidth) {
18
+ const widths = spans.map(
19
+ (m, i) => mostPasses[i] > 1 ? threadsWidth(m) : laneWidth * Math.min(1, m)
20
+ );
21
+ let seen = 0;
22
+ const gaps = spans.map((m) => {
23
+ const share = Math.min(1, m);
24
+ const gap = SLOT_GAP_PX * share * seen;
25
+ seen = Math.max(seen, share);
26
+ return gap;
27
+ });
28
+ return { widths, gaps, band: sum(widths) + sum(gaps) };
29
+ }
30
+ var THREAD_PITCH_PX = THREAD_PX + THREAD_GAP_PX;
31
+ function slotCentres(spans, mostPasses, held = spans) {
32
+ const { widths, gaps, band } = slotLayout(spans, mostPasses, THREAD_PX);
33
+ let at = -band / 2;
34
+ return widths.map((width, i) => {
35
+ at += gaps[i];
36
+ const slack = mostPasses[i] > 1 ? Math.max(0, width - threadsWidth(held[i])) : 0;
37
+ const centre = at + slack / 2 + THREAD_PX / 2;
38
+ at += width;
39
+ return centre;
40
+ });
41
+ }
42
+ var bandWidthPx = (mostPasses) => slotLayout(mostPasses, mostPasses, THREAD_PX).band;
43
+ var bandScale = (mostPasses) => Math.min(1, MAX_BAND_PX / (bandWidthPx(mostPasses) || 1));
44
+ var passesKey = (walks) => bandScale(walks.map((w) => w.mostPasses)) < 1 ? `${PASSES_KEY}, thinned to fit` : PASSES_KEY;
45
+ function laneSlideAt(start, end, along) {
46
+ const vector = ({ normal, offsetX: offsetX2 }) => ({
47
+ dx: normal.nx * offsetX2,
48
+ dy: normal.ny * offsetX2
49
+ });
50
+ const { dx, dy } = slideAt(vector(start), vector(end), along);
51
+ const offsetX = Math.hypot(dx, dy);
52
+ return offsetX > 0 ? { normal: { nx: dx / offsetX, ny: dy / offsetX }, offsetX } : { normal: { nx: 0, ny: 0 }, offsetX: 0 };
53
+ }
54
+ var gapOf = (a, b) => Math.hypot(b.x - a.x, b.y - a.y);
55
+ function arcOf(run) {
56
+ const s = [0];
57
+ for (let i = 1; i < run.length; i++) {
58
+ s.push(s[i - 1] + gapOf(run[i - 1], run[i]));
59
+ }
60
+ return s;
61
+ }
62
+ function laneRuns(routes, nodePositions, yToX, jump) {
63
+ const seen = /* @__PURE__ */ new Set();
64
+ const owns = routes.map(
65
+ (route) => route.map((id) => {
66
+ const first = !!nodePositions[id]?.length && !seen.has(id);
67
+ seen.add(id);
68
+ return first;
69
+ })
70
+ );
71
+ const gap = (a, b) => Math.hypot(b.x - a.x, b.y * yToX - a.y);
72
+ const runs = [];
73
+ const owned = [];
74
+ let run = [];
75
+ let mine = [];
76
+ const breakRun = () => {
77
+ if (run.length) {
78
+ runs.push(run);
79
+ owned.push(mine);
80
+ }
81
+ run = [];
82
+ mine = [];
83
+ };
84
+ routes.forEach((route, r) => {
85
+ if (!owns[r].includes(true)) {
86
+ return;
87
+ }
88
+ route.forEach((id, step) => {
89
+ const segments = nodePositions[id];
90
+ if (!segments?.length) {
91
+ breakRun();
92
+ return;
93
+ }
94
+ const first = segments[0];
95
+ const last = segments.at(-1);
96
+ const prev = run.at(-1);
97
+ const next = nodePositions[route[step + 1] ?? ""];
98
+ const toNext = (p) => {
99
+ const at = { x: p.x, y: p.y * yToX };
100
+ return next?.length ? Math.min(gap(at, next[0]), gap(at, next.at(-1))) : 0;
101
+ };
102
+ const forward = prev ? gap(prev, first) <= gap(prev, last) : toNext(last) <= toNext(first);
103
+ if (prev && gap(prev, forward ? first : last) > jump) {
104
+ breakRun();
105
+ }
106
+ const middle = Math.floor(segments.length / 2);
107
+ for (let k = 0; k < segments.length; k++) {
108
+ const index = forward ? k : segments.length - 1 - k;
109
+ const p = segments[index];
110
+ run.push({
111
+ x: p.x,
112
+ y: p.y * yToX,
113
+ id,
114
+ index,
115
+ middle: index === middle,
116
+ forward
117
+ });
118
+ mine.push(owns[r][step]);
119
+ }
120
+ });
121
+ breakRun();
122
+ });
123
+ return { runs, owned };
124
+ }
125
+ function ownedWindows(s, owned, margin) {
126
+ const windows = [];
127
+ for (let i = 0; i < owned.length; i++) {
128
+ if (!owned[i]) {
129
+ continue;
130
+ }
131
+ let j = i;
132
+ while (j + 1 < owned.length && owned[j + 1]) {
133
+ j++;
134
+ }
135
+ const last = windows.at(-1);
136
+ const floor = last ? last[1] : 0;
137
+ let lo = i;
138
+ while (lo > floor && s[i] - s[lo - 1] <= margin) {
139
+ lo--;
140
+ }
141
+ let hi = j;
142
+ while (hi + 1 < s.length && s[hi + 1] - s[j] <= margin) {
143
+ hi++;
144
+ }
145
+ if (last && lo <= last[1] + 1) {
146
+ last[1] = hi;
147
+ } else {
148
+ windows.push([lo, hi]);
149
+ }
150
+ i = j;
151
+ }
152
+ return windows;
153
+ }
154
+ function smoothedNormals(run, reach) {
155
+ const s = arcOf(run);
156
+ const end = s.at(-1);
157
+ const pointAt = (at, j) => {
158
+ const a = run[j];
159
+ const b = run[Math.min(j + 1, run.length - 1)];
160
+ const span = s[Math.min(j + 1, run.length - 1)] - s[j];
161
+ const f = span > 0 ? (at - s[j]) / span : 0;
162
+ return { x: a.x + (b.x - a.x) * f, y: a.y + (b.y - a.y) * f };
163
+ };
164
+ let lo = 0;
165
+ let hi = 0;
166
+ return run.map((_, i) => {
167
+ const back = Math.max(0, s[i] - reach / 2);
168
+ const ahead = Math.min(end, s[i] + reach / 2);
169
+ while (lo < run.length - 1 && s[lo + 1] <= back) {
170
+ lo++;
171
+ }
172
+ while (hi < run.length - 1 && s[hi + 1] <= ahead) {
173
+ hi++;
174
+ }
175
+ const a = pointAt(back, lo);
176
+ const b = pointAt(ahead, hi);
177
+ const len = gapOf(a, b);
178
+ return len > 0 ? { nx: -(b.y - a.y) / len, ny: (b.x - a.x) / len } : void 0;
179
+ });
180
+ }
181
+ function cutCurls(run, close, length) {
182
+ const s = arcOf(run);
183
+ const out = run.slice();
184
+ const picks = [];
185
+ s.forEach((at, k) => {
186
+ const last = picks.at(-1);
187
+ if (last === void 0 || at - s[last] >= close / 4) {
188
+ picks.push(k);
189
+ }
190
+ });
191
+ if (picks.at(-1) !== run.length - 1) {
192
+ picks.push(run.length - 1);
193
+ }
194
+ let a = 0;
195
+ while (a < picks.length) {
196
+ const i = picks[a];
197
+ let to = -1;
198
+ for (let b = a + 1; b < picks.length && s[picks[b]] - s[i] <= length; b++) {
199
+ const j2 = picks[b];
200
+ if (s[j2] - s[i] > Math.PI * close && gapOf(run[i], run[j2]) <= close) {
201
+ to = b;
202
+ }
203
+ }
204
+ if (to < 0) {
205
+ a++;
206
+ continue;
207
+ }
208
+ const j = picks[to];
209
+ const p = run[i];
210
+ const q = run[j];
211
+ for (let k = i + 1; k < j; k++) {
212
+ const f = (s[k] - s[i]) / (s[j] - s[i]);
213
+ out[k] = {
214
+ ...run[k],
215
+ x: p.x + (q.x - p.x) * f,
216
+ y: p.y + (q.y - p.y) * f
217
+ };
218
+ }
219
+ a = to;
220
+ }
221
+ return out;
222
+ }
223
+ function rounded(run, half) {
224
+ const s = arcOf(run);
225
+ const end = s.at(-1);
226
+ const sums = [{ w: 0, x: 0, y: 0 }];
227
+ run.forEach((p, i) => {
228
+ const w = (s[Math.min(i + 1, run.length - 1)] - s[Math.max(i - 1, 0)]) / 2;
229
+ const last = sums[i];
230
+ sums.push({ w: last.w + w, x: last.x + w * p.x, y: last.y + w * p.y });
231
+ });
232
+ const firstAtOrAfter = (at) => {
233
+ let lo = 0;
234
+ let hi = s.length;
235
+ while (lo < hi) {
236
+ const mid = lo + hi >> 1;
237
+ if (s[mid] < at) {
238
+ lo = mid + 1;
239
+ } else {
240
+ hi = mid;
241
+ }
242
+ }
243
+ return lo;
244
+ };
245
+ return run.map((p, i) => {
246
+ const reach = Math.min(half, s[i], end - s[i]);
247
+ const lo = firstAtOrAfter(s[i] - reach);
248
+ const hi = firstAtOrAfter(s[i] + reach + 1e-9 * (1 + end));
249
+ const w = sums[hi].w - sums[lo].w;
250
+ return w > 0 ? {
251
+ x: (sums[hi].x - sums[lo].x) / w,
252
+ y: (sums[hi].y - sums[lo].y) / w
253
+ } : { x: p.x, y: p.y };
254
+ });
255
+ }
256
+ function fieldSetter(field, nodePositions) {
257
+ return ({ id, index }, value) => {
258
+ const own = field.get(id) ?? new Array(nodePositions[id].length);
259
+ own[index] = value;
260
+ field.set(id, own);
261
+ };
262
+ }
263
+ function laneLines(lanes, nodePositions, yToX, reach, band, smooth = true) {
264
+ const turn = Math.max(reach, 2 * band);
265
+ const curl = Math.PI * band;
266
+ const margin = smooth ? curl + band / 2 + turn / 2 : turn / 2;
267
+ const normals = /* @__PURE__ */ new Map();
268
+ const shifts = /* @__PURE__ */ new Map();
269
+ const setNormal = fieldSetter(normals, nodePositions);
270
+ const setShift = fieldSetter(shifts, nodePositions);
271
+ lanes.runs.forEach((run, r) => {
272
+ const owned = lanes.owned[r];
273
+ const pieces = ownedWindows(arcOf(run), owned, margin).map(([lo, hi]) => {
274
+ const points = run.slice(lo, hi + 1);
275
+ const line = smooth ? rounded(cutCurls(points, band / 2, curl), band / 2) : points;
276
+ return { lo, points, line, normals: smoothedNormals(line, turn) };
277
+ });
278
+ let agree = 0;
279
+ let along = 0;
280
+ for (const piece of pieces) {
281
+ piece.points.forEach((p, i) => {
282
+ const theirs = normals.get(p.id)?.[p.index];
283
+ const mine = piece.normals[i];
284
+ if (theirs && mine) {
285
+ agree += theirs.nx * mine.nx + theirs.ny * mine.ny;
286
+ }
287
+ along += p.forward ? 1 : -1;
288
+ });
289
+ }
290
+ const sign = (agree === 0 ? along : agree) < 0 ? -1 : 1;
291
+ for (const { lo, points, line, normals: own } of pieces) {
292
+ points.forEach((p, i) => {
293
+ if (!owned[lo + i]) {
294
+ return;
295
+ }
296
+ const n = own[i];
297
+ if (n) {
298
+ setNormal(p, sign > 0 ? n : { nx: -n.nx, ny: -n.ny });
299
+ }
300
+ if (smooth) {
301
+ setShift(p, {
302
+ dx: line[i].x - p.x,
303
+ dy: (line[i].y - p.y) / yToX
304
+ });
305
+ }
306
+ });
307
+ }
308
+ });
309
+ return { normals, shifts };
310
+ }
311
+ function windowed(s, v, half, sign) {
312
+ const out = new Array(v.length);
313
+ const deque = [];
314
+ let head = 0;
315
+ let next = 0;
316
+ for (let i = 0; i < v.length; i++) {
317
+ while (next < v.length && s[next] - s[i] <= half) {
318
+ while (deque.length > head && sign * v[deque.at(-1)] <= sign * v[next]) {
319
+ deque.pop();
320
+ }
321
+ deque.push(next++);
322
+ }
323
+ while (s[i] - s[deque[head]] > half) {
324
+ head++;
325
+ }
326
+ out[i] = v[deque[head]];
327
+ }
328
+ return out;
329
+ }
330
+ function windowMean(s, v, half, counted = () => true) {
331
+ const n = v.length;
332
+ const weight = s.map(
333
+ (_, i) => !counted(v[i]) ? 0 : n > 1 ? (s[Math.min(i + 1, n - 1)] - s[Math.max(i - 1, 0)]) / 2 : 1
334
+ );
335
+ const sumW = [0];
336
+ const sumWV = [0];
337
+ for (let i = 0; i < n; i++) {
338
+ sumW.push(sumW[i] + weight[i]);
339
+ sumWV.push(sumWV[i] + weight[i] * v[i]);
340
+ }
341
+ let lo = 0;
342
+ let hi = 0;
343
+ return v.map((value, i) => {
344
+ while (s[i] - s[lo] > half) {
345
+ lo++;
346
+ }
347
+ while (hi < n && s[hi] - s[i] <= half) {
348
+ hi++;
349
+ }
350
+ const w = sumW[hi] - sumW[lo];
351
+ return w > 0 ? (sumWV[hi] - sumWV[lo]) / w : value;
352
+ });
353
+ }
354
+ function walkThreads(route, passes, nodePositions, yToX, reach) {
355
+ if (![...passes.values()].some((p) => p > 1)) {
356
+ return { span: passes, nodeThreads: /* @__PURE__ */ new Map(), linkThreads: /* @__PURE__ */ new Map() };
357
+ }
358
+ const span = /* @__PURE__ */ new Map();
359
+ const visits = /* @__PURE__ */ new Map();
360
+ const wanted = /* @__PURE__ */ new Map();
361
+ const steps = [];
362
+ for (const piece of route) {
363
+ const points = [];
364
+ for (const id of piece) {
365
+ const segments = nodePositions[id];
366
+ const mid = segments?.[Math.floor(segments.length / 2)];
367
+ points.push(
368
+ mid ? { x: mid.x, y: mid.y * yToX } : points.at(-1) ?? { x: 0, y: 0 }
369
+ );
370
+ }
371
+ const s = arcOf(points);
372
+ const p = piece.map((id) => passes.get(id) ?? 1);
373
+ const most = windowed(s, p, reach / 2, 1);
374
+ const copy = piece.map((id) => {
375
+ const visit = (visits.get(id) ?? 0) + 1;
376
+ visits.set(id, visit);
377
+ return visit;
378
+ });
379
+ const full = p.map((v, i) => v === most[i]);
380
+ const before = [];
381
+ let last = -1;
382
+ for (let i = 0; i < piece.length; i++) {
383
+ last = full[i] ? i : last;
384
+ before.push(last);
385
+ }
386
+ const after = new Array(piece.length);
387
+ last = -1;
388
+ for (let i = piece.length - 1; i >= 0; i--) {
389
+ last = full[i] ? i : last;
390
+ after[i] = last;
391
+ }
392
+ const own = [];
393
+ piece.forEach((id, i) => {
394
+ const b = before[i];
395
+ const a = after[i];
396
+ const nearest = b < 0 ? a : a < 0 ? b : s[i] - s[b] <= s[a] - s[i] ? b : a;
397
+ const thread = Math.min(copy[nearest], most[i]) - 1;
398
+ span.set(id, Math.max(span.get(id) ?? 0, most[i]));
399
+ const list = wanted.get(id) ?? [];
400
+ list.push({ visit: copy[i], thread });
401
+ wanted.set(id, list);
402
+ own.push({ id, visit: copy[i] });
403
+ });
404
+ steps.push(own);
405
+ }
406
+ const threadOf = /* @__PURE__ */ new Map();
407
+ const nodeThreads = /* @__PURE__ */ new Map();
408
+ for (const [id, list] of wanted) {
409
+ const width = span.get(id);
410
+ const taken = /* @__PURE__ */ new Set();
411
+ const free = (want) => {
412
+ for (let d = 0; d < width; d++) {
413
+ for (const c of [want - d, want + d]) {
414
+ if (c >= 0 && c < width && !taken.has(c)) {
415
+ return c;
416
+ }
417
+ }
418
+ }
419
+ return want;
420
+ };
421
+ const byVisit = [];
422
+ for (const { visit, thread } of list) {
423
+ const t = free(thread);
424
+ taken.add(t);
425
+ byVisit[visit] = t;
426
+ }
427
+ threadOf.set(id, byVisit);
428
+ nodeThreads.set(
429
+ id,
430
+ [...taken].sort((a, b) => a - b)
431
+ );
432
+ }
433
+ const linkThreads = /* @__PURE__ */ new Map();
434
+ for (const own of steps) {
435
+ own.forEach((cur, i) => {
436
+ const prev = own[i - 1];
437
+ if (!prev) {
438
+ return;
439
+ }
440
+ const key = `${prev.id}>${cur.id}`;
441
+ const pair = [
442
+ threadOf.get(prev.id)[prev.visit],
443
+ threadOf.get(cur.id)[cur.visit]
444
+ ];
445
+ const list = linkThreads.get(key) ?? [];
446
+ if (!list.some(([a, b]) => a === pair[0] && b === pair[1])) {
447
+ list.push(pair);
448
+ }
449
+ linkThreads.set(key, list);
450
+ });
451
+ }
452
+ return { span, nodeThreads, linkThreads };
453
+ }
454
+ function bandSpans(lanes, spans, { reach, taper }) {
455
+ const out = /* @__PURE__ */ new Map();
456
+ lanes.runs.forEach((run, r) => {
457
+ const s = arcOf(run);
458
+ const mids = run.flatMap((p, i) => p.middle ? [i] : []);
459
+ const along = mids.map((i) => s[i]);
460
+ const owned = mids.map((i) => lanes.owned[r][i]);
461
+ for (const [lo, hi] of ownedWindows(along, owned, reach / 2 + taper / 2)) {
462
+ const at = along.slice(lo, hi + 1);
463
+ const ids = mids.slice(lo, hi + 1).map((i) => run[i].id);
464
+ const eased = spans.map((own) => {
465
+ const raw = ids.map((id) => own.get(id) ?? 0);
466
+ return raw.every((v) => v === raw[0]) ? { slot: raw, held: raw } : {
467
+ slot: windowMean(at, windowed(at, raw, reach / 2, 1), taper / 2),
468
+ held: windowMean(at, raw, taper / 2, (v) => v > 0)
469
+ };
470
+ });
471
+ ids.forEach((id, i) => {
472
+ if (owned[lo + i]) {
473
+ out.set(id, {
474
+ slot: eased.map((e) => e.slot[i]),
475
+ held: eased.map((e) => e.held[i])
476
+ });
477
+ }
478
+ });
479
+ }
480
+ });
481
+ return out;
482
+ }
483
+
484
+ export {
485
+ THREAD_PX,
486
+ THREAD_GAP_PX,
487
+ SLOT_GAP_PX,
488
+ LANE_SMOOTHING_PX,
489
+ THREAD_REACH_PX,
490
+ THREAD_TAPER_PX,
491
+ MAX_BAND_PX,
492
+ PASSES_KEY,
493
+ threadsPasses,
494
+ THREAD_PITCH_PX,
495
+ slotCentres,
496
+ bandWidthPx,
497
+ bandScale,
498
+ passesKey,
499
+ laneSlideAt,
500
+ laneRuns,
501
+ laneLines,
502
+ walkThreads,
503
+ bandSpans
504
+ };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  baseEdgeCurves
3
- } from "./chunk-33KUO34R.js";
3
+ } from "./chunk-VSRRIFRF.js";
4
4
  import {
5
5
  curveBounds,
6
6
  pathRibbonOffsets
7
- } from "./chunk-VACK24HF.js";
7
+ } from "./chunk-735MPLUL.js";
8
8
 
9
9
  // src/util/SpatialIndex.ts
10
10
  var FALLBACK_CELL_SIZE = 50;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pathSteps
3
- } from "./chunk-63XWYNID.js";
3
+ } from "./chunk-G4WQBRDS.js";
4
4
  import {
5
5
  pathOrigin
6
6
  } from "./chunk-3YBP46PB.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  walkRowRuns,
3
3
  walkRowsOf
4
- } from "./chunk-G7SG4XMK.js";
4
+ } from "./chunk-LODKC7PJ.js";
5
5
 
6
6
  // src/layout/walkRows.ts
7
7
  function filterSamples(rows, samples) {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  nodesUnderBox
3
- } from "./chunk-LH436HS2.js";
3
+ } from "./chunk-NHOXLUIL.js";
4
4
  import {
5
5
  graphLabels,
6
6
  rowLabelBox
7
- } from "./chunk-N73RA4DR.js";
7
+ } from "./chunk-6CXLLJ2C.js";
8
8
  import {
9
9
  LABEL_CHAR_PX,
10
10
  LABEL_PAD,
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  stepName,
3
3
  tubeMapReads
4
- } from "./chunk-SDI6BVG7.js";
4
+ } from "./chunk-J4PDHWP2.js";
5
5
  import {
6
6
  referenceWalkName
7
- } from "./chunk-G7SG4XMK.js";
7
+ } from "./chunk-LODKC7PJ.js";
8
8
  import {
9
9
  pathCssColor,
10
10
  pathGreyCssColor
11
- } from "./chunk-GD6RUZU5.js";
11
+ } from "./chunk-JKIAHYAW.js";
12
12
  import {
13
13
  pathOrigin
14
14
  } from "./chunk-3YBP46PB.js";
15
15
  import {
16
16
  ownStrand,
17
17
  strandSides
18
- } from "./chunk-VACK24HF.js";
18
+ } from "./chunk-735MPLUL.js";
19
19
  import {
20
20
  groupBy
21
21
  } from "./chunk-BSOZW3FC.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-5OJZV6YT.js";
4
4
  import {
5
5
  ownStrand
6
- } from "./chunk-VACK24HF.js";
6
+ } from "./chunk-735MPLUL.js";
7
7
 
8
8
  // src/tubeMap/reads.ts
9
9
  function stepName(node, strand) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  groupDigits
3
- } from "./chunk-N73RA4DR.js";
3
+ } from "./chunk-6CXLLJ2C.js";
4
4
  import {
5
5
  pathOrigin
6
6
  } from "./chunk-3YBP46PB.js";
@@ -52,18 +52,24 @@ function pathLegend(paths, colors) {
52
52
  firstOf.set(path.name, i);
53
53
  }
54
54
  });
55
- const entries = [...firstOf].map(([name, i]) => ({ name, i }));
55
+ const entries = [...firstOf].map(([name2, i]) => ({ name: name2, i }));
56
56
  const named = entries.map((e) => labelTiers(e.name));
57
- const candidates = [0, 1, 2].flatMap((t) => [
58
- entries.map((_, k) => named[k][t]),
59
- entries.map((e, k) => offsetTiers(e.name, named[k])[t])
60
- ]);
57
+ const name = (t) => entries.map((_, k) => named[k][t]);
58
+ const offset = (t) => entries.map((e, k) => offsetTiers(e.name, named[k])[t]);
59
+ const candidates = [
60
+ name(0),
61
+ name(1),
62
+ offset(0),
63
+ name(2),
64
+ offset(1),
65
+ offset(2)
66
+ ];
61
67
  const distinct = candidates.find(
62
68
  (labels2) => new Set(labels2).size === entries.length
63
69
  );
64
70
  const labels = distinct ?? candidates.at(-1);
65
- return entries.map(({ name, i }, k) => ({
66
- name,
71
+ return entries.map(({ name: name2, i }, k) => ({
72
+ name: name2,
67
73
  label: labels[k],
68
74
  color: colors?.[i] ?? pathCssColor(i, paths.length)
69
75
  }));