@jbrowse/bandage-core 11.7.3 → 11.9.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-K4KVAMEF.js → chunk-2A6Z2DHF.js} +1 -1
  6. package/dist/chunks/{chunk-4SN7R4UF.js → chunk-2MXFG3W7.js} +1 -1
  7. package/dist/chunks/chunk-3BDQQTXY.js +568 -0
  8. package/dist/chunks/{chunk-QXBQDHKL.js → chunk-3BOREHCJ.js} +80 -38
  9. package/dist/chunks/{chunk-F4CDC72I.js → chunk-3DHGSHC3.js} +1 -1
  10. package/dist/chunks/{chunk-P276TWPM.js → chunk-3GORESBG.js} +1 -1
  11. package/dist/chunks/{chunk-4KOYS5WR.js → chunk-4BYGFDQB.js} +1 -1
  12. package/dist/chunks/{chunk-LH436HS2.js → chunk-4JDO7ZLN.js} +2 -2
  13. package/dist/chunks/{chunk-2US43KNG.js → chunk-7YSIP3O2.js} +2 -2
  14. package/dist/chunks/{chunk-VNF236GG.js → chunk-ARXDNQLF.js} +2 -2
  15. package/dist/chunks/{chunk-N73RA4DR.js → chunk-AURNOR27.js} +3 -3
  16. package/dist/chunks/{chunk-63XWYNID.js → chunk-BDS5VZYZ.js} +4 -4
  17. package/dist/chunks/{chunk-BXE3SBBN.js → chunk-C3KAPREU.js} +1 -1
  18. package/dist/chunks/{chunk-VXKL2ZLR.js → chunk-GDZQFELP.js} +259 -60
  19. package/dist/chunks/{chunk-2T3V6Z3M.js → chunk-GYJMPVOU.js} +1 -1
  20. package/dist/chunks/{chunk-BFUS3ENH.js → chunk-HEYIAMTC.js} +2 -2
  21. package/dist/chunks/{chunk-HMEAGAGC.js → chunk-HIN56TLA.js} +2 -2
  22. package/dist/chunks/{chunk-2VMKQKVO.js → chunk-HNNP5QUN.js} +1 -1
  23. package/dist/chunks/{chunk-W5YRBEWQ.js → chunk-I53QYITQ.js} +1 -1
  24. package/dist/chunks/{chunk-UTDMFVC7.js → chunk-I5CHAYDZ.js} +8 -8
  25. package/dist/chunks/{chunk-EPOPGV5G.js → chunk-IXLT3JJX.js} +1 -1
  26. package/dist/chunks/{chunk-SG7VQB4F.js → chunk-IZKM4NY7.js} +1 -1
  27. package/dist/chunks/{chunk-KMLATBNT.js → chunk-J7RNXBXA.js} +3 -3
  28. package/dist/chunks/{chunk-VACK24HF.js → chunk-JJV2GT2Z.js} +43 -9
  29. package/dist/chunks/{chunk-PKGPSTRW.js → chunk-JO2T77NW.js} +2 -2
  30. package/dist/chunks/{chunk-XKPXOIWD.js → chunk-JVT3XTKN.js} +3 -3
  31. package/dist/chunks/{chunk-SDI6BVG7.js → chunk-KKHXMJEE.js} +1 -1
  32. package/dist/chunks/{chunk-G7SG4XMK.js → chunk-LODKC7PJ.js} +7 -0
  33. package/dist/chunks/{chunk-AJQRKN3X.js → chunk-N3DCLT7M.js} +1 -1
  34. package/dist/chunks/{chunk-ZZZ6NVLW.js → chunk-NAYS472K.js} +1 -1
  35. package/dist/chunks/{chunk-AWJJSA7D.js → chunk-NWEVR2OQ.js} +1 -1
  36. package/dist/chunks/{chunk-33KUO34R.js → chunk-P2GJBFNA.js} +1 -1
  37. package/dist/chunks/{chunk-IHM43Y7B.js → chunk-RT4U3DH3.js} +1 -1
  38. package/dist/chunks/{chunk-XTCIKBWM.js → chunk-S6E3PMH7.js} +1 -1
  39. package/dist/chunks/{chunk-BLDUBB7X.js → chunk-SZYFZNCO.js} +2 -2
  40. package/dist/chunks/{chunk-T3MEG5D3.js → chunk-T2UNTWU5.js} +90 -21
  41. package/dist/chunks/{chunk-QJC63NFR.js → chunk-TQ26XEFL.js} +2 -2
  42. package/dist/chunks/{chunk-6RB5ZQ34.js → chunk-UEWIGD52.js} +51 -14
  43. package/dist/chunks/{chunk-SENPGW4O.js → chunk-VCUOYVLC.js} +1 -1
  44. package/dist/chunks/{chunk-CL6ELLVF.js → chunk-VNCHWWLG.js} +1 -1
  45. package/dist/chunks/{chunk-GD6RUZU5.js → chunk-VQ353UDK.js} +14 -8
  46. package/dist/chunks/{chunk-QLLKALSH.js → chunk-VTGQCLGI.js} +1 -1
  47. package/dist/chunks/{chunk-Z4HXW6ZH.js → chunk-ZWBQ4RRK.js} +24 -10
  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 +46 -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 +76 -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 +9 -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 +13 -10
  97. package/dist/walkHighlight.js +10 -9
  98. package/dist/walkKey.js +5 -5
  99. package/package.json +1 -1
