@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.
Files changed (41) hide show
  1. package/dist/ChordVariantDisplay/models/stateModelFactory.d.ts +3 -3
  2. package/dist/ChordVariantDisplay/models/stateModelFactory.js +3 -1
  3. package/dist/LinearVariantDisplay/model.d.ts +1 -1
  4. package/dist/StructuralVariantChordRenderer/Chord.js +7 -5
  5. package/dist/StructuralVariantChordRenderer/ReactComponent.d.ts +2 -2
  6. package/dist/StructuralVariantChordRenderer/ReactComponent.js +3 -6
  7. package/dist/VariantFeatureWidget/AnnotGrid.js +3 -1
  8. package/dist/VariantFeatureWidget/BreakendOptionDialog.js +9 -3
  9. package/dist/VariantFeatureWidget/BreakendPanel.js +4 -2
  10. package/dist/VariantFeatureWidget/VariantAnnotationTable.js +1 -1
  11. package/dist/VariantFeatureWidget/VariantFeatureWidget.js +6 -6
  12. package/dist/VariantFeatureWidget/VariantSampleGrid.js +7 -3
  13. package/dist/VcfAdapter/VcfAdapter.d.ts +11 -7
  14. package/dist/VcfAdapter/VcfAdapter.js +69 -50
  15. package/dist/VcfFeature/index.d.ts +1 -1
  16. package/dist/VcfFeature/index.js +3 -6
  17. package/dist/VcfFeature/util.d.ts +1 -1
  18. package/dist/VcfFeature/util.js +15 -13
  19. package/dist/VcfTabixAdapter/VcfTabixAdapter.js +1 -1
  20. package/dist/extensionPoints.js +1 -1
  21. package/esm/ChordVariantDisplay/models/stateModelFactory.d.ts +3 -3
  22. package/esm/ChordVariantDisplay/models/stateModelFactory.js +3 -1
  23. package/esm/LinearVariantDisplay/model.d.ts +1 -1
  24. package/esm/StructuralVariantChordRenderer/Chord.js +7 -5
  25. package/esm/StructuralVariantChordRenderer/ReactComponent.d.ts +2 -2
  26. package/esm/StructuralVariantChordRenderer/ReactComponent.js +3 -6
  27. package/esm/VariantFeatureWidget/AnnotGrid.js +3 -1
  28. package/esm/VariantFeatureWidget/BreakendOptionDialog.js +9 -3
  29. package/esm/VariantFeatureWidget/BreakendPanel.js +4 -2
  30. package/esm/VariantFeatureWidget/VariantAnnotationTable.js +1 -1
  31. package/esm/VariantFeatureWidget/VariantFeatureWidget.js +6 -6
  32. package/esm/VariantFeatureWidget/VariantSampleGrid.js +7 -3
  33. package/esm/VcfAdapter/VcfAdapter.d.ts +11 -7
  34. package/esm/VcfAdapter/VcfAdapter.js +69 -50
  35. package/esm/VcfFeature/index.d.ts +1 -1
  36. package/esm/VcfFeature/index.js +3 -6
  37. package/esm/VcfFeature/util.d.ts +1 -1
  38. package/esm/VcfFeature/util.js +15 -13
  39. package/esm/VcfTabixAdapter/VcfTabixAdapter.js +1 -1
  40. package/esm/extensionPoints.js +1 -1
  41. 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) => self.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?: Function;
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
- if (endBlock && startBlock) {
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, displayModel: { selectedFeatureId }, onChordClick, }: {
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, displayModel: { selectedFeatureId }, onChordClick, }) {
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", { id: `chords-${typeof jest !== 'undefined' ? 'test' : displayModel.id}`, "data-testid": "structuralVariantChordRenderer" }, [...features.values()].map(feature => {
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 => setChecked(event.target.checked) }), label: React.createElement(Typography, { variant: "body2" }, "Show options") }),
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 => setCopyTracks(event.target.checked), label: "Copy tracks into the new view" }),
29
- React.createElement(Checkbox2, { checked: mirror, onChange: event => setMirror(event.target.checked), label: "Mirror tracks vertically in vertically stacked view" })),
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: () => handleClose(), color: "secondary", variant: "contained" }, "Cancel"))));
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) => (React.createElement("li", { key: `${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: `${JSON.stringify(locString)}` },
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
- })) || [], columns: fields.map(c => ({ field: c })) }))) : null;
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 = ((_d = feature.INFO) === null || _d === void 0 ? void 0 : _d.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 = ((_d = feature.INFO) === null || _d === void 0 ? void 0 : _d.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 => setFilter({ ...filter, [field]: event.target.value }) })))));
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
- ? row[key].match(new RegExp(currFilter, 'i'))
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 => setChecked(event.target.checked) }), label: React.createElement(Typography, { variant: "body2" }, "Show options") }),
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
- import VcfFeature from '../VcfFeature';
4
+ type StatusCallback = (arg: string) => void;
5
5
  export default class VcfAdapter extends BaseFeatureDataAdapter {
6
- static capabilities: string[];
7
- protected vcfFeatures?: Promise<{
6
+ calculatedIntervalTreeMap: Record<string, IntervalTree>;
7
+ vcfFeatures?: Promise<{
8
8
  header: string;
9
- intervalTree: Record<string, IntervalTree<VcfFeature>>;
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
- intervalTree: Record<string, IntervalTree<VcfFeature>>;
16
+ intervalTreeMap: {
17
+ [k: string]: (sc?: (arg: string) => void) => IntervalTree<any>;
18
+ };
16
19
  }>;
17
20
  setup(): Promise<{
18
21
  header: string;
19
- intervalTree: Record<string, IntervalTree<VcfFeature>>;
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: header });
21
+ const parser = new VCF({ header });
36
22
  return parser.getMetadata();
37
23
  }
38
- // converts lines into an interval tree
39
- async setupP() {
40
- const pm = this.pluginManager;
41
- const buf = await openLocation(this.getConf('vcfLocation'), pm).readFile();
42
- const buffer = isGzip(buf) ? await unzip(buf) : buf;
43
- // 512MB max chrome string length is 512MB
44
- if (buffer.length > 536870888) {
45
- throw new Error('Data exceeds maximum string length (512MB)');
46
- }
47
- const str = new TextDecoder().decode(buffer);
48
- const { header, lines } = readVcf(str);
49
- const intervalTree = {};
50
- const parser = new VCF({ header });
51
- let idx = 0;
52
- for (const line of lines) {
53
- const f = new VcfFeature({
54
- variant: parser.parseLine(line),
55
- parser,
56
- id: `${this.id}-${idx++}`,
57
- });
58
- const key = f.get('refName');
59
- if (!intervalTree[key]) {
60
- intervalTree[key] = new IntervalTree();
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
- intervalTree[key].insert([f.get('start'), f.get('end')], f);
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
- return { header, intervalTree };
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 { intervalTree } = await this.setup();
77
- return Object.keys(intervalTree);
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 { intervalTree } = await this.setup();
85
- (_a = intervalTree[refName]) === null || _a === void 0 ? void 0 : _a.search([start, end]).forEach((f) => {
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();
@@ -36,7 +36,7 @@ export default class VCFFeature implements Feature {
36
36
  ALT: string[];
37
37
  CHROM: string;
38
38
  INFO: any;
39
- ID: string[];
39
+ ID?: string[];
40
40
  }): FeatureData;
41
41
  toJSON(): any;
42
42
  }
@@ -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 === null || ALT === void 0 ? void 0 : ALT.some(f => f === '<TRA>');
36
- const isSymbolic = ALT === null || ALT === void 0 ? void 0 : ALT.some(f => f.includes('<'));
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 ? variant.ID.slice(1) : undefined,
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,
@@ -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[];
@@ -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.map(desc => {
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
- else if (ref.length === 1 && alt.length === 1) {
88
+ if (ref.length === 1 && alt.length === 1) {
87
89
  return ['SNV', makeDescriptionString('SNV', ref, alt)];
88
90
  }
89
- else if (alt === '<INS>') {
91
+ if (alt === '<INS>') {
90
92
  return ['insertion', alt];
91
93
  }
92
- else if (alt === '<DEL>') {
94
+ if (alt === '<DEL>') {
93
95
  return ['deletion', alt];
94
96
  }
95
- else if (alt === '<INV>') {
97
+ if (alt === '<INV>') {
96
98
  return ['inversion', alt];
97
99
  }
98
- else if (alt === '<TRA>') {
100
+ if (alt === '<TRA>') {
99
101
  return ['translocation', alt];
100
102
  }
101
- else if (alt.includes('<')) {
103
+ if (alt.includes('<')) {
102
104
  return ['sv', alt];
103
105
  }
104
- else if (ref.length === alt.length) {
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
- else if (ref.length <= alt.length) {
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 + 'bp INS' : makeDescriptionString('insertion', ref, alt),
116
+ len > 5 ? `${lena}bp INS` : makeDescriptionString('insertion', ref, alt),
115
117
  ];
116
118
  }
117
- else if (ref.length > alt.length) {
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 + 'bp DEL' : makeDescriptionString('deletion', ref, alt),
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
  });
@@ -17,7 +17,7 @@ export default function ExtensionPointsF(pluginManager) {
17
17
  if (regexGuess.test(fileName) && !adapterHint) {
18
18
  return obj;
19
19
  }
20
- else if (adapterHint === adapterName) {
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.13.1",
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": "fcebca71cc1d066654603e1a9accfa6c6d4f764d"
65
+ "gitHead": "9fb8231d932db40adf0a283081765431756c66ff"
66
66
  }