@jbrowse/bandage-core 5.0.1 → 5.2.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.
- package/dist/chunks/{chunk-RS23442A.js → chunk-ZMVFSZI5.js} +800 -230
- package/dist/cli/figure.js +4 -2
- package/dist/index.js +11 -361
- package/dist/types/cli/figure.d.ts +2 -0
- package/dist/types/colorSchemes.d.ts +1 -1
- package/dist/types/deletionEdges.d.ts +3 -1
- package/dist/types/figure.d.ts +1 -0
- package/dist/types/gbzWindow.d.ts +2 -0
- package/dist/types/genes/exonOutline.d.ts +18 -0
- package/dist/types/genes/genePins.d.ts +1 -0
- package/dist/types/graphLabels.d.ts +2 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/labelLayout.d.ts +4 -2
- package/dist/types/overlayLabels.d.ts +1 -0
- package/dist/types/pipeline.d.ts +3 -0
- package/dist/types/reference.d.ts +3 -0
- package/dist/types/renderer/GeometryBuilder.d.ts +1 -0
- package/dist/types/types.d.ts +1 -0
- package/dist/types/util/SpatialIndex.d.ts +1 -1
- package/dist/types/util/edgeCurves.d.ts +1 -1
- package/dist/types/util/geometry.d.ts +10 -2
- package/dist/types/util/hitDetection.d.ts +8 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -297,6 +297,16 @@ function attachment(segments, side) {
|
|
|
297
297
|
}
|
|
298
298
|
var TANGENT_SPAN_FACTOR = 0.5;
|
|
299
299
|
var MAX_TANGENT = 80;
|
|
300
|
+
function ownStrand(id) {
|
|
301
|
+
return id.endsWith("-") ? "-" : "+";
|
|
302
|
+
}
|
|
303
|
+
function strandSides(edge) {
|
|
304
|
+
const own = (id, strand) => (strand ?? ownStrand(id)) === ownStrand(id);
|
|
305
|
+
return {
|
|
306
|
+
from: own(edge.from, edge.fromStrand) ? "end" : "start",
|
|
307
|
+
to: own(edge.to, edge.toStrand) ? "start" : "end"
|
|
308
|
+
};
|
|
309
|
+
}
|
|
300
310
|
function spanOf(segments) {
|
|
301
311
|
let min = Infinity;
|
|
302
312
|
let max = -Infinity;
|
|
@@ -409,6 +419,14 @@ function selfLinkOf(edge) {
|
|
|
409
419
|
}
|
|
410
420
|
return edge.fromStrand && edge.toStrand && edge.fromStrand !== edge.toStrand ? edge.fromStrand === "+" ? "end" : "start" : true;
|
|
411
421
|
}
|
|
422
|
+
function edgeJoinOf(edge, stranded = false) {
|
|
423
|
+
const self = selfLinkOf(edge);
|
|
424
|
+
if (!stranded) {
|
|
425
|
+
return self;
|
|
426
|
+
}
|
|
427
|
+
const sides = strandSides(edge);
|
|
428
|
+
return self === false ? sides : sides.from === sides.to ? sides.from : true;
|
|
429
|
+
}
|
|
412
430
|
function hairpinCurves(attach, reach, offsetX, offsetY) {
|
|
413
431
|
const { at, inward } = attach;
|
|
414
432
|
const dx = inward ? at.x - inward.x : 1;
|
|
@@ -447,7 +465,7 @@ function hairpinCurves(attach, reach, offsetX, offsetY) {
|
|
|
447
465
|
}
|
|
448
466
|
];
|
|
449
467
|
}
|
|
450
|
-
function computeEdgeCurves(fromSegments, toSegments,
|
|
468
|
+
function computeEdgeCurves(fromSegments, toSegments, join, offsetX, offsetY, axis, bypassed = [], maxBowIn) {
|
|
451
469
|
const scale = axis.scaleX;
|
|
452
470
|
const yToX = yToXOf(axis);
|
|
453
471
|
const maxBow = maxBowIn ?? (axis.pixelRows ? MAX_ROW_BOW_PX : void 0);
|
|
@@ -456,7 +474,7 @@ function computeEdgeCurves(fromSegments, toSegments, selfLink, offsetX, offsetY,
|
|
|
456
474
|
computeEdgeCurves(
|
|
457
475
|
scaleYOf(fromSegments, yToX),
|
|
458
476
|
scaleYOf(toSegments, yToX),
|
|
459
|
-
|
|
477
|
+
join,
|
|
460
478
|
offsetX,
|
|
461
479
|
offsetY * yToX,
|
|
462
480
|
{ scaleX: scale, scaleY: scale },
|
|
@@ -468,15 +486,16 @@ function computeEdgeCurves(fromSegments, toSegments, selfLink, offsetX, offsetY,
|
|
|
468
486
|
yToX
|
|
469
487
|
);
|
|
470
488
|
}
|
|
471
|
-
if (typeof
|
|
489
|
+
if (typeof join === "string") {
|
|
472
490
|
return hairpinCurves(
|
|
473
|
-
attachment(fromSegments,
|
|
491
|
+
attachment(fromSegments, join),
|
|
474
492
|
selfLoopReach(fromSegments, scale),
|
|
475
493
|
offsetX,
|
|
476
494
|
offsetY
|
|
477
495
|
);
|
|
478
496
|
}
|
|
479
|
-
const
|
|
497
|
+
const loop = join === true;
|
|
498
|
+
const sides = loop ? { from: "end", to: "start" } : join || facingSides(fromSegments, toSegments);
|
|
480
499
|
const fromAttach = attachment(fromSegments, sides.from);
|
|
481
500
|
const toAttach = attachment(toSegments, sides.to);
|
|
482
501
|
const fromEnd = fromAttach.at;
|
|
@@ -485,7 +504,7 @@ function computeEdgeCurves(fromSegments, toSegments, selfLink, offsetX, offsetY,
|
|
|
485
504
|
const p1y = fromEnd.y + offsetY;
|
|
486
505
|
const p2x = toStart.x + offsetX;
|
|
487
506
|
const p2y = toStart.y + offsetY;
|
|
488
|
-
if (
|
|
507
|
+
if (loop) {
|
|
489
508
|
let segDirX = 1;
|
|
490
509
|
let segDirY = 0;
|
|
491
510
|
if (fromAttach.inward) {
|
|
@@ -591,12 +610,15 @@ function nearerEnd(segments, to) {
|
|
|
591
610
|
const end = segments.at(-1);
|
|
592
611
|
return Math.hypot(start.x - to.x, start.y - to.y) < Math.hypot(end.x - to.x, end.y - to.y) ? "start" : "end";
|
|
593
612
|
}
|
|
594
|
-
function routedEdgeCurves(fromSegments, toSegments, route) {
|
|
613
|
+
function routedEdgeCurves(fromSegments, toSegments, route, sides) {
|
|
595
614
|
const fromAttach = attachment(
|
|
596
615
|
fromSegments,
|
|
597
|
-
nearerEnd(fromSegments, route[0])
|
|
616
|
+
sides?.from ?? nearerEnd(fromSegments, route[0])
|
|
617
|
+
);
|
|
618
|
+
const toAttach = attachment(
|
|
619
|
+
toSegments,
|
|
620
|
+
sides?.to ?? nearerEnd(toSegments, route.at(-1))
|
|
598
621
|
);
|
|
599
|
-
const toAttach = attachment(toSegments, nearerEnd(toSegments, route.at(-1)));
|
|
600
622
|
const points = [fromAttach.at, ...route, toAttach.at];
|
|
601
623
|
const tangent = (i) => {
|
|
602
624
|
const p = points[i];
|
|
@@ -646,16 +668,21 @@ var MIN_DELETION_BP = 1;
|
|
|
646
668
|
function bypassedPoints(nodePositions, bypassed) {
|
|
647
669
|
return bypassed.flatMap((id) => nodePositions[id] ?? []);
|
|
648
670
|
}
|
|
649
|
-
function deletionArcCurves(nodePositions, deletion, axis, route) {
|
|
671
|
+
function deletionArcCurves(nodePositions, deletion, axis, route, stranded = false) {
|
|
650
672
|
const from = nodePositions[deletion.from];
|
|
651
673
|
const to = nodePositions[deletion.to];
|
|
652
674
|
if (!from?.length || !to?.length) {
|
|
653
675
|
return void 0;
|
|
654
676
|
}
|
|
655
|
-
return route ? routedEdgeCurves(
|
|
677
|
+
return route ? routedEdgeCurves(
|
|
678
|
+
from,
|
|
679
|
+
to,
|
|
680
|
+
route,
|
|
681
|
+
stranded ? strandSides(deletion) : void 0
|
|
682
|
+
) : computeEdgeCurves(
|
|
656
683
|
from,
|
|
657
684
|
to,
|
|
658
|
-
deletion
|
|
685
|
+
edgeJoinOf(deletion, stranded),
|
|
659
686
|
0,
|
|
660
687
|
0,
|
|
661
688
|
axis,
|
|
@@ -723,6 +750,8 @@ function deletionEdges(graph) {
|
|
|
723
750
|
edgeIndex,
|
|
724
751
|
from: edge.from,
|
|
725
752
|
to: edge.to,
|
|
753
|
+
...edge.fromStrand ? { fromStrand: edge.fromStrand } : {},
|
|
754
|
+
...edge.toStrand ? { toStrand: edge.toStrand } : {},
|
|
726
755
|
refName,
|
|
727
756
|
start,
|
|
728
757
|
end,
|
|
@@ -1207,19 +1236,11 @@ function takeRoutes(positions, routes) {
|
|
|
1207
1236
|
}
|
|
1208
1237
|
|
|
1209
1238
|
// src/layout/mergeRuns.ts
|
|
1210
|
-
function ownStrand(id) {
|
|
1211
|
-
return id.endsWith("-") ? "-" : "+";
|
|
1212
|
-
}
|
|
1213
1239
|
function flip(strand) {
|
|
1214
1240
|
return strand === "+" ? "-" : "+";
|
|
1215
1241
|
}
|
|
1216
|
-
function sidesOf(edge) {
|
|
1217
|
-
const from = (edge.fromStrand ?? ownStrand(edge.from)) === ownStrand(edge.from) ? "end" : "start";
|
|
1218
|
-
const to = (edge.toStrand ?? ownStrand(edge.to)) === ownStrand(edge.to) ? "start" : "end";
|
|
1219
|
-
return { from, to };
|
|
1220
|
-
}
|
|
1221
1242
|
function mergeRuns(graph, alone = /* @__PURE__ */ new Set()) {
|
|
1222
|
-
const sides = graph.edges.map(
|
|
1243
|
+
const sides = graph.edges.map(strandSides);
|
|
1223
1244
|
const at = /* @__PURE__ */ new Map();
|
|
1224
1245
|
const attach = (id, side, ei) => {
|
|
1225
1246
|
const key = `${id}|${side}`;
|
|
@@ -1589,24 +1610,33 @@ function loadGraph(text, name, opts = {}) {
|
|
|
1589
1610
|
}
|
|
1590
1611
|
return graph;
|
|
1591
1612
|
}
|
|
1613
|
+
var COMPONENT_SEPARATION = 15;
|
|
1592
1614
|
function engineRequest(graph, scaling, settings) {
|
|
1593
1615
|
const anchored = graph.nodes.some(isBackbone);
|
|
1616
|
+
const spaced = {
|
|
1617
|
+
...scaling,
|
|
1618
|
+
opts: {
|
|
1619
|
+
...scaling.opts,
|
|
1620
|
+
edgeLength: scaling.opts.edgeLength * (settings.spacing ?? 1)
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1594
1623
|
return {
|
|
1595
1624
|
graph: {
|
|
1596
|
-
nodes: anchored ? seededNodes(graph,
|
|
1625
|
+
nodes: anchored ? seededNodes(graph, spaced) : spaced.nodes,
|
|
1597
1626
|
edges: graph.edges
|
|
1598
1627
|
},
|
|
1599
1628
|
options: {
|
|
1600
1629
|
quality: settings.quality,
|
|
1601
1630
|
linearLayout: settings.linearLayout,
|
|
1602
|
-
...
|
|
1631
|
+
...spaced.opts,
|
|
1632
|
+
componentSeparation: COMPONENT_SEPARATION * (settings.componentSeparation ?? 1),
|
|
1603
1633
|
...anchored ? { rotateComponents: false } : {}
|
|
1604
1634
|
}
|
|
1605
1635
|
};
|
|
1606
1636
|
}
|
|
1607
1637
|
function engineKey(graph, settings) {
|
|
1608
1638
|
const anchored = graph.nodes.some(isBackbone);
|
|
1609
|
-
return `${settings.quality}|${settings.linearLayout}|${settings.bubbleSpread}|${anchored}|${graph.referencePath ?? ""}`;
|
|
1639
|
+
return `${settings.quality}|${settings.linearLayout}|${settings.bubbleSpread}|${settings.spacing ?? 1}|${settings.componentSeparation ?? 1}|${anchored}|${graph.referencePath ?? ""}`;
|
|
1610
1640
|
}
|
|
1611
1641
|
async function forceLayout(graph, settings, engine) {
|
|
1612
1642
|
const spread = spreadFor(settings.bubbleSpread);
|
|
@@ -1654,6 +1684,7 @@ async function forceLayout(graph, settings, engine) {
|
|
|
1654
1684
|
result: {
|
|
1655
1685
|
...result,
|
|
1656
1686
|
nodePositions,
|
|
1687
|
+
stranded: true,
|
|
1657
1688
|
...deletions.length ? { deletionRoutes, extent: layoutExtent(deletionRoutes) } : {}
|
|
1658
1689
|
},
|
|
1659
1690
|
duration
|
|
@@ -1773,7 +1804,7 @@ function placeLabels(candidates, frame, take) {
|
|
|
1773
1804
|
if (offFrame(box, frame)) {
|
|
1774
1805
|
return void 0;
|
|
1775
1806
|
}
|
|
1776
|
-
if (insideFrame(box, frame) && take(box)) {
|
|
1807
|
+
if (insideFrame(box, frame) && (c.fits?.(box) ?? true) && take(box)) {
|
|
1777
1808
|
return { ...c, text, y, w };
|
|
1778
1809
|
}
|
|
1779
1810
|
}
|
|
@@ -1888,10 +1919,10 @@ function labelOrder(nodePositions, nodeLengths) {
|
|
|
1888
1919
|
return order;
|
|
1889
1920
|
}
|
|
1890
1921
|
var arcCache = /* @__PURE__ */ new WeakMap();
|
|
1891
|
-
function arcPlacements(nodePositions, deletions, axis, version2, routes) {
|
|
1922
|
+
function arcPlacements(nodePositions, deletions, axis, version2, routes, stranded) {
|
|
1892
1923
|
const { scaleX, scaleY } = axis;
|
|
1893
1924
|
const cached2 = arcCache.get(nodePositions);
|
|
1894
|
-
if (cached2?.deletions === deletions && cached2.routes === routes && cached2.scaleX === scaleX && cached2.scaleY === scaleY && cached2.version === version2) {
|
|
1925
|
+
if (cached2?.deletions === deletions && cached2.routes === routes && cached2.stranded === stranded && cached2.scaleX === scaleX && cached2.scaleY === scaleY && cached2.version === version2) {
|
|
1895
1926
|
return cached2.arcs;
|
|
1896
1927
|
}
|
|
1897
1928
|
const arcs = [];
|
|
@@ -1900,7 +1931,8 @@ function arcPlacements(nodePositions, deletions, axis, version2, routes) {
|
|
|
1900
1931
|
nodePositions,
|
|
1901
1932
|
deletion,
|
|
1902
1933
|
axis,
|
|
1903
|
-
routes?.[deletion.edgeIndex]
|
|
1934
|
+
routes?.[deletion.edgeIndex],
|
|
1935
|
+
stranded
|
|
1904
1936
|
);
|
|
1905
1937
|
const apex = curves ? curveMidpoint(curves) : void 0;
|
|
1906
1938
|
if (curves && apex) {
|
|
@@ -1917,6 +1949,7 @@ function arcPlacements(nodePositions, deletions, axis, version2, routes) {
|
|
|
1917
1949
|
arcCache.set(nodePositions, {
|
|
1918
1950
|
deletions,
|
|
1919
1951
|
routes,
|
|
1952
|
+
stranded,
|
|
1920
1953
|
scaleX,
|
|
1921
1954
|
scaleY,
|
|
1922
1955
|
version: version2,
|
|
@@ -1929,6 +1962,7 @@ function sizeLabelCandidates({
|
|
|
1929
1962
|
nodeLengths,
|
|
1930
1963
|
deletions,
|
|
1931
1964
|
deletionRoutes,
|
|
1965
|
+
stranded = false,
|
|
1932
1966
|
alleleDeletions,
|
|
1933
1967
|
axis,
|
|
1934
1968
|
translateX,
|
|
@@ -1945,7 +1979,8 @@ function sizeLabelCandidates({
|
|
|
1945
1979
|
deletions,
|
|
1946
1980
|
axis,
|
|
1947
1981
|
version2,
|
|
1948
|
-
deletionRoutes
|
|
1982
|
+
deletionRoutes,
|
|
1983
|
+
stranded
|
|
1949
1984
|
)) {
|
|
1950
1985
|
if (extent >= MIN_DELETION_LABEL_PX) {
|
|
1951
1986
|
const text = deletionText(deletion.bp);
|
|
@@ -3374,7 +3409,7 @@ var COLOR_SCHEMES = [
|
|
|
3374
3409
|
{ value: "random", label: "Random" },
|
|
3375
3410
|
{ value: "rainbow", label: "Rainbow" },
|
|
3376
3411
|
{ value: "depth", label: "Depth" },
|
|
3377
|
-
{ value: "node-length", label: "Node
|
|
3412
|
+
{ value: "node-length", label: "Node length" },
|
|
3378
3413
|
{ value: "stable-rank", label: "Stable rank" },
|
|
3379
3414
|
{ value: "reference-position", label: "Reference position" },
|
|
3380
3415
|
{ value: "grey", label: "Grey" }
|
|
@@ -3465,10 +3500,10 @@ function brightenAbgr(c, factor) {
|
|
|
3465
3500
|
|
|
3466
3501
|
// src/util/edgeCurves.ts
|
|
3467
3502
|
var cache = /* @__PURE__ */ new WeakMap();
|
|
3468
|
-
function baseEdgeCurves(nodePositions, graph, axis, deletions, version2, routes) {
|
|
3503
|
+
function baseEdgeCurves(nodePositions, graph, axis, deletions, version2, routes, stranded = false) {
|
|
3469
3504
|
const { scaleX, scaleY } = axis;
|
|
3470
3505
|
const cached2 = cache.get(nodePositions);
|
|
3471
|
-
if (cached2?.graph === graph && cached2.scaleX === scaleX && cached2.scaleY === scaleY && cached2.deletions === deletions && cached2.routes === routes && cached2.version === version2) {
|
|
3506
|
+
if (cached2?.graph === graph && cached2.scaleX === scaleX && cached2.scaleY === scaleY && cached2.deletions === deletions && cached2.routes === routes && cached2.stranded === stranded && cached2.version === version2) {
|
|
3472
3507
|
return cached2.curves;
|
|
3473
3508
|
}
|
|
3474
3509
|
const curves = /* @__PURE__ */ new Map();
|
|
@@ -3481,10 +3516,15 @@ function baseEdgeCurves(nodePositions, graph, axis, deletions, version2, routes)
|
|
|
3481
3516
|
const route = bypassed && routes?.[ei];
|
|
3482
3517
|
curves.set(
|
|
3483
3518
|
ei,
|
|
3484
|
-
route ? routedEdgeCurves(
|
|
3519
|
+
route ? routedEdgeCurves(
|
|
3520
|
+
from,
|
|
3521
|
+
to,
|
|
3522
|
+
route,
|
|
3523
|
+
stranded ? strandSides(edge) : void 0
|
|
3524
|
+
) : computeEdgeCurves(
|
|
3485
3525
|
from,
|
|
3486
3526
|
to,
|
|
3487
|
-
|
|
3527
|
+
edgeJoinOf(edge, stranded),
|
|
3488
3528
|
0,
|
|
3489
3529
|
0,
|
|
3490
3530
|
axis,
|
|
@@ -3499,6 +3539,7 @@ function baseEdgeCurves(nodePositions, graph, axis, deletions, version2, routes)
|
|
|
3499
3539
|
scaleY,
|
|
3500
3540
|
deletions,
|
|
3501
3541
|
routes,
|
|
3542
|
+
stranded,
|
|
3502
3543
|
version: version2,
|
|
3503
3544
|
curves
|
|
3504
3545
|
});
|
|
@@ -3523,6 +3564,7 @@ var ARROW_LENGTH_BASE_PX = 7;
|
|
|
3523
3564
|
var ARROW_LENGTH_PER_EDGE_PX = 3;
|
|
3524
3565
|
var ARROW_HALF_WIDTH_BASE_PX = 3;
|
|
3525
3566
|
var ARROW_HALF_WIDTH_PER_EDGE_PX = 1.5;
|
|
3567
|
+
var MIN_LINK_HEADS = 3;
|
|
3526
3568
|
var MIN_ARROW_FIT = 0.5;
|
|
3527
3569
|
var MIN_ARROW_SCALE = 0.45;
|
|
3528
3570
|
var ARROW_CURVE_STEPS = 12;
|
|
@@ -3587,12 +3629,17 @@ function arrowheadFor({
|
|
|
3587
3629
|
toSegments
|
|
3588
3630
|
}) {
|
|
3589
3631
|
const length = ARROW_LENGTH_BASE_PX + ARROW_LENGTH_PER_EDGE_PX * edgeHalfWidth;
|
|
3590
|
-
const n2 = backFromEnd(
|
|
3632
|
+
const n2 = backFromEnd(
|
|
3633
|
+
curves,
|
|
3634
|
+
scale,
|
|
3635
|
+
yToX,
|
|
3636
|
+
nodeHalfWidth + MIN_LINK_HEADS * length
|
|
3637
|
+
);
|
|
3591
3638
|
const lengthPx = walkPx[n2 - 1];
|
|
3639
|
+
const abutting = lengthPx < ABUTTING_PX;
|
|
3592
3640
|
const inset = Math.min(nodeHalfWidth, lengthPx);
|
|
3593
|
-
const
|
|
3594
|
-
|
|
3595
|
-
if (fit < MIN_ARROW_FIT) {
|
|
3641
|
+
const fit = abutting ? Math.min(1, polylinePx(fromSegments, scale, yToX) / length) : 1;
|
|
3642
|
+
if (abutting ? fit < MIN_ARROW_FIT : lengthPx < MIN_LINK_HEADS * length) {
|
|
3596
3643
|
return void 0;
|
|
3597
3644
|
}
|
|
3598
3645
|
const tip = walkPointAt(n2, inset);
|
|
@@ -3619,6 +3666,13 @@ function polylinePx(segments, scale, yToX) {
|
|
|
3619
3666
|
}
|
|
3620
3667
|
return length * scale;
|
|
3621
3668
|
}
|
|
3669
|
+
function leavesStart(edge, segments, first, stranded, yToX) {
|
|
3670
|
+
if (stranded) {
|
|
3671
|
+
return strandSides(edge).from === "start";
|
|
3672
|
+
}
|
|
3673
|
+
const gap = (p) => Math.hypot(p.x - first.x0, (p.y - first.y0) * yToX);
|
|
3674
|
+
return gap(segments[0]) < gap(segments.at(-1));
|
|
3675
|
+
}
|
|
3622
3676
|
function intoDirection(last, toSegments) {
|
|
3623
3677
|
if (toSegments.length < 2) {
|
|
3624
3678
|
return void 0;
|
|
@@ -3964,6 +4018,7 @@ function buildGeometry(options) {
|
|
|
3964
4018
|
referenceRamp,
|
|
3965
4019
|
deletions,
|
|
3966
4020
|
deletionRoutes,
|
|
4021
|
+
stranded,
|
|
3967
4022
|
hiddenEdges,
|
|
3968
4023
|
version: version2 = 0
|
|
3969
4024
|
} = options;
|
|
@@ -3976,7 +4031,8 @@ function buildGeometry(options) {
|
|
|
3976
4031
|
axis,
|
|
3977
4032
|
deletions,
|
|
3978
4033
|
version2,
|
|
3979
|
-
deletionRoutes
|
|
4034
|
+
deletionRoutes,
|
|
4035
|
+
stranded
|
|
3980
4036
|
);
|
|
3981
4037
|
const nodeStrokes = [];
|
|
3982
4038
|
const nodeStrokeRuns = /* @__PURE__ */ new Map();
|
|
@@ -4028,7 +4084,7 @@ function buildGeometry(options) {
|
|
|
4028
4084
|
}
|
|
4029
4085
|
const head = showArrows && arrowColor !== void 0 ? arrowheadFor({
|
|
4030
4086
|
curves,
|
|
4031
|
-
edgeHalfWidth:
|
|
4087
|
+
edgeHalfWidth: connectorThickness / 2,
|
|
4032
4088
|
nodeHalfWidth: intoHalfWidth,
|
|
4033
4089
|
scale,
|
|
4034
4090
|
yToX,
|
|
@@ -4048,7 +4104,13 @@ function buildGeometry(options) {
|
|
|
4048
4104
|
const width = fromNode ? nodeWidthPx(fromNode, contigThickness, nodeWidth2, depthNorm) : contigThickness;
|
|
4049
4105
|
const laneWidth = Math.max(width / laneWalks.length, MIN_WALK_LANE_PX);
|
|
4050
4106
|
const normals = pointNormalsOf(fromSegments, yToX);
|
|
4051
|
-
const { nx, ny } = (
|
|
4107
|
+
const { nx, ny } = (leavesStart(
|
|
4108
|
+
edge,
|
|
4109
|
+
fromSegments,
|
|
4110
|
+
baseCurves[0],
|
|
4111
|
+
stranded,
|
|
4112
|
+
yToX
|
|
4113
|
+
) ? normals[0] : normals.at(-1)) ?? {
|
|
4052
4114
|
nx: 0,
|
|
4053
4115
|
ny: 0
|
|
4054
4116
|
};
|
|
@@ -4370,6 +4432,415 @@ function groupByPaint(items, paintOf) {
|
|
|
4370
4432
|
return [...groups.values(), ...deferred.values()];
|
|
4371
4433
|
}
|
|
4372
4434
|
|
|
4435
|
+
// src/util/SpatialIndex.ts
|
|
4436
|
+
var FALLBACK_CELL_SIZE = 50;
|
|
4437
|
+
function meanExtentCellSize(extentTotal, count) {
|
|
4438
|
+
return count > 0 ? Math.max(1, extentTotal / count) : FALLBACK_CELL_SIZE;
|
|
4439
|
+
}
|
|
4440
|
+
function rowPitchOf(boxes) {
|
|
4441
|
+
const distinct = /* @__PURE__ */ new Set();
|
|
4442
|
+
let min = Infinity;
|
|
4443
|
+
let max = -Infinity;
|
|
4444
|
+
for (const box of boxes) {
|
|
4445
|
+
distinct.add(box.minY);
|
|
4446
|
+
min = Math.min(min, box.minY);
|
|
4447
|
+
max = Math.max(max, box.minY);
|
|
4448
|
+
}
|
|
4449
|
+
return distinct.size > 1 ? (max - min) / (distinct.size - 1) : void 0;
|
|
4450
|
+
}
|
|
4451
|
+
function cellSizeFrom(boxes, extentX, extentY) {
|
|
4452
|
+
const x = meanExtentCellSize(extentX, boxes.length);
|
|
4453
|
+
return {
|
|
4454
|
+
x,
|
|
4455
|
+
y: extentY > 0 ? meanExtentCellSize(extentY, boxes.length) : rowPitchOf(boxes) ?? x
|
|
4456
|
+
};
|
|
4457
|
+
}
|
|
4458
|
+
function resolveCellSize(given, boxes, extentX, extentY) {
|
|
4459
|
+
return given === void 0 ? cellSizeFrom(boxes, extentX, extentY) : typeof given === "number" ? { x: given, y: given } : given;
|
|
4460
|
+
}
|
|
4461
|
+
function getOrCreateCell(cells, cx, cy) {
|
|
4462
|
+
let row = cells.get(cx);
|
|
4463
|
+
if (!row) {
|
|
4464
|
+
row = /* @__PURE__ */ new Map();
|
|
4465
|
+
cells.set(cx, row);
|
|
4466
|
+
}
|
|
4467
|
+
let cell = row.get(cy);
|
|
4468
|
+
if (!cell) {
|
|
4469
|
+
cell = [];
|
|
4470
|
+
row.set(cy, cell);
|
|
4471
|
+
}
|
|
4472
|
+
return cell;
|
|
4473
|
+
}
|
|
4474
|
+
function addToGrid(cells, cellSize, minX, minY, maxX, maxY, entry) {
|
|
4475
|
+
const cx0 = Math.floor(minX / cellSize.x);
|
|
4476
|
+
const cx1 = Math.floor(maxX / cellSize.x);
|
|
4477
|
+
const cy0 = Math.floor(minY / cellSize.y);
|
|
4478
|
+
const cy1 = Math.floor(maxY / cellSize.y);
|
|
4479
|
+
for (let cx = cx0; cx <= cx1; cx++) {
|
|
4480
|
+
for (let cy = cy0; cy <= cy1; cy++) {
|
|
4481
|
+
getOrCreateCell(cells, cx, cy).push(entry);
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
function queryGrid(cells, cellSize, x, y, radiusX, radiusY) {
|
|
4486
|
+
const cx0 = Math.floor((x - radiusX) / cellSize.x);
|
|
4487
|
+
const cx1 = Math.floor((x + radiusX) / cellSize.x);
|
|
4488
|
+
const cy0 = Math.floor((y - radiusY) / cellSize.y);
|
|
4489
|
+
const cy1 = Math.floor((y + radiusY) / cellSize.y);
|
|
4490
|
+
const results = [];
|
|
4491
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4492
|
+
for (let cx = cx0; cx <= cx1; cx++) {
|
|
4493
|
+
const row = cells.get(cx);
|
|
4494
|
+
if (row) {
|
|
4495
|
+
for (let cy = cy0; cy <= cy1; cy++) {
|
|
4496
|
+
const cell = row.get(cy);
|
|
4497
|
+
if (cell) {
|
|
4498
|
+
for (const entry of cell) {
|
|
4499
|
+
if (!seen.has(entry)) {
|
|
4500
|
+
seen.add(entry);
|
|
4501
|
+
results.push(entry);
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
return results;
|
|
4509
|
+
}
|
|
4510
|
+
var SpatialIndex = class {
|
|
4511
|
+
constructor(nodePositions, cellSize) {
|
|
4512
|
+
this.cells = /* @__PURE__ */ new Map();
|
|
4513
|
+
const boxes = [];
|
|
4514
|
+
let extentX = 0;
|
|
4515
|
+
let extentY = 0;
|
|
4516
|
+
for (const [nodeId, segments] of Object.entries(nodePositions)) {
|
|
4517
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
4518
|
+
const seg = segments[i];
|
|
4519
|
+
const next = segments[i + 1];
|
|
4520
|
+
const minX = Math.min(seg.x, next.x);
|
|
4521
|
+
const minY = Math.min(seg.y, next.y);
|
|
4522
|
+
const maxX = Math.max(seg.x, next.x);
|
|
4523
|
+
const maxY = Math.max(seg.y, next.y);
|
|
4524
|
+
extentX += maxX - minX;
|
|
4525
|
+
extentY += maxY - minY;
|
|
4526
|
+
boxes.push({ entry: { nodeId, segmentIdx: i }, minX, minY, maxX, maxY });
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
this.cellSize = resolveCellSize(cellSize, boxes, extentX, extentY);
|
|
4530
|
+
for (const b of boxes) {
|
|
4531
|
+
addToGrid(
|
|
4532
|
+
this.cells,
|
|
4533
|
+
this.cellSize,
|
|
4534
|
+
b.minX,
|
|
4535
|
+
b.minY,
|
|
4536
|
+
b.maxX,
|
|
4537
|
+
b.maxY,
|
|
4538
|
+
b.entry
|
|
4539
|
+
);
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
query(x, y, radiusX, radiusY = radiusX) {
|
|
4543
|
+
return queryGrid(this.cells, this.cellSize, x, y, radiusX, radiusY);
|
|
4544
|
+
}
|
|
4545
|
+
};
|
|
4546
|
+
var EdgeSpatialIndex = class {
|
|
4547
|
+
constructor(nodePositions, graph, drawPaths, axis, cellSize, deletions, version2 = 0, routes, stranded = false) {
|
|
4548
|
+
this.cells = /* @__PURE__ */ new Map();
|
|
4549
|
+
this.edgeCurves = baseEdgeCurves(
|
|
4550
|
+
nodePositions,
|
|
4551
|
+
graph,
|
|
4552
|
+
axis,
|
|
4553
|
+
deletions,
|
|
4554
|
+
version2,
|
|
4555
|
+
routes,
|
|
4556
|
+
stranded
|
|
4557
|
+
);
|
|
4558
|
+
const boxes = [];
|
|
4559
|
+
let extentX = 0;
|
|
4560
|
+
let extentY = 0;
|
|
4561
|
+
for (let ei = 0; ei < graph.edges.length; ei++) {
|
|
4562
|
+
const edge = graph.edges[ei];
|
|
4563
|
+
const fromSegments = nodePositions[edge.from];
|
|
4564
|
+
const toSegments = nodePositions[edge.to];
|
|
4565
|
+
const curves = this.edgeCurves.get(ei);
|
|
4566
|
+
if (fromSegments?.length && toSegments?.length && curves) {
|
|
4567
|
+
let { minX, minY, maxX, maxY } = curveBounds(curves);
|
|
4568
|
+
const numPaths = edge.pathIds?.length ?? 0;
|
|
4569
|
+
if (drawPaths && numPaths > 0) {
|
|
4570
|
+
let padX = 0;
|
|
4571
|
+
let padY = 0;
|
|
4572
|
+
for (const o of pathRibbonOffsets(
|
|
4573
|
+
fromSegments,
|
|
4574
|
+
toSegments,
|
|
4575
|
+
numPaths,
|
|
4576
|
+
axis
|
|
4577
|
+
)) {
|
|
4578
|
+
padX = Math.max(padX, Math.abs(o.x));
|
|
4579
|
+
padY = Math.max(padY, Math.abs(o.y));
|
|
4580
|
+
}
|
|
4581
|
+
minX -= padX;
|
|
4582
|
+
minY -= padY;
|
|
4583
|
+
maxX += padX;
|
|
4584
|
+
maxY += padY;
|
|
4585
|
+
}
|
|
4586
|
+
extentX += maxX - minX;
|
|
4587
|
+
extentY += maxY - minY;
|
|
4588
|
+
boxes.push({ ei, minX, minY, maxX, maxY });
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
this.cellSize = resolveCellSize(cellSize, boxes, extentX, extentY);
|
|
4592
|
+
for (const b of boxes) {
|
|
4593
|
+
addToGrid(this.cells, this.cellSize, b.minX, b.minY, b.maxX, b.maxY, b.ei);
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
getCurves(ei) {
|
|
4597
|
+
return this.edgeCurves.get(ei);
|
|
4598
|
+
}
|
|
4599
|
+
query(x, y, radiusX, radiusY = radiusX) {
|
|
4600
|
+
return queryGrid(this.cells, this.cellSize, x, y, radiusX, radiusY);
|
|
4601
|
+
}
|
|
4602
|
+
};
|
|
4603
|
+
|
|
4604
|
+
// src/util/hitDetection.ts
|
|
4605
|
+
function distanceToSegment(px, py, x1, y1, x2, y2) {
|
|
4606
|
+
const dx = x2 - x1;
|
|
4607
|
+
const dy = y2 - y1;
|
|
4608
|
+
const lenSq = dx * dx + dy * dy;
|
|
4609
|
+
if (lenSq === 0) {
|
|
4610
|
+
return Math.hypot(px - x1, py - y1);
|
|
4611
|
+
}
|
|
4612
|
+
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
4613
|
+
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
4614
|
+
}
|
|
4615
|
+
function distanceToCubicBezier(px, py, x1, y1, cx1, cy1, cx2, cy2, x2, y2) {
|
|
4616
|
+
let minDist = Infinity;
|
|
4617
|
+
const samples = 20;
|
|
4618
|
+
let lastX = x1;
|
|
4619
|
+
let lastY = y1;
|
|
4620
|
+
for (let i = 1; i <= samples; i++) {
|
|
4621
|
+
const t = i / samples;
|
|
4622
|
+
const u = 1 - t;
|
|
4623
|
+
const bx = u * u * u * x1 + 3 * u * u * t * cx1 + 3 * u * t * t * cx2 + t * t * t * x2;
|
|
4624
|
+
const by = u * u * u * y1 + 3 * u * u * t * cy1 + 3 * u * t * t * cy2 + t * t * t * y2;
|
|
4625
|
+
minDist = Math.min(minDist, distanceToSegment(px, py, lastX, lastY, bx, by));
|
|
4626
|
+
lastX = bx;
|
|
4627
|
+
lastY = by;
|
|
4628
|
+
}
|
|
4629
|
+
return minDist;
|
|
4630
|
+
}
|
|
4631
|
+
function distanceToBezierCurve(px, py, c) {
|
|
4632
|
+
return distanceToCubicBezier(
|
|
4633
|
+
px,
|
|
4634
|
+
py,
|
|
4635
|
+
c.x0,
|
|
4636
|
+
c.y0,
|
|
4637
|
+
c.cx0,
|
|
4638
|
+
c.cy0,
|
|
4639
|
+
c.cx1,
|
|
4640
|
+
c.cy1,
|
|
4641
|
+
c.x1,
|
|
4642
|
+
c.y1
|
|
4643
|
+
);
|
|
4644
|
+
}
|
|
4645
|
+
function distanceToEdgeCurves(px, py, curves, yToX = 1) {
|
|
4646
|
+
let minDist = Infinity;
|
|
4647
|
+
for (const c of curves) {
|
|
4648
|
+
const d = distanceToBezierCurve(
|
|
4649
|
+
px,
|
|
4650
|
+
py,
|
|
4651
|
+
yToX === 1 ? c : {
|
|
4652
|
+
x0: c.x0,
|
|
4653
|
+
y0: c.y0 * yToX,
|
|
4654
|
+
cx0: c.cx0,
|
|
4655
|
+
cy0: c.cy0 * yToX,
|
|
4656
|
+
cx1: c.cx1,
|
|
4657
|
+
cy1: c.cy1 * yToX,
|
|
4658
|
+
x1: c.x1,
|
|
4659
|
+
y1: c.y1 * yToX
|
|
4660
|
+
}
|
|
4661
|
+
);
|
|
4662
|
+
if (d < minDist) {
|
|
4663
|
+
minDist = d;
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
return minDist;
|
|
4667
|
+
}
|
|
4668
|
+
var nodeCache = /* @__PURE__ */ new WeakMap();
|
|
4669
|
+
function getSpatialIndex(nodePositions, version2) {
|
|
4670
|
+
const cached2 = nodeCache.get(nodePositions);
|
|
4671
|
+
if (cached2?.version === version2) {
|
|
4672
|
+
return cached2.index;
|
|
4673
|
+
}
|
|
4674
|
+
const index = new SpatialIndex(nodePositions);
|
|
4675
|
+
nodeCache.set(nodePositions, { version: version2, index });
|
|
4676
|
+
return index;
|
|
4677
|
+
}
|
|
4678
|
+
var edgeCache = /* @__PURE__ */ new WeakMap();
|
|
4679
|
+
function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis, version2, deletions, routes, stranded = false) {
|
|
4680
|
+
const cached2 = edgeCache.get(nodePositions);
|
|
4681
|
+
if (cached2?.graph === graph && cached2.drawPaths === drawPaths && cached2.scaleX === axis.scaleX && cached2.scaleY === axis.scaleY && cached2.version === version2 && cached2.deletions === deletions && cached2.routes === routes && cached2.stranded === stranded) {
|
|
4682
|
+
return cached2.index;
|
|
4683
|
+
}
|
|
4684
|
+
const index = new EdgeSpatialIndex(
|
|
4685
|
+
nodePositions,
|
|
4686
|
+
graph,
|
|
4687
|
+
drawPaths,
|
|
4688
|
+
axis,
|
|
4689
|
+
void 0,
|
|
4690
|
+
deletions,
|
|
4691
|
+
version2,
|
|
4692
|
+
routes,
|
|
4693
|
+
stranded
|
|
4694
|
+
);
|
|
4695
|
+
edgeCache.set(nodePositions, {
|
|
4696
|
+
graph,
|
|
4697
|
+
drawPaths,
|
|
4698
|
+
scaleX: axis.scaleX,
|
|
4699
|
+
scaleY: axis.scaleY,
|
|
4700
|
+
version: version2,
|
|
4701
|
+
index,
|
|
4702
|
+
deletions,
|
|
4703
|
+
routes,
|
|
4704
|
+
stranded
|
|
4705
|
+
});
|
|
4706
|
+
return index;
|
|
4707
|
+
}
|
|
4708
|
+
var MIN_NODE_REACH_PX = 5;
|
|
4709
|
+
function findHoveredNode(nodePositions, graphX, graphY, axis, version2 = 0, ink) {
|
|
4710
|
+
const yToX = yToXOf(axis);
|
|
4711
|
+
const index = getSpatialIndex(nodePositions, version2);
|
|
4712
|
+
const reach = (px) => Math.max(MIN_NODE_REACH_PX, px) / axis.scaleX;
|
|
4713
|
+
const queryReach = reach(ink?.maxHalfWidthPx ?? 0);
|
|
4714
|
+
const candidates = index.query(graphX, graphY, queryReach, queryReach / yToX);
|
|
4715
|
+
let hovered = null;
|
|
4716
|
+
let best = Infinity;
|
|
4717
|
+
for (const { nodeId, segmentIdx } of candidates) {
|
|
4718
|
+
const segments = nodePositions[nodeId];
|
|
4719
|
+
const dist = distanceToSegment(
|
|
4720
|
+
graphX,
|
|
4721
|
+
graphY * yToX,
|
|
4722
|
+
segments[segmentIdx].x,
|
|
4723
|
+
segments[segmentIdx].y * yToX,
|
|
4724
|
+
segments[segmentIdx + 1].x,
|
|
4725
|
+
segments[segmentIdx + 1].y * yToX
|
|
4726
|
+
);
|
|
4727
|
+
if (dist < best && dist < reach(ink?.halfWidthPx(nodeId) ?? 0)) {
|
|
4728
|
+
best = dist;
|
|
4729
|
+
hovered = nodeId;
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
return hovered;
|
|
4733
|
+
}
|
|
4734
|
+
function segmentMeetsBox(a, b, box) {
|
|
4735
|
+
const dx = b.x - a.x;
|
|
4736
|
+
const dy = b.y - a.y;
|
|
4737
|
+
let t0 = 0;
|
|
4738
|
+
let t1 = 1;
|
|
4739
|
+
for (const [p, q] of [
|
|
4740
|
+
[-dx, a.x - box.x0],
|
|
4741
|
+
[dx, box.x1 - a.x],
|
|
4742
|
+
[-dy, a.y - box.y0],
|
|
4743
|
+
[dy, box.y1 - a.y]
|
|
4744
|
+
]) {
|
|
4745
|
+
if (p === 0) {
|
|
4746
|
+
if (q < 0) {
|
|
4747
|
+
return false;
|
|
4748
|
+
}
|
|
4749
|
+
} else if (p < 0) {
|
|
4750
|
+
t0 = Math.max(t0, q / p);
|
|
4751
|
+
} else {
|
|
4752
|
+
t1 = Math.min(t1, q / p);
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
return t0 <= t1;
|
|
4756
|
+
}
|
|
4757
|
+
function nodesUnderBox(nodePositions, t, version2 = 0) {
|
|
4758
|
+
const index = getSpatialIndex(nodePositions, version2);
|
|
4759
|
+
const screen = (p) => ({
|
|
4760
|
+
x: p.x * t.scaleX + t.translateX,
|
|
4761
|
+
y: p.y * t.scaleY + t.translateY
|
|
4762
|
+
});
|
|
4763
|
+
return (box) => {
|
|
4764
|
+
const hits = /* @__PURE__ */ new Set();
|
|
4765
|
+
const candidates = index.query(
|
|
4766
|
+
((box.x0 + box.x1) / 2 - t.translateX) / t.scaleX,
|
|
4767
|
+
((box.y0 + box.y1) / 2 - t.translateY) / t.scaleY,
|
|
4768
|
+
Math.abs((box.x1 - box.x0) / 2 / t.scaleX),
|
|
4769
|
+
Math.abs((box.y1 - box.y0) / 2 / t.scaleY)
|
|
4770
|
+
);
|
|
4771
|
+
for (const { nodeId, segmentIdx } of candidates) {
|
|
4772
|
+
const line = nodePositions[nodeId];
|
|
4773
|
+
if (!hits.has(nodeId) && segmentMeetsBox(
|
|
4774
|
+
screen(line[segmentIdx]),
|
|
4775
|
+
screen(line[segmentIdx + 1]),
|
|
4776
|
+
box
|
|
4777
|
+
)) {
|
|
4778
|
+
hits.add(nodeId);
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
return hits;
|
|
4782
|
+
};
|
|
4783
|
+
}
|
|
4784
|
+
function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths, version2 = 0, deletions, hiddenEdges, deletionRoutes, stranded = false) {
|
|
4785
|
+
const yToX = yToXOf(axis);
|
|
4786
|
+
const edgeThreshold = 10 / axis.scaleX;
|
|
4787
|
+
const edgeIndex = getEdgeSpatialIndex(
|
|
4788
|
+
nodePositions,
|
|
4789
|
+
graph,
|
|
4790
|
+
drawPaths,
|
|
4791
|
+
axis,
|
|
4792
|
+
version2,
|
|
4793
|
+
deletions,
|
|
4794
|
+
deletionRoutes,
|
|
4795
|
+
stranded
|
|
4796
|
+
);
|
|
4797
|
+
const candidates = edgeIndex.query(
|
|
4798
|
+
graphX,
|
|
4799
|
+
graphY,
|
|
4800
|
+
edgeThreshold,
|
|
4801
|
+
edgeThreshold / yToX
|
|
4802
|
+
);
|
|
4803
|
+
let hovered = null;
|
|
4804
|
+
let best = edgeThreshold;
|
|
4805
|
+
for (const edgeIdx of candidates) {
|
|
4806
|
+
const edge = graph.edges[edgeIdx];
|
|
4807
|
+
const fromSegments = nodePositions[edge.from];
|
|
4808
|
+
const toSegments = nodePositions[edge.to];
|
|
4809
|
+
if (!fromSegments?.length || !toSegments?.length || hiddenEdges?.has(edgeIdx)) {
|
|
4810
|
+
continue;
|
|
4811
|
+
}
|
|
4812
|
+
const numPaths = edge.pathIds?.length ?? 0;
|
|
4813
|
+
const baseCurves = edgeIndex.getCurves(edgeIdx);
|
|
4814
|
+
if (!baseCurves) {
|
|
4815
|
+
continue;
|
|
4816
|
+
}
|
|
4817
|
+
let dist;
|
|
4818
|
+
if (!drawPaths || numPaths === 0) {
|
|
4819
|
+
dist = distanceToEdgeCurves(graphX, graphY * yToX, baseCurves, yToX);
|
|
4820
|
+
} else {
|
|
4821
|
+
let minDist = Infinity;
|
|
4822
|
+
for (const offset of pathRibbonOffsets(
|
|
4823
|
+
fromSegments,
|
|
4824
|
+
toSegments,
|
|
4825
|
+
numPaths,
|
|
4826
|
+
axis
|
|
4827
|
+
)) {
|
|
4828
|
+
const curves = offset.x === 0 && offset.y === 0 ? baseCurves : translateCurves(baseCurves, offset.x, offset.y);
|
|
4829
|
+
const d = distanceToEdgeCurves(graphX, graphY * yToX, curves, yToX);
|
|
4830
|
+
if (d < minDist) {
|
|
4831
|
+
minDist = d;
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
dist = minDist;
|
|
4835
|
+
}
|
|
4836
|
+
if (dist < best) {
|
|
4837
|
+
best = dist;
|
|
4838
|
+
hovered = edgeIdx;
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
return hovered;
|
|
4842
|
+
}
|
|
4843
|
+
|
|
4373
4844
|
// src/walkEncoding.ts
|
|
4374
4845
|
import { packAbgr as packAbgr3 } from "@jbrowse/core/util/colorBits";
|
|
4375
4846
|
var WALK_FIELDS = [
|
|
@@ -4752,11 +5223,71 @@ function facetCells(names, by, domain = []) {
|
|
|
4752
5223
|
return cells.includes(-1) || new Set(cells).size < cells.length ? wrap : { columns, count: rows.length * columns, cells };
|
|
4753
5224
|
}
|
|
4754
5225
|
|
|
5226
|
+
// src/genes/exonOutline.ts
|
|
5227
|
+
var EXON_COLOR = "#daa520";
|
|
5228
|
+
var EXON_GAP_PX = 1;
|
|
5229
|
+
var EXON_LINE_PX = 2;
|
|
5230
|
+
var MIN_LANE_PX = 4;
|
|
5231
|
+
function exonStretches(pins, halfWidthPx, lift, pathOf = (d) => d) {
|
|
5232
|
+
const inkPx = (nodeId) => {
|
|
5233
|
+
const own = halfWidthPx(nodeId) * 2;
|
|
5234
|
+
return lift?.nodeIds.has(nodeId) ? Math.max(own, lift.walks.length * MIN_LANE_PX) : own;
|
|
5235
|
+
};
|
|
5236
|
+
return pins.flatMap(
|
|
5237
|
+
(pin) => pin.exonsByNode.map(({ nodeId, d }) => ({
|
|
5238
|
+
key: `${pin.gene.name}-${pin.gene.start}-${nodeId}`,
|
|
5239
|
+
d: pathOf(d),
|
|
5240
|
+
inner: inkPx(nodeId) + 2 * EXON_GAP_PX
|
|
5241
|
+
}))
|
|
5242
|
+
);
|
|
5243
|
+
}
|
|
5244
|
+
function exonOutlineTree(stretches, o) {
|
|
5245
|
+
if (stretches.length === 0) {
|
|
5246
|
+
return void 0;
|
|
5247
|
+
}
|
|
5248
|
+
const stroke = (s, color, width) => el("path", {
|
|
5249
|
+
d: s.d,
|
|
5250
|
+
fill: "none",
|
|
5251
|
+
stroke: color,
|
|
5252
|
+
"stroke-width": width,
|
|
5253
|
+
"stroke-linecap": "round",
|
|
5254
|
+
"stroke-linejoin": "round",
|
|
5255
|
+
"vector-effect": o.transform ? "non-scaling-stroke" : void 0
|
|
5256
|
+
});
|
|
5257
|
+
return el(
|
|
5258
|
+
"g",
|
|
5259
|
+
{},
|
|
5260
|
+
el(
|
|
5261
|
+
"mask",
|
|
5262
|
+
{
|
|
5263
|
+
id: o.id,
|
|
5264
|
+
maskUnits: "userSpaceOnUse",
|
|
5265
|
+
x: 0,
|
|
5266
|
+
y: 0,
|
|
5267
|
+
width: o.width,
|
|
5268
|
+
height: o.height
|
|
5269
|
+
},
|
|
5270
|
+
el(
|
|
5271
|
+
"g",
|
|
5272
|
+
{ transform: o.transform },
|
|
5273
|
+
...stretches.map((s) => stroke(s, "#fff", s.inner + 2 * EXON_LINE_PX)),
|
|
5274
|
+
...stretches.map((s) => stroke(s, "#000", s.inner))
|
|
5275
|
+
)
|
|
5276
|
+
),
|
|
5277
|
+
el("rect", {
|
|
5278
|
+
width: o.width,
|
|
5279
|
+
height: o.height,
|
|
5280
|
+
fill: EXON_COLOR,
|
|
5281
|
+
mask: `url(#${o.id})`
|
|
5282
|
+
})
|
|
5283
|
+
);
|
|
5284
|
+
}
|
|
5285
|
+
|
|
4755
5286
|
// src/genes/genePins.ts
|
|
4756
5287
|
function genePins(graph, genes, positions) {
|
|
4757
5288
|
const byRefName = /* @__PURE__ */ new Map();
|
|
4758
5289
|
for (const node of graph.nodes) {
|
|
4759
|
-
if (
|
|
5290
|
+
if (isAnchored(node)) {
|
|
4760
5291
|
const { refName } = node.stable;
|
|
4761
5292
|
if (positions[node.id]?.length) {
|
|
4762
5293
|
const entry = byRefName.get(refName) ?? byRefName.set(refName, { nodes: [], reach: 0 }).get(refName);
|
|
@@ -4770,17 +5301,18 @@ function genePins(graph, genes, positions) {
|
|
|
4770
5301
|
}
|
|
4771
5302
|
const pins = [];
|
|
4772
5303
|
for (const gene of genes) {
|
|
4773
|
-
const
|
|
4774
|
-
if (!
|
|
5304
|
+
const onRefName = byRefName.get(gene.refName);
|
|
5305
|
+
if (!onRefName) {
|
|
4775
5306
|
continue;
|
|
4776
5307
|
}
|
|
4777
5308
|
const parts = [];
|
|
4778
5309
|
const exonsByNode = [];
|
|
5310
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
4779
5311
|
let at;
|
|
4780
5312
|
let atDistance = Infinity;
|
|
4781
5313
|
let covered = 0;
|
|
4782
5314
|
const mid = (gene.start + gene.end) / 2;
|
|
4783
|
-
const { nodes, reach } =
|
|
5315
|
+
const { nodes, reach } = onRefName;
|
|
4784
5316
|
for (let i = firstNodeAtOrAfter(nodes, gene.start - reach); i < nodes.length && nodes[i].stable.start < gene.end; i++) {
|
|
4785
5317
|
const node = nodes[i];
|
|
4786
5318
|
const nodeStart = node.stable.start;
|
|
@@ -4789,6 +5321,7 @@ function genePins(graph, genes, positions) {
|
|
|
4789
5321
|
continue;
|
|
4790
5322
|
}
|
|
4791
5323
|
const line = positions[node.id];
|
|
5324
|
+
nodeIds.add(node.id);
|
|
4792
5325
|
covered += Math.min(nodeEnd, gene.end) - Math.max(nodeStart, gene.start);
|
|
4793
5326
|
const nodeParts = [];
|
|
4794
5327
|
for (const exon of gene.exons) {
|
|
@@ -4828,6 +5361,7 @@ function genePins(graph, genes, positions) {
|
|
|
4828
5361
|
gene,
|
|
4829
5362
|
exons: parts.join(""),
|
|
4830
5363
|
exonsByNode,
|
|
5364
|
+
nodeIds,
|
|
4831
5365
|
at,
|
|
4832
5366
|
covered: covered / (gene.end - gene.start)
|
|
4833
5367
|
});
|
|
@@ -4852,7 +5386,8 @@ function geneCoverageNote(pin) {
|
|
|
4852
5386
|
const amount = (bp) => +(bp / per).toFixed(per === 1 ? 0 : 1);
|
|
4853
5387
|
return `${amount(pin.covered * length)} of ${amount(length)} ${unit}`;
|
|
4854
5388
|
}
|
|
4855
|
-
function geneLabelCandidates(pins, screen, contigThickness) {
|
|
5389
|
+
function geneLabelCandidates(pins, screen, contigThickness, nodesUnder) {
|
|
5390
|
+
const ink = contigThickness / 2;
|
|
4856
5391
|
return byExtent(pins, (pin) => pin.gene.end - pin.gene.start).map((pin) => {
|
|
4857
5392
|
const { x, y } = screen(pin.at);
|
|
4858
5393
|
const note = geneCoverageNote(pin);
|
|
@@ -4862,7 +5397,15 @@ function geneLabelCandidates(pins, screen, contigThickness) {
|
|
|
4862
5397
|
y: y + contigThickness + GENE_PIN_DROP_PX,
|
|
4863
5398
|
text: note ? `${pin.gene.name} \xB7 ${note}` : pin.gene.name,
|
|
4864
5399
|
fallback: note ? `${pin.gene.name} \u2026` : void 0,
|
|
4865
|
-
stack: GENE_STACK
|
|
5400
|
+
stack: GENE_STACK,
|
|
5401
|
+
fits: (box) => [
|
|
5402
|
+
...nodesUnder({
|
|
5403
|
+
x0: box.x0 - ink,
|
|
5404
|
+
x1: box.x1 + ink,
|
|
5405
|
+
y0: box.y0 - ink,
|
|
5406
|
+
y1: box.y1 + ink
|
|
5407
|
+
})
|
|
5408
|
+
].every((id) => pin.nodeIds.has(id))
|
|
4866
5409
|
};
|
|
4867
5410
|
});
|
|
4868
5411
|
}
|
|
@@ -4923,7 +5466,16 @@ function layoutLabels(m) {
|
|
|
4923
5466
|
take
|
|
4924
5467
|
);
|
|
4925
5468
|
const genes = placeLabels(
|
|
4926
|
-
geneLabelCandidates(
|
|
5469
|
+
geneLabelCandidates(
|
|
5470
|
+
m.genePins,
|
|
5471
|
+
screen,
|
|
5472
|
+
m.contigThickness,
|
|
5473
|
+
nodesUnderBox(
|
|
5474
|
+
m.nodePositions ?? {},
|
|
5475
|
+
{ scaleX, scaleY, translateX, translateY },
|
|
5476
|
+
m.positionsVersion
|
|
5477
|
+
)
|
|
5478
|
+
),
|
|
4927
5479
|
frame,
|
|
4928
5480
|
take
|
|
4929
5481
|
);
|
|
@@ -4932,6 +5484,7 @@ function layoutLabels(m) {
|
|
|
4932
5484
|
nodeLengths: m.nodeLengths,
|
|
4933
5485
|
deletions: m.showDeletionEdges ? m.deletions : [],
|
|
4934
5486
|
deletionRoutes: m.deletionRoutes,
|
|
5487
|
+
stranded: m.stranded,
|
|
4935
5488
|
alleleDeletions: m.alleleDeletions,
|
|
4936
5489
|
axis: m.axisScale,
|
|
4937
5490
|
translateX,
|
|
@@ -5609,6 +6162,154 @@ function stripGeneGaps(frame, rowGenes, gaps) {
|
|
|
5609
6162
|
return frame.boxesGenes ? gaps : { untracked: 0, unread: 0, ...gaps, crowded: true };
|
|
5610
6163
|
}
|
|
5611
6164
|
|
|
6165
|
+
// src/reference.ts
|
|
6166
|
+
var WELL_KNOWN_SAMPLES = /* @__PURE__ */ new Map([
|
|
6167
|
+
["hg38", "GRCh38"],
|
|
6168
|
+
["hg19", "GRCh37"],
|
|
6169
|
+
["hs1", "CHM13"],
|
|
6170
|
+
["t2t-chm13", "CHM13"],
|
|
6171
|
+
["chm13v2", "CHM13"]
|
|
6172
|
+
]);
|
|
6173
|
+
function wellKnownSample(assemblyName) {
|
|
6174
|
+
return WELL_KNOWN_SAMPLES.get(assemblyName.toLowerCase());
|
|
6175
|
+
}
|
|
6176
|
+
function sampleOf2(refName) {
|
|
6177
|
+
const sample = refName.includes("#") ? panSNSample(refName) : void 0;
|
|
6178
|
+
return sample === void 0 || isGenericSample(sample) ? void 0 : sample;
|
|
6179
|
+
}
|
|
6180
|
+
function haplotypeOf(refName) {
|
|
6181
|
+
return panSNHaplotype(refName) ?? panSNSample(refName);
|
|
6182
|
+
}
|
|
6183
|
+
function graphHaplotypes(graph) {
|
|
6184
|
+
const refNames = new Set((graph.anchorPaths ?? []).map((p) => p.name));
|
|
6185
|
+
for (const node of graph.nodes) {
|
|
6186
|
+
if (node.stable) {
|
|
6187
|
+
refNames.add(node.stable.refName);
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
6190
|
+
const bySample = /* @__PURE__ */ new Map();
|
|
6191
|
+
for (const refName of refNames) {
|
|
6192
|
+
const sample = sampleOf2(refName);
|
|
6193
|
+
if (sample !== void 0) {
|
|
6194
|
+
const haplotypes = bySample.get(sample) ?? bySample.set(sample, /* @__PURE__ */ new Set()).get(sample);
|
|
6195
|
+
haplotypes.add(haplotypeOf(refName));
|
|
6196
|
+
}
|
|
6197
|
+
}
|
|
6198
|
+
return bySample;
|
|
6199
|
+
}
|
|
6200
|
+
function backboneFrom(contigs, haplotypes) {
|
|
6201
|
+
const samples = new Set(contigs.map((c) => sampleOf2(c.refName)));
|
|
6202
|
+
const [sample] = samples;
|
|
6203
|
+
if (samples.size !== 1 || sample === void 0) {
|
|
6204
|
+
return { contigs, prefixes: [], named: false };
|
|
6205
|
+
}
|
|
6206
|
+
const own = new Set(contigs.map((c) => haplotypeOf(c.refName)));
|
|
6207
|
+
const unique = [...haplotypes.get(sample) ?? []].every((h) => own.has(h));
|
|
6208
|
+
const shared = new Set(contigs.map((c) => panSNHaplotype(c.refName)));
|
|
6209
|
+
const [haplotype] = shared;
|
|
6210
|
+
return {
|
|
6211
|
+
contigs,
|
|
6212
|
+
prefixes: [
|
|
6213
|
+
...unique ? [sample] : [],
|
|
6214
|
+
...shared.size === 1 && haplotype ? [haplotype] : []
|
|
6215
|
+
],
|
|
6216
|
+
named: true
|
|
6217
|
+
};
|
|
6218
|
+
}
|
|
6219
|
+
function graphBackbone(graph) {
|
|
6220
|
+
const spans = /* @__PURE__ */ new Map();
|
|
6221
|
+
for (const node of graph.nodes) {
|
|
6222
|
+
if (isBackbone(node)) {
|
|
6223
|
+
const { refName, start } = node.stable;
|
|
6224
|
+
const end = start + node.length;
|
|
6225
|
+
const span = spans.get(refName);
|
|
6226
|
+
if (span) {
|
|
6227
|
+
span.start = Math.min(span.start, start);
|
|
6228
|
+
span.end = Math.max(span.end, end);
|
|
6229
|
+
} else {
|
|
6230
|
+
spans.set(refName, {
|
|
6231
|
+
refName,
|
|
6232
|
+
contig: panSNContig(refName),
|
|
6233
|
+
start,
|
|
6234
|
+
end
|
|
6235
|
+
});
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
const contigs = [...spans.values()];
|
|
6240
|
+
return contigs.length ? backboneFrom(contigs, graphHaplotypes(graph)) : void 0;
|
|
6241
|
+
}
|
|
6242
|
+
function referenceLabel(graph, region) {
|
|
6243
|
+
const backbone = graphBackbone(graph);
|
|
6244
|
+
const contig = region ? panSNContig(region.refName) : backbone?.contigs.length === 1 ? backbone.contigs[0].contig : void 0;
|
|
6245
|
+
const sample = backbone?.named ? panSNSample(backbone.contigs[0].refName) : void 0;
|
|
6246
|
+
return contig && (sample ? `${sample} ${contig}` : contig);
|
|
6247
|
+
}
|
|
6248
|
+
function backboneAssembly(backbone, assemblies) {
|
|
6249
|
+
const names = (a) => [a.name, ...a.aliases ?? []];
|
|
6250
|
+
const naming = (prefix) => (a) => names(a).some((n2) => n2.toLowerCase() === prefix.toLowerCase());
|
|
6251
|
+
const prefixes = [...backbone?.prefixes ?? []].reverse();
|
|
6252
|
+
const sample = backbone?.prefixes.find((p) => !p.includes("#"))?.toLowerCase();
|
|
6253
|
+
for (const prefix of prefixes) {
|
|
6254
|
+
const assembly = assemblies.find(naming(prefix));
|
|
6255
|
+
if (assembly) {
|
|
6256
|
+
return assembly;
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
return sample === void 0 ? void 0 : assemblies.find(
|
|
6260
|
+
(a) => names(a).some((n2) => wellKnownSample(n2)?.toLowerCase() === sample)
|
|
6261
|
+
);
|
|
6262
|
+
}
|
|
6263
|
+
function assemblyWalk(graph, assembly) {
|
|
6264
|
+
const haplotypes = graphHaplotypes(graph);
|
|
6265
|
+
return graph.anchorPaths?.find(
|
|
6266
|
+
(walk) => backboneAssembly(
|
|
6267
|
+
backboneFrom(
|
|
6268
|
+
[
|
|
6269
|
+
{
|
|
6270
|
+
refName: walk.name,
|
|
6271
|
+
contig: panSNContig(walk.name),
|
|
6272
|
+
start: walk.start,
|
|
6273
|
+
end: walk.start + walk.length
|
|
6274
|
+
}
|
|
6275
|
+
],
|
|
6276
|
+
haplotypes
|
|
6277
|
+
),
|
|
6278
|
+
[assembly]
|
|
6279
|
+
)
|
|
6280
|
+
);
|
|
6281
|
+
}
|
|
6282
|
+
function contigNames(contig) {
|
|
6283
|
+
const bare = contig.replace(/^chr/i, "");
|
|
6284
|
+
const forms = !bare ? [] : /^(M|MT)$/i.test(bare) ? ["M", "MT"] : [bare];
|
|
6285
|
+
return [.../* @__PURE__ */ new Set([contig, ...forms.flatMap((f) => [f, `chr${f}`])])];
|
|
6286
|
+
}
|
|
6287
|
+
function refNameBinding(refNames, aliases = {}) {
|
|
6288
|
+
const exact = new Set(refNames);
|
|
6289
|
+
const byName = /* @__PURE__ */ new Map();
|
|
6290
|
+
for (const refName of exact) {
|
|
6291
|
+
const contig = panSNContig(refName);
|
|
6292
|
+
const alias = aliases[contig];
|
|
6293
|
+
for (const name of /* @__PURE__ */ new Set([
|
|
6294
|
+
...contigNames(contig),
|
|
6295
|
+
...alias ? [alias] : []
|
|
6296
|
+
])) {
|
|
6297
|
+
byName.set(name, byName.has(name) ? void 0 : refName);
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
return (name) => exact.has(name) ? name : byName.get(name);
|
|
6301
|
+
}
|
|
6302
|
+
function featuresOnBackbone(features, backbone, aliases) {
|
|
6303
|
+
const bind = refNameBinding(
|
|
6304
|
+
backbone.contigs.map((c) => c.refName),
|
|
6305
|
+
aliases
|
|
6306
|
+
);
|
|
6307
|
+
return features.flatMap((f) => {
|
|
6308
|
+
const refName = bind(f.refName);
|
|
6309
|
+
return refName === void 0 ? [] : [refName === f.refName ? f : { ...f, refName }];
|
|
6310
|
+
});
|
|
6311
|
+
}
|
|
6312
|
+
|
|
5612
6313
|
// src/renderer/svgCanvas.ts
|
|
5613
6314
|
var n = (v) => +v.toFixed(1);
|
|
5614
6315
|
function paint(css) {
|
|
@@ -5722,7 +6423,7 @@ function svgCanvas(width, height) {
|
|
|
5722
6423
|
}
|
|
5723
6424
|
|
|
5724
6425
|
// src/version.ts
|
|
5725
|
-
var version = "5.0
|
|
6426
|
+
var version = "5.2.0";
|
|
5726
6427
|
|
|
5727
6428
|
// src/viewport.ts
|
|
5728
6429
|
function axisScaleOf(scale, pixelRows) {
|
|
@@ -5772,11 +6473,7 @@ var SWATCH_PX = 18;
|
|
|
5772
6473
|
var KEY_GAP_PX = 16;
|
|
5773
6474
|
var STRIP_KEY_PX = 22;
|
|
5774
6475
|
var FADED = "rgb(160,160,160)";
|
|
5775
|
-
var EXON_COLOR = "#daa520";
|
|
5776
6476
|
var GENE_INK2 = "#1c1c22";
|
|
5777
|
-
var MIN_LANE_PX = 4;
|
|
5778
|
-
var EXON_GAP_PX = 1;
|
|
5779
|
-
var EXON_LINE_PX = 2;
|
|
5780
6477
|
function esc(s) {
|
|
5781
6478
|
return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
5782
6479
|
}
|
|
@@ -5793,6 +6490,12 @@ function keySvg(id, x, y, stops, label, after, under, opacity = 1) {
|
|
|
5793
6490
|
markup: `${defs}<g transform="translate(${x} ${y})" ${FONT}><rect y="5" width="${bar}" height="8" rx="2" ${fill}/><text x="${bar + 5}" y="13"><tspan font-weight="bold">${esc(label)}</tspan>${esc(after)}</text>${under ? `<text y="28">${esc(under)}</text>` : ""}</g>`
|
|
5794
6491
|
};
|
|
5795
6492
|
}
|
|
6493
|
+
function exonKeySvg(x, y) {
|
|
6494
|
+
return {
|
|
6495
|
+
width: SWATCH_PX + 5 + 4 * LABEL_CHAR_PX,
|
|
6496
|
+
markup: `<g transform="translate(${x} ${y})" ${FONT}><rect x="1" y="4" width="${SWATCH_PX - 2}" height="10" rx="4" fill="none" stroke="${EXON_COLOR}" stroke-width="2"/><text x="${SWATCH_PX + 5}" y="13">exon</text></g>`
|
|
6497
|
+
};
|
|
6498
|
+
}
|
|
5796
6499
|
function walkKeySvg(id, x, y, walk, label, reference) {
|
|
5797
6500
|
const key = walkKey(walk, reference);
|
|
5798
6501
|
return keySvg(
|
|
@@ -5845,9 +6548,10 @@ function figureSvg(graph, layout, o = {}) {
|
|
|
5845
6548
|
])
|
|
5846
6549
|
);
|
|
5847
6550
|
const labelOf2 = (name) => labels.get(name) ?? name;
|
|
6551
|
+
const referenceName = o.referenceName ?? referenceLabel(graph, region);
|
|
5848
6552
|
const reference = lift?.referenceDomain && {
|
|
5849
6553
|
...lift.referenceDomain,
|
|
5850
|
-
name:
|
|
6554
|
+
name: referenceName
|
|
5851
6555
|
};
|
|
5852
6556
|
const contigThickness = o.contigThickness ?? 6;
|
|
5853
6557
|
const nodeWidth2 = o.nodeWidth ?? "depth";
|
|
@@ -5856,24 +6560,12 @@ function figureSvg(graph, layout, o = {}) {
|
|
|
5856
6560
|
let masks = 0;
|
|
5857
6561
|
function overlays(highlight, t, w, h) {
|
|
5858
6562
|
const out = [];
|
|
5859
|
-
const
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
};
|
|
5863
|
-
const stretches = pins.flatMap(
|
|
5864
|
-
(pin) => pin.exonsByNode.map(({ nodeId, d }) => ({
|
|
5865
|
-
d: screenPath(d, t),
|
|
5866
|
-
inner: inkPx(nodeId) + 2 * EXON_GAP_PX
|
|
5867
|
-
}))
|
|
6563
|
+
const outlines = exonOutlineTree(
|
|
6564
|
+
exonStretches(pins, ink.halfWidthPx, highlight, (d) => screenPath(d, t)),
|
|
6565
|
+
{ id: `exons${masks++}`, width: w, height: h }
|
|
5868
6566
|
);
|
|
5869
|
-
if (
|
|
5870
|
-
|
|
5871
|
-
const stroke = (color, width2, d) => `<path d="${d}" fill="none" stroke="${color}" stroke-width="${width2}" stroke-linecap="round" stroke-linejoin="round"/>`;
|
|
5872
|
-
out.push(
|
|
5873
|
-
`<mask id="${id}" maskUnits="userSpaceOnUse" x="0" y="0" width="${w}" height="${h}">${stretches.map((s) => stroke("#fff", s.inner + 2 * EXON_LINE_PX, s.d)).join("")}${stretches.map((s) => stroke("#000", s.inner, s.d)).join(
|
|
5874
|
-
""
|
|
5875
|
-
)}</mask><rect width="${w}" height="${h}" fill="${EXON_COLOR}" mask="url(#${id})"/>`
|
|
5876
|
-
);
|
|
6567
|
+
if (outlines) {
|
|
6568
|
+
out.push(serializeEl(outlines));
|
|
5877
6569
|
}
|
|
5878
6570
|
const screen = (p) => ({
|
|
5879
6571
|
x: p.x * t.scaleX + t.translateX,
|
|
@@ -5881,7 +6573,12 @@ function figureSvg(graph, layout, o = {}) {
|
|
|
5881
6573
|
});
|
|
5882
6574
|
const frame = { width: w, height: h };
|
|
5883
6575
|
for (const { item: pin, x, y, w: cw, text } of placeLabels(
|
|
5884
|
-
geneLabelCandidates(
|
|
6576
|
+
geneLabelCandidates(
|
|
6577
|
+
pins,
|
|
6578
|
+
screen,
|
|
6579
|
+
contigThickness,
|
|
6580
|
+
nodesUnderBox(layout.nodePositions, t)
|
|
6581
|
+
),
|
|
5885
6582
|
frame,
|
|
5886
6583
|
occupancy(frame)
|
|
5887
6584
|
)) {
|
|
@@ -5953,6 +6650,7 @@ function figureSvg(graph, layout, o = {}) {
|
|
|
5953
6650
|
referenceRamp,
|
|
5954
6651
|
deletions: new Map(deletions.map((d) => [d.edgeIndex, d.bypassed])),
|
|
5955
6652
|
deletionRoutes: layout.deletionRoutes,
|
|
6653
|
+
stranded: layout.stranded,
|
|
5956
6654
|
hiddenEdges: new Set(
|
|
5957
6655
|
o.showDeletionEdges ? [] : deletions.map((d) => d.edgeIndex)
|
|
5958
6656
|
)
|
|
@@ -6036,16 +6734,19 @@ function figureSvg(graph, layout, o = {}) {
|
|
|
6036
6734
|
x,
|
|
6037
6735
|
2,
|
|
6038
6736
|
encodingStops({ field: "reference", scheme: "rainbow" }),
|
|
6039
|
-
"Reference position",
|
|
6737
|
+
referenceName ? `${referenceName} position` : "Reference position",
|
|
6040
6738
|
"",
|
|
6041
6739
|
rangeText(
|
|
6042
|
-
|
|
6740
|
+
referenceName,
|
|
6043
6741
|
referenceRamp.start,
|
|
6044
6742
|
referenceRamp.start + referenceRamp.span
|
|
6045
6743
|
)
|
|
6046
6744
|
)
|
|
6047
6745
|
);
|
|
6048
6746
|
}
|
|
6747
|
+
if (pins.some((pin) => pin.exonsByNode.length > 0)) {
|
|
6748
|
+
push(exonKeySvg(x, 2));
|
|
6749
|
+
}
|
|
6049
6750
|
const header = parts.length > 0 ? FACET_TITLE_PX : 0;
|
|
6050
6751
|
const usable = width - 2 * FIT_PADDING;
|
|
6051
6752
|
const drawn = pixelRows || !(bounds.w > 0) ? bounds.h : bounds.h * usable / bounds.w;
|
|
@@ -6198,14 +6899,22 @@ function genesFromGff3Lines(lines) {
|
|
|
6198
6899
|
const start = top?.start ?? Math.min(...rows2.map((r) => r.start));
|
|
6199
6900
|
const end = top?.end ?? Math.max(...rows2.map((r) => r.end));
|
|
6200
6901
|
const a = first.attrs;
|
|
6201
|
-
const
|
|
6902
|
+
const spans = (type) => rows2.filter((r) => r.type === type).map((r) => ({ start: r.start, end: r.end }));
|
|
6903
|
+
const exons = spans("exon");
|
|
6904
|
+
const coding = spans("CDS");
|
|
6905
|
+
const transcribed = rows2.some(
|
|
6906
|
+
(r) => r !== top && r.type !== "exon" && r.type !== "CDS"
|
|
6907
|
+
);
|
|
6202
6908
|
genes.push({
|
|
6203
6909
|
name: a.get("Name") ?? a.get("gene_name") ?? a.get("gene") ?? a.get("gene_id") ?? key.split(" ")[1],
|
|
6204
6910
|
refName: first.refName,
|
|
6205
6911
|
start,
|
|
6206
6912
|
end,
|
|
6207
6913
|
strand: first.strand,
|
|
6208
|
-
|
|
6914
|
+
// A range read holds only the rows over its window, so a gene with
|
|
6915
|
+
// transcripts but no exon or CDS there is intronic in it. Only a gene
|
|
6916
|
+
// with no transcripts, as a bacterium's gene and CDS, is one exon.
|
|
6917
|
+
exons: exons.length ? mergedIntervals(exons) : transcribed ? mergedIntervals(coding) : [{ start, end }]
|
|
6209
6918
|
});
|
|
6210
6919
|
}
|
|
6211
6920
|
return genes;
|
|
@@ -6261,148 +6970,6 @@ function genesFromText(text) {
|
|
|
6261
6970
|
return /^\d+$/.test(cols[1] ?? "") && /^\d+$/.test(cols[2] ?? "") ? genesFromBed(text) : genesFromGff3Lines(text.split(/\r?\n/));
|
|
6262
6971
|
}
|
|
6263
6972
|
|
|
6264
|
-
// src/reference.ts
|
|
6265
|
-
var WELL_KNOWN_SAMPLES = /* @__PURE__ */ new Map([
|
|
6266
|
-
["hg38", "GRCh38"],
|
|
6267
|
-
["hg19", "GRCh37"],
|
|
6268
|
-
["hs1", "CHM13"],
|
|
6269
|
-
["t2t-chm13", "CHM13"],
|
|
6270
|
-
["chm13v2", "CHM13"]
|
|
6271
|
-
]);
|
|
6272
|
-
function wellKnownSample(assemblyName) {
|
|
6273
|
-
return WELL_KNOWN_SAMPLES.get(assemblyName.toLowerCase());
|
|
6274
|
-
}
|
|
6275
|
-
function sampleOf2(refName) {
|
|
6276
|
-
const sample = refName.includes("#") ? panSNSample(refName) : void 0;
|
|
6277
|
-
return sample === void 0 || isGenericSample(sample) ? void 0 : sample;
|
|
6278
|
-
}
|
|
6279
|
-
function haplotypeOf(refName) {
|
|
6280
|
-
return panSNHaplotype(refName) ?? panSNSample(refName);
|
|
6281
|
-
}
|
|
6282
|
-
function graphHaplotypes(graph) {
|
|
6283
|
-
const refNames = new Set((graph.anchorPaths ?? []).map((p) => p.name));
|
|
6284
|
-
for (const node of graph.nodes) {
|
|
6285
|
-
if (node.stable) {
|
|
6286
|
-
refNames.add(node.stable.refName);
|
|
6287
|
-
}
|
|
6288
|
-
}
|
|
6289
|
-
const bySample = /* @__PURE__ */ new Map();
|
|
6290
|
-
for (const refName of refNames) {
|
|
6291
|
-
const sample = sampleOf2(refName);
|
|
6292
|
-
if (sample !== void 0) {
|
|
6293
|
-
const haplotypes = bySample.get(sample) ?? bySample.set(sample, /* @__PURE__ */ new Set()).get(sample);
|
|
6294
|
-
haplotypes.add(haplotypeOf(refName));
|
|
6295
|
-
}
|
|
6296
|
-
}
|
|
6297
|
-
return bySample;
|
|
6298
|
-
}
|
|
6299
|
-
function backboneFrom(contigs, haplotypes) {
|
|
6300
|
-
const samples = new Set(contigs.map((c) => sampleOf2(c.refName)));
|
|
6301
|
-
const [sample] = samples;
|
|
6302
|
-
if (samples.size !== 1 || sample === void 0) {
|
|
6303
|
-
return { contigs, prefixes: [], named: false };
|
|
6304
|
-
}
|
|
6305
|
-
const own = new Set(contigs.map((c) => haplotypeOf(c.refName)));
|
|
6306
|
-
const unique = [...haplotypes.get(sample) ?? []].every((h) => own.has(h));
|
|
6307
|
-
const shared = new Set(contigs.map((c) => panSNHaplotype(c.refName)));
|
|
6308
|
-
const [haplotype] = shared;
|
|
6309
|
-
return {
|
|
6310
|
-
contigs,
|
|
6311
|
-
prefixes: [
|
|
6312
|
-
...unique ? [sample] : [],
|
|
6313
|
-
...shared.size === 1 && haplotype ? [haplotype] : []
|
|
6314
|
-
],
|
|
6315
|
-
named: true
|
|
6316
|
-
};
|
|
6317
|
-
}
|
|
6318
|
-
function graphBackbone(graph) {
|
|
6319
|
-
const spans = /* @__PURE__ */ new Map();
|
|
6320
|
-
for (const node of graph.nodes) {
|
|
6321
|
-
if (isBackbone(node)) {
|
|
6322
|
-
const { refName, start } = node.stable;
|
|
6323
|
-
const end = start + node.length;
|
|
6324
|
-
const span = spans.get(refName);
|
|
6325
|
-
if (span) {
|
|
6326
|
-
span.start = Math.min(span.start, start);
|
|
6327
|
-
span.end = Math.max(span.end, end);
|
|
6328
|
-
} else {
|
|
6329
|
-
spans.set(refName, {
|
|
6330
|
-
refName,
|
|
6331
|
-
contig: panSNContig(refName),
|
|
6332
|
-
start,
|
|
6333
|
-
end
|
|
6334
|
-
});
|
|
6335
|
-
}
|
|
6336
|
-
}
|
|
6337
|
-
}
|
|
6338
|
-
const contigs = [...spans.values()];
|
|
6339
|
-
return contigs.length ? backboneFrom(contigs, graphHaplotypes(graph)) : void 0;
|
|
6340
|
-
}
|
|
6341
|
-
function backboneAssembly(backbone, assemblies) {
|
|
6342
|
-
const names = (a) => [a.name, ...a.aliases ?? []];
|
|
6343
|
-
const naming = (prefix) => (a) => names(a).some((n2) => n2.toLowerCase() === prefix.toLowerCase());
|
|
6344
|
-
const prefixes = [...backbone?.prefixes ?? []].reverse();
|
|
6345
|
-
const sample = backbone?.prefixes.find((p) => !p.includes("#"))?.toLowerCase();
|
|
6346
|
-
for (const prefix of prefixes) {
|
|
6347
|
-
const assembly = assemblies.find(naming(prefix));
|
|
6348
|
-
if (assembly) {
|
|
6349
|
-
return assembly;
|
|
6350
|
-
}
|
|
6351
|
-
}
|
|
6352
|
-
return sample === void 0 ? void 0 : assemblies.find(
|
|
6353
|
-
(a) => names(a).some((n2) => wellKnownSample(n2)?.toLowerCase() === sample)
|
|
6354
|
-
);
|
|
6355
|
-
}
|
|
6356
|
-
function assemblyWalk(graph, assembly) {
|
|
6357
|
-
const haplotypes = graphHaplotypes(graph);
|
|
6358
|
-
return graph.anchorPaths?.find(
|
|
6359
|
-
(walk) => backboneAssembly(
|
|
6360
|
-
backboneFrom(
|
|
6361
|
-
[
|
|
6362
|
-
{
|
|
6363
|
-
refName: walk.name,
|
|
6364
|
-
contig: panSNContig(walk.name),
|
|
6365
|
-
start: walk.start,
|
|
6366
|
-
end: walk.start + walk.length
|
|
6367
|
-
}
|
|
6368
|
-
],
|
|
6369
|
-
haplotypes
|
|
6370
|
-
),
|
|
6371
|
-
[assembly]
|
|
6372
|
-
)
|
|
6373
|
-
);
|
|
6374
|
-
}
|
|
6375
|
-
function contigNames(contig) {
|
|
6376
|
-
const bare = contig.replace(/^chr/i, "");
|
|
6377
|
-
const forms = !bare ? [] : /^(M|MT)$/i.test(bare) ? ["M", "MT"] : [bare];
|
|
6378
|
-
return [.../* @__PURE__ */ new Set([contig, ...forms.flatMap((f) => [f, `chr${f}`])])];
|
|
6379
|
-
}
|
|
6380
|
-
function refNameBinding(refNames, aliases = {}) {
|
|
6381
|
-
const exact = new Set(refNames);
|
|
6382
|
-
const byName = /* @__PURE__ */ new Map();
|
|
6383
|
-
for (const refName of exact) {
|
|
6384
|
-
const contig = panSNContig(refName);
|
|
6385
|
-
const alias = aliases[contig];
|
|
6386
|
-
for (const name of /* @__PURE__ */ new Set([
|
|
6387
|
-
...contigNames(contig),
|
|
6388
|
-
...alias ? [alias] : []
|
|
6389
|
-
])) {
|
|
6390
|
-
byName.set(name, byName.has(name) ? void 0 : refName);
|
|
6391
|
-
}
|
|
6392
|
-
}
|
|
6393
|
-
return (name) => exact.has(name) ? name : byName.get(name);
|
|
6394
|
-
}
|
|
6395
|
-
function featuresOnBackbone(features, backbone, aliases) {
|
|
6396
|
-
const bind = refNameBinding(
|
|
6397
|
-
backbone.contigs.map((c) => c.refName),
|
|
6398
|
-
aliases
|
|
6399
|
-
);
|
|
6400
|
-
return features.flatMap((f) => {
|
|
6401
|
-
const refName = bind(f.refName);
|
|
6402
|
-
return refName === void 0 ? [] : [refName === f.refName ? f : { ...f, refName }];
|
|
6403
|
-
});
|
|
6404
|
-
}
|
|
6405
|
-
|
|
6406
6973
|
// src/gbzJoin.ts
|
|
6407
6974
|
var UNKNOWN_SAMPLE = "unknown";
|
|
6408
6975
|
function parseWalk(line) {
|
|
@@ -6546,7 +7113,9 @@ var NodeLimitError = class extends Error {
|
|
|
6546
7113
|
super(
|
|
6547
7114
|
`this ${windowBp.toLocaleString()} bp window reads more than nodeLimit (${limit.toLocaleString()}) graph nodes; zoom in to about ${fitsBp.toLocaleString()} bp or raise nodeLimit`
|
|
6548
7115
|
);
|
|
7116
|
+
this.fitsBp = fitsBp;
|
|
6549
7117
|
this.name = "NodeLimitError";
|
|
7118
|
+
this.regionTooLarge = true;
|
|
6550
7119
|
}
|
|
6551
7120
|
};
|
|
6552
7121
|
function haplotypePrefix(name) {
|
|
@@ -6691,10 +7260,6 @@ export {
|
|
|
6691
7260
|
BUBBLE_SPREADS,
|
|
6692
7261
|
ROW_HEIGHT_PX,
|
|
6693
7262
|
svgPath,
|
|
6694
|
-
curveBounds,
|
|
6695
|
-
translateCurves,
|
|
6696
|
-
yToXOf,
|
|
6697
|
-
pathRibbonOffsets,
|
|
6698
7263
|
deletionEdges,
|
|
6699
7264
|
panSNSample,
|
|
6700
7265
|
panSNHaplotype,
|
|
@@ -6730,7 +7295,6 @@ export {
|
|
|
6730
7295
|
nodeWidthPx,
|
|
6731
7296
|
maxNodeWidthPx,
|
|
6732
7297
|
nodeInk,
|
|
6733
|
-
baseEdgeCurves,
|
|
6734
7298
|
LIFT_BACKDROP_CSS,
|
|
6735
7299
|
REFERENCE_RAMP_MAX_HUE,
|
|
6736
7300
|
computeReferenceRamp,
|
|
@@ -6738,6 +7302,8 @@ export {
|
|
|
6738
7302
|
getNodeColor,
|
|
6739
7303
|
buildGeometry,
|
|
6740
7304
|
Canvas2DRenderer,
|
|
7305
|
+
findHoveredNode,
|
|
7306
|
+
findHoveredEdge,
|
|
6741
7307
|
WALK_FIELDS,
|
|
6742
7308
|
WALK_SCHEMES,
|
|
6743
7309
|
resolveEncoding,
|
|
@@ -6758,6 +7324,9 @@ export {
|
|
|
6758
7324
|
facetGrid,
|
|
6759
7325
|
facetSettingOf,
|
|
6760
7326
|
facetCells,
|
|
7327
|
+
EXON_COLOR,
|
|
7328
|
+
exonStretches,
|
|
7329
|
+
exonOutlineTree,
|
|
6761
7330
|
genePins,
|
|
6762
7331
|
HALO_FACTOR,
|
|
6763
7332
|
LEGEND_INSET_PX,
|
|
@@ -6796,6 +7365,14 @@ export {
|
|
|
6796
7365
|
walkMarksTree,
|
|
6797
7366
|
walkStripLabelsTree,
|
|
6798
7367
|
stripGeneGaps,
|
|
7368
|
+
WELL_KNOWN_SAMPLES,
|
|
7369
|
+
wellKnownSample,
|
|
7370
|
+
graphBackbone,
|
|
7371
|
+
referenceLabel,
|
|
7372
|
+
backboneAssembly,
|
|
7373
|
+
assemblyWalk,
|
|
7374
|
+
refNameBinding,
|
|
7375
|
+
featuresOnBackbone,
|
|
6799
7376
|
svgCanvas,
|
|
6800
7377
|
version,
|
|
6801
7378
|
axisScaleOf,
|
|
@@ -6809,13 +7386,6 @@ export {
|
|
|
6809
7386
|
mergeOverlappingByName,
|
|
6810
7387
|
genesFromBed,
|
|
6811
7388
|
genesFromText,
|
|
6812
|
-
WELL_KNOWN_SAMPLES,
|
|
6813
|
-
wellKnownSample,
|
|
6814
|
-
graphBackbone,
|
|
6815
|
-
backboneAssembly,
|
|
6816
|
-
assemblyWalk,
|
|
6817
|
-
refNameBinding,
|
|
6818
|
-
featuresOnBackbone,
|
|
6819
7389
|
haplotypeWanted,
|
|
6820
7390
|
referenceSamplesOf,
|
|
6821
7391
|
resolveReferenceSample,
|