@jbrowse/bandage-core 5.1.0 → 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-Y2MDXT4O.js → chunk-ZMVFSZI5.js} +526 -36
- package/dist/cli/figure.js +1 -1
- package/dist/index.js +9 -365
- 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/index.d.ts +2 -0
- package/dist/types/labelLayout.d.ts +3 -2
- package/dist/types/overlayLabels.d.ts +1 -0
- package/dist/types/util/hitDetection.d.ts +7 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1804,7 +1804,7 @@ function placeLabels(candidates, frame, take) {
|
|
|
1804
1804
|
if (offFrame(box, frame)) {
|
|
1805
1805
|
return void 0;
|
|
1806
1806
|
}
|
|
1807
|
-
if (insideFrame(box, frame) && take(box)) {
|
|
1807
|
+
if (insideFrame(box, frame) && (c.fits?.(box) ?? true) && take(box)) {
|
|
1808
1808
|
return { ...c, text, y, w };
|
|
1809
1809
|
}
|
|
1810
1810
|
}
|
|
@@ -4432,6 +4432,415 @@ function groupByPaint(items, paintOf) {
|
|
|
4432
4432
|
return [...groups.values(), ...deferred.values()];
|
|
4433
4433
|
}
|
|
4434
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
|
+
|
|
4435
4844
|
// src/walkEncoding.ts
|
|
4436
4845
|
import { packAbgr as packAbgr3 } from "@jbrowse/core/util/colorBits";
|
|
4437
4846
|
var WALK_FIELDS = [
|
|
@@ -4814,11 +5223,71 @@ function facetCells(names, by, domain = []) {
|
|
|
4814
5223
|
return cells.includes(-1) || new Set(cells).size < cells.length ? wrap : { columns, count: rows.length * columns, cells };
|
|
4815
5224
|
}
|
|
4816
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
|
+
|
|
4817
5286
|
// src/genes/genePins.ts
|
|
4818
5287
|
function genePins(graph, genes, positions) {
|
|
4819
5288
|
const byRefName = /* @__PURE__ */ new Map();
|
|
4820
5289
|
for (const node of graph.nodes) {
|
|
4821
|
-
if (
|
|
5290
|
+
if (isAnchored(node)) {
|
|
4822
5291
|
const { refName } = node.stable;
|
|
4823
5292
|
if (positions[node.id]?.length) {
|
|
4824
5293
|
const entry = byRefName.get(refName) ?? byRefName.set(refName, { nodes: [], reach: 0 }).get(refName);
|
|
@@ -4832,17 +5301,18 @@ function genePins(graph, genes, positions) {
|
|
|
4832
5301
|
}
|
|
4833
5302
|
const pins = [];
|
|
4834
5303
|
for (const gene of genes) {
|
|
4835
|
-
const
|
|
4836
|
-
if (!
|
|
5304
|
+
const onRefName = byRefName.get(gene.refName);
|
|
5305
|
+
if (!onRefName) {
|
|
4837
5306
|
continue;
|
|
4838
5307
|
}
|
|
4839
5308
|
const parts = [];
|
|
4840
5309
|
const exonsByNode = [];
|
|
5310
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
4841
5311
|
let at;
|
|
4842
5312
|
let atDistance = Infinity;
|
|
4843
5313
|
let covered = 0;
|
|
4844
5314
|
const mid = (gene.start + gene.end) / 2;
|
|
4845
|
-
const { nodes, reach } =
|
|
5315
|
+
const { nodes, reach } = onRefName;
|
|
4846
5316
|
for (let i = firstNodeAtOrAfter(nodes, gene.start - reach); i < nodes.length && nodes[i].stable.start < gene.end; i++) {
|
|
4847
5317
|
const node = nodes[i];
|
|
4848
5318
|
const nodeStart = node.stable.start;
|
|
@@ -4851,6 +5321,7 @@ function genePins(graph, genes, positions) {
|
|
|
4851
5321
|
continue;
|
|
4852
5322
|
}
|
|
4853
5323
|
const line = positions[node.id];
|
|
5324
|
+
nodeIds.add(node.id);
|
|
4854
5325
|
covered += Math.min(nodeEnd, gene.end) - Math.max(nodeStart, gene.start);
|
|
4855
5326
|
const nodeParts = [];
|
|
4856
5327
|
for (const exon of gene.exons) {
|
|
@@ -4890,6 +5361,7 @@ function genePins(graph, genes, positions) {
|
|
|
4890
5361
|
gene,
|
|
4891
5362
|
exons: parts.join(""),
|
|
4892
5363
|
exonsByNode,
|
|
5364
|
+
nodeIds,
|
|
4893
5365
|
at,
|
|
4894
5366
|
covered: covered / (gene.end - gene.start)
|
|
4895
5367
|
});
|
|
@@ -4914,7 +5386,8 @@ function geneCoverageNote(pin) {
|
|
|
4914
5386
|
const amount = (bp) => +(bp / per).toFixed(per === 1 ? 0 : 1);
|
|
4915
5387
|
return `${amount(pin.covered * length)} of ${amount(length)} ${unit}`;
|
|
4916
5388
|
}
|
|
4917
|
-
function geneLabelCandidates(pins, screen, contigThickness) {
|
|
5389
|
+
function geneLabelCandidates(pins, screen, contigThickness, nodesUnder) {
|
|
5390
|
+
const ink = contigThickness / 2;
|
|
4918
5391
|
return byExtent(pins, (pin) => pin.gene.end - pin.gene.start).map((pin) => {
|
|
4919
5392
|
const { x, y } = screen(pin.at);
|
|
4920
5393
|
const note = geneCoverageNote(pin);
|
|
@@ -4924,7 +5397,15 @@ function geneLabelCandidates(pins, screen, contigThickness) {
|
|
|
4924
5397
|
y: y + contigThickness + GENE_PIN_DROP_PX,
|
|
4925
5398
|
text: note ? `${pin.gene.name} \xB7 ${note}` : pin.gene.name,
|
|
4926
5399
|
fallback: note ? `${pin.gene.name} \u2026` : void 0,
|
|
4927
|
-
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))
|
|
4928
5409
|
};
|
|
4929
5410
|
});
|
|
4930
5411
|
}
|
|
@@ -4985,7 +5466,16 @@ function layoutLabels(m) {
|
|
|
4985
5466
|
take
|
|
4986
5467
|
);
|
|
4987
5468
|
const genes = placeLabels(
|
|
4988
|
-
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
|
+
),
|
|
4989
5479
|
frame,
|
|
4990
5480
|
take
|
|
4991
5481
|
);
|
|
@@ -5933,7 +6423,7 @@ function svgCanvas(width, height) {
|
|
|
5933
6423
|
}
|
|
5934
6424
|
|
|
5935
6425
|
// src/version.ts
|
|
5936
|
-
var version = "5.
|
|
6426
|
+
var version = "5.2.0";
|
|
5937
6427
|
|
|
5938
6428
|
// src/viewport.ts
|
|
5939
6429
|
function axisScaleOf(scale, pixelRows) {
|
|
@@ -5983,11 +6473,7 @@ var SWATCH_PX = 18;
|
|
|
5983
6473
|
var KEY_GAP_PX = 16;
|
|
5984
6474
|
var STRIP_KEY_PX = 22;
|
|
5985
6475
|
var FADED = "rgb(160,160,160)";
|
|
5986
|
-
var EXON_COLOR = "#daa520";
|
|
5987
6476
|
var GENE_INK2 = "#1c1c22";
|
|
5988
|
-
var MIN_LANE_PX = 4;
|
|
5989
|
-
var EXON_GAP_PX = 1;
|
|
5990
|
-
var EXON_LINE_PX = 2;
|
|
5991
6477
|
function esc(s) {
|
|
5992
6478
|
return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
5993
6479
|
}
|
|
@@ -6004,6 +6490,12 @@ function keySvg(id, x, y, stops, label, after, under, opacity = 1) {
|
|
|
6004
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>`
|
|
6005
6491
|
};
|
|
6006
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
|
+
}
|
|
6007
6499
|
function walkKeySvg(id, x, y, walk, label, reference) {
|
|
6008
6500
|
const key = walkKey(walk, reference);
|
|
6009
6501
|
return keySvg(
|
|
@@ -6068,24 +6560,12 @@ function figureSvg(graph, layout, o = {}) {
|
|
|
6068
6560
|
let masks = 0;
|
|
6069
6561
|
function overlays(highlight, t, w, h) {
|
|
6070
6562
|
const out = [];
|
|
6071
|
-
const
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
};
|
|
6075
|
-
const stretches = pins.flatMap(
|
|
6076
|
-
(pin) => pin.exonsByNode.map(({ nodeId, d }) => ({
|
|
6077
|
-
d: screenPath(d, t),
|
|
6078
|
-
inner: inkPx(nodeId) + 2 * EXON_GAP_PX
|
|
6079
|
-
}))
|
|
6563
|
+
const outlines = exonOutlineTree(
|
|
6564
|
+
exonStretches(pins, ink.halfWidthPx, highlight, (d) => screenPath(d, t)),
|
|
6565
|
+
{ id: `exons${masks++}`, width: w, height: h }
|
|
6080
6566
|
);
|
|
6081
|
-
if (
|
|
6082
|
-
|
|
6083
|
-
const stroke = (color, width2, d) => `<path d="${d}" fill="none" stroke="${color}" stroke-width="${width2}" stroke-linecap="round" stroke-linejoin="round"/>`;
|
|
6084
|
-
out.push(
|
|
6085
|
-
`<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(
|
|
6086
|
-
""
|
|
6087
|
-
)}</mask><rect width="${w}" height="${h}" fill="${EXON_COLOR}" mask="url(#${id})"/>`
|
|
6088
|
-
);
|
|
6567
|
+
if (outlines) {
|
|
6568
|
+
out.push(serializeEl(outlines));
|
|
6089
6569
|
}
|
|
6090
6570
|
const screen = (p) => ({
|
|
6091
6571
|
x: p.x * t.scaleX + t.translateX,
|
|
@@ -6093,7 +6573,12 @@ function figureSvg(graph, layout, o = {}) {
|
|
|
6093
6573
|
});
|
|
6094
6574
|
const frame = { width: w, height: h };
|
|
6095
6575
|
for (const { item: pin, x, y, w: cw, text } of placeLabels(
|
|
6096
|
-
geneLabelCandidates(
|
|
6576
|
+
geneLabelCandidates(
|
|
6577
|
+
pins,
|
|
6578
|
+
screen,
|
|
6579
|
+
contigThickness,
|
|
6580
|
+
nodesUnderBox(layout.nodePositions, t)
|
|
6581
|
+
),
|
|
6097
6582
|
frame,
|
|
6098
6583
|
occupancy(frame)
|
|
6099
6584
|
)) {
|
|
@@ -6259,6 +6744,9 @@ function figureSvg(graph, layout, o = {}) {
|
|
|
6259
6744
|
)
|
|
6260
6745
|
);
|
|
6261
6746
|
}
|
|
6747
|
+
if (pins.some((pin) => pin.exonsByNode.length > 0)) {
|
|
6748
|
+
push(exonKeySvg(x, 2));
|
|
6749
|
+
}
|
|
6262
6750
|
const header = parts.length > 0 ? FACET_TITLE_PX : 0;
|
|
6263
6751
|
const usable = width - 2 * FIT_PADDING;
|
|
6264
6752
|
const drawn = pixelRows || !(bounds.w > 0) ? bounds.h : bounds.h * usable / bounds.w;
|
|
@@ -6625,7 +7113,9 @@ var NodeLimitError = class extends Error {
|
|
|
6625
7113
|
super(
|
|
6626
7114
|
`this ${windowBp.toLocaleString()} bp window reads more than nodeLimit (${limit.toLocaleString()}) graph nodes; zoom in to about ${fitsBp.toLocaleString()} bp or raise nodeLimit`
|
|
6627
7115
|
);
|
|
7116
|
+
this.fitsBp = fitsBp;
|
|
6628
7117
|
this.name = "NodeLimitError";
|
|
7118
|
+
this.regionTooLarge = true;
|
|
6629
7119
|
}
|
|
6630
7120
|
};
|
|
6631
7121
|
function haplotypePrefix(name) {
|
|
@@ -6770,10 +7260,6 @@ export {
|
|
|
6770
7260
|
BUBBLE_SPREADS,
|
|
6771
7261
|
ROW_HEIGHT_PX,
|
|
6772
7262
|
svgPath,
|
|
6773
|
-
curveBounds,
|
|
6774
|
-
translateCurves,
|
|
6775
|
-
yToXOf,
|
|
6776
|
-
pathRibbonOffsets,
|
|
6777
7263
|
deletionEdges,
|
|
6778
7264
|
panSNSample,
|
|
6779
7265
|
panSNHaplotype,
|
|
@@ -6809,7 +7295,6 @@ export {
|
|
|
6809
7295
|
nodeWidthPx,
|
|
6810
7296
|
maxNodeWidthPx,
|
|
6811
7297
|
nodeInk,
|
|
6812
|
-
baseEdgeCurves,
|
|
6813
7298
|
LIFT_BACKDROP_CSS,
|
|
6814
7299
|
REFERENCE_RAMP_MAX_HUE,
|
|
6815
7300
|
computeReferenceRamp,
|
|
@@ -6817,6 +7302,8 @@ export {
|
|
|
6817
7302
|
getNodeColor,
|
|
6818
7303
|
buildGeometry,
|
|
6819
7304
|
Canvas2DRenderer,
|
|
7305
|
+
findHoveredNode,
|
|
7306
|
+
findHoveredEdge,
|
|
6820
7307
|
WALK_FIELDS,
|
|
6821
7308
|
WALK_SCHEMES,
|
|
6822
7309
|
resolveEncoding,
|
|
@@ -6837,6 +7324,9 @@ export {
|
|
|
6837
7324
|
facetGrid,
|
|
6838
7325
|
facetSettingOf,
|
|
6839
7326
|
facetCells,
|
|
7327
|
+
EXON_COLOR,
|
|
7328
|
+
exonStretches,
|
|
7329
|
+
exonOutlineTree,
|
|
6840
7330
|
genePins,
|
|
6841
7331
|
HALO_FACTOR,
|
|
6842
7332
|
LEGEND_INSET_PX,
|
package/dist/cli/figure.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
COLOR_SCHEMES,
|
|
5
5
|
Canvas2DRenderer,
|
|
6
6
|
DISAGREES,
|
|
7
|
+
EXON_COLOR,
|
|
7
8
|
FACET_GAP_PX,
|
|
8
9
|
FACET_PAD_PX,
|
|
9
10
|
FACET_TITLE_PX,
|
|
@@ -35,14 +36,12 @@ import {
|
|
|
35
36
|
assemblyWalk,
|
|
36
37
|
axisScaleOf,
|
|
37
38
|
backboneAssembly,
|
|
38
|
-
baseEdgeCurves,
|
|
39
39
|
buildGeometry,
|
|
40
40
|
clampZoom,
|
|
41
41
|
coalesceRuns,
|
|
42
42
|
computeColorSchemeRange,
|
|
43
43
|
computeReferenceRamp,
|
|
44
44
|
contains,
|
|
45
|
-
curveBounds,
|
|
46
45
|
cutGbzRegion,
|
|
47
46
|
cutWindowGFA,
|
|
48
47
|
deletionEdges,
|
|
@@ -52,6 +51,8 @@ import {
|
|
|
52
51
|
encodingSwatchCss,
|
|
53
52
|
engineKey,
|
|
54
53
|
engineRequest,
|
|
54
|
+
exonOutlineTree,
|
|
55
|
+
exonStretches,
|
|
55
56
|
facetCells,
|
|
56
57
|
facetGrid,
|
|
57
58
|
facetLifts,
|
|
@@ -59,6 +60,8 @@ import {
|
|
|
59
60
|
featuresOnBackbone,
|
|
60
61
|
figureSvg,
|
|
61
62
|
filterSamples,
|
|
63
|
+
findHoveredEdge,
|
|
64
|
+
findHoveredNode,
|
|
62
65
|
fitTransform,
|
|
63
66
|
forceLayout,
|
|
64
67
|
formatBp,
|
|
@@ -91,7 +94,6 @@ import {
|
|
|
91
94
|
parseRegion,
|
|
92
95
|
pathColorsLegible,
|
|
93
96
|
pathLegend,
|
|
94
|
-
pathRibbonOffsets,
|
|
95
97
|
placeRowGenes,
|
|
96
98
|
rampHue,
|
|
97
99
|
rampHueCss,
|
|
@@ -118,7 +120,6 @@ import {
|
|
|
118
120
|
stripRowAt,
|
|
119
121
|
svgCanvas,
|
|
120
122
|
svgPath,
|
|
121
|
-
translateCurves,
|
|
122
123
|
trimOrigins,
|
|
123
124
|
unitTicks,
|
|
124
125
|
version,
|
|
@@ -137,372 +138,12 @@ import {
|
|
|
137
138
|
walkStripFrame,
|
|
138
139
|
walkStripLabelsTree,
|
|
139
140
|
wellKnownSample,
|
|
140
|
-
yToXOf,
|
|
141
141
|
zoomAbout
|
|
142
|
-
} from "./chunks/chunk-
|
|
142
|
+
} from "./chunks/chunk-ZMVFSZI5.js";
|
|
143
143
|
|
|
144
144
|
// src/index.ts
|
|
145
145
|
import { getDpr } from "@jbrowse/render-core/canvas2dUtils";
|
|
146
146
|
|
|
147
|
-
// src/util/SpatialIndex.ts
|
|
148
|
-
var FALLBACK_CELL_SIZE = 50;
|
|
149
|
-
function meanExtentCellSize(extentTotal, count) {
|
|
150
|
-
return count > 0 ? Math.max(1, extentTotal / count) : FALLBACK_CELL_SIZE;
|
|
151
|
-
}
|
|
152
|
-
function rowPitchOf(boxes) {
|
|
153
|
-
const distinct = /* @__PURE__ */ new Set();
|
|
154
|
-
let min = Infinity;
|
|
155
|
-
let max = -Infinity;
|
|
156
|
-
for (const box of boxes) {
|
|
157
|
-
distinct.add(box.minY);
|
|
158
|
-
min = Math.min(min, box.minY);
|
|
159
|
-
max = Math.max(max, box.minY);
|
|
160
|
-
}
|
|
161
|
-
return distinct.size > 1 ? (max - min) / (distinct.size - 1) : void 0;
|
|
162
|
-
}
|
|
163
|
-
function cellSizeFrom(boxes, extentX, extentY) {
|
|
164
|
-
const x = meanExtentCellSize(extentX, boxes.length);
|
|
165
|
-
return {
|
|
166
|
-
x,
|
|
167
|
-
y: extentY > 0 ? meanExtentCellSize(extentY, boxes.length) : rowPitchOf(boxes) ?? x
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
function resolveCellSize(given, boxes, extentX, extentY) {
|
|
171
|
-
return given === void 0 ? cellSizeFrom(boxes, extentX, extentY) : typeof given === "number" ? { x: given, y: given } : given;
|
|
172
|
-
}
|
|
173
|
-
function getOrCreateCell(cells, cx, cy) {
|
|
174
|
-
let row = cells.get(cx);
|
|
175
|
-
if (!row) {
|
|
176
|
-
row = /* @__PURE__ */ new Map();
|
|
177
|
-
cells.set(cx, row);
|
|
178
|
-
}
|
|
179
|
-
let cell = row.get(cy);
|
|
180
|
-
if (!cell) {
|
|
181
|
-
cell = [];
|
|
182
|
-
row.set(cy, cell);
|
|
183
|
-
}
|
|
184
|
-
return cell;
|
|
185
|
-
}
|
|
186
|
-
function addToGrid(cells, cellSize, minX, minY, maxX, maxY, entry) {
|
|
187
|
-
const cx0 = Math.floor(minX / cellSize.x);
|
|
188
|
-
const cx1 = Math.floor(maxX / cellSize.x);
|
|
189
|
-
const cy0 = Math.floor(minY / cellSize.y);
|
|
190
|
-
const cy1 = Math.floor(maxY / cellSize.y);
|
|
191
|
-
for (let cx = cx0; cx <= cx1; cx++) {
|
|
192
|
-
for (let cy = cy0; cy <= cy1; cy++) {
|
|
193
|
-
getOrCreateCell(cells, cx, cy).push(entry);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
function queryGrid(cells, cellSize, x, y, radiusX, radiusY) {
|
|
198
|
-
const cx0 = Math.floor((x - radiusX) / cellSize.x);
|
|
199
|
-
const cx1 = Math.floor((x + radiusX) / cellSize.x);
|
|
200
|
-
const cy0 = Math.floor((y - radiusY) / cellSize.y);
|
|
201
|
-
const cy1 = Math.floor((y + radiusY) / cellSize.y);
|
|
202
|
-
const results = [];
|
|
203
|
-
const seen = /* @__PURE__ */ new Set();
|
|
204
|
-
for (let cx = cx0; cx <= cx1; cx++) {
|
|
205
|
-
const row = cells.get(cx);
|
|
206
|
-
if (row) {
|
|
207
|
-
for (let cy = cy0; cy <= cy1; cy++) {
|
|
208
|
-
const cell = row.get(cy);
|
|
209
|
-
if (cell) {
|
|
210
|
-
for (const entry of cell) {
|
|
211
|
-
if (!seen.has(entry)) {
|
|
212
|
-
seen.add(entry);
|
|
213
|
-
results.push(entry);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return results;
|
|
221
|
-
}
|
|
222
|
-
var SpatialIndex = class {
|
|
223
|
-
constructor(nodePositions, cellSize) {
|
|
224
|
-
this.cells = /* @__PURE__ */ new Map();
|
|
225
|
-
const boxes = [];
|
|
226
|
-
let extentX = 0;
|
|
227
|
-
let extentY = 0;
|
|
228
|
-
for (const [nodeId, segments] of Object.entries(nodePositions)) {
|
|
229
|
-
for (let i = 0; i < segments.length - 1; i++) {
|
|
230
|
-
const seg = segments[i];
|
|
231
|
-
const next = segments[i + 1];
|
|
232
|
-
const minX = Math.min(seg.x, next.x);
|
|
233
|
-
const minY = Math.min(seg.y, next.y);
|
|
234
|
-
const maxX = Math.max(seg.x, next.x);
|
|
235
|
-
const maxY = Math.max(seg.y, next.y);
|
|
236
|
-
extentX += maxX - minX;
|
|
237
|
-
extentY += maxY - minY;
|
|
238
|
-
boxes.push({ entry: { nodeId, segmentIdx: i }, minX, minY, maxX, maxY });
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
this.cellSize = resolveCellSize(cellSize, boxes, extentX, extentY);
|
|
242
|
-
for (const b of boxes) {
|
|
243
|
-
addToGrid(
|
|
244
|
-
this.cells,
|
|
245
|
-
this.cellSize,
|
|
246
|
-
b.minX,
|
|
247
|
-
b.minY,
|
|
248
|
-
b.maxX,
|
|
249
|
-
b.maxY,
|
|
250
|
-
b.entry
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
query(x, y, radiusX, radiusY = radiusX) {
|
|
255
|
-
return queryGrid(this.cells, this.cellSize, x, y, radiusX, radiusY);
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
var EdgeSpatialIndex = class {
|
|
259
|
-
constructor(nodePositions, graph, drawPaths, axis, cellSize, deletions, version2 = 0, routes2, stranded = false) {
|
|
260
|
-
this.cells = /* @__PURE__ */ new Map();
|
|
261
|
-
this.edgeCurves = baseEdgeCurves(
|
|
262
|
-
nodePositions,
|
|
263
|
-
graph,
|
|
264
|
-
axis,
|
|
265
|
-
deletions,
|
|
266
|
-
version2,
|
|
267
|
-
routes2,
|
|
268
|
-
stranded
|
|
269
|
-
);
|
|
270
|
-
const boxes = [];
|
|
271
|
-
let extentX = 0;
|
|
272
|
-
let extentY = 0;
|
|
273
|
-
for (let ei = 0; ei < graph.edges.length; ei++) {
|
|
274
|
-
const edge = graph.edges[ei];
|
|
275
|
-
const fromSegments = nodePositions[edge.from];
|
|
276
|
-
const toSegments = nodePositions[edge.to];
|
|
277
|
-
const curves = this.edgeCurves.get(ei);
|
|
278
|
-
if (fromSegments?.length && toSegments?.length && curves) {
|
|
279
|
-
let { minX, minY, maxX, maxY } = curveBounds(curves);
|
|
280
|
-
const numPaths = edge.pathIds?.length ?? 0;
|
|
281
|
-
if (drawPaths && numPaths > 0) {
|
|
282
|
-
let padX = 0;
|
|
283
|
-
let padY = 0;
|
|
284
|
-
for (const o of pathRibbonOffsets(
|
|
285
|
-
fromSegments,
|
|
286
|
-
toSegments,
|
|
287
|
-
numPaths,
|
|
288
|
-
axis
|
|
289
|
-
)) {
|
|
290
|
-
padX = Math.max(padX, Math.abs(o.x));
|
|
291
|
-
padY = Math.max(padY, Math.abs(o.y));
|
|
292
|
-
}
|
|
293
|
-
minX -= padX;
|
|
294
|
-
minY -= padY;
|
|
295
|
-
maxX += padX;
|
|
296
|
-
maxY += padY;
|
|
297
|
-
}
|
|
298
|
-
extentX += maxX - minX;
|
|
299
|
-
extentY += maxY - minY;
|
|
300
|
-
boxes.push({ ei, minX, minY, maxX, maxY });
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
this.cellSize = resolveCellSize(cellSize, boxes, extentX, extentY);
|
|
304
|
-
for (const b of boxes) {
|
|
305
|
-
addToGrid(this.cells, this.cellSize, b.minX, b.minY, b.maxX, b.maxY, b.ei);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
getCurves(ei) {
|
|
309
|
-
return this.edgeCurves.get(ei);
|
|
310
|
-
}
|
|
311
|
-
query(x, y, radiusX, radiusY = radiusX) {
|
|
312
|
-
return queryGrid(this.cells, this.cellSize, x, y, radiusX, radiusY);
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
// src/util/hitDetection.ts
|
|
317
|
-
function distanceToSegment(px, py, x1, y1, x2, y2) {
|
|
318
|
-
const dx = x2 - x1;
|
|
319
|
-
const dy = y2 - y1;
|
|
320
|
-
const lenSq = dx * dx + dy * dy;
|
|
321
|
-
if (lenSq === 0) {
|
|
322
|
-
return Math.hypot(px - x1, py - y1);
|
|
323
|
-
}
|
|
324
|
-
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
325
|
-
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
326
|
-
}
|
|
327
|
-
function distanceToCubicBezier(px, py, x1, y1, cx1, cy1, cx2, cy2, x2, y2) {
|
|
328
|
-
let minDist = Infinity;
|
|
329
|
-
const samples = 20;
|
|
330
|
-
let lastX = x1;
|
|
331
|
-
let lastY = y1;
|
|
332
|
-
for (let i = 1; i <= samples; i++) {
|
|
333
|
-
const t = i / samples;
|
|
334
|
-
const u = 1 - t;
|
|
335
|
-
const bx = u * u * u * x1 + 3 * u * u * t * cx1 + 3 * u * t * t * cx2 + t * t * t * x2;
|
|
336
|
-
const by = u * u * u * y1 + 3 * u * u * t * cy1 + 3 * u * t * t * cy2 + t * t * t * y2;
|
|
337
|
-
minDist = Math.min(minDist, distanceToSegment(px, py, lastX, lastY, bx, by));
|
|
338
|
-
lastX = bx;
|
|
339
|
-
lastY = by;
|
|
340
|
-
}
|
|
341
|
-
return minDist;
|
|
342
|
-
}
|
|
343
|
-
function distanceToBezierCurve(px, py, c) {
|
|
344
|
-
return distanceToCubicBezier(
|
|
345
|
-
px,
|
|
346
|
-
py,
|
|
347
|
-
c.x0,
|
|
348
|
-
c.y0,
|
|
349
|
-
c.cx0,
|
|
350
|
-
c.cy0,
|
|
351
|
-
c.cx1,
|
|
352
|
-
c.cy1,
|
|
353
|
-
c.x1,
|
|
354
|
-
c.y1
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
function distanceToEdgeCurves(px, py, curves, yToX = 1) {
|
|
358
|
-
let minDist = Infinity;
|
|
359
|
-
for (const c of curves) {
|
|
360
|
-
const d = distanceToBezierCurve(
|
|
361
|
-
px,
|
|
362
|
-
py,
|
|
363
|
-
yToX === 1 ? c : {
|
|
364
|
-
x0: c.x0,
|
|
365
|
-
y0: c.y0 * yToX,
|
|
366
|
-
cx0: c.cx0,
|
|
367
|
-
cy0: c.cy0 * yToX,
|
|
368
|
-
cx1: c.cx1,
|
|
369
|
-
cy1: c.cy1 * yToX,
|
|
370
|
-
x1: c.x1,
|
|
371
|
-
y1: c.y1 * yToX
|
|
372
|
-
}
|
|
373
|
-
);
|
|
374
|
-
if (d < minDist) {
|
|
375
|
-
minDist = d;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return minDist;
|
|
379
|
-
}
|
|
380
|
-
var nodeCache = /* @__PURE__ */ new WeakMap();
|
|
381
|
-
function getSpatialIndex(nodePositions, version2) {
|
|
382
|
-
const cached = nodeCache.get(nodePositions);
|
|
383
|
-
if (cached?.version === version2) {
|
|
384
|
-
return cached.index;
|
|
385
|
-
}
|
|
386
|
-
const index = new SpatialIndex(nodePositions);
|
|
387
|
-
nodeCache.set(nodePositions, { version: version2, index });
|
|
388
|
-
return index;
|
|
389
|
-
}
|
|
390
|
-
var edgeCache = /* @__PURE__ */ new WeakMap();
|
|
391
|
-
function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis, version2, deletions, routes2, stranded = false) {
|
|
392
|
-
const cached = edgeCache.get(nodePositions);
|
|
393
|
-
if (cached?.graph === graph && cached.drawPaths === drawPaths && cached.scaleX === axis.scaleX && cached.scaleY === axis.scaleY && cached.version === version2 && cached.deletions === deletions && cached.routes === routes2 && cached.stranded === stranded) {
|
|
394
|
-
return cached.index;
|
|
395
|
-
}
|
|
396
|
-
const index = new EdgeSpatialIndex(
|
|
397
|
-
nodePositions,
|
|
398
|
-
graph,
|
|
399
|
-
drawPaths,
|
|
400
|
-
axis,
|
|
401
|
-
void 0,
|
|
402
|
-
deletions,
|
|
403
|
-
version2,
|
|
404
|
-
routes2,
|
|
405
|
-
stranded
|
|
406
|
-
);
|
|
407
|
-
edgeCache.set(nodePositions, {
|
|
408
|
-
graph,
|
|
409
|
-
drawPaths,
|
|
410
|
-
scaleX: axis.scaleX,
|
|
411
|
-
scaleY: axis.scaleY,
|
|
412
|
-
version: version2,
|
|
413
|
-
index,
|
|
414
|
-
deletions,
|
|
415
|
-
routes: routes2,
|
|
416
|
-
stranded
|
|
417
|
-
});
|
|
418
|
-
return index;
|
|
419
|
-
}
|
|
420
|
-
var MIN_NODE_REACH_PX = 5;
|
|
421
|
-
function findHoveredNode(nodePositions, graphX, graphY, axis, version2 = 0, ink) {
|
|
422
|
-
const yToX = yToXOf(axis);
|
|
423
|
-
const index = getSpatialIndex(nodePositions, version2);
|
|
424
|
-
const reach = (px) => Math.max(MIN_NODE_REACH_PX, px) / axis.scaleX;
|
|
425
|
-
const queryReach = reach(ink?.maxHalfWidthPx ?? 0);
|
|
426
|
-
const candidates = index.query(graphX, graphY, queryReach, queryReach / yToX);
|
|
427
|
-
let hovered = null;
|
|
428
|
-
let best = Infinity;
|
|
429
|
-
for (const { nodeId, segmentIdx } of candidates) {
|
|
430
|
-
const segments = nodePositions[nodeId];
|
|
431
|
-
const dist = distanceToSegment(
|
|
432
|
-
graphX,
|
|
433
|
-
graphY * yToX,
|
|
434
|
-
segments[segmentIdx].x,
|
|
435
|
-
segments[segmentIdx].y * yToX,
|
|
436
|
-
segments[segmentIdx + 1].x,
|
|
437
|
-
segments[segmentIdx + 1].y * yToX
|
|
438
|
-
);
|
|
439
|
-
if (dist < best && dist < reach(ink?.halfWidthPx(nodeId) ?? 0)) {
|
|
440
|
-
best = dist;
|
|
441
|
-
hovered = nodeId;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return hovered;
|
|
445
|
-
}
|
|
446
|
-
function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths, version2 = 0, deletions, hiddenEdges, deletionRoutes, stranded = false) {
|
|
447
|
-
const yToX = yToXOf(axis);
|
|
448
|
-
const edgeThreshold = 10 / axis.scaleX;
|
|
449
|
-
const edgeIndex = getEdgeSpatialIndex(
|
|
450
|
-
nodePositions,
|
|
451
|
-
graph,
|
|
452
|
-
drawPaths,
|
|
453
|
-
axis,
|
|
454
|
-
version2,
|
|
455
|
-
deletions,
|
|
456
|
-
deletionRoutes,
|
|
457
|
-
stranded
|
|
458
|
-
);
|
|
459
|
-
const candidates = edgeIndex.query(
|
|
460
|
-
graphX,
|
|
461
|
-
graphY,
|
|
462
|
-
edgeThreshold,
|
|
463
|
-
edgeThreshold / yToX
|
|
464
|
-
);
|
|
465
|
-
let hovered = null;
|
|
466
|
-
let best = edgeThreshold;
|
|
467
|
-
for (const edgeIdx of candidates) {
|
|
468
|
-
const edge = graph.edges[edgeIdx];
|
|
469
|
-
const fromSegments = nodePositions[edge.from];
|
|
470
|
-
const toSegments = nodePositions[edge.to];
|
|
471
|
-
if (!fromSegments?.length || !toSegments?.length || hiddenEdges?.has(edgeIdx)) {
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
const numPaths = edge.pathIds?.length ?? 0;
|
|
475
|
-
const baseCurves = edgeIndex.getCurves(edgeIdx);
|
|
476
|
-
if (!baseCurves) {
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
let dist;
|
|
480
|
-
if (!drawPaths || numPaths === 0) {
|
|
481
|
-
dist = distanceToEdgeCurves(graphX, graphY * yToX, baseCurves, yToX);
|
|
482
|
-
} else {
|
|
483
|
-
let minDist = Infinity;
|
|
484
|
-
for (const offset of pathRibbonOffsets(
|
|
485
|
-
fromSegments,
|
|
486
|
-
toSegments,
|
|
487
|
-
numPaths,
|
|
488
|
-
axis
|
|
489
|
-
)) {
|
|
490
|
-
const curves = offset.x === 0 && offset.y === 0 ? baseCurves : translateCurves(baseCurves, offset.x, offset.y);
|
|
491
|
-
const d = distanceToEdgeCurves(graphX, graphY * yToX, curves, yToX);
|
|
492
|
-
if (d < minDist) {
|
|
493
|
-
minDist = d;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
dist = minDist;
|
|
497
|
-
}
|
|
498
|
-
if (dist < best) {
|
|
499
|
-
best = dist;
|
|
500
|
-
hovered = edgeIdx;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
return hovered;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
147
|
// src/util/wheelZoom.ts
|
|
507
148
|
var ZOOM_PER_NOTCH = 1.1;
|
|
508
149
|
var NOTCH_PX = 100;
|
|
@@ -1797,6 +1438,7 @@ export {
|
|
|
1797
1438
|
BUBBLE_SPREADS,
|
|
1798
1439
|
COLOR_SCHEMES,
|
|
1799
1440
|
Canvas2DRenderer,
|
|
1441
|
+
EXON_COLOR,
|
|
1800
1442
|
FACET_GAP_PX,
|
|
1801
1443
|
FACET_PAD_PX,
|
|
1802
1444
|
FACET_TITLE_PX,
|
|
@@ -1855,6 +1497,8 @@ export {
|
|
|
1855
1497
|
encodingSwatchCss,
|
|
1856
1498
|
engineKey,
|
|
1857
1499
|
engineRequest,
|
|
1500
|
+
exonOutlineTree,
|
|
1501
|
+
exonStretches,
|
|
1858
1502
|
facetCells,
|
|
1859
1503
|
facetGrid,
|
|
1860
1504
|
facetLifts,
|
|
@@ -4,7 +4,9 @@ export declare class NoReferenceSampleError extends Error {
|
|
|
4
4
|
constructor(anchor: string, referenceSamples: string[]);
|
|
5
5
|
}
|
|
6
6
|
export declare class NodeLimitError extends Error {
|
|
7
|
+
readonly fitsBp: number;
|
|
7
8
|
name: string;
|
|
9
|
+
readonly regionTooLarge = true;
|
|
8
10
|
constructor(limit: number, windowBp: number, fitsBp: number);
|
|
9
11
|
}
|
|
10
12
|
export declare function haplotypePrefix(name: Pick<PathName, 'sample' | 'haplotype'>): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { El } from '../el.js';
|
|
2
|
+
import type { GenePin } from './genePins.js';
|
|
3
|
+
export declare const EXON_COLOR = "#daa520";
|
|
4
|
+
export interface ExonStretch {
|
|
5
|
+
key: string;
|
|
6
|
+
d: string;
|
|
7
|
+
inner: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function exonStretches(pins: GenePin[], halfWidthPx: (nodeId: string) => number, lift?: {
|
|
10
|
+
nodeIds: ReadonlySet<string>;
|
|
11
|
+
walks: readonly unknown[];
|
|
12
|
+
}, pathOf?: (d: string) => string): ExonStretch[];
|
|
13
|
+
export declare function exonOutlineTree(stretches: ExonStretch[], o: {
|
|
14
|
+
id: string;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
transform?: string;
|
|
18
|
+
}): El | undefined;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -68,6 +68,8 @@ export { ROW_HEIGHT_PX } from './layout/rowSpacing.js';
|
|
|
68
68
|
export { HALO_FACTOR, LEGEND_INSET_PX, geneCoverageNote, layoutLabels, } from './labelLayout.js';
|
|
69
69
|
export type { LabelLayout, LabelLayoutSource } from './labelLayout.js';
|
|
70
70
|
export { formatBp } from './graphLabels.js';
|
|
71
|
+
export { EXON_COLOR, exonOutlineTree, exonStretches } from './genes/exonOutline.js';
|
|
72
|
+
export type { ExonStretch } from './genes/exonOutline.js';
|
|
71
73
|
export { genePins } from './genes/genePins.js';
|
|
72
74
|
export type { GeneModel, GenePin } from './genes/genePins.js';
|
|
73
75
|
export { WELL_KNOWN_SAMPLES, assemblyWalk, backboneAssembly, featuresOnBackbone, graphBackbone, refNameBinding, referenceLabel, wellKnownSample, } from './reference.js';
|
|
@@ -3,7 +3,7 @@ import type { DeletionEdge } from './deletionEdges.js';
|
|
|
3
3
|
import type { GenePin } from './genes/genePins.js';
|
|
4
4
|
import type { GraphLabel } from './graphLabels.js';
|
|
5
5
|
import type { DeletionRoutes } from './layout/deletionRoutes.js';
|
|
6
|
-
import type { PlacedLabel } from './overlayLabels.js';
|
|
6
|
+
import type { Box, PlacedLabel } from './overlayLabels.js';
|
|
7
7
|
import type { AlleleDeletion, NodeSegment } from './types.js';
|
|
8
8
|
import type { AxisScale } from './util/geometry.js';
|
|
9
9
|
export declare const HALO_FACTOR = 3.4;
|
|
@@ -55,12 +55,13 @@ export declare function geneLabelCandidates(pins: GenePin[], screen: (p: {
|
|
|
55
55
|
}) => {
|
|
56
56
|
x: number;
|
|
57
57
|
y: number;
|
|
58
|
-
}, contigThickness: number): {
|
|
58
|
+
}, contigThickness: number, nodesUnder: (box: Box) => ReadonlySet<string>): {
|
|
59
59
|
item: GenePin;
|
|
60
60
|
x: number;
|
|
61
61
|
y: number;
|
|
62
62
|
text: string;
|
|
63
63
|
fallback: string | undefined;
|
|
64
64
|
stack: number;
|
|
65
|
+
fits: (box: Box) => boolean;
|
|
65
66
|
}[];
|
|
66
67
|
export declare function layoutLabels(m: LabelLayoutSource): LabelLayout;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DeletionRoutes } from '../layout/deletionRoutes.js';
|
|
2
|
+
import type { Box } from '../overlayLabels.js';
|
|
2
3
|
import type { Graph, NodeSegment } from '../types.js';
|
|
3
4
|
import type { AxisScale } from './geometry.js';
|
|
4
5
|
export declare function distanceToSegment(px: number, py: number, x1: number, y1: number, x2: number, y2: number): number;
|
|
@@ -8,4 +9,10 @@ export interface NodeInk {
|
|
|
8
9
|
maxHalfWidthPx: number;
|
|
9
10
|
}
|
|
10
11
|
export declare function findHoveredNode(nodePositions: Record<string, NodeSegment[]>, graphX: number, graphY: number, axis: AxisScale, version?: number, ink?: NodeInk): string | null;
|
|
12
|
+
export declare function nodesUnderBox(nodePositions: Record<string, NodeSegment[]>, t: {
|
|
13
|
+
scaleX: number;
|
|
14
|
+
scaleY: number;
|
|
15
|
+
translateX: number;
|
|
16
|
+
translateY: number;
|
|
17
|
+
}, version?: number): (box: Box) => Set<string>;
|
|
11
18
|
export declare function findHoveredEdge(nodePositions: Record<string, NodeSegment[]>, graph: Graph, graphX: number, graphY: number, axis: AxisScale, drawPaths: boolean, version?: number, deletions?: Map<number, string[]>, hiddenEdges?: ReadonlySet<number>, deletionRoutes?: DeletionRoutes, stranded?: boolean): number | null;
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "5.
|
|
1
|
+
export declare const version = "5.2.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/bandage-core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Pangenome graph layout and drawing with no host: GFA in, the Bandage force-directed engine and reference-anchored layouts, a Canvas2D renderer, hit testing and labels out",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"repository": {
|