@@ -1,3 +1,13 @@
1
+ import {
2
+ THREAD_PITCH_PX,
3
+ THREAD_PX,
4
+ bandScale,
5
+ bandWidthPx,
6
+ laneSlideAt,
7
+ liftLanes,
8
+ slotCentres,
9
+ threadsPasses
10
+ } from "./chunk-3BDQQTXY.js";
1
11
  import {
2
12
  abgrToCssRgba,
3
13
  fadeAbgr,
@@ -10,13 +20,13 @@ import {
10
20
  } from "./chunk-GQZVR5DW.js";
11
21
  import {
12
22
  baseEdgeCurves
13
- } from "./chunk-33KUO34R.js";
23
+ } from "./chunk-P2GJBFNA.js";
14
24
  import {
15
25
  PATH_LIGHTNESS,
16
26
  PATH_SATURATION,
17
27
  nameHue,
18
28
  pathHueAt
19
- } from "./chunk-GD6RUZU5.js";
29
+ } from "./chunk-VQ353UDK.js";
20
30
  import {
21
31
  meanDepth,
22
32
  nodeWidthPx,
@@ -26,13 +36,17 @@ import {
26
36
  referenceMidpoints
27
37
  } from "./chunk-VX656OAZ.js";
28
38
  import {
29
- dashCurves,
39
+ weakMemo
40
+ } from "./chunk-HMXCN6PG.js";
41
+ import {
42
+ dashSpans,
30
43
  pathRibbonOffsets,
31
44
  polylinePx,
45
+ slideCurves,
32
46
  strandSides,
33
47
  translateCurves,
34
48
  yToXOf
35
- } from "./chunk-VACK24HF.js";
49
+ } from "./chunk-JJV2GT2Z.js";
36
50
  import {
37
51
  pushTo
38
52
  } from "./chunk-BSOZW3FC.js";
@@ -149,13 +163,16 @@ function arrowheadFor({
149
163
  color
150
164
  };
151
165
  }
