@jbrowse/bandage-core 4.0.24 → 4.0.26
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-75KVO2WV.js} +610 -40
- package/dist/cli/figure.js +129 -65
- package/dist/index.js +100 -252
- package/dist/types/cli/figure.d.ts +8 -8
- package/dist/types/figure.d.ts +8 -1
- package/dist/types/gbzCut.d.ts +33 -0
- package/dist/types/genes/geneFiles.d.ts +10 -0
- package/dist/types/index.d.ts +7 -2
- package/dist/types/labelLayout.d.ts +14 -0
- package/dist/types/pathAnchoring.d.ts +5 -1
- package/dist/types/referenceStrip.d.ts +4 -3
- package/dist/types/tubeMap/draw.d.ts +6 -1
- package/dist/types/tubeMap/nodeColors.d.ts +4 -0
- 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,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
HPRC_GBZ,
|
|
4
|
+
LAYOUT_MODE_VALUES,
|
|
5
|
+
cutGbzRegion,
|
|
6
|
+
featuresOnBackbone,
|
|
4
7
|
figureSvg,
|
|
5
8
|
forceLayout,
|
|
6
|
-
|
|
9
|
+
genesFromBed,
|
|
10
|
+
genesFromGff3Lines,
|
|
11
|
+
genesFromText,
|
|
12
|
+
graphBackbone,
|
|
7
13
|
layoutModeByValue,
|
|
8
14
|
loadBandage,
|
|
9
15
|
loadGraph,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "../chunks/chunk-4AOHEU76.js";
|
|
16
|
+
openGbz,
|
|
17
|
+
parseRegion
|
|
18
|
+
} from "../chunks/chunk-75KVO2WV.js";
|
|
14
19
|
|
|
15
20
|
// src/cli/figure.ts
|
|
16
|
-
import { open, readFile, writeFile } from "node:fs/promises";
|
|
21
|
+
import { access, open, readFile, writeFile } from "node:fs/promises";
|
|
17
22
|
import path from "node:path";
|
|
18
23
|
import { parseArgs } from "node:util";
|
|
19
24
|
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
|
-
}
|
|
25
|
+
import { TabixIndexedFile } from "@gmod/tabix";
|
|
36
26
|
var isUrl = (s) => /^https?:\/\//.test(s);
|
|
37
27
|
function byteSource(location) {
|
|
38
28
|
if (isUrl(location)) {
|
|
@@ -47,7 +37,7 @@ function byteSource(location) {
|
|
|
47
37
|
return new Uint8Array(await res.arrayBuffer());
|
|
48
38
|
},
|
|
49
39
|
async stat() {
|
|
50
|
-
const res = await
|
|
40
|
+
const res = await fetchOk(location, { method: "HEAD" });
|
|
51
41
|
return { size: Number(res.headers.get("content-length")) };
|
|
52
42
|
}
|
|
53
43
|
};
|
|
@@ -64,36 +54,47 @@ function byteSource(location) {
|
|
|
64
54
|
}
|
|
65
55
|
};
|
|
66
56
|
}
|
|
67
|
-
async function
|
|
68
|
-
const
|
|
57
|
+
async function fetchOk(location, init) {
|
|
58
|
+
const res = await fetch(location, init);
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
throw new Error(`HTTP ${res.status} reading ${location}`);
|
|
61
|
+
}
|
|
62
|
+
return res;
|
|
63
|
+
}
|
|
64
|
+
async function text(location) {
|
|
65
|
+
const bytes = isUrl(location) ? new Uint8Array(await (await fetchOk(location)).arrayBuffer()) : await readFile(location);
|
|
69
66
|
const gzipped = bytes[0] === 31 && bytes[1] === 139;
|
|
70
67
|
return new TextDecoder().decode(gzipped ? gunzipSync(bytes) : bytes);
|
|
71
68
|
}
|
|
72
|
-
async function
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
async function readGenes(genes, graph, resolve) {
|
|
70
|
+
const backbone = graphBackbone(graph);
|
|
71
|
+
if (!backbone) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const file = resolve(genes.file);
|
|
75
|
+
const format = genes.format ?? (/\.bed(\.gz)?$/i.test(genes.file) ? "bed" : "gff3");
|
|
76
|
+
if (!genes.index) {
|
|
77
|
+
return featuresOnBackbone(genesFromText(await text(file)), backbone);
|
|
78
|
+
}
|
|
79
|
+
const index = resolve(genes.index);
|
|
80
|
+
const csi = index.endsWith(".csi");
|
|
81
|
+
const tabix = new TabixIndexedFile(
|
|
82
|
+
isUrl(file) ? { url: file, ...csi ? { csiUrl: index } : { tbiUrl: index } } : { path: file, ...csi ? { csiPath: index } : { tbiPath: index } }
|
|
77
83
|
);
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
throw new Error(`${sample} has no indexed path named ${region.refName}`);
|
|
84
|
+
const indexed = new Set(await tabix.getReferenceSequenceNames());
|
|
85
|
+
const lines = [];
|
|
86
|
+
for (const contig of backbone.contigs) {
|
|
87
|
+
const refName = [contig.contig, contig.refName].find((n) => indexed.has(n));
|
|
88
|
+
if (refName !== void 0) {
|
|
89
|
+
await tabix.getLines(refName, contig.start, contig.end, {
|
|
90
|
+
lineCallback: (line) => lines.push(line)
|
|
91
|
+
});
|
|
92
|
+
}
|
|
88
93
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
limit: WALK_LIMIT,
|
|
94
|
-
...wanted ? { keep: (name) => haplotypeWanted(name, wanted) } : {}
|
|
95
|
-
});
|
|
96
|
-
return { text, region, name: `${sample} ${gbz.region}` };
|
|
94
|
+
return featuresOnBackbone(
|
|
95
|
+
format === "gff3" ? genesFromGff3Lines(lines) : genesFromBed(lines.join("\n")),
|
|
96
|
+
backbone
|
|
97
|
+
);
|
|
97
98
|
}
|
|
98
99
|
var engine = async (request) => {
|
|
99
100
|
const bandage = await loadBandage();
|
|
@@ -101,15 +102,54 @@ var engine = async (request) => {
|
|
|
101
102
|
const result = bandage.computeLayout(request.graph, request.options);
|
|
102
103
|
return { result, duration: performance.now() - start };
|
|
103
104
|
};
|
|
104
|
-
async function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
async function exists(location) {
|
|
106
|
+
try {
|
|
107
|
+
await (isUrl(location) ? fetchOk(location, { method: "HEAD" }) : access(location));
|
|
108
|
+
return true;
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function siblingIndex(db) {
|
|
114
|
+
const sibling = db.replace(/\.gbz\.db$/i, ".haplotype-index.db");
|
|
115
|
+
return sibling !== db && await exists(sibling) ? sibling : void 0;
|
|
116
|
+
}
|
|
117
|
+
var UNDRAWABLE = /* @__PURE__ */ new Set(["walkrows", "tubemap", "tubemapref"]);
|
|
118
|
+
function checkLayout(layout) {
|
|
119
|
+
if (layout === void 0) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!LAYOUT_MODE_VALUES.includes(layout)) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`unknown layout "${layout}": one of ${LAYOUT_MODE_VALUES.filter((v) => !UNDRAWABLE.has(v)).join(", ")}`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (UNDRAWABLE.has(layout)) {
|
|
128
|
+
throw new Error(`a figure cannot draw the "${layout}" layout yet`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function renderSpec(spec, base) {
|
|
132
|
+
checkLayout(spec.layout);
|
|
133
|
+
const resolve = (location) => isUrl(location) ? location : path.resolve(base, location);
|
|
134
|
+
let source;
|
|
135
|
+
if (spec.gbz) {
|
|
136
|
+
const { db, index } = spec.gbz.db === "hprc" ? HPRC_GBZ : spec.gbz;
|
|
137
|
+
const indexLocation = index ? resolve(index) : await siblingIndex(resolve(db));
|
|
138
|
+
const cut = await cutGbzRegion(
|
|
139
|
+
await openGbz(
|
|
140
|
+
byteSource(resolve(db)),
|
|
141
|
+
indexLocation ? byteSource(indexLocation) : void 0
|
|
142
|
+
),
|
|
143
|
+
spec.gbz
|
|
144
|
+
);
|
|
145
|
+
source = { ...cut, name: `${cut.sample} ${spec.gbz.region}` };
|
|
146
|
+
} else if (spec.gfa) {
|
|
147
|
+
source = {
|
|
148
|
+
text: await text(resolve(spec.gfa)),
|
|
149
|
+
name: path.basename(spec.gfa),
|
|
150
|
+
region: spec.region ? parseRegion(spec.region) : void 0
|
|
151
|
+
};
|
|
152
|
+
} else {
|
|
113
153
|
throw new Error("a spec names its graph as `gfa` or `gbz`");
|
|
114
154
|
}
|
|
115
155
|
const graph = loadGraph(source.text, source.name, {
|
|
@@ -128,17 +168,36 @@ async function renderSpec(spec, base = ".") {
|
|
|
128
168
|
...spec,
|
|
129
169
|
walks: spec.walks?.map((w) => typeof w === "string" ? { walk: w } : w),
|
|
130
170
|
region: source.region,
|
|
131
|
-
|
|
171
|
+
genes: spec.genes ? await readGenes(spec.genes, graph, resolve) : void 0,
|
|
172
|
+
spec
|
|
132
173
|
});
|
|
133
174
|
}
|
|
175
|
+
var USAGE = "usage: bandage-figure <spec.json> [-o figure.svg]";
|
|
176
|
+
function args() {
|
|
177
|
+
try {
|
|
178
|
+
return parseArgs({
|
|
179
|
+
allowPositionals: true,
|
|
180
|
+
options: {
|
|
181
|
+
out: { type: "string", short: "o" },
|
|
182
|
+
help: { type: "boolean", short: "h" }
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
} catch (e) {
|
|
186
|
+
console.error(`${e instanceof Error ? e.message : String(e)}
|
|
187
|
+
${USAGE}`);
|
|
188
|
+
process.exit(2);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
134
191
|
async function main() {
|
|
135
|
-
const { positionals, values } =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
192
|
+
const { positionals, values } = args();
|
|
193
|
+
if (values.help) {
|
|
194
|
+
process.stdout.write(`${USAGE}
|
|
195
|
+
`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
139
198
|
const file = positionals[0];
|
|
140
199
|
if (!file) {
|
|
141
|
-
console.error(
|
|
200
|
+
console.error(USAGE);
|
|
142
201
|
process.exit(2);
|
|
143
202
|
}
|
|
144
203
|
const spec = JSON.parse(await readFile(file, "utf8"));
|
|
@@ -149,4 +208,9 @@ async function main() {
|
|
|
149
208
|
process.stdout.write(svg);
|
|
150
209
|
}
|
|
151
210
|
}
|
|
152
|
-
|
|
211
|
+
try {
|
|
212
|
+
await main();
|
|
213
|
+
} catch (e) {
|
|
214
|
+
console.error(`bandage-figure: ${e instanceof Error ? e.message : String(e)}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|