@jbrowse/bandage-core 0.1.19 → 4.0.24
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/README.md +9 -3
- package/dist/chunks/chunk-4AOHEU76.js +4982 -0
- package/dist/cli/figure.js +152 -0
- package/dist/index.js +201 -4640
- package/dist/types/cli/figure.d.ts +20 -0
- package/dist/types/facetGrid.d.ts +10 -0
- package/dist/types/figure.d.ts +24 -0
- package/dist/types/genes/genePins.d.ts +4 -0
- package/dist/types/index.d.ts +8 -5
- package/dist/types/labelLayout.d.ts +1 -0
- package/dist/types/overlayLabels.d.ts +1 -0
- package/dist/types/referenceStrip.d.ts +15 -6
- package/dist/types/renderer/svgCanvas.d.ts +4 -0
- package/dist/types/walkEncoding.d.ts +5 -1
- package/dist/types/walkKey.d.ts +1 -0
- package/package.json +4 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
cutWindowGFA,
|
|
4
|
+
figureSvg,
|
|
5
|
+
forceLayout,
|
|
6
|
+
haplotypeWanted,
|
|
7
|
+
layoutModeByValue,
|
|
8
|
+
loadBandage,
|
|
9
|
+
loadGraph,
|
|
10
|
+
referencePathQuery,
|
|
11
|
+
referenceSamplesOf,
|
|
12
|
+
resolveReferenceSample
|
|
13
|
+
} from "../chunks/chunk-4AOHEU76.js";
|
|
14
|
+
|
|
15
|
+
// src/cli/figure.ts
|
|
16
|
+
import { open, readFile, writeFile } from "node:fs/promises";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { parseArgs } from "node:util";
|
|
19
|
+
import { gunzipSync } from "node:zlib";
|
|
20
|
+
import { GBZBase } from "@gmod/gbz-base";
|
|
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
|
+
}
|
|
36
|
+
var isUrl = (s) => /^https?:\/\//.test(s);
|
|
37
|
+
function byteSource(location) {
|
|
38
|
+
if (isUrl(location)) {
|
|
39
|
+
return {
|
|
40
|
+
async read(length, position) {
|
|
41
|
+
const res = await fetch(location, {
|
|
42
|
+
headers: { range: `bytes=${position}-${position + length - 1}` }
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
throw new Error(`HTTP ${res.status} reading ${location}`);
|
|
46
|
+
}
|
|
47
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
48
|
+
},
|
|
49
|
+
async stat() {
|
|
50
|
+
const res = await fetch(location, { method: "HEAD" });
|
|
51
|
+
return { size: Number(res.headers.get("content-length")) };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const handle = open(location);
|
|
56
|
+
return {
|
|
57
|
+
async read(length, position) {
|
|
58
|
+
const buffer = new Uint8Array(length);
|
|
59
|
+
const { bytesRead } = await (await handle).read(buffer, 0, length, position);
|
|
60
|
+
return buffer.subarray(0, bytesRead);
|
|
61
|
+
},
|
|
62
|
+
async stat() {
|
|
63
|
+
return { size: (await (await handle).stat()).size };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function gfaText(location) {
|
|
68
|
+
const bytes = isUrl(location) ? new Uint8Array(await (await fetch(location)).arrayBuffer()) : await readFile(location);
|
|
69
|
+
const gzipped = bytes[0] === 31 && bytes[1] === 139;
|
|
70
|
+
return new TextDecoder().decode(gzipped ? gunzipSync(bytes) : bytes);
|
|
71
|
+
}
|
|
72
|
+
async function cutGbz(gbz) {
|
|
73
|
+
const { db, index } = gbz.db === "hprc" ? HPRC : gbz;
|
|
74
|
+
const base = await GBZBase.open(
|
|
75
|
+
byteSource(db),
|
|
76
|
+
index ? { haplotypeIndex: byteSource(index) } : {}
|
|
77
|
+
);
|
|
78
|
+
const samples = await referenceSamplesOf(base);
|
|
79
|
+
const sample = resolveReferenceSample({
|
|
80
|
+
configured: "",
|
|
81
|
+
anchorPrefix: gbz.referenceSample ?? samples[0] ?? "",
|
|
82
|
+
referenceSamples: samples
|
|
83
|
+
});
|
|
84
|
+
const region = parseRegion(gbz.region);
|
|
85
|
+
const query = await referencePathQuery(base, sample, region.refName);
|
|
86
|
+
if (!query) {
|
|
87
|
+
throw new Error(`${sample} has no indexed path named ${region.refName}`);
|
|
88
|
+
}
|
|
89
|
+
const wanted = gbz.haplotypes?.length ? gbz.haplotypes : void 0;
|
|
90
|
+
const text = await cutWindowGFA(base, query, region.start, region.end, {
|
|
91
|
+
context: 1e3,
|
|
92
|
+
snarls: "contained",
|
|
93
|
+
limit: WALK_LIMIT,
|
|
94
|
+
...wanted ? { keep: (name) => haplotypeWanted(name, wanted) } : {}
|
|
95
|
+
});
|
|
96
|
+
return { text, region, name: `${sample} ${gbz.region}` };
|
|
97
|
+
}
|
|
98
|
+
var engine = async (request) => {
|
|
99
|
+
const bandage = await loadBandage();
|
|
100
|
+
const start = performance.now();
|
|
101
|
+
const result = bandage.computeLayout(request.graph, request.options);
|
|
102
|
+
return { result, duration: performance.now() - start };
|
|
103
|
+
};
|
|
104
|
+
async function renderSpec(spec, base = ".") {
|
|
105
|
+
const source = spec.gbz ? await cutGbz(spec.gbz) : spec.gfa ? {
|
|
106
|
+
text: await gfaText(
|
|
107
|
+
isUrl(spec.gfa) ? spec.gfa : path.resolve(base, spec.gfa)
|
|
108
|
+
),
|
|
109
|
+
region: spec.region ? parseRegion(spec.region) : void 0,
|
|
110
|
+
name: path.basename(spec.gfa)
|
|
111
|
+
} : void 0;
|
|
112
|
+
if (!source) {
|
|
113
|
+
throw new Error("a spec names its graph as `gfa` or `gbz`");
|
|
114
|
+
}
|
|
115
|
+
const graph = loadGraph(source.text, source.name, {
|
|
116
|
+
referencePath: spec.referencePath
|
|
117
|
+
});
|
|
118
|
+
const layout = layoutModeByValue(spec.layout ?? "force").run(graph, source.region) ?? (await forceLayout(
|
|
119
|
+
graph,
|
|
120
|
+
{
|
|
121
|
+
quality: spec.quality ?? 2,
|
|
122
|
+
linearLayout: false,
|
|
123
|
+
bubbleSpread: spec.bubbleSpread ?? "auto"
|
|
124
|
+
},
|
|
125
|
+
engine
|
|
126
|
+
)).result;
|
|
127
|
+
return figureSvg(graph, layout, {
|
|
128
|
+
...spec,
|
|
129
|
+
walks: spec.walks?.map((w) => typeof w === "string" ? { walk: w } : w),
|
|
130
|
+
region: source.region,
|
|
131
|
+
metadata: JSON.stringify(spec)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async function main() {
|
|
135
|
+
const { positionals, values } = parseArgs({
|
|
136
|
+
allowPositionals: true,
|
|
137
|
+
options: { out: { type: "string", short: "o" } }
|
|
138
|
+
});
|
|
139
|
+
const file = positionals[0];
|
|
140
|
+
if (!file) {
|
|
141
|
+
console.error("usage: bandage-figure <spec.json> [-o figure.svg]");
|
|
142
|
+
process.exit(2);
|
|
143
|
+
}
|
|
144
|
+
const spec = JSON.parse(await readFile(file, "utf8"));
|
|
145
|
+
const svg = await renderSpec(spec, path.dirname(file));
|
|
146
|
+
if (values.out) {
|
|
147
|
+
await writeFile(values.out, svg);
|
|
148
|
+
} else {
|
|
149
|
+
process.stdout.write(svg);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
await main();
|