152
- function leavesStart(edge, segments, first, stranded, yToX) {
153
- if (stranded) {
154
- return strandSides(edge).from === "start";
155
- }
156
- const gap = (p) => Math.hypot(p.x - first.x0, (p.y - first.y0) * yToX);
166
+ var FIRST_THREADS = [[0, 0]];
167
+ var ONE_PASS = [0];
168
+ var NO_SHIFT = { dx: 0, dy: 0 };
169
+ function nearerStart(at, segments, yToX) {
170
+ const gap = (p) => Math.hypot(p.x - at.x, (p.y - at.y) * yToX);
157
171
  return gap(segments[0]) < gap(segments.at(-1));
158
172
  }
173
+ function leavesStart(edge, segments, first, stranded, yToX) {
174
+ return stranded ? strandSides(edge).from === "start" : nearerStart({ x: first.x0, y: first.y0 }, segments, yToX);
175
+ }
159
176
  function intoDirection(last, toSegments) {
160
177
  if (toSegments.length < 2) {
161
178
  return void 0;
@@ -174,6 +191,8 @@ function normal(a, b) {
174
191
  const len = Math.hypot(dx, dy);
175
192
  return len > 0 ? { nx: -dy / len, ny: dx / len } : void 0;
176
193
  }
194
+ var MITRE_LIMIT = 0.1;
195
+ var LONGEST_NORMAL = 1 / Math.sqrt(MITRE_LIMIT);
177
196
  function pointNormalsOf(rawPoints, yToX = 1) {
178
197
  const points = yToX === 1 ? rawPoints : rawPoints.map((p) => ({ x: p.x, y: p.y * yToX }));
179
198
  return points.map((p, i) => {
@@ -183,16 +202,20 @@ function pointNormalsOf(rawPoints, yToX = 1) {
183
202
  const nx = (before.nx + after.nx) / 2;
184
203
  const ny = (before.ny + after.ny) / 2;
185
204
  const dot = before.nx * nx + before.ny * ny;
186
- return dot > 0.1 ? { nx: nx / dot, ny: ny / dot } : { nx, ny };
205
+ return dot > MITRE_LIMIT ? { nx: nx / dot, ny: ny / dot } : { nx, ny };
187
206
  }
188
207
  return before ?? after ?? { nx: 0, ny: 0 };
189
208
  });
190
209
  }
191
210
  function offsetPolyline(points, normals, distance, yToX = 1) {
192
- return points.map((p, i) => ({
193
- x: p.x + normals[i].nx * distance,
194
- y: p.y + normals[i].ny * distance / yToX
195
- }));
211
+ const out = new Array(points.length);
212
+ for (let i = 0; i < points.length; i++) {
213
+ out[i] = {
214
+ x: points[i].x + normals[i].nx * distance,
215
+ y: points[i].y + normals[i].ny * distance / yToX
216
+ };
217
+ }
218
+ return out;
196
219
  }
197
220
  function hslToRgb(h, s, l) {
198
221
  const c = (1 - Math.abs(2 * l - 1)) * s;
@@ -393,13 +416,13 @@ function isPolylineInBounds(segments, bounds) {
393
416
  }
394
417
  return hit;
395
418
  }
396
- function isBezierInBounds(curves, bounds) {
419
+ function isBezierInBounds(curves, bounds, growX = 0, growY = 0) {
397
420
  for (const c of curves) {
398
421
  const cMinX = Math.min(c.x0, c.cx0, c.cx1, c.x1);
399
422
  const cMaxX = Math.max(c.x0, c.cx0, c.cx1, c.x1);
400
423
  const cMinY = Math.min(c.y0, c.cy0, c.cy1, c.y1);
401
424
  const cMaxY = Math.max(c.y0, c.cy0, c.cy1, c.y1);
402
- if (cMaxX >= bounds.minX && cMinX <= bounds.maxX && cMaxY >= bounds.minY && cMinY <= bounds.maxY) {
425
+ if (cMaxX >= bounds.minX - growX && cMinX <= bounds.maxX + growX && cMaxY >= bounds.minY - growY && cMinY <= bounds.maxY + growY) {
403
426
  return true;
404
427
  }
405
428
  }
@@ -445,6 +468,24 @@ function pathSlotsOf(graph) {
445
468
  }
446
469
  return slots;
447
470
  }
471
+ var lanesCache = weakMemo();
472
+ var linksCache = /* @__PURE__ */ new WeakMap();
473
+ function linksOf(graph) {
474
+ let links = linksCache.get(graph);
475
+ if (!links) {
476
+ links = /* @__PURE__ */ new Map();
477
+ for (const { from, to } of graph.edges) {
478
+ let out = links.get(from);
479
+ if (!out) {
480
+ out = /* @__PURE__ */ new Set();
481
+ links.set(from, out);
482
+ }
483
+ out.add(to);
484
+ }
485
+ linksCache.set(graph, links);
486
+ }
487
+ return links;
488
+ }
448
489
  function buildGeometry(options) {
449
490
  const {
450
491
  nodePositions,
@@ -496,6 +537,94 @@ function buildGeometry(options) {
496
537
  const { pathColorByIndex, pathIndexByName } = pathPaletteOf(graph);
497
538
  const pathColorByName = (name) => pathColorByIndex[pathIndexByName.get(name) ?? -1] ?? EDGE_PATH_FALLBACK_COLOR;
498
539
  const nodePathSlots = drawPaths && pathCount > 1 ? pathSlotsOf(graph) : NO_SLOTS;
540
+ const liftedWalks = highlight?.walks ?? [];
541
+ const threaded = threadsPasses(liftedWalks);
542
+ const lanePasses = liftedWalks.map((w) => w.mostPasses);
543
+ const thinned = threaded ? bandScale(lanePasses) : 1;
544
+ const fields = highlight && liftedWalks.length > 1 ? lanesCache(
545
+ highlight,
546
+ [nodePositions, version, axis.scaleX, axis.scaleY],
547
+ () => liftLanes(liftedWalks, nodePositions, yToX, worldPerScreenPx)
548
+ ) : void 0;
549
+ const lines = fields?.lines;
550
+ const threads = fields?.threads;
551
+ const spanField = fields?.spans;
552
+ const plainCentres = threaded ? slotCentres(lanePasses, lanePasses) : [];
553
+ const centresByNode = /* @__PURE__ */ new Map();
554
+ const centresOf = (nodeId) => {
555
+ let centres = centresByNode.get(nodeId);
556
+ if (!centres) {
557
+ const spans = spanField?.get(nodeId);
558
+ centres = spans ? slotCentres(spans.slot, lanePasses, spans.held) : plainCentres;
559
+ centresByNode.set(nodeId, centres);
560
+ }
561
+ return centres;
562
+ };
563
+ const laneOffset = (nodeId, laneWidth, lane, pass) => threaded ? (centresOf(nodeId)[lane] + pass * THREAD_PITCH_PX) * thinned : (lane - (liftedWalks.length - 1) / 2) * laneWidth;
564
+ const laneHalfWidth = (laneWidth) => threaded ? THREAD_PX / 2 * thinned : laneWidth / 2;
565
+ const laneNormals = /* @__PURE__ */ new Map();
566
+ const laneNormalsOf = (nodeId, segments) => {
567
+ let normals = laneNormals.get(nodeId);
568
+ if (!normals) {
569
+ const smoothed = lines?.normals.get(nodeId);
570
+ normals = smoothed ? pointNormalsOf(segments, yToX).map((n, i) => smoothed[i] ?? n) : pointNormalsOf(segments, yToX);
571
+ laneNormals.set(nodeId, normals);
572
+ }
573
+ return normals;
574
+ };
575
+ const laneWidths = /* @__PURE__ */ new Map();
576
+ const laneWidthOf = (id) => {
577
+ let width = laneWidths.get(id);
578
+ if (width === void 0) {
579
+ const node = nodeById.get(id);
580
+ width = walkLaneWidthPx(
581
+ node ? nodeWidthPx(node, contigThickness, nodeWidth, depthNorm) : contigThickness,
582
+ liftedWalks.length
583
+ );
584
+ laneWidths.set(id, width);
585
+ }
586
+ return width;
587
+ };
588
+ const laneShiftOf = (nodeId, index) => lines?.shifts.get(nodeId)?.[index] ?? NO_SHIFT;
589
+ const laneEndOf = (id, segments, atStart) => {
590
+ const at = atStart ? 0 : segments.length - 1;
591
+ return {
592
+ id,
593
+ laneWidth: laneWidthOf(id),
594
+ normal: laneNormalsOf(id, segments)[at] ?? { nx: 0, ny: 0 },
595
+ shift: laneShiftOf(id, at)
596
+ };
597
+ };
598
+ const lanesMayShow = (curves, fromId) => {
599
+ if (!viewportBounds) {
600
+ return true;
601
+ }
602
+ const offset = threaded ? bandWidthPx(lanePasses) * thinned : (liftedWalks.length - 1) / 2 * laneWidthOf(fromId);
603
+ const reach = LONGEST_NORMAL * offset * worldPerScreenPx;
604
+ return isBezierInBounds(
605
+ curves,
606
+ viewportBounds,
607
+ reach + (fields?.furthestShift.dx ?? 0),
608
+ reach / yToX + (fields?.furthestShift.dy ?? 0)
609
+ );
610
+ };
611
+ const threadPairs = (lane, from, to) => {
612
+ const own = threads?.[lane]?.linkThreads;
613
+ if (!own?.size) {
614
+ return FIRST_THREADS;
615
+ }
616
+ const ahead = own.get(from)?.get(to);
617
+ const back = linksOf(graph).get(to)?.has(from) ? void 0 : own.get(to)?.get(from);
618
+ if (!back) {
619
+ return ahead ?? FIRST_THREADS;
620
+ }
621
+ const turned = back.map(([a, b]) => [b, a]);
622
+ return ahead ? [...ahead, ...turned] : turned;
623
+ };
624
+ const lanePointsOf = (nodeId, segments) => lines?.shifts.has(nodeId) ? segments.map((p, i) => {
625
+ const { dx, dy } = laneShiftOf(nodeId, i);
626
+ return { ...p, x: p.x + dx, y: p.y + dy };
627
+ }) : segments;
499
628
  const showArrows = scale > (linearLayout ? 1 : MIN_ARROW_SCALE);
500
629
  for (let ei = 0; ei < graph.edges.length; ei++) {
501
630
  const edge = graph.edges[ei];
@@ -516,18 +645,47 @@ function buildGeometry(options) {
516
645
  const toNode = nodeById.get(edge.to);
517
646
  const intoHalfWidth = (toNode ? nodeWidthPx(toNode, contigThickness, nodeWidth, depthNorm) : contigThickness) / 2;
518
647
  const inView = (curves) => !viewportBounds || isBezierInBounds(curves, viewportBounds);
648
+ const dashesOf = (curves) => dashSpans(curves, DELETION_DASH_PX / scale, yToX, viewportBounds);
519
649
  const stroke = (curves, thickness, color) => {
520
- if (isDeletion) {
521
- for (const dash of dashCurves(
522
- curves,
523
- DELETION_DASH_PX / scale,
524
- yToX,
525
- viewportBounds
526
- )) {
527
- edgeCurves.push({ curves: dash, thickness, color });
528
- }
529
- } else {
650
+ if (!isDeletion) {
530
651
  edgeCurves.push({ curves, thickness, color });
652
+ return;
653
+ }
654
+ for (const dash of dashesOf(curves)) {
655
+ edgeCurves.push({ curves: dash.curves, thickness, color });
656
+ }
657
+ };
658
+ const laneStroke = (curves, thickness, color, walk, pass, normal2, offsetX, endNormal, endOffsetX) => {
659
+ if (!isDeletion) {
660
+ edgeCurves.push({
661
+ curves,
662
+ thickness,
663
+ color,
664
+ walk,
665
+ pass,
666
+ normal: normal2,
667
+ offsetX,
668
+ endNormal,
669
+ endOffsetX
670
+ });
671
+ return;
672
+ }
673
+ const start = { normal: normal2, offsetX };
674
+ const end = { normal: endNormal, offsetX: endOffsetX };
675
+ for (const dash of dashesOf(curves)) {
676
+ const a = dash.startAlong === 0 ? start : laneSlideAt(start, end, dash.startAlong);
677
+ const b = dash.endAlong === 1 ? end : laneSlideAt(start, end, dash.endAlong);
678
+ edgeCurves.push({
679
+ curves: dash.curves,
680
+ thickness,
681
+ color,
682
+ walk,
683
+ pass,
684
+ normal: a.normal,
685
+ offsetX: a.offsetX,
686
+ endNormal: b.normal,
687
+ endOffsetX: b.offsetX
688
+ });
531
689
  }
532
690
  };
533
691
  const arrowOn = (curves) => {
@@ -548,35 +706,57 @@ function buildGeometry(options) {
548
706
  const edgeCurveStart = edgeCurves.length;
549
707
  const arrowStart = arrows.length;
550
708
  const laneWalks = highlight?.walks.length && onWalk && !ribbons ? highlight.walks : void 0;
709
+ if (laneWalks && !lanesMayShow(baseCurves, edge.from)) {
710
+ continue;
711
+ }
551
712
  if (laneWalks) {
552
- const fromNode = nodeById.get(edge.from);
553
- const width = fromNode ? nodeWidthPx(fromNode, contigThickness, nodeWidth, depthNorm) : contigThickness;
554
- const laneWidth = walkLaneWidthPx(width, laneWalks.length);
555
- const normals = pointNormalsOf(fromSegments, yToX);
556
- const { nx, ny } = (leavesStart(
557
- edge,
713
+ const from = laneEndOf(
714
+ edge.from,
558
715
  fromSegments,
559
- baseCurves[0],
560
- stranded,
561
- yToX
562
- ) ? normals[0] : normals.at(-1)) ?? {
563
- nx: 0,
564
- ny: 0
565
- };
566
- laneWalks.forEach((walk, lane) => {
716
+ leavesStart(edge, fromSegments, baseCurves[0], stranded, yToX)
717
+ );
718
+ const last = baseCurves.at(-1);
719
+ const to = threaded ? laneEndOf(
720
+ edge.to,
721
+ toSegments,
722
+ nearerStart({ x: last.x1, y: last.y1 }, toSegments, yToX)
723
+ ) : from;
724
+ const single = laneWalks.length === 1;
725
+ const thickness = laneHalfWidth(from.laneWidth);
726
+ for (let lane = 0; lane < laneWalks.length; lane++) {
727
+ const walk = laneWalks[lane];
567
728
  if (!walk.edgeIndexes.has(ei)) {
568
- return;
729
+ continue;
569
730
  }
570
- const offset = (lane - (laneWalks.length - 1) / 2) * laneWidth * worldPerScreenPx;
571
- const curves = offset === 0 ? baseCurves : translateCurves(baseCurves, nx * offset, ny * offset / yToX);
572
- if (inView(curves)) {
573
- stroke(
574
- curves,
575
- laneWidth / 2,
576
- walk.colors.get(edge.from) ?? walk.colors.get(edge.to) ?? plainEdgeColor
731
+ for (const [pass, toPass] of threadPairs(lane, edge.from, edge.to)) {
732
+ const offsetX = single ? 0 : laneOffset(from.id, from.laneWidth, lane, pass) * worldPerScreenPx;
733
+ const endOffsetX = threaded ? laneOffset(to.id, to.laneWidth, lane, toPass) * worldPerScreenPx : offsetX;
734
+ const curves = threaded ? slideCurves(
735
+ baseCurves,
736
+ from.shift.dx + from.normal.nx * offsetX,
737
+ from.shift.dy + from.normal.ny * offsetX / yToX,
738
+ to.shift.dx + to.normal.nx * endOffsetX,
739
+ to.shift.dy + to.normal.ny * endOffsetX / yToX
740
+ ) : offsetX === 0 ? baseCurves : translateCurves(
741
+ baseCurves,
742
+ from.normal.nx * offsetX,
743
+ from.normal.ny * offsetX / yToX
577
744
  );
745
+ if (inView(curves)) {
746
+ laneStroke(
747
+ curves,
748
+ thickness,
749
+ walk.colors.get(edge.from) ?? walk.colors.get(edge.to) ?? plainEdgeColor,
750
+ lane,
751
+ pass,
752
+ from.normal,
753
+ offsetX,
754
+ to.normal,
755
+ endOffsetX
756
+ );
757
+ }
578
758
  }
579
- });
759
+ }
580
760
  if (edgeCurves.length > edgeCurveStart) {
581
761
  arrowOn(baseCurves);
582
762
  }
@@ -644,19 +824,38 @@ function buildGeometry(options) {
644
824
  const lanes = highlight?.walks.length && !faded && !slots?.length ? highlight.walks : void 0;
645
825
  if (drawable && lanes) {
646
826
  const laneWidth = walkLaneWidthPx(width, lanes.length);
647
- const normals = pointNormalsOf(segments, yToX);
648
- lanes.forEach((walk, lane) => {
649
- const laneColor = walk.colors.get(nodeId);
827
+ const normals = lanes.length > 1 ? laneNormalsOf(nodeId, segments) : [];
828
+ const points = lanes.length > 1 ? lanePointsOf(nodeId, segments) : segments;
829
+ const thickness = laneHalfWidth(laneWidth);
830
+ for (let lane = 0; lane < lanes.length; lane++) {
831
+ const laneColor = lanes[lane].colors.get(nodeId);
650
832
  if (laneColor === void 0) {
651
- return;
833
+ continue;
652
834
  }
653
- const offset = (lane - (lanes.length - 1) / 2) * laneWidth * worldPerScreenPx;
654
- nodeStrokes.push({
655
- points: lanes.length === 1 ? segments : offsetPolyline(segments, normals, offset, yToX),
656
- thickness: laneWidth / 2,
657
- color: laneColor
658
- });
659
- });
835
+ const onThreads = threads?.[lane]?.nodeThreads.get(nodeId) ?? ONE_PASS;
836
+ for (const pass of onThreads) {
837
+ if (lanes.length === 1) {
838
+ nodeStrokes.push({
839
+ points: segments,
840
+ thickness,
841
+ color: laneColor,
842
+ walk: lane,
843
+ pass
844
+ });
845
+ continue;
846
+ }
847
+ const offsetX = laneOffset(nodeId, laneWidth, lane, pass) * worldPerScreenPx;
848
+ nodeStrokes.push({
849
+ points: offsetPolyline(points, normals, offsetX, yToX),
850
+ thickness,
851
+ color: laneColor,
852
+ walk: lane,
853
+ pass,
854
+ normals,
855
+ offsetX
856
+ });
857
+ }
858
+ }
660
859
  } else if (drawable && slots?.length && slotWidth >= MIN_PATH_STRIPE_PX) {
661
860
  const normals = pointNormalsOf(segments, yToX);
662
861
  for (const slot of slots) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  computeColorSchemeRange,
3
3
  getNodeColor
4
- } from "./chunk-VXKL2ZLR.js";
4
+ } from "./chunk-GDZQFELP.js";
5
5
  import {
6
6
  abgrToCssRgba
7
7
  } from "./chunk-G2FD7S3C.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  polylineSlice
3
- } from "./chunk-2VMKQKVO.js";
3
+ } from "./chunk-HNNP5QUN.js";
4
4
  import {
5
5
  svgPath
6
- } from "./chunk-VACK24HF.js";
6
+ } from "./chunk-JJV2GT2Z.js";
7
7
  import {
8
8
  firstNodeAtOrAfter,
9
9
  isAnchored
@@ -6,10 +6,10 @@ import {
6
6
  } from "./chunk-MYDHU2UV.js";
7
7
  import {
8
8
  readColor
9
- } from "./chunk-SDI6BVG7.js";
9
+ } from "./chunk-KKHXMJEE.js";
10
10
  import {
11
11
  referenceWalkName
12
- } from "./chunk-G7SG4XMK.js";
12
+ } from "./chunk-LODKC7PJ.js";
13
13
  import {
14
14
  pathOrigin
15
15
  } from "./chunk-3YBP46PB.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ownStrand,
3
3
  strandSides
4
- } from "./chunk-VACK24HF.js";
4
+ } from "./chunk-JJV2GT2Z.js";
5
5
  import {
6
6
  pushTo
7
7
  } from "./chunk-BSOZW3FC.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  formatBp
3
- } from "./chunk-N73RA4DR.js";
3
+ } from "./chunk-AURNOR27.js";
4
4
  import {
5
5
  pushTo
6
6
  } from "./chunk-BSOZW3FC.js";
@@ -4,27 +4,27 @@ import {
4
4
  import {
5
5
  facetLifts,
6
6
  walkLift
7
- } from "./chunk-Z4HXW6ZH.js";
7
+ } from "./chunk-ZWBQ4RRK.js";
8
8
  import {
9
9
  buildGeometry,
10
10
  computeReferenceRamp
11
- } from "./chunk-VXKL2ZLR.js";
11
+ } from "./chunk-GDZQFELP.js";
12
12
  import {
13
13
  walkKey
14
- } from "./chunk-SG7VQB4F.js";
14
+ } from "./chunk-IZKM4NY7.js";
15
15
  import {
16
16
  walkRowsExtent
17
- } from "./chunk-SENPGW4O.js";
17
+ } from "./chunk-VCUOYVLC.js";
18
18
  import {
19
19
  pathLegend,
20
20
  walkLabelsOf
21
- } from "./chunk-GD6RUZU5.js";
21
+ } from "./chunk-VQ353UDK.js";
22
22
  import {
23
23
  FIGURE_DEFAULTS
24
- } from "./chunk-4SN7R4UF.js";
24
+ } from "./chunk-2MXFG3W7.js";
25
25
  import {
26
26
  drawingBounds
27
- } from "./chunk-XKPXOIWD.js";
27
+ } from "./chunk-JVT3XTKN.js";
28
28
  import {
29
29
  referenceLabel
30
30
  } from "./chunk-5ORP7EFJ.js";
@@ -34,7 +34,7 @@ import {
34
34
  import {
35
35
  deletionDrawing,
36
36
  deletionEdges
37
- } from "./chunk-P276TWPM.js";
37
+ } from "./chunk-3GORESBG.js";
38
38
  import {
39
39
  facetCells,
40
40
  facetSettingOf
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-H2H4Y3TV.js";
4
4
  import {
5
5
  REFERENCE_RAMP_ALT_CSS
6
- } from "./chunk-VXKL2ZLR.js";
6
+ } from "./chunk-GDZQFELP.js";
7
7
  import {
8
8
  RAMP_GRADIENT_CSS,
9
9
  RAMP_GRADIENT_STOPS,
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  formatBp,
7
7
  groupDigits
8
- } from "./chunk-N73RA4DR.js";
8
+ } from "./chunk-AURNOR27.js";
9
9
 
10
10
  // src/walkKey.ts
11
11
  function rangeText(contig, start, end) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  coversGraph
3
- } from "./chunk-BLDUBB7X.js";
3
+ } from "./chunk-SZYFZNCO.js";
4
4
  import {
5
5
  bubblesFromGraph,
6
6
  derivedBubble,
@@ -10,13 +10,13 @@ import {
10
10
  } from "./chunk-RFG42VIO.js";
11
11
  import {
12
12
  bubbleSegmentIds
13
- } from "./chunk-AWJJSA7D.js";
13
+ } from "./chunk-NWEVR2OQ.js";
14
14
  import {
15
15
  bubbleSubgraph
16
16
  } from "./chunk-MHZTTLBU.js";
