@jbrowse/bandage-core 11.7.1 → 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 (101) hide show
  1. package/README.md +106 -48
  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-3PZSBNLI.js → chunk-22ZMVREZ.js} +19 -14
  6. package/dist/chunks/{chunk-VB4IXIPM.js → chunk-2H3ALI6L.js} +223 -45
  7. package/dist/chunks/{chunk-FBPAX5K6.js → chunk-2RCKHCF7.js} +1 -1
  8. package/dist/chunks/{chunk-5NZRT7GL.js → chunk-3CXZUC2Y.js} +2 -2
  9. package/dist/chunks/{chunk-WUZH2OJ6.js → chunk-3XYLEK4H.js} +2 -2
  10. package/dist/chunks/{chunk-F7O56DVR.js → chunk-4OVIUCSB.js} +2 -2
  11. package/dist/chunks/{chunk-JGH2V54E.js → chunk-4YPCUVMC.js} +2 -2
  12. package/dist/chunks/{chunk-FU57ZNRQ.js → chunk-6CXLLJ2C.js} +3 -3
  13. package/dist/chunks/{chunk-SACSQ45G.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-IN4IYSA2.js → chunk-7PEYA3NA.js} +2 -2
  17. package/dist/chunks/{chunk-CWCVQJYY.js → chunk-AXE3RR7J.js} +1 -1
  18. package/dist/chunks/{chunk-BXE3SBBN.js → chunk-C3KAPREU.js} +1 -1
  19. package/dist/chunks/{chunk-ATDVS2CW.js → chunk-G3GLGDKV.js} +2 -2
  20. package/dist/chunks/{chunk-RB2MDFQU.js → chunk-G4WQBRDS.js} +4 -4
  21. package/dist/chunks/{chunk-SDI6BVG7.js → chunk-J4PDHWP2.js} +1 -1
  22. package/dist/chunks/{chunk-3UGWYQNY.js → chunk-JKIAHYAW.js} +14 -8
  23. package/dist/chunks/{chunk-TCSV3II7.js → chunk-L2RQV23D.js} +1 -1
  24. package/dist/chunks/{chunk-6XDTAKO7.js → chunk-LA6JHEVV.js} +1 -1
  25. package/dist/chunks/{chunk-G7SG4XMK.js → chunk-LODKC7PJ.js} +7 -0
  26. package/dist/chunks/{chunk-RK7ZOFPR.js → chunk-MO4DIRHD.js} +80 -38
  27. package/dist/chunks/{chunk-PCHEUVHB.js → chunk-N2GAA7RB.js} +1 -1
  28. package/dist/chunks/{chunk-I5SGYTRM.js → chunk-N4AJV4GH.js} +25 -14
  29. package/dist/chunks/{chunk-Q7I563MH.js → chunk-NHOXLUIL.js} +2 -2
  30. package/dist/chunks/{chunk-RT4K2JMZ.js → chunk-NQWEYJDD.js} +1 -1
  31. package/dist/chunks/{chunk-3Y2EFDID.js → chunk-OIOAK7BS.js} +1 -1
  32. package/dist/chunks/{chunk-EZGNBDHD.js → chunk-R3NPBUKT.js} +8 -8
  33. package/dist/chunks/{chunk-KJDERUQF.js → chunk-R4XCTRIU.js} +45 -10
  34. package/dist/chunks/{chunk-HTF32WSW.js → chunk-S3ICPPXC.js} +3 -3
  35. package/dist/chunks/{chunk-OXW5OHYQ.js → chunk-S4CH434Z.js} +1 -1
  36. package/dist/chunks/{chunk-HMEAGAGC.js → chunk-S7BCZVRK.js} +2 -2
  37. package/dist/chunks/{chunk-UV2IJMIG.js → chunk-SDDMV2MM.js} +1 -1
  38. package/dist/chunks/{chunk-J6XHW4KI.js → chunk-SIXFXXRI.js} +3 -3
  39. package/dist/chunks/{chunk-LKQOIPGE.js → chunk-U6JPSXJ3.js} +90 -21
  40. package/dist/chunks/{chunk-SENPGW4O.js → chunk-VCUOYVLC.js} +1 -1
  41. package/dist/chunks/{chunk-XTF2VWJN.js → chunk-VSRRIFRF.js} +1 -1
  42. package/dist/chunks/{chunk-BFUS3ENH.js → chunk-X5ULFAMM.js} +2 -2
  43. package/dist/chunks/{chunk-PJWLYGRL.js → chunk-XOIYBEU7.js} +1 -1
  44. package/dist/chunks/{chunk-JLIVX2SF.js → chunk-YEDZ37PH.js} +1 -1
  45. package/dist/chunks/{chunk-APLWFV4I.js → chunk-YF6T33PU.js} +10 -2
  46. package/dist/chunks/{chunk-2VMKQKVO.js → chunk-YIWN32ZX.js} +1 -1
  47. package/dist/chunks/{chunk-3WSA4NNP.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 +3 -1
  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/foldVariants.d.ts +3 -1
  85. package/dist/types/renderer/types.d.ts +20 -0
  86. package/dist/types/renderer/walkLanes.d.ts +60 -0
  87. package/dist/types/types.d.ts +1 -0
  88. package/dist/types/util/geometry.d.ts +13 -1
  89. package/dist/types/version.d.ts +1 -1
  90. package/dist/types/walkEncoding.d.ts +7 -5
  91. package/dist/types/walkHighlight.d.ts +3 -0
  92. package/dist/util/SpatialIndex.js +4 -4
  93. package/dist/util/edgeCurves.js +3 -3
  94. package/dist/util/geometry.js +7 -1
  95. package/dist/util/hitDetection.js +5 -5
  96. package/dist/version.js +1 -1
  97. package/dist/viewport.js +6 -6
  98. package/dist/walkEncoding.js +9 -8
  99. package/dist/walkHighlight.js +10 -9
  100. package/dist/walkKey.js +5 -5
  101. package/package.json +1 -1
@@ -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-XTF2VWJN.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-RB2MDFQU.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-Q7I563MH.js";
3
+ } from "./chunk-NHOXLUIL.js";
4
4
  import {
5
5
  graphLabels,
6
6
  rowLabelBox
7
- } from "./chunk-FU57ZNRQ.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-3UGWYQNY.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-FU57ZNRQ.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
  }));
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var version = "11.7.1";
2
+ var version = "11.8.0";
3
3
 
