@jbrowse/graph-walks 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +93 -2
- package/esm/index.d.ts +7 -0
- package/esm/index.js +4 -0
- package/esm/util.d.ts +39 -0
- package/esm/util.js +34 -0
- package/esm/walkChunk.d.ts +65 -0
- package/esm/walkChunk.js +609 -0
- package/esm/walkReader.d.ts +75 -0
- package/esm/walkReader.js +218 -0
- package/esm/walkRows.d.ts +160 -0
- package/esm/walkRows.js +480 -0
- package/package.json +57 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Colin Diesh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,94 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @jbrowse/graph-walks
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cuts a window out of a pangenome graph stored as
|
|
4
|
+
[gfa-to-tabix](https://github.com/GMOD/gfa-to-tabix) walk files, by range
|
|
5
|
+
requests.
|
|
6
|
+
|
|
7
|
+
- **In:** a `gfa-to-tabix --walks` file, `<prefix>.<sample>.walks.bed.gz` with
|
|
8
|
+
its `.tbi`, and a region on its reference
|
|
9
|
+
- **Out:** the reference over the region, every haplotype's walk through it, and
|
|
10
|
+
the nodes and links they visit, as typed arrays
|
|
11
|
+
- Reads walk format 2, which gfa-to-tabix writes from 1.0. The reader refuses a
|
|
12
|
+
format 1 file, from gfa-to-tabix 0.12 or earlier, with a `WalkFormatError`
|
|
13
|
+
asking for a rebuild
|
|
14
|
+
- Has no runtime dependencies: it reads through any object with
|
|
15
|
+
[@gmod/tabix](https://github.com/GMOD/tabix-js)'s `TabixIndexedFile` methods
|
|
16
|
+
|
|
17
|
+
The JBrowse graph plugin, BandageJS and the sequenceTubeMap fork read walk files
|
|
18
|
+
through it.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @jbrowse/graph-walks @gmod/tabix
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { TabixIndexedFile } from '@gmod/tabix'
|
|
30
|
+
import {
|
|
31
|
+
WalkReader,
|
|
32
|
+
panSNHaplotype,
|
|
33
|
+
walkNameFilter,
|
|
34
|
+
} from '@jbrowse/graph-walks'
|
|
35
|
+
import { RemoteFile } from 'generic-filehandle2'
|
|
36
|
+
|
|
37
|
+
const url = 'https://example.org/hprc-v2.1-mc-grch38.GRCh38.walks.bed.gz'
|
|
38
|
+
const reader = new WalkReader(
|
|
39
|
+
new TabixIndexedFile({
|
|
40
|
+
filehandle: new RemoteFile(url),
|
|
41
|
+
tbiFilehandle: new RemoteFile(`${url}.tbi`),
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const refName = 'GRCh38#0#chr6'
|
|
46
|
+
const { graph, kept, fragments } = await reader.cut(
|
|
47
|
+
refName,
|
|
48
|
+
{ start: 160_614_798, end: 160_647_758 },
|
|
49
|
+
{ keep: walkNameFilter(['HG00097', 'HG00133'], panSNHaplotype(refName)) },
|
|
50
|
+
)
|
|
51
|
+
const tables = graph.tables(kept, fragments)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `tables` lists the nodes with their lengths and reference positions, the links
|
|
55
|
+
between them, and each walk's steps. `graphTablesGFA` in
|
|
56
|
+
[@jbrowse/bandage-core](https://github.com/GMOD/bandage-core) writes it as GFA
|
|
57
|
+
- `reader.header()` lists the reference sample and every haplotype with rows,
|
|
58
|
+
read from the walk file's first block
|
|
59
|
+
- `reader.referenceSequences()` lists the sequences a region can be on, in full
|
|
60
|
+
PanSN: `GRCh38#0#chr6`
|
|
61
|
+
|
|
62
|
+
## Cut options
|
|
63
|
+
|
|
64
|
+
| Option | Effect |
|
|
65
|
+
| ------------ | ------------------------------------------------------------------------- |
|
|
66
|
+
| `keep` | Which walks to decode, by `sample#haplotype#contig`; every one by default |
|
|
67
|
+
| `context` | bp of reference kept past each end of the region; default 1000 |
|
|
68
|
+
| `stepBudget` | The most steps a cut keeps; no limit by default |
|
|
69
|
+
| `byteBudget` | The most compressed bytes a cut fetches; no limit by default |
|
|
70
|
+
| `links` | `false` lists no links, for a caller that draws no edges |
|
|
71
|
+
| `sequences` | Keep each node's bases in `graph.sequences`, from a `--sequences` file |
|
|
72
|
+
| `signal` | An `AbortSignal` that stops the reads |
|
|
73
|
+
|
|
74
|
+
A cut over a budget throws a `RegionTooLargeError`. Its `fitsBp` holds about how
|
|
75
|
+
wide a window would fit, and JBrowse shows it as a zoom-in notice.
|
|
76
|
+
|
|
77
|
+
## How a cut works
|
|
78
|
+
|
|
79
|
+
- A chunk holds the reference's path through it once, each way a haplotype
|
|
80
|
+
leaves that path once, and every other path's piece as the list of ways it
|
|
81
|
+
takes; gfa-to-tabix's
|
|
82
|
+
[walks-format.md](https://github.com/GMOD/gfa-to-tabix/blob/main/docs/walks-format.md)
|
|
83
|
+
describes the rows
|
|
84
|
+
- A cut reads every chunk under the region, and the chunks before it as far as
|
|
85
|
+
the header's `maxnode` reaches, since a node is filed under the chunk holding
|
|
86
|
+
its start, and decodes each chunk once
|
|
87
|
+
- A haplotype that leaves the reference near the region is followed to where it
|
|
88
|
+
rejoins, and the reference is cut out as far as those rejoins reach
|
|
89
|
+
- A walk the read cuts off goes on in the chunk its row names (`pv:`/`nx:`
|
|
90
|
+
tags); the cut reads on to that chunk, in up to three rounds
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, walkNameFilter, } from './walkRows.ts';
|
|
2
|
+
export type { BudgetNames, WalkChunk, WalkFragment, WalkHeader, WalkNode, WalkRow, WalkRun, WalkWindow, } from './walkRows.ts';
|
|
3
|
+
export { WalkFormatError, WalkReader } from './walkReader.ts';
|
|
4
|
+
export { referenceNodes } from './walkChunk.ts';
|
|
5
|
+
export type { TabixSource, WalkCutOptions, WalkReaderHeader, } from './walkReader.ts';
|
|
6
|
+
export { RegionTooLargeError, bpString, panSNHaplotype, panSNMatchesPrefix, } from './util.ts';
|
|
7
|
+
export type { GraphTables } from './util.ts';
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, walkNameFilter, } from "./walkRows.js";
|
|
2
|
+
export { WalkFormatError, WalkReader } from "./walkReader.js";
|
|
3
|
+
export { referenceNodes } from "./walkChunk.js";
|
|
4
|
+
export { RegionTooLargeError, bpString, panSNHaplotype, panSNMatchesPrefix, } from "./util.js";
|
package/esm/util.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface GraphTables {
|
|
2
|
+
nodes: {
|
|
3
|
+
names: string[];
|
|
4
|
+
lengths: Int32Array;
|
|
5
|
+
refs: Int32Array;
|
|
6
|
+
starts: Float64Array;
|
|
7
|
+
ranks: Int32Array;
|
|
8
|
+
refNames: string[];
|
|
9
|
+
};
|
|
10
|
+
links: {
|
|
11
|
+
from: Int32Array;
|
|
12
|
+
to: Int32Array;
|
|
13
|
+
strands: Uint8Array;
|
|
14
|
+
};
|
|
15
|
+
walks: {
|
|
16
|
+
names: string[];
|
|
17
|
+
starts: Float64Array;
|
|
18
|
+
ends: Float64Array;
|
|
19
|
+
offsets: Int32Array;
|
|
20
|
+
steps: Int32Array;
|
|
21
|
+
reversed: Uint8Array;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A window too large to cut. `regionTooLarge` and the name are what JBrowse
|
|
26
|
+
* and @jbrowse/bandage-core read a zoom-in notice by; `fitsBp` is about how
|
|
27
|
+
* wide a window would fit.
|
|
28
|
+
*/
|
|
29
|
+
export declare class RegionTooLargeError extends Error {
|
|
30
|
+
readonly regionTooLarge = true;
|
|
31
|
+
name: string;
|
|
32
|
+
readonly limit: number;
|
|
33
|
+
readonly windowBp: number;
|
|
34
|
+
readonly fitsBp: number;
|
|
35
|
+
constructor(message: string, limit: number, windowBp: number, fitsBp: number);
|
|
36
|
+
}
|
|
37
|
+
export declare function panSNHaplotype(refName: string): string | undefined;
|
|
38
|
+
export declare function panSNMatchesPrefix(refName: string, prefix: string | undefined): boolean;
|
|
39
|
+
export declare function bpString(bp: number): string;
|
package/esm/util.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A window too large to cut. `regionTooLarge` and the name are what JBrowse
|
|
3
|
+
* and @jbrowse/bandage-core read a zoom-in notice by; `fitsBp` is about how
|
|
4
|
+
* wide a window would fit.
|
|
5
|
+
*/
|
|
6
|
+
export class RegionTooLargeError extends Error {
|
|
7
|
+
constructor(message, limit, windowBp, fitsBp) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.regionTooLarge = true;
|
|
10
|
+
this.name = 'NodeLimitError';
|
|
11
|
+
this.limit = limit;
|
|
12
|
+
this.windowBp = windowBp;
|
|
13
|
+
this.fitsBp = fitsBp;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const SEP = '#';
|
|
17
|
+
export function panSNHaplotype(refName) {
|
|
18
|
+
const parts = refName.split(SEP);
|
|
19
|
+
return parts.length >= 3 ? `${parts[0]}${SEP}${parts[1]}` : undefined;
|
|
20
|
+
}
|
|
21
|
+
export function panSNMatchesPrefix(refName, prefix) {
|
|
22
|
+
return (prefix !== undefined &&
|
|
23
|
+
(refName === prefix || refName.startsWith(prefix + SEP)));
|
|
24
|
+
}
|
|
25
|
+
const precise = (n) => Number.parseFloat(n.toPrecision(3));
|
|
26
|
+
// `1.5Kbp`, `12Mbp`, `850bp`, as JBrowse writes a span
|
|
27
|
+
export function bpString(bp) {
|
|
28
|
+
const mb = precise(bp / 1000000);
|
|
29
|
+
return mb >= 1
|
|
30
|
+
? `${mb.toLocaleString('en-US')}Mbp`
|
|
31
|
+
: bp >= 1000
|
|
32
|
+
? `${precise(bp / 1000).toLocaleString('en-US')}Kbp`
|
|
33
|
+
: `${Math.floor(bp)}bp`;
|
|
34
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { WalkRow } from './walkRows.ts';
|
|
2
|
+
export interface Route {
|
|
3
|
+
entry: number;
|
|
4
|
+
interior: number[];
|
|
5
|
+
exit: number;
|
|
6
|
+
}
|
|
7
|
+
export interface PlacedNode {
|
|
8
|
+
refName: string;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
rank: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function canonicalLink(a: number, b: number): [number, number];
|
|
14
|
+
export declare class RefPath {
|
|
15
|
+
readonly handles: number[];
|
|
16
|
+
readonly before: number[];
|
|
17
|
+
readonly occurrences: Map<number, number[]>;
|
|
18
|
+
add(handles: number[], lengths: number[]): void;
|
|
19
|
+
bp(i: number): number;
|
|
20
|
+
first(handle: number): number | undefined;
|
|
21
|
+
nearest(handle: number, i: number): number | undefined;
|
|
22
|
+
reach(i: number, h: number, target: number): number | undefined;
|
|
23
|
+
handleAt(i: number, forward: boolean): number;
|
|
24
|
+
}
|
|
25
|
+
export declare class WalkRowError extends Error {
|
|
26
|
+
}
|
|
27
|
+
export interface Chunk {
|
|
28
|
+
path: RefPath;
|
|
29
|
+
routes: Route[];
|
|
30
|
+
walks: {
|
|
31
|
+
cols: string[];
|
|
32
|
+
choices: number[];
|
|
33
|
+
}[];
|
|
34
|
+
lengths: Map<number, number>;
|
|
35
|
+
}
|
|
36
|
+
export interface DecodedChunk {
|
|
37
|
+
sequences: Map<number, string>;
|
|
38
|
+
rows: WalkRow[];
|
|
39
|
+
nodes: Map<number, PlacedNode>;
|
|
40
|
+
ends: {
|
|
41
|
+
name: string;
|
|
42
|
+
fragStart: number;
|
|
43
|
+
piece: number;
|
|
44
|
+
first: number;
|
|
45
|
+
last: number;
|
|
46
|
+
}[];
|
|
47
|
+
chunk: Chunk;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Decodes one chunk's rows, each split on tabs. `keep` names the walk rows
|
|
51
|
+
* whose steps a cut wants, undefined for every one.
|
|
52
|
+
*/
|
|
53
|
+
export declare function decodeChunk(rows: string[][], keep: ((name: string) => boolean) | undefined, withSequences?: boolean): DecodedChunk;
|
|
54
|
+
/**
|
|
55
|
+
* The positions of the cut's nodes off R, and the links among the cut's
|
|
56
|
+
* nodes, from the chunks a cut read: `kept` holds the node ids the cut draws.
|
|
57
|
+
* A node off R takes its position from the path whose name sorts first among
|
|
58
|
+
* those visiting it in the chunks read, at its earliest offset, as format 1's
|
|
59
|
+
* node rows had it. Links are R's and the routes', both ends in the cut.
|
|
60
|
+
*/
|
|
61
|
+
export declare function placeAndLink(chunks: Chunk[], kept: Set<number>, place: (id: number, node: PlacedNode) => void, link: (a: number, b: number) => void): void;
|
|
62
|
+
export declare function referenceNodes(rows: string[][]): (PlacedNode & {
|
|
63
|
+
id: number;
|
|
64
|
+
})[];
|
|
65
|
+
export declare function pieceLinks(ends: DecodedChunk['ends']): [number, number][];
|