@jbrowse/bandage-core 4.0.24 → 4.0.25
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-4AOHEU76.js → chunk-B5ZHLU4N.js} +589 -36
- package/dist/cli/figure.js +61 -56
- package/dist/index.js +47 -242
- package/dist/types/cli/figure.d.ts +8 -8
- package/dist/types/figure.d.ts +3 -1
- package/dist/types/gbzCut.d.ts +33 -0
- package/dist/types/genes/geneFiles.d.ts +10 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/labelLayout.d.ts +14 -0
- package/dist/types/referenceStrip.d.ts +4 -3
- package/dist/types/version.d.ts +1 -0
- package/dist/types/walkKey.d.ts +1 -0
- package/package.json +3 -2
package/dist/cli/figure.js
CHANGED
|
@@ -1,38 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
HPRC_GBZ,
|
|
4
|
+
cutGbzRegion,
|
|
5
|
+
featuresOnBackbone,
|
|
4
6
|
figureSvg,
|
|
5
7
|
forceLayout,
|
|
6
|
-
|
|
8
|
+
genesFromBed,
|
|
9
|
+
genesFromGff3Lines,
|
|
10
|
+
genesFromText,
|
|
11
|
+
graphBackbone,
|
|
7
12
|
layoutModeByValue,
|
|
8
13
|
loadBandage,
|
|
9
14
|
loadGraph,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "../chunks/chunk-4AOHEU76.js";
|
|
15
|
+
openGbz,
|
|
16
|
+
parseRegion
|
|
17
|
+
} from "../chunks/chunk-B5ZHLU4N.js";
|
|
14
18
|
|
|
15
19
|
// src/cli/figure.ts
|
|
16
20
|
import { open, readFile, writeFile } from "node:fs/promises";
|
|
17
21
|
import path from "node:path";
|
|
18
22
|
import { parseArgs } from "node:util";
|
|
19
23
|
import { gunzipSync } from "node:zlib";
|
|
20
|
-
import {
|
|
21
|
-
var HPRC = {
|
|
22
|
-
db: "https://s3-us-west-2.amazonaws.com/human-pangenomics/pangenomes/freeze/release2/minigraph-cactus/v2.1/hprc-v2.1-mc-grch38/hprc-v2.1-mc-grch38.gbz.db",
|
|
23
|
-
index: "https://jbrowse.org/demos/hprc/hprc-v2.1-mc-grch38.haplotype-index.anchored.db"
|
|
24
|
-
};
|
|
25
|
-
var WALK_LIMIT = 1e5;
|
|
26
|
-
function parseRegion(text) {
|
|
27
|
-
const m = /^\s*([^:\s]+):([\d,]+)-([\d,]+)\s*$/.exec(text);
|
|
28
|
-
if (!m) {
|
|
29
|
-
throw new Error(
|
|
30
|
-
`"${text}" is not a region like chr6:160,614,798-160,647,758`
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
const n = (s) => Number(s.replaceAll(",", ""));
|
|
34
|
-
return { refName: m[1], start: n(m[2]), end: n(m[3]) };
|
|
35
|
-
}
|
|
24
|
+
import { TabixIndexedFile } from "@gmod/tabix";
|
|
36
25
|
var isUrl = (s) => /^https?:\/\//.test(s);
|
|
37
26
|
function byteSource(location) {
|
|
38
27
|
if (isUrl(location)) {
|
|
@@ -64,36 +53,40 @@ function byteSource(location) {
|
|
|
64
53
|
}
|
|
65
54
|
};
|
|
66
55
|
}
|
|
67
|
-
async function
|
|
56
|
+
async function text(location) {
|
|
68
57
|
const bytes = isUrl(location) ? new Uint8Array(await (await fetch(location)).arrayBuffer()) : await readFile(location);
|
|
69
58
|
const gzipped = bytes[0] === 31 && bytes[1] === 139;
|
|
70
59
|
return new TextDecoder().decode(gzipped ? gunzipSync(bytes) : bytes);
|
|
71
60
|
}
|
|
72
|
-
async function
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
61
|
+
async function readGenes(genes, graph, resolve) {
|
|
62
|
+
const backbone = graphBackbone(graph);
|
|
63
|
+
if (!backbone) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const file = resolve(genes.file);
|
|
67
|
+
const format = genes.format ?? (/\.bed(\.gz)?$/i.test(genes.file) ? "bed" : "gff3");
|
|
68
|
+
if (!genes.index) {
|
|
69
|
+
return featuresOnBackbone(genesFromText(await text(file)), backbone);
|
|
70
|
+
}
|
|
71
|
+
const index = resolve(genes.index);
|
|
72
|
+
const csi = index.endsWith(".csi");
|
|
73
|
+
const tabix = new TabixIndexedFile(
|
|
74
|
+
isUrl(file) ? { url: file, ...csi ? { csiUrl: index } : { tbiUrl: index } } : { path: file, ...csi ? { csiPath: index } : { tbiPath: index } }
|
|
77
75
|
);
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
throw new Error(`${sample} has no indexed path named ${region.refName}`);
|
|
76
|
+
const indexed = new Set(await tabix.getReferenceSequenceNames());
|
|
77
|
+
const lines = [];
|
|
78
|
+
for (const contig of backbone.contigs) {
|
|
79
|
+
const refName = [contig.contig, contig.refName].find((n) => indexed.has(n));
|
|
80
|
+
if (refName !== void 0) {
|
|
81
|
+
await tabix.getLines(refName, contig.start, contig.end, {
|
|
82
|
+
lineCallback: (line) => lines.push(line)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
88
85
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
limit: WALK_LIMIT,
|
|
94
|
-
...wanted ? { keep: (name) => haplotypeWanted(name, wanted) } : {}
|
|
95
|
-
});
|
|
96
|
-
return { text, region, name: `${sample} ${gbz.region}` };
|
|
86
|
+
return featuresOnBackbone(
|
|
87
|
+
format === "gff3" ? genesFromGff3Lines(lines) : genesFromBed(lines.join("\n")),
|
|
88
|
+
backbone
|
|
89
|
+
);
|
|
97
90
|
}
|
|
98
91
|
var engine = async (request) => {
|
|
99
92
|
const bandage = await loadBandage();
|
|
@@ -101,15 +94,26 @@ var engine = async (request) => {
|
|
|
101
94
|
const result = bandage.computeLayout(request.graph, request.options);
|
|
102
95
|
return { result, duration: performance.now() - start };
|
|
103
96
|
};
|
|
104
|
-
async function renderSpec(spec, base
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
97
|
+
async function renderSpec(spec, base) {
|
|
98
|
+
const resolve = (location) => isUrl(location) ? location : path.resolve(base, location);
|
|
99
|
+
let source;
|
|
100
|
+
if (spec.gbz) {
|
|
101
|
+
const { db, index } = spec.gbz.db === "hprc" ? HPRC_GBZ : spec.gbz;
|
|
102
|
+
const cut = await cutGbzRegion(
|
|
103
|
+
await openGbz(
|
|
104
|
+
byteSource(resolve(db)),
|
|
105
|
+
index ? byteSource(resolve(index)) : void 0
|
|
106
|
+
),
|
|
107
|
+
spec.gbz
|
|
108
|
+
);
|
|
109
|
+
source = { ...cut, name: `${cut.sample} ${spec.gbz.region}` };
|
|
110
|
+
} else if (spec.gfa) {
|
|
111
|
+
source = {
|
|
112
|
+
text: await text(resolve(spec.gfa)),
|
|
113
|
+
name: path.basename(spec.gfa),
|
|
114
|
+
region: spec.region ? parseRegion(spec.region) : void 0
|
|
115
|
+
};
|
|
116
|
+
} else {
|
|
113
117
|
throw new Error("a spec names its graph as `gfa` or `gbz`");
|
|
114
118
|
}
|
|
115
119
|
const graph = loadGraph(source.text, source.name, {
|
|
@@ -128,7 +132,8 @@ async function renderSpec(spec, base = ".") {
|
|
|
128
132
|
...spec,
|
|
129
133
|
walks: spec.walks?.map((w) => typeof w === "string" ? { walk: w } : w),
|
|
130
134
|
region: source.region,
|
|
131
|
-
|
|
135
|
+
genes: spec.genes ? await readGenes(spec.genes, graph, resolve) : void 0,
|
|
136
|
+
spec
|
|
132
137
|
});
|
|
133
138
|
}
|
|
134
139
|
async function main() {
|
package/dist/index.js
CHANGED
|
@@ -8,10 +8,13 @@ import {
|
|
|
8
8
|
FADED_ALPHA,
|
|
9
9
|
FIT_PADDING,
|
|
10
10
|
FORCE_LAYOUT_LABEL,
|
|
11
|
+
HALO_FACTOR,
|
|
12
|
+
HPRC_GBZ,
|
|
11
13
|
LABEL_CHAR_PX,
|
|
12
14
|
LABEL_PAD,
|
|
13
15
|
LABEL_PX,
|
|
14
16
|
LAYOUT_MODES,
|
|
17
|
+
LEGEND_INSET_PX,
|
|
15
18
|
LIFT_BACKDROP_COLOR,
|
|
16
19
|
NODE_WIDTHS,
|
|
17
20
|
REFERENCE_RAMP_MAX_HUE,
|
|
@@ -29,6 +32,7 @@ import {
|
|
|
29
32
|
computeReferenceRamp,
|
|
30
33
|
contains,
|
|
31
34
|
curveBounds,
|
|
35
|
+
cutGbzRegion,
|
|
32
36
|
cutWindowGFA,
|
|
33
37
|
deletionEdges,
|
|
34
38
|
drawingBounds,
|
|
@@ -42,15 +46,20 @@ import {
|
|
|
42
46
|
fadeAbgr,
|
|
43
47
|
featuresOnBackbone,
|
|
44
48
|
figureSvg,
|
|
45
|
-
firstNodeAtOrAfter,
|
|
46
49
|
fitTransform,
|
|
47
50
|
forceLayout,
|
|
48
51
|
formatBp,
|
|
52
|
+
geneCoverageNote,
|
|
53
|
+
genePins,
|
|
54
|
+
genesFromBed,
|
|
55
|
+
genesFromGff3Lines,
|
|
56
|
+
genesFromText,
|
|
49
57
|
getNodeColor,
|
|
50
58
|
graphBackbone,
|
|
51
59
|
haplotypeWanted,
|
|
52
60
|
isBackbone,
|
|
53
61
|
layoutExtent,
|
|
62
|
+
layoutLabels,
|
|
54
63
|
layoutModeByValue,
|
|
55
64
|
loadBandage,
|
|
56
65
|
loadGraph,
|
|
@@ -58,19 +67,16 @@ import {
|
|
|
58
67
|
meanDepth,
|
|
59
68
|
modeUsesLayoutEngine,
|
|
60
69
|
nodeInk,
|
|
61
|
-
nodeLabelBudget,
|
|
62
70
|
nodeWidthPx,
|
|
63
|
-
|
|
71
|
+
openGbz,
|
|
64
72
|
padded,
|
|
65
73
|
panSNContig,
|
|
66
74
|
panSNHaplotype,
|
|
67
75
|
panSNSample,
|
|
76
|
+
parseRegion,
|
|
68
77
|
pathColorsLegible,
|
|
69
78
|
pathLegend,
|
|
70
79
|
pathRibbonOffsets,
|
|
71
|
-
placeLabels,
|
|
72
|
-
placeSizeLabels,
|
|
73
|
-
polylineSlice,
|
|
74
80
|
rangeText,
|
|
75
81
|
refNameBinding,
|
|
76
82
|
referenceOrder,
|
|
@@ -79,23 +85,23 @@ import {
|
|
|
79
85
|
resolveColorScheme,
|
|
80
86
|
resolveEncoding,
|
|
81
87
|
resolveReferenceSample,
|
|
82
|
-
rowLabelBox,
|
|
83
88
|
schemeCss,
|
|
84
89
|
screenToLayout,
|
|
85
|
-
sizeLabelCandidates,
|
|
86
90
|
svgCanvas,
|
|
87
91
|
svgPath,
|
|
88
92
|
translateCurves,
|
|
93
|
+
version,
|
|
89
94
|
viewportOf,
|
|
90
95
|
walkHighlight,
|
|
91
96
|
walkKey,
|
|
92
97
|
walkLift,
|
|
98
|
+
walkPosition,
|
|
93
99
|
walkRows,
|
|
94
100
|
walkRowsExtent,
|
|
95
101
|
wellKnownSample,
|
|
96
102
|
yToXOf,
|
|
97
103
|
zoomAbout
|
|
98
|
-
} from "./chunks/chunk-
|
|
104
|
+
} from "./chunks/chunk-B5ZHLU4N.js";
|
|
99
105
|
|
|
100
106
|
// src/index.ts
|
|
101
107
|
import { getDpr } from "@jbrowse/render-core/canvas2dUtils";
|
|
@@ -212,14 +218,14 @@ var SpatialIndex = class {
|
|
|
212
218
|
}
|
|
213
219
|
};
|
|
214
220
|
var EdgeSpatialIndex = class {
|
|
215
|
-
constructor(nodePositions, graph, drawPaths, axis, cellSize, deletions,
|
|
221
|
+
constructor(nodePositions, graph, drawPaths, axis, cellSize, deletions, version2 = 0) {
|
|
216
222
|
this.cells = /* @__PURE__ */ new Map();
|
|
217
223
|
this.edgeCurves = baseEdgeCurves(
|
|
218
224
|
nodePositions,
|
|
219
225
|
graph,
|
|
220
226
|
axis,
|
|
221
227
|
deletions,
|
|
222
|
-
|
|
228
|
+
version2
|
|
223
229
|
);
|
|
224
230
|
const boxes = [];
|
|
225
231
|
let extentX = 0;
|
|
@@ -332,19 +338,19 @@ function distanceToEdgeCurves(px, py, curves, yToX = 1) {
|
|
|
332
338
|
return minDist;
|
|
333
339
|
}
|
|
334
340
|
var nodeCache = /* @__PURE__ */ new WeakMap();
|
|
335
|
-
function getSpatialIndex(nodePositions,
|
|
341
|
+
function getSpatialIndex(nodePositions, version2) {
|
|
336
342
|
const cached = nodeCache.get(nodePositions);
|
|
337
|
-
if (cached?.version ===
|
|
343
|
+
if (cached?.version === version2) {
|
|
338
344
|
return cached.index;
|
|
339
345
|
}
|
|
340
346
|
const index = new SpatialIndex(nodePositions);
|
|
341
|
-
nodeCache.set(nodePositions, { version, index });
|
|
347
|
+
nodeCache.set(nodePositions, { version: version2, index });
|
|
342
348
|
return index;
|
|
343
349
|
}
|
|
344
350
|
var edgeCache = /* @__PURE__ */ new WeakMap();
|
|
345
|
-
function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis,
|
|
351
|
+
function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis, version2, deletions) {
|
|
346
352
|
const cached = edgeCache.get(nodePositions);
|
|
347
|
-
if (cached?.graph === graph && cached.drawPaths === drawPaths && cached.scaleX === axis.scaleX && cached.scaleY === axis.scaleY && cached.version ===
|
|
353
|
+
if (cached?.graph === graph && cached.drawPaths === drawPaths && cached.scaleX === axis.scaleX && cached.scaleY === axis.scaleY && cached.version === version2 && cached.deletions === deletions) {
|
|
348
354
|
return cached.index;
|
|
349
355
|
}
|
|
350
356
|
const index = new EdgeSpatialIndex(
|
|
@@ -354,23 +360,23 @@ function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis, version, del
|
|
|
354
360
|
axis,
|
|
355
361
|
void 0,
|
|
356
362
|
deletions,
|
|
357
|
-
|
|
363
|
+
version2
|
|
358
364
|
);
|
|
359
365
|
edgeCache.set(nodePositions, {
|
|
360
366
|
graph,
|
|
361
367
|
drawPaths,
|
|
362
368
|
scaleX: axis.scaleX,
|
|
363
369
|
scaleY: axis.scaleY,
|
|
364
|
-
version,
|
|
370
|
+
version: version2,
|
|
365
371
|
index,
|
|
366
372
|
deletions
|
|
367
373
|
});
|
|
368
374
|
return index;
|
|
369
375
|
}
|
|
370
376
|
var MIN_NODE_REACH_PX = 5;
|
|
371
|
-
function findHoveredNode(nodePositions, graphX, graphY, axis,
|
|
377
|
+
function findHoveredNode(nodePositions, graphX, graphY, axis, version2 = 0, ink) {
|
|
372
378
|
const yToX = yToXOf(axis);
|
|
373
|
-
const index = getSpatialIndex(nodePositions,
|
|
379
|
+
const index = getSpatialIndex(nodePositions, version2);
|
|
374
380
|
const reach = (px) => Math.max(MIN_NODE_REACH_PX, px) / axis.scaleX;
|
|
375
381
|
const queryReach = reach(ink?.maxHalfWidthPx ?? 0);
|
|
376
382
|
const candidates = index.query(graphX, graphY, queryReach, queryReach / yToX);
|
|
@@ -393,7 +399,7 @@ function findHoveredNode(nodePositions, graphX, graphY, axis, version = 0, ink)
|
|
|
393
399
|
}
|
|
394
400
|
return hovered;
|
|
395
401
|
}
|
|
396
|
-
function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths,
|
|
402
|
+
function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths, version2 = 0, deletions, hiddenEdges) {
|
|
397
403
|
const yToX = yToXOf(axis);
|
|
398
404
|
const edgeThreshold = 10 / axis.scaleX;
|
|
399
405
|
const edgeIndex = getEdgeSpatialIndex(
|
|
@@ -401,7 +407,7 @@ function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths,
|
|
|
401
407
|
graph,
|
|
402
408
|
drawPaths,
|
|
403
409
|
axis,
|
|
404
|
-
|
|
410
|
+
version2,
|
|
405
411
|
deletions
|
|
406
412
|
);
|
|
407
413
|
const candidates = edgeIndex.query(
|
|
@@ -470,6 +476,7 @@ var REFERENCE_STRIP_PX = 12;
|
|
|
470
476
|
var REFERENCE_STRIP_ZONE_PX = REFERENCE_STRIP_PX + 8;
|
|
471
477
|
var OVERHANG_PX = 8;
|
|
472
478
|
var OVERHANG_CAP_PX = OVERHANG_PX + 3;
|
|
479
|
+
var MIN_STAGGER_PX = 12;
|
|
473
480
|
var PALE = abgrToCssRgba(fadeAbgr(LIFT_BACKDROP_COLOR, FADED_ALPHA));
|
|
474
481
|
function referenceStripBlocks(graph, {
|
|
475
482
|
colorScheme,
|
|
@@ -553,7 +560,8 @@ function stripPixels(blocks, frame, width, dpr) {
|
|
|
553
560
|
claimed = px1;
|
|
554
561
|
}
|
|
555
562
|
}
|
|
556
|
-
|
|
563
|
+
const covered = out.length > 0 ? out.at(-1).x1 - out[0].x0 : 0;
|
|
564
|
+
return covered >= out.length * MIN_STAGGER_PX ? out : out.map(({ tier: _, ...b }) => b);
|
|
557
565
|
}
|
|
558
566
|
function drawOverhang(ctx, edgeX, inward, ink) {
|
|
559
567
|
const capX = inward > 0 ? edgeX : edgeX - OVERHANG_CAP_PX;
|
|
@@ -580,9 +588,11 @@ function drawReferenceStrip(ctx, blocks, frame, {
|
|
|
580
588
|
const ink = darkMode ? "#ffffff" : "#18181c";
|
|
581
589
|
ctx.clearRect(0, 0, width, REFERENCE_STRIP_ZONE_PX);
|
|
582
590
|
for (const b of stripPixels(blocks, frame, width, dpr)) {
|
|
583
|
-
const
|
|
591
|
+
const { tier } = b;
|
|
592
|
+
const staggered = tier !== void 0 && b.colors.length === 1;
|
|
593
|
+
const rows = staggered ? 2 : b.colors.length;
|
|
584
594
|
b.colors.forEach((color, i) => {
|
|
585
|
-
const row =
|
|
595
|
+
const row = staggered ? tier : i;
|
|
586
596
|
const y0 = rowEdge(row, rows, dpr);
|
|
587
597
|
ctx.fillStyle = color;
|
|
588
598
|
ctx.fillRect(b.x0, y0, b.x1 - b.x0, rowEdge(row + 1, rows, dpr) - y0);
|
|
@@ -1052,7 +1062,7 @@ function niceStep(raw) {
|
|
|
1052
1062
|
const m = raw / pow;
|
|
1053
1063
|
return (m <= 1 ? 1 : m <= 2 ? 2 : m <= 5 ? 5 : 10) * pow;
|
|
1054
1064
|
}
|
|
1055
|
-
function
|
|
1065
|
+
function placeLabels(candidates) {
|
|
1056
1066
|
const placed = [];
|
|
1057
1067
|
for (const a of candidates) {
|
|
1058
1068
|
if (placed.every(
|
|
@@ -1089,7 +1099,7 @@ function scaleMarks(boxes, x, width, measure) {
|
|
|
1089
1099
|
return {
|
|
1090
1100
|
spans: [{ x0: from, x1: to }],
|
|
1091
1101
|
ticks,
|
|
1092
|
-
labels:
|
|
1102
|
+
labels: placeLabels(positions)
|
|
1093
1103
|
};
|
|
1094
1104
|
}
|
|
1095
1105
|
function boxMarks(boxes, x, width, measure) {
|
|
@@ -1130,7 +1140,7 @@ function boxMarks(boxes, x, width, measure) {
|
|
|
1130
1140
|
return {
|
|
1131
1141
|
spans,
|
|
1132
1142
|
ticks,
|
|
1133
|
-
labels:
|
|
1143
|
+
labels: placeLabels([...ends, ...lengths, ...boundaries])
|
|
1134
1144
|
};
|
|
1135
1145
|
}
|
|
1136
1146
|
function rulerMarks(boxes, x, width, measure, referenceAxis = false) {
|
|
@@ -1698,220 +1708,6 @@ function bubbleSubgraph(graph, segmentIds) {
|
|
|
1698
1708
|
referencePath: graph.referencePath
|
|
1699
1709
|
};
|
|
1700
1710
|
}
|
|
1701
|
-
|
|
1702
|
-
// src/labelLayout.ts
|
|
1703
|
-
var HALO_FACTOR = 3.4;
|
|
1704
|
-
var LEGEND_INSET_PX = 6;
|
|
1705
|
-
var ROUTE_STACK = 8;
|
|
1706
|
-
var GENE_PIN_DROP_PX = 18;
|
|
1707
|
-
var GENE_STACK = 2;
|
|
1708
|
-
var TOPMOST_BASELINE = LABEL_PX + LABEL_PAD;
|
|
1709
|
-
function geneCoverageNote(pin) {
|
|
1710
|
-
const length = pin.gene.end - pin.gene.start;
|
|
1711
|
-
if (pin.covered >= 0.98) {
|
|
1712
|
-
return void 0;
|
|
1713
|
-
}
|
|
1714
|
-
const [per, unit] = length >= 1e6 ? [1e6, "Mb"] : length >= 1e3 ? [1e3, "kb"] : [1, "bp"];
|
|
1715
|
-
const amount = (bp) => +(bp / per).toFixed(per === 1 ? 0 : 1);
|
|
1716
|
-
return `${amount(pin.covered * length)} of ${amount(length)} ${unit}`;
|
|
1717
|
-
}
|
|
1718
|
-
function byExtent(items, extent) {
|
|
1719
|
-
return [...items].sort((a, b) => extent(b) - extent(a));
|
|
1720
|
-
}
|
|
1721
|
-
function layoutLabels(m) {
|
|
1722
|
-
const { paneWidth: width, canvasHeight: height, translateX, translateY } = m;
|
|
1723
|
-
const { scaleX, scaleY } = m.axisScale;
|
|
1724
|
-
const frame = { width, height };
|
|
1725
|
-
const screen = (p) => ({
|
|
1726
|
-
x: p.x * scaleX + translateX,
|
|
1727
|
-
y: p.y * scaleY + translateY
|
|
1728
|
-
});
|
|
1729
|
-
const reserved = m.drawnRowLabels.map(
|
|
1730
|
-
({ label, y }) => rowLabelBox(label, y * scaleY + translateY)
|
|
1731
|
-
);
|
|
1732
|
-
const stripPx = m.referenceStripZonePx ?? 0;
|
|
1733
|
-
if (stripPx > 0) {
|
|
1734
|
-
reserved.push({ x0: 0, x1: width, y0: 0, y1: stripPx });
|
|
1735
|
-
}
|
|
1736
|
-
if (m.legendSize.width > 0) {
|
|
1737
|
-
reserved.push({
|
|
1738
|
-
x0: width - LEGEND_INSET_PX - m.legendSize.width,
|
|
1739
|
-
x1: width,
|
|
1740
|
-
y0: 0,
|
|
1741
|
-
y1: stripPx + LEGEND_INSET_PX + m.legendSize.height
|
|
1742
|
-
});
|
|
1743
|
-
}
|
|
1744
|
-
if (m.poppedFrom) {
|
|
1745
|
-
reserved.push({
|
|
1746
|
-
x0: 0,
|
|
1747
|
-
x1: 60 + m.poppedFrom.label.length * LABEL_CHAR_PX * 1.2,
|
|
1748
|
-
y0: 0,
|
|
1749
|
-
y1: 44
|
|
1750
|
-
});
|
|
1751
|
-
}
|
|
1752
|
-
const take = occupancy(frame, reserved);
|
|
1753
|
-
const onRows = m.drawnRowLabels.length > 0;
|
|
1754
|
-
const rowsTop = onRows ? Math.min(...m.drawnRowLabels.map((r) => r.y)) : Infinity;
|
|
1755
|
-
const halo = m.contigThickness * HALO_FACTOR;
|
|
1756
|
-
const byBubble = byExtent(m.bubbleHalos, (h) => h.members);
|
|
1757
|
-
const bubbles = placeLabels(
|
|
1758
|
-
byBubble.map((h) => {
|
|
1759
|
-
const { x, y } = screen({
|
|
1760
|
-
x: h.labelAt.x,
|
|
1761
|
-
y: Math.min(h.labelAt.y, rowsTop)
|
|
1762
|
-
});
|
|
1763
|
-
const baseline = y - halo / 2 - 6;
|
|
1764
|
-
return {
|
|
1765
|
-
item: h,
|
|
1766
|
-
x,
|
|
1767
|
-
y: onRows ? Math.max(baseline, TOPMOST_BASELINE) : baseline,
|
|
1768
|
-
text: h.label
|
|
1769
|
-
};
|
|
1770
|
-
}),
|
|
1771
|
-
frame,
|
|
1772
|
-
take
|
|
1773
|
-
);
|
|
1774
|
-
const genes = placeLabels(
|
|
1775
|
-
byExtent(m.genePins, (pin) => pin.gene.end - pin.gene.start).map((pin) => {
|
|
1776
|
-
const { x, y } = screen(pin.at);
|
|
1777
|
-
const note = geneCoverageNote(pin);
|
|
1778
|
-
return {
|
|
1779
|
-
item: pin,
|
|
1780
|
-
x,
|
|
1781
|
-
y: y + m.contigThickness + GENE_PIN_DROP_PX,
|
|
1782
|
-
text: note ? `${pin.gene.name} \xB7 ${note}` : pin.gene.name,
|
|
1783
|
-
fallback: note ? `${pin.gene.name} \u2026` : void 0,
|
|
1784
|
-
stack: GENE_STACK
|
|
1785
|
-
};
|
|
1786
|
-
}),
|
|
1787
|
-
frame,
|
|
1788
|
-
take
|
|
1789
|
-
);
|
|
1790
|
-
const sizeCandidates = m.nodePositions && m.labelsNodeSizes ? sizeLabelCandidates({
|
|
1791
|
-
nodePositions: m.nodePositions,
|
|
1792
|
-
nodeLengths: m.nodeLengths,
|
|
1793
|
-
deletions: m.showDeletionEdges ? m.deletions : [],
|
|
1794
|
-
alleleDeletions: m.alleleDeletions,
|
|
1795
|
-
axis: m.axisScale,
|
|
1796
|
-
translateX,
|
|
1797
|
-
translateY,
|
|
1798
|
-
width,
|
|
1799
|
-
height,
|
|
1800
|
-
version: m.positionsVersion
|
|
1801
|
-
}) : { deletions: [], nodes: [] };
|
|
1802
|
-
const deletionLabels = placeSizeLabels(sizeCandidates.deletions, take);
|
|
1803
|
-
const routes2 = placeLabels(
|
|
1804
|
-
byBubble.flatMap(
|
|
1805
|
-
(h) => h.routes.map((route) => {
|
|
1806
|
-
const { x, y } = screen(route.at);
|
|
1807
|
-
return {
|
|
1808
|
-
item: { halo: h, route },
|
|
1809
|
-
x,
|
|
1810
|
-
y: y + 4,
|
|
1811
|
-
text: route.text,
|
|
1812
|
-
stack: ROUTE_STACK
|
|
1813
|
-
};
|
|
1814
|
-
})
|
|
1815
|
-
),
|
|
1816
|
-
frame,
|
|
1817
|
-
take
|
|
1818
|
-
);
|
|
1819
|
-
const nodeLabels = placeSizeLabels(
|
|
1820
|
-
sizeCandidates.nodes,
|
|
1821
|
-
take,
|
|
1822
|
-
nodeLabelBudget(width, height)
|
|
1823
|
-
);
|
|
1824
|
-
return {
|
|
1825
|
-
bubbles,
|
|
1826
|
-
genes,
|
|
1827
|
-
routes: routes2,
|
|
1828
|
-
sizes: [...deletionLabels, ...nodeLabels]
|
|
1829
|
-
};
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
// src/genes/genePins.ts
|
|
1833
|
-
function genePins(graph, genes, positions) {
|
|
1834
|
-
const byRefName = /* @__PURE__ */ new Map();
|
|
1835
|
-
for (const node of graph.nodes) {
|
|
1836
|
-
if (isBackbone(node)) {
|
|
1837
|
-
const { refName } = node.stable;
|
|
1838
|
-
if (positions[node.id]?.length) {
|
|
1839
|
-
const entry = byRefName.get(refName) ?? byRefName.set(refName, { nodes: [], reach: 0 }).get(refName);
|
|
1840
|
-
entry.nodes.push(node);
|
|
1841
|
-
entry.reach = Math.max(entry.reach, node.length);
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
for (const { nodes } of byRefName.values()) {
|
|
1846
|
-
nodes.sort((a, b) => a.stable.start - b.stable.start);
|
|
1847
|
-
}
|
|
1848
|
-
const pins = [];
|
|
1849
|
-
for (const gene of genes) {
|
|
1850
|
-
const backbone = byRefName.get(gene.refName);
|
|
1851
|
-
if (!backbone) {
|
|
1852
|
-
continue;
|
|
1853
|
-
}
|
|
1854
|
-
const parts = [];
|
|
1855
|
-
const exonsByNode = [];
|
|
1856
|
-
let at;
|
|
1857
|
-
let atDistance = Infinity;
|
|
1858
|
-
let covered = 0;
|
|
1859
|
-
const mid = (gene.start + gene.end) / 2;
|
|
1860
|
-
const { nodes, reach } = backbone;
|
|
1861
|
-
for (let i = firstNodeAtOrAfter(nodes, gene.start - reach); i < nodes.length && nodes[i].stable.start < gene.end; i++) {
|
|
1862
|
-
const node = nodes[i];
|
|
1863
|
-
const nodeStart = node.stable.start;
|
|
1864
|
-
const nodeEnd = nodeStart + node.length;
|
|
1865
|
-
if (nodeEnd <= gene.start) {
|
|
1866
|
-
continue;
|
|
1867
|
-
}
|
|
1868
|
-
const line = positions[node.id];
|
|
1869
|
-
covered += Math.min(nodeEnd, gene.end) - Math.max(nodeStart, gene.start);
|
|
1870
|
-
const nodeParts = [];
|
|
1871
|
-
for (const exon of gene.exons) {
|
|
1872
|
-
const a = Math.max(exon.start, nodeStart);
|
|
1873
|
-
const b = Math.min(exon.end, nodeEnd);
|
|
1874
|
-
if (b <= a) {
|
|
1875
|
-
continue;
|
|
1876
|
-
}
|
|
1877
|
-
const stretch = polylineSlice(
|
|
1878
|
-
line,
|
|
1879
|
-
(a - nodeStart) / node.length,
|
|
1880
|
-
(b - nodeStart) / node.length
|
|
1881
|
-
);
|
|
1882
|
-
if (stretch.length === 1) {
|
|
1883
|
-
stretch.push({ ...stretch[0] });
|
|
1884
|
-
}
|
|
1885
|
-
nodeParts.push(svgPath(stretch));
|
|
1886
|
-
}
|
|
1887
|
-
if (nodeParts.length > 0) {
|
|
1888
|
-
parts.push(...nodeParts);
|
|
1889
|
-
exonsByNode.push({ nodeId: node.id, d: nodeParts.join("") });
|
|
1890
|
-
}
|
|
1891
|
-
const pinBp = Math.min(Math.max(mid, nodeStart), nodeEnd);
|
|
1892
|
-
const distance = Math.abs(pinBp - mid);
|
|
1893
|
-
if (distance < atDistance) {
|
|
1894
|
-
atDistance = distance;
|
|
1895
|
-
const [p] = polylineSlice(
|
|
1896
|
-
line,
|
|
1897
|
-
(pinBp - nodeStart) / node.length,
|
|
1898
|
-
(pinBp - nodeStart) / node.length
|
|
1899
|
-
);
|
|
1900
|
-
at = p;
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
if (at) {
|
|
1904
|
-
pins.push({
|
|
1905
|
-
gene,
|
|
1906
|
-
exons: parts.join(""),
|
|
1907
|
-
exonsByNode,
|
|
1908
|
-
at,
|
|
1909
|
-
covered: covered / (gene.end - gene.start)
|
|
1910
|
-
});
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
|
-
return pins;
|
|
1914
|
-
}
|
|
1915
1711
|
export {
|
|
1916
1712
|
BUBBLE_KIND_COLORS,
|
|
1917
1713
|
BUBBLE_KIND_NAMES,
|
|
@@ -1924,6 +1720,7 @@ export {
|
|
|
1924
1720
|
FIT_PADDING,
|
|
1925
1721
|
FORCE_LAYOUT_LABEL,
|
|
1926
1722
|
HALO_FACTOR,
|
|
1723
|
+
HPRC_GBZ,
|
|
1927
1724
|
LABEL_CHAR_PX,
|
|
1928
1725
|
LABEL_PAD,
|
|
1929
1726
|
LABEL_PX,
|
|
@@ -1949,6 +1746,7 @@ export {
|
|
|
1949
1746
|
computeReferenceRamp,
|
|
1950
1747
|
connectorAt,
|
|
1951
1748
|
contains,
|
|
1749
|
+
cutGbzRegion,
|
|
1952
1750
|
cutWindowGFA,
|
|
1953
1751
|
deletionEdges,
|
|
1954
1752
|
drawReferenceStrip,
|
|
@@ -1973,6 +1771,9 @@ export {
|
|
|
1973
1771
|
formatBp,
|
|
1974
1772
|
geneCoverageNote,
|
|
1975
1773
|
genePins,
|
|
1774
|
+
genesFromBed,
|
|
1775
|
+
genesFromGff3Lines,
|
|
1776
|
+
genesFromText,
|
|
1976
1777
|
getDpr,
|
|
1977
1778
|
graphBackbone,
|
|
1978
1779
|
haplotypeWanted,
|
|
@@ -1986,10 +1787,12 @@ export {
|
|
|
1986
1787
|
modeUsesLayoutEngine,
|
|
1987
1788
|
nodeInk,
|
|
1988
1789
|
nodeWidthPx,
|
|
1790
|
+
openGbz,
|
|
1989
1791
|
padded,
|
|
1990
1792
|
panSNContig,
|
|
1991
1793
|
panSNHaplotype,
|
|
1992
1794
|
panSNSample,
|
|
1795
|
+
parseRegion,
|
|
1993
1796
|
pathColorsLegible,
|
|
1994
1797
|
pathLegend,
|
|
1995
1798
|
rangeText,
|
|
@@ -2014,10 +1817,12 @@ export {
|
|
|
2014
1817
|
tubeMapNodeAt,
|
|
2015
1818
|
tubeMapPicture,
|
|
2016
1819
|
tubeX,
|
|
1820
|
+
version,
|
|
2017
1821
|
viewportOf,
|
|
2018
1822
|
walkHighlight,
|
|
2019
1823
|
walkKey,
|
|
2020
1824
|
walkLift,
|
|
1825
|
+
walkPosition,
|
|
2021
1826
|
walkRows,
|
|
2022
1827
|
walkRowsExtent,
|
|
2023
1828
|
warpX,
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { BubbleSpread } from '../bubbleSpreads.js';
|
|
2
2
|
import type { FigureOptions } from '../figure.js';
|
|
3
|
+
import type { GbzSource } from '../gbzCut.js';
|
|
3
4
|
import type { LayoutModeValue } from '../layoutModes.js';
|
|
4
5
|
import type { WalkLayer } from '../walkEncoding.js';
|
|
5
|
-
export interface FigureSpec extends Omit<FigureOptions, 'walks' | 'region'> {
|
|
6
|
+
export interface FigureSpec extends Omit<FigureOptions, 'walks' | 'region' | 'genes' | 'spec'> {
|
|
6
7
|
gfa?: string;
|
|
7
|
-
gbz?:
|
|
8
|
-
db: string;
|
|
9
|
-
index?: string;
|
|
10
|
-
region: string;
|
|
11
|
-
haplotypes?: string[];
|
|
12
|
-
referenceSample?: string;
|
|
13
|
-
};
|
|
8
|
+
gbz?: GbzSource;
|
|
14
9
|
region?: string;
|
|
15
10
|
referencePath?: string;
|
|
16
11
|
layout?: LayoutModeValue;
|
|
17
12
|
quality?: number;
|
|
18
13
|
bubbleSpread?: BubbleSpread;
|
|
19
14
|
walks?: (string | WalkLayer)[];
|
|
15
|
+
genes?: {
|
|
16
|
+
file: string;
|
|
17
|
+
index?: string;
|
|
18
|
+
format?: 'gff3' | 'bed';
|
|
19
|
+
};
|
|
20
20
|
}
|