@jbrowse/bandage-core 11.8.0 → 11.10.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 (90) hide show
  1. package/dist/bubbles/bubbleHalos.js +6 -6
  2. package/dist/bubbles/classifyBubble.js +5 -5
  3. package/dist/bubbles/superbubbles.js +7 -7
  4. package/dist/chunks/{chunk-S4CH434Z.js → chunk-2A6Z2DHF.js} +1 -1
  5. package/dist/chunks/{chunk-6MD4VS2F.js → chunk-2MXFG3W7.js} +1 -1
  6. package/dist/chunks/{chunk-L2RQV23D.js → chunk-2NCZ5YYN.js} +1 -1
  7. package/dist/chunks/{chunk-ZKIH2USC.js → chunk-337LZP7C.js} +1 -1
  8. package/dist/chunks/{chunk-2RCKHCF7.js → chunk-3DHGSHC3.js} +1 -1
  9. package/dist/chunks/{chunk-3CXZUC2Y.js → chunk-3GORESBG.js} +1 -1
  10. package/dist/chunks/{chunk-R4XCTRIU.js → chunk-4BYGFDQB.js} +1 -1
  11. package/dist/chunks/{chunk-NHOXLUIL.js → chunk-4JDO7ZLN.js} +2 -2
  12. package/dist/chunks/{chunk-7GDNDQD7.js → chunk-4KBZ7WPP.js} +176 -101
  13. package/dist/chunks/{chunk-2H3ALI6L.js → chunk-53DZE3SA.js} +184 -161
  14. package/dist/chunks/{chunk-YF6T33PU.js → chunk-54C73ICS.js} +37 -17
  15. package/dist/chunks/{chunk-U6JPSXJ3.js → chunk-55DRWHBF.js} +9 -9
  16. package/dist/chunks/{chunk-4OVIUCSB.js → chunk-77O67GIB.js} +1 -1
  17. package/dist/chunks/{chunk-7PEYA3NA.js → chunk-7YSIP3O2.js} +2 -2
  18. package/dist/chunks/{chunk-LA6JHEVV.js → chunk-ALOF5SOT.js} +1 -1
  19. package/dist/chunks/{chunk-3XYLEK4H.js → chunk-ARXDNQLF.js} +1 -1
  20. package/dist/chunks/{chunk-6CXLLJ2C.js → chunk-AURNOR27.js} +3 -3
  21. package/dist/chunks/{chunk-G4WQBRDS.js → chunk-BDS5VZYZ.js} +3 -3
  22. package/dist/chunks/{chunk-MO4DIRHD.js → chunk-CHGTUO3C.js} +13 -13
  23. package/dist/chunks/{chunk-X5ULFAMM.js → chunk-HEYIAMTC.js} +2 -2
  24. package/dist/chunks/{chunk-S7BCZVRK.js → chunk-HIN56TLA.js} +1 -1
  25. package/dist/chunks/{chunk-YIWN32ZX.js → chunk-HNNP5QUN.js} +1 -1
  26. package/dist/chunks/{chunk-XOIYBEU7.js → chunk-I53QYITQ.js} +1 -1
  27. package/dist/chunks/{chunk-SDDMV2MM.js → chunk-IZKM4NY7.js} +1 -1
  28. package/dist/chunks/{chunk-SIXFXXRI.js → chunk-J7RNXBXA.js} +3 -3
  29. package/dist/chunks/{chunk-735MPLUL.js → chunk-JJV2GT2Z.js} +22 -14
  30. package/dist/chunks/{chunk-S3ICPPXC.js → chunk-JVT3XTKN.js} +3 -3
  31. package/dist/chunks/{chunk-J4PDHWP2.js → chunk-KKHXMJEE.js} +1 -1
  32. package/dist/chunks/{chunk-22ZMVREZ.js → chunk-NAYS472K.js} +1 -1
  33. package/dist/chunks/{chunk-OIOAK7BS.js → chunk-NWEVR2OQ.js} +1 -1
  34. package/dist/chunks/{chunk-VSRRIFRF.js → chunk-P2GJBFNA.js} +1 -1
  35. package/dist/chunks/{chunk-N2GAA7RB.js → chunk-RT4U3DH3.js} +1 -1
  36. package/dist/chunks/{chunk-AXE3RR7J.js → chunk-S6E3PMH7.js} +1 -1
  37. package/dist/chunks/{chunk-NQWEYJDD.js → chunk-SPEB75UH.js} +1 -1
  38. package/dist/chunks/{chunk-4YPCUVMC.js → chunk-SZYFZNCO.js} +2 -2
  39. package/dist/chunks/{chunk-G3GLGDKV.js → chunk-TQ26XEFL.js} +2 -2
  40. package/dist/chunks/{chunk-N4AJV4GH.js → chunk-UVRCXTBY.js} +37 -10
  41. package/dist/chunks/{chunk-YEDZ37PH.js → chunk-VNCHWWLG.js} +1 -1
  42. package/dist/chunks/{chunk-JKIAHYAW.js → chunk-VQ353UDK.js} +1 -1
  43. package/dist/chunks/{chunk-R3NPBUKT.js → chunk-YQAZNH37.js} +7 -7
  44. package/dist/cli/figure.js +28 -28
  45. package/dist/cli/fold.js +6 -6
  46. package/dist/deletionEdges.js +2 -2
  47. package/dist/figure.js +24 -24
  48. package/dist/figureData.js +22 -22
  49. package/dist/figureScene.js +15 -15
  50. package/dist/figureSettings.js +6 -6
  51. package/dist/foldVariants.js +2 -2
  52. package/dist/genes/genePins.js +3 -3
  53. package/dist/graphLabels.js +4 -4
  54. package/dist/index.js +38 -38
  55. package/dist/labelLayout.js +8 -8
  56. package/dist/layout/forceCache.js +6 -6
  57. package/dist/layout/mergeRuns.js +2 -2
  58. package/dist/layout/orientToReference.js +2 -2
  59. package/dist/layout/tubeMapLayout.js +7 -7
  60. package/dist/layout/walkRowDraw.js +9 -9
  61. package/dist/layout/walkStrip.js +10 -10
  62. package/dist/layoutModes.js +8 -8
  63. package/dist/pathColors.js +5 -5
  64. package/dist/pipeline.js +5 -5
  65. package/dist/referenceStrip.js +9 -9
  66. package/dist/renderer/GeometryBuilder.js +8 -8
  67. package/dist/renderer/walkLanes.js +4 -2
  68. package/dist/tubeMap/axis.js +5 -5
  69. package/dist/tubeMap/bundle.js +8 -8
  70. package/dist/tubeMap/coarsen.js +8 -8
  71. package/dist/tubeMap/deviations.js +9 -9
  72. package/dist/tubeMap/draw.js +3 -3
  73. package/dist/tubeMap/genes.js +6 -6
  74. package/dist/tubeMap/nodeColors.js +9 -9
  75. package/dist/tubeMap/reads.js +2 -2
  76. package/dist/types/renderer/walkLanes.d.ts +19 -2
  77. package/dist/types/util/geometry.d.ts +1 -1
  78. package/dist/types/version.d.ts +1 -1
  79. package/dist/types/walkEncoding.d.ts +8 -2
  80. package/dist/types/walkHighlight.d.ts +2 -0
  81. package/dist/util/SpatialIndex.js +4 -4
  82. package/dist/util/edgeCurves.js +3 -3
  83. package/dist/util/geometry.js +1 -1
  84. package/dist/util/hitDetection.js +5 -5
  85. package/dist/version.js +1 -1
  86. package/dist/viewport.js +6 -6
  87. package/dist/walkEncoding.js +13 -11
  88. package/dist/walkHighlight.js +10 -10
  89. package/dist/walkKey.js +5 -5
  90. package/package.json +1 -1
