@jbrowse/plugin-variants 2.13.1 → 2.14.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/ChordVariantDisplay/models/stateModelFactory.d.ts +3 -3
- package/dist/ChordVariantDisplay/models/stateModelFactory.js +3 -1
- package/dist/LinearVariantDisplay/model.d.ts +1 -1
- package/dist/StructuralVariantChordRenderer/Chord.js +7 -5
- package/dist/StructuralVariantChordRenderer/ReactComponent.d.ts +2 -2
- package/dist/StructuralVariantChordRenderer/ReactComponent.js +3 -6
- package/dist/VariantFeatureWidget/AnnotGrid.js +3 -1
- package/dist/VariantFeatureWidget/BreakendOptionDialog.js +9 -3
- package/dist/VariantFeatureWidget/BreakendPanel.js +4 -2
- package/dist/VariantFeatureWidget/VariantAnnotationTable.js +1 -1
- package/dist/VariantFeatureWidget/VariantFeatureWidget.js +6 -6
- package/dist/VariantFeatureWidget/VariantSampleGrid.js +7 -3
- package/dist/VcfAdapter/VcfAdapter.d.ts +11 -7
- package/dist/VcfAdapter/VcfAdapter.js +69 -50
- package/dist/VcfFeature/index.d.ts +1 -1
- package/dist/VcfFeature/index.js +3 -6
- package/dist/VcfFeature/util.d.ts +1 -1
- package/dist/VcfFeature/util.js +15 -13
- package/dist/VcfTabixAdapter/VcfTabixAdapter.js +1 -1
- package/dist/extensionPoints.js +1 -1
- package/esm/ChordVariantDisplay/models/stateModelFactory.d.ts +3 -3
- package/esm/ChordVariantDisplay/models/stateModelFactory.js +3 -1
- package/esm/LinearVariantDisplay/model.d.ts +1 -1
- package/esm/StructuralVariantChordRenderer/Chord.js +7 -5
- package/esm/StructuralVariantChordRenderer/ReactComponent.d.ts +2 -2
- package/esm/StructuralVariantChordRenderer/ReactComponent.js +3 -6
- package/esm/VariantFeatureWidget/AnnotGrid.js +3 -1
- package/esm/VariantFeatureWidget/BreakendOptionDialog.js +9 -3
- package/esm/VariantFeatureWidget/BreakendPanel.js +4 -2
- package/esm/VariantFeatureWidget/VariantAnnotationTable.js +1 -1
- package/esm/VariantFeatureWidget/VariantFeatureWidget.js +6 -6
- package/esm/VariantFeatureWidget/VariantSampleGrid.js +7 -3
- package/esm/VcfAdapter/VcfAdapter.d.ts +11 -7
- package/esm/VcfAdapter/VcfAdapter.js +69 -50
- package/esm/VcfFeature/index.d.ts +1 -1
- package/esm/VcfFeature/index.js +3 -6
- package/esm/VcfFeature/util.d.ts +1 -1
- package/esm/VcfFeature/util.js +15 -13
- package/esm/VcfTabixAdapter/VcfTabixAdapter.js +1 -1
- package/esm/extensionPoints.js +1 -1
- package/package.json +3 -3
|
@@ -40,7 +40,9 @@ const stateModelFactory = (configSchema) => {
|
|
|
40
40
|
radius: view.radiusPx,
|
|
41
41
|
blockDefinitions: self.blockDefinitions,
|
|
42
42
|
config: self.configuration.renderer,
|
|
43
|
-
onChordClick: (arg) =>
|
|
43
|
+
onChordClick: (arg) => {
|
|
44
|
+
self.onChordClick(arg);
|
|
45
|
+
},
|
|
44
46
|
};
|
|
45
47
|
},
|
|
46
48
|
}));
|
|
@@ -139,7 +139,7 @@ export default function stateModelFactory(configSchema: AnyConfigurationSchemaTy
|
|
|
139
139
|
error: unknown;
|
|
140
140
|
message: string | undefined;
|
|
141
141
|
}, import("mobx-state-tree")._NotCustomized, import("mobx-state-tree")._NotCustomized>>;
|
|
142
|
-
onHorizontalScroll?:
|
|
142
|
+
onHorizontalScroll?: () => void;
|
|
143
143
|
blockState?: Record<string, any>;
|
|
144
144
|
}>;
|
|
145
145
|
readonly DisplayBlurb: import("react").FC<{
|
|
@@ -29,6 +29,7 @@ const Chord = observer(function Chord({ feature, blocksForRefs, radius, config,
|
|
|
29
29
|
let endBlock;
|
|
30
30
|
const alt = (_a = feature.get('ALT')) === null || _a === void 0 ? void 0 : _a[0];
|
|
31
31
|
const bnd = alt && parseBreakend(alt);
|
|
32
|
+
const startPos = feature.get('start');
|
|
32
33
|
if (bnd) {
|
|
33
34
|
// VCF BND
|
|
34
35
|
const matePosition = bnd.MatePosition.split(':');
|
|
@@ -39,7 +40,7 @@ const Chord = observer(function Chord({ feature, blocksForRefs, radius, config,
|
|
|
39
40
|
// VCF TRA
|
|
40
41
|
const chr2 = (_c = (_b = feature.get('INFO')) === null || _b === void 0 ? void 0 : _b.CHR2) === null || _c === void 0 ? void 0 : _c[0];
|
|
41
42
|
const end = (_e = (_d = feature.get('INFO')) === null || _d === void 0 ? void 0 : _d.END) === null || _e === void 0 ? void 0 : _e[0];
|
|
42
|
-
endPosition = parseInt(end, 10);
|
|
43
|
+
endPosition = Number.parseInt(end, 10);
|
|
43
44
|
endBlock = blocksForRefs[chr2];
|
|
44
45
|
}
|
|
45
46
|
else if (svType === 'mate') {
|
|
@@ -49,8 +50,11 @@ const Chord = observer(function Chord({ feature, blocksForRefs, radius, config,
|
|
|
49
50
|
endPosition = mate.start;
|
|
50
51
|
endBlock = blocksForRefs[chr2];
|
|
51
52
|
}
|
|
53
|
+
else {
|
|
54
|
+
console.warn('unknown sv type', svType);
|
|
55
|
+
endPosition = startPos + 1;
|
|
56
|
+
}
|
|
52
57
|
if (endBlock) {
|
|
53
|
-
const startPos = feature.get('start');
|
|
54
58
|
const startRadians = bpToRadians(startBlock, startPos);
|
|
55
59
|
const endRadians = bpToRadians(endBlock, endPosition);
|
|
56
60
|
const startXY = polarToCartesian(radius, startRadians);
|
|
@@ -63,9 +67,7 @@ const Chord = observer(function Chord({ feature, blocksForRefs, radius, config,
|
|
|
63
67
|
feature,
|
|
64
68
|
});
|
|
65
69
|
return (React.createElement("path", { "data-testid": `chord-${feature.id()}`, cursor: "crosshair", fill: "none", d: ['M', ...startXY, 'Q', ...controlXY, ...endXY].join(' '), ...getStrokeProps(hovered ? hoverStrokeColor : strokeColor), strokeWidth: hovered ? 3 : 1, onClick: evt => {
|
|
66
|
-
|
|
67
|
-
onClick(feature, startBlock.region, endBlock.region, evt);
|
|
68
|
-
}
|
|
70
|
+
onClick(feature, startBlock.region, endBlock.region, evt);
|
|
69
71
|
}, onMouseOver: () => {
|
|
70
72
|
if (!selected) {
|
|
71
73
|
setHovered(true);
|
|
@@ -2,11 +2,11 @@ import React from 'react';
|
|
|
2
2
|
import { Feature } from '@jbrowse/core/util';
|
|
3
3
|
import { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
4
4
|
import { Block, AnyRegion } from './Chord';
|
|
5
|
-
declare const StructuralVariantChordsReactComponent: ({ features, config, displayModel, blockDefinitions, radius, bezierRadius,
|
|
5
|
+
declare const StructuralVariantChordsReactComponent: ({ features, config, displayModel, blockDefinitions, radius, bezierRadius, onChordClick, }: {
|
|
6
6
|
features: Map<string, Feature>;
|
|
7
7
|
radius: number;
|
|
8
8
|
config: AnyConfigurationModel;
|
|
9
|
-
displayModel
|
|
9
|
+
displayModel?: {
|
|
10
10
|
id: string;
|
|
11
11
|
selectedFeatureId: string;
|
|
12
12
|
};
|
|
@@ -2,7 +2,8 @@ import React, { useMemo } from 'react';
|
|
|
2
2
|
import { observer } from 'mobx-react';
|
|
3
3
|
// locals
|
|
4
4
|
import Chord from './Chord';
|
|
5
|
-
const StructuralVariantChordsReactComponent = observer(function ({ features, config, displayModel, blockDefinitions, radius, bezierRadius,
|
|
5
|
+
const StructuralVariantChordsReactComponent = observer(function ({ features, config, displayModel, blockDefinitions, radius, bezierRadius, onChordClick, }) {
|
|
6
|
+
const { id, selectedFeatureId } = displayModel || {};
|
|
6
7
|
// make a map of refName -> blockDefinition
|
|
7
8
|
const blocksForRefsMemo = useMemo(() => {
|
|
8
9
|
const blocksForRefs = {};
|
|
@@ -16,10 +17,6 @@ const StructuralVariantChordsReactComponent = observer(function ({ features, con
|
|
|
16
17
|
}
|
|
17
18
|
return blocksForRefs;
|
|
18
19
|
}, [blockDefinitions]);
|
|
19
|
-
return (React.createElement("g", {
|
|
20
|
-
const id = feature.id();
|
|
21
|
-
const selected = String(selectedFeatureId) === String(id);
|
|
22
|
-
return (React.createElement(Chord, { key: id, feature: feature, config: config, radius: radius, bezierRadius: bezierRadius, blocksForRefs: blocksForRefsMemo, selected: selected, onClick: onChordClick }));
|
|
23
|
-
})));
|
|
20
|
+
return (React.createElement("g", { "data-testid": "structuralVariantChordRenderer" }, [...features.values()].map(feature => (React.createElement(Chord, { key: feature.id(), feature: feature, config: config, radius: radius, bezierRadius: bezierRadius, blocksForRefs: blocksForRefsMemo, selected: String(selectedFeatureId) === String(id), onClick: onChordClick })))));
|
|
24
21
|
});
|
|
25
22
|
export default StructuralVariantChordsReactComponent;
|
|
@@ -6,6 +6,8 @@ export default function VariantAnnotPanel({ rows, columns, }) {
|
|
|
6
6
|
const [checked, setChecked] = useState(false);
|
|
7
7
|
const widths = columns.map(e => measureGridWidth(rows.map(r => r[e.field])));
|
|
8
8
|
return rows.length ? (React.createElement("div", null,
|
|
9
|
-
React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: checked, onChange: event =>
|
|
9
|
+
React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: checked, onChange: event => {
|
|
10
|
+
setChecked(event.target.checked);
|
|
11
|
+
} }), label: React.createElement(Typography, { variant: "body2" }, "Show options") }),
|
|
10
12
|
React.createElement(DataGrid, { rowHeight: 25, rows: rows, columns: columns.map((c, i) => ({ ...c, width: widths[i] })), slots: { toolbar: checked ? GridToolbar : null } }))) : null;
|
|
11
13
|
}
|
|
@@ -25,8 +25,12 @@ const BreakendOptionDialog = observer(function ({ model, handleClose, feature, v
|
|
|
25
25
|
const [mirror, setMirror] = useState(true);
|
|
26
26
|
return (React.createElement(Dialog, { open: true, onClose: handleClose, title: "Breakpoint split view options" },
|
|
27
27
|
React.createElement(DialogContent, null,
|
|
28
|
-
React.createElement(Checkbox2, { checked: copyTracks, onChange: event =>
|
|
29
|
-
|
|
28
|
+
React.createElement(Checkbox2, { checked: copyTracks, onChange: event => {
|
|
29
|
+
setCopyTracks(event.target.checked);
|
|
30
|
+
}, label: "Copy tracks into the new view" }),
|
|
31
|
+
React.createElement(Checkbox2, { checked: mirror, onChange: event => {
|
|
32
|
+
setMirror(event.target.checked);
|
|
33
|
+
}, label: "Mirror tracks vertically in vertically stacked view" })),
|
|
30
34
|
React.createElement(DialogActions, null,
|
|
31
35
|
React.createElement(Button, { onClick: () => {
|
|
32
36
|
const { view } = model;
|
|
@@ -59,6 +63,8 @@ const BreakendOptionDialog = observer(function ({ model, handleClose, feature, v
|
|
|
59
63
|
}
|
|
60
64
|
handleClose();
|
|
61
65
|
}, variant: "contained", color: "primary", autoFocus: true }, "OK"),
|
|
62
|
-
React.createElement(Button, { onClick: () =>
|
|
66
|
+
React.createElement(Button, { onClick: () => {
|
|
67
|
+
handleClose();
|
|
68
|
+
}, color: "secondary", variant: "contained" }, "Cancel"))));
|
|
63
69
|
});
|
|
64
70
|
export default BreakendOptionDialog;
|
|
@@ -8,7 +8,9 @@ function LocStringList({ locStrings, model, }) {
|
|
|
8
8
|
const session = getSession(model);
|
|
9
9
|
return (React.createElement("div", null,
|
|
10
10
|
React.createElement(Typography, null, "Link to linear view of breakend endpoints"),
|
|
11
|
-
React.createElement("ul", null, locStrings.map((locString, index) => (
|
|
11
|
+
React.createElement("ul", null, locStrings.map((locString, index) => (
|
|
12
|
+
/* biome-ignore lint/suspicious/noArrayIndexKey: */
|
|
13
|
+
React.createElement("li", { key: `${locString}-${index}` },
|
|
12
14
|
React.createElement(Link, { href: "#", onClick: event => {
|
|
13
15
|
var _a;
|
|
14
16
|
event.preventDefault();
|
|
@@ -32,7 +34,7 @@ function LaunchBreakpointSplitViewPanel({ locStrings, model, feature, viewType,
|
|
|
32
34
|
const simpleFeature = new SimpleFeature(feature);
|
|
33
35
|
return (React.createElement("div", null,
|
|
34
36
|
React.createElement(Typography, null, "Launch split views with breakend source and target"),
|
|
35
|
-
React.createElement("ul", null, locStrings.map(locString => (React.createElement("li", { key:
|
|
37
|
+
React.createElement("ul", null, locStrings.map(locString => (React.createElement("li", { key: JSON.stringify(locString) },
|
|
36
38
|
React.createElement(Link, { href: "#", onClick: event => {
|
|
37
39
|
event.preventDefault();
|
|
38
40
|
session.queueDialog(handleClose => [
|
|
@@ -6,5 +6,5 @@ export default function VariantAnnotationTable({ data, fields, title, }) {
|
|
|
6
6
|
React.createElement(AnnotGrid, { rows: data.map((elt, id) => ({
|
|
7
7
|
id,
|
|
8
8
|
...Object.fromEntries(elt.split('|').map((e, i) => [fields[i], e])),
|
|
9
|
-
}))
|
|
9
|
+
})), columns: fields.map(c => ({ field: c })) }))) : null;
|
|
10
10
|
}
|
|
@@ -9,17 +9,17 @@ import BreakendPanel from './BreakendPanel';
|
|
|
9
9
|
import VariantAnnotationTable from './VariantAnnotationTable';
|
|
10
10
|
import { variantFieldDescriptions } from './variantFieldDescriptions';
|
|
11
11
|
function AnnPanel({ descriptions, feature, }) {
|
|
12
|
-
var _a, _b, _c, _d;
|
|
12
|
+
var _a, _b, _c, _d, _e;
|
|
13
13
|
const annDesc = (_b = (_a = descriptions === null || descriptions === void 0 ? void 0 : descriptions.INFO) === null || _a === void 0 ? void 0 : _a.ANN) === null || _b === void 0 ? void 0 : _b.Description;
|
|
14
|
-
const annFields = ((_c = annDesc === null || annDesc === void 0 ? void 0 : annDesc.match(/.*Functional annotations:'(.*)'$/)) === null || _c === void 0 ? void 0 : _c[1].split('|')) || [];
|
|
15
|
-
const ann = ((
|
|
14
|
+
const annFields = ((_d = (_c = annDesc === null || annDesc === void 0 ? void 0 : annDesc.match(/.*Functional annotations:'(.*)'$/)) === null || _c === void 0 ? void 0 : _c[1]) === null || _d === void 0 ? void 0 : _d.split('|')) || [];
|
|
15
|
+
const ann = ((_e = feature.INFO) === null || _e === void 0 ? void 0 : _e.ANN) || [];
|
|
16
16
|
return (React.createElement(VariantAnnotationTable, { fields: annFields, data: ann, title: "Variant ANN field" }));
|
|
17
17
|
}
|
|
18
18
|
function CsqPanel({ descriptions, feature, }) {
|
|
19
|
-
var _a, _b, _c, _d;
|
|
19
|
+
var _a, _b, _c, _d, _e;
|
|
20
20
|
const csqDescription = (_b = (_a = descriptions === null || descriptions === void 0 ? void 0 : descriptions.INFO) === null || _a === void 0 ? void 0 : _a.CSQ) === null || _b === void 0 ? void 0 : _b.Description;
|
|
21
|
-
const csqFields = ((_c = csqDescription === null || csqDescription === void 0 ? void 0 : csqDescription.match(/.*Format: (.*)/)) === null || _c === void 0 ? void 0 : _c[1].split('|')) || [];
|
|
22
|
-
const csq = ((
|
|
21
|
+
const csqFields = ((_d = (_c = csqDescription === null || csqDescription === void 0 ? void 0 : csqDescription.match(/.*Format: (.*)/)) === null || _c === void 0 ? void 0 : _c[1]) === null || _d === void 0 ? void 0 : _d.split('|')) || [];
|
|
22
|
+
const csq = ((_e = feature.INFO) === null || _e === void 0 ? void 0 : _e.CSQ) || [];
|
|
23
23
|
return (React.createElement(VariantAnnotationTable, { fields: csqFields, data: csq, title: "Variant CSQ field" }));
|
|
24
24
|
}
|
|
25
25
|
const VariantFeatureWidget = observer(function (props) {
|
|
@@ -6,7 +6,9 @@ import { measureGridWidth } from '@jbrowse/core/util';
|
|
|
6
6
|
function SampleFilters({ columns, filter, setFilter, }) {
|
|
7
7
|
return (React.createElement(React.Fragment, null,
|
|
8
8
|
React.createElement(Typography, null, "These filters can use a plain text search or regex style query, e.g. in the genotype field, entering 1 will query for all genotypes that include the first alternate allele e.g. 0|1 or 1|1, entering [1-9]\\d* will find any non-zero allele e.g. 0|2 or 2/33"),
|
|
9
|
-
columns.map(({ field }) => (React.createElement(TextField, { key: `filter-${field}`, placeholder: `Filter ${field}`, value: filter[field] || '', onChange: event =>
|
|
9
|
+
columns.map(({ field }) => (React.createElement(TextField, { key: `filter-${field}`, placeholder: `Filter ${field}`, value: filter[field] || '', onChange: event => {
|
|
10
|
+
setFilter({ ...filter, [field]: event.target.value });
|
|
11
|
+
} })))));
|
|
10
12
|
}
|
|
11
13
|
export default function VariantSamples(props) {
|
|
12
14
|
var _a;
|
|
@@ -33,7 +35,7 @@ export default function VariantSamples(props) {
|
|
|
33
35
|
? filters.every(key => {
|
|
34
36
|
const currFilter = filter[key];
|
|
35
37
|
return currFilter
|
|
36
|
-
?
|
|
38
|
+
? new RegExp(currFilter, 'i').exec(row[key])
|
|
37
39
|
: true;
|
|
38
40
|
})
|
|
39
41
|
: true);
|
|
@@ -56,7 +58,9 @@ export default function VariantSamples(props) {
|
|
|
56
58
|
// https://github.com/mui-org/material-ui-x/issues/1197
|
|
57
59
|
return !preFilteredRows.length ? null : (React.createElement(BaseCard, { ...props, title: "Samples" },
|
|
58
60
|
error ? React.createElement(Typography, { color: "error" }, `${error}`) : null,
|
|
59
|
-
React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: checked, onChange: event =>
|
|
61
|
+
React.createElement(FormControlLabel, { control: React.createElement(Checkbox, { checked: checked, onChange: event => {
|
|
62
|
+
setChecked(event.target.checked);
|
|
63
|
+
} }), label: React.createElement(Typography, { variant: "body2" }, "Show options") }),
|
|
60
64
|
checked ? (React.createElement(SampleFilters, { setFilter: setFilter, columns: columns, filter: filter })) : null,
|
|
61
65
|
React.createElement(DataGrid, { autoHeight: true, rows: rows, hideFooter: rows.length < 100, columns: columns, disableRowSelectionOnClick: true, rowHeight: 25, columnHeaderHeight: 35, disableColumnMenu: true, slots: { toolbar: checked ? GridToolbar : null }, slotProps: {
|
|
62
66
|
toolbar: {
|
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
import { BaseFeatureDataAdapter, BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
2
|
import { Region, Feature } from '@jbrowse/core/util';
|
|
3
3
|
import IntervalTree from '@flatten-js/interval-tree';
|
|
4
|
-
|
|
4
|
+
type StatusCallback = (arg: string) => void;
|
|
5
5
|
export default class VcfAdapter extends BaseFeatureDataAdapter {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
calculatedIntervalTreeMap: Record<string, IntervalTree>;
|
|
7
|
+
vcfFeatures?: Promise<{
|
|
8
8
|
header: string;
|
|
9
|
-
|
|
9
|
+
intervalTreeMap: Record<string, (sc?: StatusCallback) => IntervalTree>;
|
|
10
10
|
}>;
|
|
11
|
+
static capabilities: string[];
|
|
11
12
|
getHeader(): Promise<string>;
|
|
12
13
|
getMetadata(): Promise<any>;
|
|
13
|
-
setupP(): Promise<{
|
|
14
|
+
setupP(opts?: BaseOptions): Promise<{
|
|
14
15
|
header: string;
|
|
15
|
-
|
|
16
|
+
intervalTreeMap: {
|
|
17
|
+
[k: string]: (sc?: (arg: string) => void) => IntervalTree<any>;
|
|
18
|
+
};
|
|
16
19
|
}>;
|
|
17
20
|
setup(): Promise<{
|
|
18
21
|
header: string;
|
|
19
|
-
|
|
22
|
+
intervalTreeMap: Record<string, (sc?: StatusCallback) => IntervalTree>;
|
|
20
23
|
}>;
|
|
21
24
|
getRefNames(_?: BaseOptions): Promise<string[]>;
|
|
22
25
|
getFeatures(region: Region, opts?: BaseOptions): import("rxjs").Observable<Feature>;
|
|
23
26
|
freeResources(): void;
|
|
24
27
|
}
|
|
28
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseFeatureDataAdapter, } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
|
+
import { updateStatus, isGzip } from '@jbrowse/core/util';
|
|
2
3
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
3
4
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs';
|
|
4
5
|
import IntervalTree from '@flatten-js/interval-tree';
|
|
@@ -6,66 +7,84 @@ import { unzip } from '@gmod/bgzf-filehandle';
|
|
|
6
7
|
import VCF from '@gmod/vcf';
|
|
7
8
|
// local
|
|
8
9
|
import VcfFeature from '../VcfFeature';
|
|
9
|
-
const readVcf = (f) => {
|
|
10
|
-
const header = [];
|
|
11
|
-
const rest = [];
|
|
12
|
-
f.split(/\n|\r\n|\r/)
|
|
13
|
-
.map(f => f.trim())
|
|
14
|
-
.filter(f => !!f)
|
|
15
|
-
.forEach(line => {
|
|
16
|
-
if (line.startsWith('#')) {
|
|
17
|
-
header.push(line);
|
|
18
|
-
}
|
|
19
|
-
else if (line) {
|
|
20
|
-
rest.push(line);
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
return { header: header.join('\n'), lines: rest };
|
|
24
|
-
};
|
|
25
|
-
function isGzip(buf) {
|
|
26
|
-
return buf[0] === 31 && buf[1] === 139 && buf[2] === 8;
|
|
27
|
-
}
|
|
28
10
|
class VcfAdapter extends BaseFeatureDataAdapter {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
this.calculatedIntervalTreeMap = {};
|
|
14
|
+
}
|
|
29
15
|
async getHeader() {
|
|
30
16
|
const { header } = await this.setup();
|
|
31
17
|
return header;
|
|
32
18
|
}
|
|
33
19
|
async getMetadata() {
|
|
34
20
|
const { header } = await this.setup();
|
|
35
|
-
const parser = new VCF({ header
|
|
21
|
+
const parser = new VCF({ header });
|
|
36
22
|
return parser.getMetadata();
|
|
37
23
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
24
|
+
async setupP(opts) {
|
|
25
|
+
const { statusCallback = () => { } } = opts || {};
|
|
26
|
+
const buf = (await openLocation(this.getConf('vcfLocation'), this.pluginManager).readFile(opts));
|
|
27
|
+
const buffer = isGzip(buf)
|
|
28
|
+
? await updateStatus('Unzipping', statusCallback, () => unzip(buf))
|
|
29
|
+
: buf;
|
|
30
|
+
const headerLines = [];
|
|
31
|
+
const featureMap = {};
|
|
32
|
+
let blockStart = 0;
|
|
33
|
+
const decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf8') : undefined;
|
|
34
|
+
let i = 0;
|
|
35
|
+
while (blockStart < buffer.length) {
|
|
36
|
+
const n = buffer.indexOf('\n', blockStart);
|
|
37
|
+
// could be a non-newline ended file, so slice to end of file if n===-1
|
|
38
|
+
const b = n === -1 ? buffer.slice(blockStart) : buffer.slice(blockStart, n);
|
|
39
|
+
const line = ((decoder === null || decoder === void 0 ? void 0 : decoder.decode(b)) || b.toString()).trim();
|
|
40
|
+
if (line) {
|
|
41
|
+
if (line.startsWith('#')) {
|
|
42
|
+
headerLines.push(line);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const ret = line.indexOf('\t');
|
|
46
|
+
const refName = line.slice(0, ret);
|
|
47
|
+
if (!featureMap[refName]) {
|
|
48
|
+
featureMap[refName] = [];
|
|
49
|
+
}
|
|
50
|
+
featureMap[refName].push(line);
|
|
51
|
+
}
|
|
61
52
|
}
|
|
62
|
-
|
|
53
|
+
if (i++ % 10000 === 0) {
|
|
54
|
+
statusCallback(`Loading ${Math.floor(blockStart / 1000000).toLocaleString('en-US')}/${Math.floor(buffer.length / 1000000).toLocaleString('en-US')} MB`);
|
|
55
|
+
}
|
|
56
|
+
blockStart = n + 1;
|
|
63
57
|
}
|
|
64
|
-
|
|
58
|
+
const header = headerLines.join('\n');
|
|
59
|
+
const parser = new VCF({ header });
|
|
60
|
+
const intervalTreeMap = Object.fromEntries(Object.entries(featureMap).map(([refName, lines]) => [
|
|
61
|
+
refName,
|
|
62
|
+
(sc) => {
|
|
63
|
+
if (!this.calculatedIntervalTreeMap[refName]) {
|
|
64
|
+
sc === null || sc === void 0 ? void 0 : sc('Parsing VCF data');
|
|
65
|
+
let idx = 0;
|
|
66
|
+
const intervalTree = new IntervalTree();
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
const f = new VcfFeature({
|
|
69
|
+
variant: parser.parseLine(line),
|
|
70
|
+
parser,
|
|
71
|
+
id: `${this.id}-${refName}-${idx++}`,
|
|
72
|
+
});
|
|
73
|
+
intervalTree.insert([f.get('start'), f.get('end')], f);
|
|
74
|
+
}
|
|
75
|
+
this.calculatedIntervalTreeMap[refName] = intervalTree;
|
|
76
|
+
}
|
|
77
|
+
return this.calculatedIntervalTreeMap[refName];
|
|
78
|
+
},
|
|
79
|
+
]));
|
|
80
|
+
return {
|
|
81
|
+
header,
|
|
82
|
+
intervalTreeMap,
|
|
83
|
+
};
|
|
65
84
|
}
|
|
66
85
|
async setup() {
|
|
67
86
|
if (!this.vcfFeatures) {
|
|
68
|
-
this.vcfFeatures = this.setupP().catch(e => {
|
|
87
|
+
this.vcfFeatures = this.setupP().catch((e) => {
|
|
69
88
|
this.vcfFeatures = undefined;
|
|
70
89
|
throw e;
|
|
71
90
|
});
|
|
@@ -73,16 +92,16 @@ class VcfAdapter extends BaseFeatureDataAdapter {
|
|
|
73
92
|
return this.vcfFeatures;
|
|
74
93
|
}
|
|
75
94
|
async getRefNames(_ = {}) {
|
|
76
|
-
const {
|
|
77
|
-
return Object.keys(
|
|
95
|
+
const { intervalTreeMap } = await this.setup();
|
|
96
|
+
return Object.keys(intervalTreeMap);
|
|
78
97
|
}
|
|
79
98
|
getFeatures(region, opts = {}) {
|
|
80
99
|
return ObservableCreate(async (observer) => {
|
|
81
100
|
var _a;
|
|
82
101
|
try {
|
|
83
102
|
const { start, end, refName } = region;
|
|
84
|
-
const {
|
|
85
|
-
(_a =
|
|
103
|
+
const { intervalTreeMap } = await this.setup();
|
|
104
|
+
(_a = intervalTreeMap[refName]) === null || _a === void 0 ? void 0 : _a.call(intervalTreeMap, opts.statusCallback).search([start, end]).forEach(f => {
|
|
86
105
|
observer.next(f);
|
|
87
106
|
});
|
|
88
107
|
observer.complete();
|
package/esm/VcfFeature/index.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
// locals
|
|
2
2
|
import { getSOTermAndDescription } from './util';
|
|
3
3
|
export default class VCFFeature {
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
4
|
constructor(args) {
|
|
6
5
|
this.variant = args.variant;
|
|
7
6
|
this.parser = args.parser;
|
|
8
7
|
this.data = this.dataFromVariant(this.variant);
|
|
9
8
|
this._id = args.id;
|
|
10
9
|
}
|
|
11
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
10
|
get(field) {
|
|
13
11
|
var _a;
|
|
14
12
|
return field === 'samples'
|
|
@@ -32,8 +30,8 @@ export default class VCFFeature {
|
|
|
32
30
|
const { REF, ALT, POS, CHROM, INFO, ID } = variant;
|
|
33
31
|
const start = POS - 1;
|
|
34
32
|
const [type, description] = getSOTermAndDescription(REF, ALT, this.parser);
|
|
35
|
-
const isTRA = ALT
|
|
36
|
-
const isSymbolic = ALT
|
|
33
|
+
const isTRA = ALT.includes('<TRA>');
|
|
34
|
+
const isSymbolic = ALT.some(f => f.includes('<'));
|
|
37
35
|
return {
|
|
38
36
|
refName: CHROM,
|
|
39
37
|
start,
|
|
@@ -41,10 +39,9 @@ export default class VCFFeature {
|
|
|
41
39
|
description,
|
|
42
40
|
type,
|
|
43
41
|
name: ID === null || ID === void 0 ? void 0 : ID.join(','),
|
|
44
|
-
aliases: ID && ID.length > 1 ?
|
|
42
|
+
aliases: ID && ID.length > 1 ? ID.slice(1) : undefined,
|
|
45
43
|
};
|
|
46
44
|
}
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
45
|
toJSON() {
|
|
49
46
|
return {
|
|
50
47
|
uniqueId: this._id,
|
package/esm/VcfFeature/util.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import VCF from '@gmod/vcf';
|
|
|
2
2
|
/**
|
|
3
3
|
* Get a sequence ontology (SO) term that describes the variant type
|
|
4
4
|
*/
|
|
5
|
-
export declare function getSOTermAndDescription(ref: string, alt: string[], parser: VCF): string[];
|
|
5
|
+
export declare function getSOTermAndDescription(ref: string, alt: string[] | undefined, parser: VCF): string[];
|
|
6
6
|
export declare function getSOAndDescFromAltDefs(alt: string, parser: VCF): string[];
|
|
7
7
|
export declare function getSOAndDescByExamination(ref: string, alt: string): string[];
|
package/esm/VcfFeature/util.js
CHANGED
|
@@ -35,10 +35,12 @@ export function getSOTermAndDescription(ref, alt, parser) {
|
|
|
35
35
|
// Combine descriptions like ["SNV G -> A", "SNV G -> T"] to ["SNV G -> A,T"]
|
|
36
36
|
if (descriptions.size > 1) {
|
|
37
37
|
const descs = [...descriptions];
|
|
38
|
-
const prefixes = new Set(descs
|
|
38
|
+
const prefixes = new Set(descs
|
|
39
|
+
.map(desc => {
|
|
39
40
|
const prefix = desc.split('->');
|
|
40
41
|
return prefix[1] ? prefix[0] : desc;
|
|
41
|
-
})
|
|
42
|
+
})
|
|
43
|
+
.filter((f) => !!f));
|
|
42
44
|
descriptions = new Set([...prefixes]
|
|
43
45
|
.map(r => r.trim())
|
|
44
46
|
.map(prefix => {
|
|
@@ -83,43 +85,43 @@ export function getSOAndDescByExamination(ref, alt) {
|
|
|
83
85
|
if (bnd) {
|
|
84
86
|
return ['breakend', alt];
|
|
85
87
|
}
|
|
86
|
-
|
|
88
|
+
if (ref.length === 1 && alt.length === 1) {
|
|
87
89
|
return ['SNV', makeDescriptionString('SNV', ref, alt)];
|
|
88
90
|
}
|
|
89
|
-
|
|
91
|
+
if (alt === '<INS>') {
|
|
90
92
|
return ['insertion', alt];
|
|
91
93
|
}
|
|
92
|
-
|
|
94
|
+
if (alt === '<DEL>') {
|
|
93
95
|
return ['deletion', alt];
|
|
94
96
|
}
|
|
95
|
-
|
|
97
|
+
if (alt === '<INV>') {
|
|
96
98
|
return ['inversion', alt];
|
|
97
99
|
}
|
|
98
|
-
|
|
100
|
+
if (alt === '<TRA>') {
|
|
99
101
|
return ['translocation', alt];
|
|
100
102
|
}
|
|
101
|
-
|
|
103
|
+
if (alt.includes('<')) {
|
|
102
104
|
return ['sv', alt];
|
|
103
105
|
}
|
|
104
|
-
|
|
106
|
+
if (ref.length === alt.length) {
|
|
105
107
|
return ref.split('').reverse().join('') === alt
|
|
106
108
|
? ['inversion', makeDescriptionString('inversion', ref, alt)]
|
|
107
109
|
: ['substitution', makeDescriptionString('substitution', ref, alt)];
|
|
108
110
|
}
|
|
109
|
-
|
|
111
|
+
if (ref.length <= alt.length) {
|
|
110
112
|
const len = alt.length - ref.length;
|
|
111
113
|
const lena = len.toLocaleString('en-US');
|
|
112
114
|
return [
|
|
113
115
|
'insertion',
|
|
114
|
-
len > 5 ? lena
|
|
116
|
+
len > 5 ? `${lena}bp INS` : makeDescriptionString('insertion', ref, alt),
|
|
115
117
|
];
|
|
116
118
|
}
|
|
117
|
-
|
|
119
|
+
if (ref.length > alt.length) {
|
|
118
120
|
const len = ref.length - alt.length;
|
|
119
121
|
const lena = len.toLocaleString('en-US');
|
|
120
122
|
return [
|
|
121
123
|
'deletion',
|
|
122
|
-
len > 5 ? lena
|
|
124
|
+
len > 5 ? `${lena}bp DEL` : makeDescriptionString('deletion', ref, alt),
|
|
123
125
|
];
|
|
124
126
|
}
|
|
125
127
|
return ['indel', makeDescriptionString('indel', ref, alt)];
|
|
@@ -28,7 +28,7 @@ export default class VcfTabixAdapter extends BaseFeatureDataAdapter {
|
|
|
28
28
|
}
|
|
29
29
|
async configure() {
|
|
30
30
|
if (!this.configured) {
|
|
31
|
-
this.configured = this.configurePre().catch(e => {
|
|
31
|
+
this.configured = this.configurePre().catch((e) => {
|
|
32
32
|
this.configured = undefined;
|
|
33
33
|
throw e;
|
|
34
34
|
});
|
package/esm/extensionPoints.js
CHANGED
|
@@ -17,7 +17,7 @@ export default function ExtensionPointsF(pluginManager) {
|
|
|
17
17
|
if (regexGuess.test(fileName) && !adapterHint) {
|
|
18
18
|
return obj;
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
if (adapterHint === adapterName) {
|
|
21
21
|
return obj;
|
|
22
22
|
}
|
|
23
23
|
return adapterGuesser(file, index, adapterHint);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-variants",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "JBrowse 2 variant adapters, tracks, etc.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jbrowse",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "npm-run-all build:*",
|
|
27
|
-
"test": "cd ../..; jest plugins/variants",
|
|
27
|
+
"test": "cd ../..; jest --passWithNoTests plugins/variants",
|
|
28
28
|
"prepublishOnly": "yarn test",
|
|
29
29
|
"prepack": "yarn build && yarn useDist",
|
|
30
30
|
"postpack": "yarn useSrc",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"distModule": "esm/index.js",
|
|
63
63
|
"srcModule": "src/index.ts",
|
|
64
64
|
"module": "esm/index.js",
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "9fb8231d932db40adf0a283081765431756c66ff"
|
|
66
66
|
}
|