4
4
  export {
5
5
  version
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-H2H4Y3TV.js";
4
4
  import {
5
5
  REFERENCE_RAMP_ALT_CSS
6
- } from "./chunk-VB4IXIPM.js";
6
+ } from "./chunk-2H3ALI6L.js";
7
7
  import {
8
8
  RAMP_GRADIENT_CSS,
9
9
  RAMP_GRADIENT_STOPS,
@@ -13,6 +13,7 @@ import {
13
13
  } from "./chunk-BSOZW3FC.js";
14
14
  import {
15
15
  panSNContig,
16
+ panSNHaplotype,
16
17
  panSNSample
17
18
  } from "./chunk-2JL2JVZ2.js";
18
19
 
@@ -466,6 +467,12 @@ function walkRowRuns(source, region, o = {}) {
466
467
  meta: rowOf(pieces),
467
468
  row: i + 1
468
469
  }));
470
+ const metas = [referenceRow, ...rows.map((r) => r.meta)];
471
+ if (new Set(metas.map((m) => m.label)).size < metas.length) {
472
+ for (const meta of metas) {
473
+ meta.label = panSNHaplotype(meta.name) ?? meta.label;
474
+ }
475
+ }
469
476
  rows.sort(
470
477
  (a, b) => Number(b.meta.complete) - Number(a.meta.complete) || b.meta.bp - a.meta.bp || a.meta.label.localeCompare(b.meta.label)
471
478
  );