@@ -1,19 +1,13 @@
1
1
  import {
2
- LANE_SMOOTHING_PX,
3
2
  THREAD_PITCH_PX,
4
3
  THREAD_PX,
5
- THREAD_REACH_PX,
6
- THREAD_TAPER_PX,
7
4
  bandScale,
8
- bandSpans,
9
5
  bandWidthPx,
10
- laneLines,
11
- laneRuns,
12
6
  laneSlideAt,
7
+ liftLanes,
13
8
  slotCentres,
14
- threadsPasses,
15
- walkThreads
16
- } from "./chunk-7GDNDQD7.js";
9
+ threadsPasses
10
+ } from "./chunk-4KBZ7WPP.js";
17
11
  import {
18
12
  abgrToCssRgba,
19
13
  fadeAbgr,
@@ -26,13 +20,13 @@ import {
26
20
  } from "./chunk-GQZVR5DW.js";
27
21
  import {
28
22
  baseEdgeCurves
29
- } from "./chunk-VSRRIFRF.js";
23
+ } from "./chunk-P2GJBFNA.js";
30
24
  import {
31
25
  PATH_LIGHTNESS,
32
26
  PATH_SATURATION,
33
27
  nameHue,
34
28
  pathHueAt
35
- } from "./chunk-JKIAHYAW.js";
29
+ } from "./chunk-VQ353UDK.js";
36
30
  import {
37
31
  meanDepth,
38
32
  nodeWidthPx,
@@ -41,6 +35,9 @@ import {
41
35
  import {
42
36
  referenceMidpoints
43
37
  } from "./chunk-VX656OAZ.js";
38
+ import {
39
+ weakMemo
40
+ } from "./chunk-HMXCN6PG.js";
44
41
  import {
45
42
  dashSpans,
46
43
  pathRibbonOffsets,
@@ -49,7 +46,7 @@ import {
49
46
  strandSides,
50
47
  translateCurves,
51
48
  yToXOf
52
- } from "./chunk-735MPLUL.js";
49
+ } from "./chunk-JJV2GT2Z.js";
53
50
  import {
54
51
  pushTo
55
52
  } from "./chunk-BSOZW3FC.js";
@@ -194,6 +191,8 @@ function normal(a, b) {
194
191
  const len = Math.hypot(dx, dy);
195
192
  return len > 0 ? { nx: -dy / len, ny: dx / len } : void 0;
196
193
  }
194
+ var MITRE_LIMIT = 0.1;
195
+ var LONGEST_NORMAL = 1 / Math.sqrt(MITRE_LIMIT);
197
196
  function pointNormalsOf(rawPoints, yToX = 1) {
198
197
  const points = yToX === 1 ? rawPoints : rawPoints.map((p) => ({ x: p.x, y: p.y * yToX }));
199
198
  return points.map((p, i) => {
@@ -203,16 +202,20 @@ function pointNormalsOf(rawPoints, yToX = 1) {
203
202
  const nx = (before.nx + after.nx) / 2;
204
203
  const ny = (before.ny + after.ny) / 2;
205
204
  const dot = before.nx * nx + before.ny * ny;
206
- 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 };
207
206
  }
208
207
  return before ?? after ?? { nx: 0, ny: 0 };
209
208
  });
210
209
  }
211
210
  function offsetPolyline(points, normals, distance, yToX = 1) {
212
- return points.map((p, i) => ({
213
- x: p.x + normals[i].nx * distance,
214
- y: p.y + normals[i].ny * distance / yToX
215
- }));
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;
216
219
  }
217
220
  function hslToRgb(h, s, l) {
218
221
  const c = (1 - Math.abs(2 * l - 1)) * s;
@@ -413,13 +416,13 @@ function isPolylineInBounds(segments, bounds) {
413
416
  }
414
417
  return hit;
415
418
  }
416
- function isBezierInBounds(curves, bounds) {
419
+ function isBezierInBounds(curves, bounds, growX = 0, growY = 0) {
417
420
  for (const c of curves) {
418
421
  const cMinX = Math.min(c.x0, c.cx0, c.cx1, c.x1);
419
422
  const cMaxX = Math.max(c.x0, c.cx0, c.cx1, c.x1);
420
423
  const cMinY = Math.min(c.y0, c.cy0, c.cy1, c.y1);
421
424
  const cMaxY = Math.max(c.y0, c.cy0, c.cy1, c.y1);
422
- 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) {
423
426
  return true;
424
427
  }
425
428
  }
@@ -465,6 +468,24 @@ function pathSlotsOf(graph) {
465
468
  }
466
469
  return slots;
467
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
+ }
468
489
  function buildGeometry(options) {
469
490
  const {
470
491
  nodePositions,
@@ -520,53 +541,26 @@ function buildGeometry(options) {
520
541
  const threaded = threadsPasses(liftedWalks);
521
542
  const lanePasses = liftedWalks.map((w) => w.mostPasses);
522
543
  const thinned = threaded ? bandScale(lanePasses) : 1;
523
- const runs = liftedWalks.length > 1 ? laneRuns(
524
- liftedWalks.flatMap((w) => w.route),
525
- nodePositions,
526
- yToX,
527
- LANE_SMOOTHING_PX * worldPerScreenPx
528
- ) : void 0;
529
- const lines = runs ? laneLines(
530
- runs,
531
- nodePositions,
532
- yToX,
533
- LANE_SMOOTHING_PX * worldPerScreenPx,
534
- bandWidthPx(lanePasses) * thinned * worldPerScreenPx,
535
- threaded
536
- ) : void 0;
537
- const threads = threaded ? liftedWalks.map(
538
- (w) => walkThreads(
539
- w.route,
540
- w.passes,
541
- nodePositions,
542
- yToX,
543
- THREAD_REACH_PX * worldPerScreenPx
544
- )
545
- ) : void 0;
546
- const spanField = runs && threads ? bandSpans(
547
- runs,
548
- threads.map((t) => t.span),
549
- {
550
- reach: THREAD_REACH_PX * worldPerScreenPx,
551
- taper: THREAD_TAPER_PX * worldPerScreenPx
552
- }
544
+ const fields = highlight && liftedWalks.length > 1 ? lanesCache(
545
+ highlight,
546
+ [nodePositions, version, axis.scaleX, axis.scaleY],
547
+ () => liftLanes(liftedWalks, nodePositions, yToX, worldPerScreenPx)
553
548
  ) : void 0;
554
- const edgeKeys = new Set(
555
- threads ? graph.edges.map((e) => `${e.from}>${e.to}`) : []
556
- );
549
+ const lines = fields?.lines;
550
+ const threads = fields?.threads;
551
+ const spanField = fields?.spans;
552
+ const plainCentres = threaded ? slotCentres(lanePasses, lanePasses) : [];
557
553
  const centresByNode = /* @__PURE__ */ new Map();
558
- const laneOffset = (nodeId, laneWidth, lane, pass) => {
559
- if (!threaded) {
560
- return (lane - (liftedWalks.length - 1) / 2) * laneWidth;
561
- }
554
+ const centresOf = (nodeId) => {
562
555
  let centres = centresByNode.get(nodeId);
563
556
  if (!centres) {
564
557
  const spans = spanField?.get(nodeId);
565
- centres = slotCentres(spans?.slot ?? lanePasses, lanePasses, spans?.held);
558
+ centres = spans ? slotCentres(spans.slot, lanePasses, spans.held) : plainCentres;
566
559
  centresByNode.set(nodeId, centres);
567
560
  }
568
- return (centres[lane] + pass * THREAD_PITCH_PX) * thinned;
561
+ return centres;
569
562
  };
563
+ const laneOffset = (nodeId, laneWidth, lane, pass) => threaded ? (centresOf(nodeId)[lane] + pass * THREAD_PITCH_PX) * thinned : (lane - (liftedWalks.length - 1) / 2) * laneWidth;
570
564
  const laneHalfWidth = (laneWidth) => threaded ? THREAD_PX / 2 * thinned : laneWidth / 2;
571
565
  const laneNormals = /* @__PURE__ */ new Map();
572
566
  const laneNormalsOf = (nodeId, segments) => {
@@ -579,7 +573,55 @@ function buildGeometry(options) {
579
573
  return normals;
580
574
  };
581
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
+ };
582
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 threadColor = (lane, nodeId, thread) => liftedWalks[lane]?.passColors.get(nodeId)?.[threads?.[lane]?.threadPasses.get(nodeId)?.[thread] ?? 0];
583
625
  const lanePointsOf = (nodeId, segments) => lines?.shifts.has(nodeId) ? segments.map((p, i) => {
584
626
  const { dx, dy } = laneShiftOf(nodeId, i);
585
627
  return { ...p, x: p.x + dx, y: p.y + dy };
@@ -604,37 +646,47 @@ function buildGeometry(options) {
604
646
  const toNode = nodeById.get(edge.to);
605
647
  const intoHalfWidth = (toNode ? nodeWidthPx(toNode, contigThickness, nodeWidth, depthNorm) : contigThickness) / 2;
606
648
  const inView = (curves) => !viewportBounds || isBezierInBounds(curves, viewportBounds);
607
- const stroke = (curves, thickness, color, lane) => {
608
- const push = (pieces, from = 0, to = 1) => {
609
- if (!lane) {
610
- edgeCurves.push({ curves: pieces, thickness, color });
611
- return;
612
- }
613
- const start = from === 0 ? lane.start : laneSlideAt(lane.start, lane.end, from);
614
- const end = to === 1 ? lane.end : laneSlideAt(lane.start, lane.end, to);
649
+ const dashesOf = (curves) => dashSpans(curves, DELETION_DASH_PX / scale, yToX, viewportBounds);
650
+ const stroke = (curves, thickness, color) => {
651
+ if (!isDeletion) {
652
+ edgeCurves.push({ curves, thickness, color });
653
+ return;
654
+ }
655
+ for (const dash of dashesOf(curves)) {
656
+ edgeCurves.push({ curves: dash.curves, thickness, color });
657
+ }
658
+ };
659
+ const laneStroke = (curves, thickness, color, walk, pass, normal2, offsetX, endNormal, endOffsetX) => {
660
+ if (!isDeletion) {
615
661
  edgeCurves.push({
616
- curves: pieces,
662
+ curves,
617
663
  thickness,
618
664
  color,
619
- walk: lane.walk,
620
- pass: lane.pass,
621
- normal: start.normal,
622
- offsetX: start.offsetX,
623
- endNormal: end.normal,
624
- endOffsetX: end.offsetX
665
+ walk,
666
+ pass,
667
+ normal: normal2,
668
+ offsetX,
669
+ endNormal,
670
+ endOffsetX
625
671
  });
626
- };
627
- if (!isDeletion) {
628
- push(curves);
629
672
  return;
630
673
  }
631
- for (const dash of dashSpans(
632
- curves,
633
- DELETION_DASH_PX / scale,
634
- yToX,
635
- viewportBounds
636
- )) {
637
- push(dash.curves, dash.startAlong, dash.endAlong);
674
+ const start = { normal: normal2, offsetX };
675
+ const end = { normal: endNormal, offsetX: endOffsetX };
676
+ for (const dash of dashesOf(curves)) {
677
+ const a = dash.startAlong === 0 ? start : laneSlideAt(start, end, dash.startAlong);
678
+ const b = dash.endAlong === 1 ? end : laneSlideAt(start, end, dash.endAlong);
679
+ edgeCurves.push({
680
+ curves: dash.curves,
681
+ thickness,
682
+ color,
683
+ walk,
684
+ pass,
685
+ normal: a.normal,
686
+ offsetX: a.offsetX,
687
+ endNormal: b.normal,
688
+ endOffsetX: b.offsetX
689
+ });
638
690
  }
639
691
  };
640
692
  const arrowOn = (curves) => {
@@ -655,89 +707,57 @@ function buildGeometry(options) {
655
707
  const edgeCurveStart = edgeCurves.length;
656
708
  const arrowStart = arrows.length;
657
709
  const laneWalks = highlight?.walks.length && onWalk && !ribbons ? highlight.walks : void 0;
710
+ if (laneWalks && !lanesMayShow(baseCurves, edge.from)) {
711
+ continue;
712
+ }
658
713
  if (laneWalks) {
659
- const laneWidthOf = (id) => {
660
- let width = laneWidths.get(id);
661
- if (width === void 0) {
662
- const node = nodeById.get(id);
663
- width = walkLaneWidthPx(
664
- node ? nodeWidthPx(node, contigThickness, nodeWidth, depthNorm) : contigThickness,
665
- laneWalks.length
666
- );
667
- laneWidths.set(id, width);
668
- }
669
- return width;
670
- };
671
- const endOf = (id, segments, atStart) => {
672
- const at = atStart ? 0 : segments.length - 1;
673
- return {
674
- id,
675
- laneWidth: laneWidthOf(id),
676
- normal: laneNormalsOf(id, segments)[at] ?? { nx: 0, ny: 0 },
677
- shift: laneShiftOf(id, at)
678
- };
679
- };
680
- const from = endOf(
714
+ const from = laneEndOf(
681
715
  edge.from,
682
716
  fromSegments,
683
717
  leavesStart(edge, fromSegments, baseCurves[0], stranded, yToX)
684
718
  );
685
719
  const last = baseCurves.at(-1);
686
- const to = threaded ? endOf(
720
+ const to = threaded ? laneEndOf(
687
721
  edge.to,
688
722
  toSegments,
689
723
  nearerStart({ x: last.x1, y: last.y1 }, toSegments, yToX)
690
724
  ) : from;
691
- const slideOf = (end, offsetX) => ({
692
- dx: end.shift.dx + end.normal.nx * offsetX,
693
- dy: end.shift.dy + end.normal.ny * offsetX / yToX
694
- });
695
- const aheadKey = threads ? `${edge.from}>${edge.to}` : "";
696
- const backKey = threads ? `${edge.to}>${edge.from}` : "";
697
- const pairsOf = (lane) => {
698
- const own = threads?.[lane]?.linkThreads;
699
- if (!own?.size) {
700
- return FIRST_THREADS;
701
- }
702
- const ahead = own.get(aheadKey);
703
- const back = edgeKeys.has(backKey) ? void 0 : own.get(backKey);
704
- if (!back) {
705
- return ahead ?? FIRST_THREADS;
706
- }
707
- const turned = back.map(([a, b]) => [b, a]);
708
- return ahead ? [...ahead, ...turned] : turned;
709
- };
710
725
  const single = laneWalks.length === 1;
711
- laneWalks.forEach((walk, lane) => {
726
+ const thickness = laneHalfWidth(from.laneWidth);
727
+ for (let lane = 0; lane < laneWalks.length; lane++) {
728
+ const walk = laneWalks[lane];
712
729
  if (!walk.edgeIndexes.has(ei)) {
713
- return;
730
+ continue;
714
731
  }
715
- for (const [pass, toPass] of pairsOf(lane)) {
732
+ for (const [pass, toPass] of threadPairs(lane, edge.from, edge.to)) {
716
733
  const offsetX = single ? 0 : laneOffset(from.id, from.laneWidth, lane, pass) * worldPerScreenPx;
717
- const start = { normal: from.normal, offsetX };
718
- const end = threaded ? {
719
- normal: to.normal,
720
- offsetX: laneOffset(to.id, to.laneWidth, lane, toPass) * worldPerScreenPx
721
- } : start;
734
+ const endOffsetX = threaded ? laneOffset(to.id, to.laneWidth, lane, toPass) * worldPerScreenPx : offsetX;
722
735
  const curves = threaded ? slideCurves(
723
736
  baseCurves,
724
- slideOf(from, start.offsetX),
725
- slideOf(to, end.offsetX)
737
+ from.shift.dx + from.normal.nx * offsetX,
738
+ from.shift.dy + from.normal.ny * offsetX / yToX,
739
+ to.shift.dx + to.normal.nx * endOffsetX,
740
+ to.shift.dy + to.normal.ny * endOffsetX / yToX
726
741
  ) : offsetX === 0 ? baseCurves : translateCurves(
727
742
  baseCurves,
728
743
  from.normal.nx * offsetX,
729
744
  from.normal.ny * offsetX / yToX
730
745
  );
731
746
  if (inView(curves)) {
732
- stroke(
747
+ laneStroke(
733
748
  curves,
734
- laneHalfWidth(from.laneWidth),
735
- walk.colors.get(edge.from) ?? walk.colors.get(edge.to) ?? plainEdgeColor,
736
- { walk: lane, pass, start, end }
749
+ thickness,
750
+ threadColor(lane, edge.from, pass) ?? threadColor(lane, edge.to, toPass) ?? plainEdgeColor,
751
+ lane,
752
+ pass,
753
+ from.normal,
754
+ offsetX,
755
+ to.normal,
756
+ endOffsetX
737
757
  );
738
758
  }
739
759
  }
740
- });
760
+ }
741
761
  if (edgeCurves.length > edgeCurveStart) {
742
762
  arrowOn(baseCurves);
743
763
  }
@@ -807,34 +827,37 @@ function buildGeometry(options) {
807
827
  const laneWidth = walkLaneWidthPx(width, lanes.length);
808
828
  const normals = lanes.length > 1 ? laneNormalsOf(nodeId, segments) : [];
809
829
  const points = lanes.length > 1 ? lanePointsOf(nodeId, segments) : segments;
810
- lanes.forEach((walk, lane) => {
811
- const laneColor = walk.colors.get(nodeId);
830
+ const thickness = laneHalfWidth(laneWidth);
831
+ for (let lane = 0; lane < lanes.length; lane++) {
832
+ const laneColor = lanes[lane].colors.get(nodeId);
812
833
  if (laneColor === void 0) {
813
- return;
834
+ continue;
814
835
  }
815
836
  const onThreads = threads?.[lane]?.nodeThreads.get(nodeId) ?? ONE_PASS;
816
- const thickness = laneHalfWidth(laneWidth);
817
- onThreads.forEach((pass) => {
818
- const offsetX = lanes.length === 1 ? 0 : laneOffset(nodeId, laneWidth, lane, pass) * worldPerScreenPx;
819
- nodeStrokes.push(
820
- lanes.length === 1 ? {
837
+ for (const pass of onThreads) {
838
+ const color2 = threadColor(lane, nodeId, pass) ?? laneColor;
839
+ if (lanes.length === 1) {
840
+ nodeStrokes.push({
821
841
  points: segments,
822
842
  thickness,
823
- color: laneColor,
843
+ color: color2,
824
844
  walk: lane,
825
845
  pass
826
- } : {
827
- points: offsetPolyline(points, normals, offsetX, yToX),
828
- thickness,
829
- color: laneColor,
830
- walk: lane,
831
- pass,
832
- normals,
833
- offsetX
834
- }
835
- );
836
- });
837
- });
846
+ });
847
+ continue;
848
+ }
849
+ const offsetX = laneOffset(nodeId, laneWidth, lane, pass) * worldPerScreenPx;
850
+ nodeStrokes.push({
851
+ points: offsetPolyline(points, normals, offsetX, yToX),
852
+ thickness,
853
+ color: color2,
854
+ walk: lane,
855
+ pass,
856
+ normals,
857
+ offsetX
858
+ });
859
+ }
860
+ }
838
861
  } else if (drawable && slots?.length && slotWidth >= MIN_PATH_STRIPE_PX) {
839
862
  const normals = pointNormalsOf(segments, yToX);
840
863
  for (const slot of slots) {
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  encodedColor,
3
- resolveEncoding
4
- } from "./chunk-N4AJV4GH.js";
3
+ resolveEncoding,
4
+ splitFamilies
5
+ } from "./chunk-UVRCXTBY.js";
5
6
  import {
6
7
  rampFraction
7
- } from "./chunk-2H3ALI6L.js";
8
+ } from "./chunk-53DZE3SA.js";
8
9
  import {
9
10
  gapParts
10
11
  } from "./chunk-LG2UK4MB.js";
@@ -54,6 +55,7 @@ function highlightOf(graph, name, { byId, edgeAt }) {
54
55
  const nodeIds = /* @__PURE__ */ new Set();
55
56
  const edgeIndexes = /* @__PURE__ */ new Set();
56
57
  const progress = /* @__PURE__ */ new Map();
58
+ const passProgress = /* @__PURE__ */ new Map();
57
59
  const passes = /* @__PURE__ */ new Map();
58
60
  let mostPasses = 1;
59
61
  let before = 0;
@@ -74,8 +76,15 @@ function highlightOf(graph, name, { byId, edgeAt }) {
74
76
  }
75
77
  }
76
78
  const length = lengthOf(id);
79
+ const at = span > 0 ? (before + length / 2) / span : 0;
77
80
  if (!progress.has(id)) {
78
- progress.set(id, span > 0 ? (before + length / 2) / span : 0);
81
+ progress.set(id, at);
82
+ }
83
+ const seen = passProgress.get(id);
84
+ if (seen) {
85
+ seen.push(at);
86
+ } else {
87
+ passProgress.set(id, [at]);
79
88
  }
80
89
  before += length;
81
90
  });
@@ -112,6 +121,7 @@ function highlightOf(graph, name, { byId, edgeAt }) {
112
121
  nodeIds,
113
122
  edgeIndexes,
114
123
  progress,
124
+ passProgress,
115
125
  passes,
116
126
  mostPasses,
117
127
  route: pieces.map((piece) => piece.nodeIds),
@@ -124,12 +134,17 @@ function highlightOf(graph, name, { byId, edgeAt }) {
124
134
  }
125
135
  function laneColors(byId, walk, encoding, ramp) {
126
136
  const colors = /* @__PURE__ */ new Map();
127
- for (const [id, progress] of walk.progress) {
137
+ const passColors = /* @__PURE__ */ new Map();
138
+ for (const [id, progress] of walk.passProgress) {
128
139
  const node = byId.get(id);
129
- const t = encoding.field === "progress" ? progress : ramp && node ? rampFraction(node, ramp) : void 0;
130
- colors.set(id, encodedColor(encoding, t));
140
+ const atNode = ramp && node ? rampFraction(node, ramp) : void 0;
141
+ const each = progress.map(
142
+ (t) => encodedColor(encoding, encoding.field === "progress" ? t : atNode)
143
+ );
144
+ colors.set(id, each[0]);
145
+ passColors.set(id, each);
131
146
  }
132
- return colors;
147
+ return { colors, passColors };
133
148
  }
134
149
  function reversedNodes(graph, byId, walk) {
135
150
  const reversed = /* @__PURE__ */ new Set();
@@ -167,18 +182,23 @@ function walkLift(graph, layers, ramp) {
167
182
  return void 0;
168
183
  }
169
184
  let picked = 0;
170
- const walks = lifted.map(({ walk, layer }) => {
171
- const encoding = resolveEncoding(
172
- layer,
173
- walk.reference,
174
- walk.reference ? 0 : picked++,
175
- lifted.length === 1
176
- );
185
+ const encodings = splitFamilies(
186
+ lifted.map(
187
+ ({ walk, layer }) => resolveEncoding(
188
+ layer,
189
+ walk.reference,
190
+ walk.reference ? 0 : picked++,
191
+ lifted.length === 1
192
+ )
193
+ )
194
+ );
195
+ const walks = lifted.map(({ walk }, i) => {
196
+ const encoding = encodings[i];
177
197
  const { reversed, bp } = reversedNodes(graph, index.byId, walk);
178
198
  return {
179
199
  ...walk,
180
200
  encoding,
181
- colors: laneColors(index.byId, walk, encoding, ramp),
201
+ ...laneColors(index.byId, walk, encoding, ramp),
182
202
  reversed,
183
203
  reversedBp: bp
184
204
  };
@@ -200,7 +220,7 @@ function facetLifts(graph, lift, layers, ramp) {
200
220
  {
201
221
  ...walk,
202
222
  encoding,
203
- colors: laneColors(byId, walk, encoding, ramp)
223
+ ...laneColors(byId, walk, encoding, ramp)
204
224
  }
205
225
  ],
206
226
  ramp
@@ -3,30 +3,30 @@ import {
3
3
  } from "./chunk-CO6RGQAD.js";
4
4
  import {
5
5
  layoutModeByValue
6
- } from "./chunk-3XYLEK4H.js";
6
+ } from "./chunk-ARXDNQLF.js";
7
7
  import {
8
8
  parseRegion
9
9
  } from "./chunk-FNDNSDKW.js";
10
10
  import {
11
11
  genePins
12
- } from "./chunk-X5ULFAMM.js";
12
+ } from "./chunk-HEYIAMTC.js";
13
13
  import {
14
14
  version
15
- } from "./chunk-L2RQV23D.js";
15
+ } from "./chunk-2NCZ5YYN.js";
16
16
  import {
17
17
  axisScaleOf
18
- } from "./chunk-2RCKHCF7.js";
18
+ } from "./chunk-3DHGSHC3.js";
19
19
  import {
20
20
  figureScene
21
- } from "./chunk-R3NPBUKT.js";
21
+ } from "./chunk-YQAZNH37.js";
22
22
  import {
23
23
  encodingStops
24
- } from "./chunk-N4AJV4GH.js";
24
+ } from "./chunk-UVRCXTBY.js";
25
25
  import {
26
26
  laneSlideAt,
27
27
  passesKey,
28
28
  threadsPasses
29
- } from "./chunk-7GDNDQD7.js";
29
+ } from "./chunk-4KBZ7WPP.js";
30
30
  import {
31
31
  abgrAlpha,
32
32
  abgrBlue,
@@ -35,13 +35,13 @@ import {
35
35
  } from "./chunk-G2FD7S3C.js";
36
36
  import {
37
37
  FIGURE_DEFAULTS
38
- } from "./chunk-6MD4VS2F.js";
38
+ } from "./chunk-2MXFG3W7.js";
39
39
  import {
40
40
  engineSettingsOf,
41
41
  fitTransform,
42
42
  forceLayout,
43
43
  loadGraph
44
- } from "./chunk-S3ICPPXC.js";
44
+ } from "./chunk-JVT3XTKN.js";
45
45
  import {
46
46
  referenceVisitStarts
47
47
  } from "./chunk-3YBP46PB.js";
@@ -3,7 +3,7 @@ import {
3
3
  boxesGenes,
4
4
  rowPitch,
5
5
  rowSpan
6
- } from "./chunk-LA6JHEVV.js";
6
+ } from "./chunk-ALOF5SOT.js";
7
7
  import {
8
8
  el
9
9
  } from "./chunk-H2H4Y3TV.js";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  baseEdgeCurves
3
- } from "./chunk-VSRRIFRF.js";
3
+ } from "./chunk-P2GJBFNA.js";
4
4
  import {
5
5
  curveBounds,
6
6
  pathRibbonOffsets
7
- } from "./chunk-735MPLUL.js";
7
+ } from "./chunk-JJV2GT2Z.js";
8
8
 
9
9
  // src/util/SpatialIndex.ts
10
10
  var FALLBACK_CELL_SIZE = 50;