17
17
  import {
18
18
  strandSides
19
- } from "./chunk-VACK24HF.js";
19
+ } from "./chunk-JJV2GT2Z.js";
20
20
  import {
21
21
  pushTo
22
22
  } from "./chunk-BSOZW3FC.js";
@@ -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,57 @@ 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, fromDx, fromDy, toDx, toDy) {
154
+ const n = curves.length;
155
+ const out = new Array(n);
156
+ for (let i = 0; i < n; i++) {
157
+ const c = curves[i];
158
+ const a = i / n;
159
+ const b = (i + 1) / n;
160
+ const adx = fromDx + (toDx - fromDx) * a;
161
+ const ady = fromDy + (toDy - fromDy) * a;
162
+ const bdx = fromDx + (toDx - fromDx) * b;
163
+ const bdy = fromDy + (toDy - fromDy) * b;
164
+ out[i] = {
165
+ x0: c.x0 + adx,
166
+ y0: c.y0 + ady,
167
+ cx0: c.cx0 + adx,
168
+ cy0: c.cy0 + ady,
169
+ cx1: c.cx1 + bdx,
170
+ cy1: c.cy1 + bdy,
171
+ x1: c.x1 + bdx,
172
+ y1: c.y1 + bdy
173
+ };
174
+ }
175
+ return out;
176
+ }
146
177
  function translateCurves(curves, dx, dy) {
147
178
  const out = new Array(curves.length);
148
179
  for (let i = 0; i < curves.length; i++) {
@@ -555,6 +586,9 @@ export {
555
586
  curveMidpoint,
556
587
  curveBounds,
557
588
  dashCurves,
589
+ dashSpans,
590
+ slideAt,
591
+ slideCurves,
558
592
  translateCurves,
559
593
  ownStrand,
560
594
  strandSides,
@@ -3,14 +3,14 @@ import {
3
3
  boxesGenes,
4
4
  rowPitch,
5
5
  rowSpan
6
- } from "./chunk-EPOPGV5G.js";
6
+ } from "./chunk-IXLT3JJX.js";
7
7
  import {
8
8
  el
9
9
  } from "./chunk-H2H4Y3TV.js";
10
10
  import {
11
11
  READOUT_ROOM,
12
12
  longestBar
13
- } from "./chunk-SENPGW4O.js";
13
+ } from "./chunk-VCUOYVLC.js";
14
14
  import {
15
15
  LABEL_CHAR_PX
16
16
  } from "./chunk-AB35TJST.js";