@jbrowse/plugin-bed 4.3.0 → 5.0.0-beta.2

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 (56) hide show
  1. package/esm/BedAdapter/BedAdapter.d.ts +4 -19
  2. package/esm/BedAdapter/BedAdapter.js +31 -88
  3. package/esm/BedAdapter/configSchema.d.ts +89 -40
  4. package/esm/BedAdapter/configSchema.js +65 -13
  5. package/esm/BedAdapter/index.js +4 -2
  6. package/esm/BedGraphAdapter/BedGraphAdapter.d.ts +4 -9
  7. package/esm/BedGraphAdapter/BedGraphAdapter.js +31 -85
  8. package/esm/BedGraphAdapter/configSchema.d.ts +35 -9
  9. package/esm/BedGraphAdapter/configSchema.js +47 -12
  10. package/esm/BedGraphAdapter/index.js +3 -2
  11. package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.d.ts +5 -5
  12. package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.js +62 -57
  13. package/esm/BedGraphTabixAdapter/configSchema.d.ts +48 -19
  14. package/esm/BedGraphTabixAdapter/configSchema.js +42 -34
  15. package/esm/BedGraphTabixAdapter/index.js +3 -2
  16. package/esm/BedTabixAdapter/BedTabixAdapter.d.ts +34 -38
  17. package/esm/BedTabixAdapter/BedTabixAdapter.js +82 -71
  18. package/esm/BedTabixAdapter/configSchema.d.ts +73 -31
  19. package/esm/BedTabixAdapter/configSchema.js +53 -35
  20. package/esm/BedTabixAdapter/index.js +4 -2
  21. package/esm/BedpeAdapter/BedpeAdapter.d.ts +4 -10
  22. package/esm/BedpeAdapter/BedpeAdapter.js +28 -69
  23. package/esm/BedpeAdapter/configSchema.d.ts +33 -10
  24. package/esm/BedpeAdapter/configSchema.js +45 -12
  25. package/esm/BedpeAdapter/index.js +4 -2
  26. package/esm/BedpeAdapter/util.js +22 -23
  27. package/esm/BigBedAdapter/BigBedAdapter.d.ts +8 -7
  28. package/esm/BigBedAdapter/BigBedAdapter.js +100 -107
  29. package/esm/BigBedAdapter/configSchema.d.ts +52 -17
  30. package/esm/BigBedAdapter/configSchema.js +53 -12
  31. package/esm/BigBedAdapter/index.js +3 -2
  32. package/esm/StarFusionAdapter/StarFusionAdapter.d.ts +15 -0
  33. package/esm/StarFusionAdapter/StarFusionAdapter.js +121 -0
  34. package/esm/StarFusionAdapter/configSchema.d.ts +31 -0
  35. package/esm/StarFusionAdapter/configSchema.js +52 -0
  36. package/esm/StarFusionAdapter/index.d.ts +2 -0
  37. package/esm/StarFusionAdapter/index.js +12 -0
  38. package/esm/adapterUtil.d.ts +5 -0
  39. package/esm/adapterUtil.js +28 -0
  40. package/esm/bedGraphUtil.d.ts +10 -0
  41. package/esm/bedGraphUtil.js +18 -0
  42. package/esm/generateBedMethylFeature.d.ts +1 -1
  43. package/esm/generateBedMethylFeature.js +19 -6
  44. package/esm/generateRastairMethylFeature.d.ts +28 -0
  45. package/esm/generateRastairMethylFeature.js +38 -0
  46. package/esm/generateRepeatMaskerFeature.d.ts +4 -51
  47. package/esm/generateRepeatMaskerFeature.js +28 -41
  48. package/esm/generateUcscTranscript.d.ts +15 -7
  49. package/esm/generateUcscTranscript.js +58 -28
  50. package/esm/index.d.ts +2 -0
  51. package/esm/index.js +10 -8
  52. package/esm/util.d.ts +32 -27
  53. package/esm/util.js +127 -97
  54. package/package.json +9 -13
  55. package/esm/GuessAdapter/index.d.ts +0 -2
  56. package/esm/GuessAdapter/index.js +0 -71
@@ -1,6 +1,34 @@
1
1
  import { ConfigurationSchema } from '@jbrowse/core/configuration';
2
- function x() { }
2
+ export function normalizeSnapshot(snap) {
3
+ return snap.uri
4
+ ? {
5
+ ...snap,
6
+ bedpeLocation: {
7
+ uri: snap.uri,
8
+ baseUri: snap.baseUri,
9
+ locationType: 'UriLocation',
10
+ },
11
+ }
12
+ : snap;
13
+ }
14
+ /**
15
+ * #config BedpeAdapter
16
+ * #trackType VariantTrack
17
+ * #fileFormat variants | BEDPE | Paired/breakend records, e.g. SV calls or Hi-C loops
18
+ * intended for SVs in a single assembly
19
+ *
20
+ * #example
21
+ * ```js
22
+ * {
23
+ * type: 'BedpeAdapter',
24
+ * uri: 'https://example.com/sv.bedpe',
25
+ * }
26
+ * ```
27
+ */
3
28
  const BedpeAdapter = ConfigurationSchema('BedpeAdapter', {
29
+ /**
30
+ * #slot
31
+ */
4
32
  bedpeLocation: {
5
33
  type: 'fileLocation',
6
34
  description: 'can be plaintext or gzipped, not indexed so loaded into memory on startup',
@@ -9,6 +37,9 @@ const BedpeAdapter = ConfigurationSchema('BedpeAdapter', {
9
37
  locationType: 'UriLocation',
10
38
  },
11
39
  },
40
+ /**
41
+ * #slot
42
+ */
12
43
  columnNames: {
13
44
  type: 'stringArray',
14
45
  description: 'List of column names',
@@ -16,16 +47,18 @@ const BedpeAdapter = ConfigurationSchema('BedpeAdapter', {
16
47
  },
17
48
  }, {
18
49
  explicitlyTyped: true,
19
- preProcessSnapshot: snap => {
20
- return snap.uri
21
- ? {
22
- ...snap,
23
- bedpeLocation: {
24
- uri: snap.uri,
25
- baseUri: snap.baseUri,
26
- },
27
- }
28
- : snap;
29
- },
50
+ /**
51
+ * #preProcessSnapshot
52
+ *
53
+ *
54
+ * preprocessor to allow minimal config:
55
+ * ```json
56
+ * {
57
+ * "type": "BedpeAdapter",
58
+ * "uri": "yourfile.bedpe.gz"
59
+ * }
60
+ * ```
61
+ */
62
+ preProcessSnapshot: normalizeSnapshot,
30
63
  });
31
64
  export default BedpeAdapter;
@@ -1,10 +1,12 @@
1
1
  import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType';
2
- import configSchema from "./configSchema.js";
2
+ import configSchema, { normalizeSnapshot } from './configSchema.js';
3
3
  export default function BedpeAdapterF(pluginManager) {
4
4
  pluginManager.addAdapterType(() => new AdapterType({
5
5
  name: 'BedpeAdapter',
6
6
  displayName: 'BEDPE adapter',
7
7
  configSchema,
8
- getAdapterClass: () => import("./BedpeAdapter.js").then(r => r.default),
8
+ getAdapterClass: () => import('./BedpeAdapter.js').then(r => r.default),
9
+ locationKey: 'bedpeLocation',
10
+ normalizeSnapshot,
9
11
  }));
10
12
  }
@@ -1,22 +1,35 @@
1
1
  import { SimpleFeature } from '@jbrowse/core/util';
2
+ import { parseStrand } from '../util.js';
2
3
  const svTypes = new Set(['DUP', 'TRA', 'INV', 'CNV', 'DEL']);
3
4
  export function featureData(line, uniqueId, flip, names) {
4
5
  const l = line.split('\t');
5
6
  const ref1 = l[flip ? 3 : 0];
6
7
  const start1 = +l[flip ? 4 : 1];
7
8
  const end1 = +l[flip ? 5 : 2];
8
- const ref2 = l[!flip ? 3 : 0];
9
- const start2 = +l[!flip ? 4 : 1];
10
- const end2 = +l[!flip ? 5 : 2];
11
- const name = l[6];
12
- const score = l[7] ? +l[7] : undefined;
13
- const strand1 = parseStrand(l[8]);
14
- const strand2 = parseStrand(l[9]);
9
+ const ref2 = l[flip ? 0 : 3];
10
+ const start2 = +l[flip ? 1 : 4];
11
+ const end2 = +l[flip ? 2 : 5];
12
+ // '.' is BED's "missing" marker, so a file that leaves the standard name and
13
+ // score columns unset must not produce the literal string '.' and a NaN
14
+ // score. Same rule `defaultParser` applies on the plain-BED path.
15
+ const name = l[6] === '.' ? undefined : l[6];
16
+ const score = l[7] && l[7] !== '.' ? +l[7] : undefined;
17
+ // Columns 9 and 10 ride with the blocks they describe. Left behind on a
18
+ // flipped row they anchored the feature at one end and gave it the other
19
+ // end's orientation, which every consumer reading a junction edge off the
20
+ // strand then got backwards for that half of the record.
21
+ const strand1 = parseStrand(l[flip ? 9 : 8]);
22
+ const strand2 = parseStrand(l[flip ? 8 : 9]);
15
23
  const extra = l.slice(10);
16
24
  const rest = names
17
25
  ? Object.fromEntries(names.slice(10).map((n, idx) => [n, extra[idx]]))
18
26
  : {};
19
27
  const ALT = svTypes.has(extra[0]) ? `<${extra[0]}>` : undefined;
28
+ // `name` and `score` are spread only when the file actually sets them, which
29
+ // is what lets a column past 10 of the same name through. juicer's bedpe is
30
+ // the case that needs it: Arrowhead writes '.' in both standard columns and
31
+ // its corner score in column 12, also called `score`, so an unconditional
32
+ // positional key shadowed the only column the caller ranks domains by.
20
33
  return new SimpleFeature({
21
34
  ...rest,
22
35
  start: start1,
@@ -24,8 +37,8 @@ export function featureData(line, uniqueId, flip, names) {
24
37
  type: 'paired_feature',
25
38
  refName: ref1,
26
39
  strand: strand1,
27
- name,
28
- score,
40
+ ...(name === undefined ? {} : { name }),
41
+ ...(score === undefined ? {} : { score }),
29
42
  uniqueId,
30
43
  mate: {
31
44
  refName: ref2,
@@ -36,17 +49,3 @@ export function featureData(line, uniqueId, flip, names) {
36
49
  ...(ALT ? { ALT: [ALT] } : {}),
37
50
  });
38
51
  }
39
- function parseStrand(strand) {
40
- if (strand === '+') {
41
- return 1;
42
- }
43
- else if (strand === '-') {
44
- return -1;
45
- }
46
- else if (strand === '.') {
47
- return 0;
48
- }
49
- else {
50
- return undefined;
51
- }
52
- }
@@ -1,32 +1,33 @@
1
1
  import { BigBed } from '@gmod/bbi';
2
2
  import BED from '@gmod/bed';
3
3
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
4
+ import type { BigBedAdapterConfig } from './configSchema.ts';
4
5
  import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
5
6
  import type { Feature } from '@jbrowse/core/util';
6
7
  import type { Region } from '@jbrowse/core/util/types';
7
8
  import type { Observer } from 'rxjs';
8
- export default class BigBedAdapter extends BaseFeatureDataAdapter {
9
- private cachedP?;
10
- configurePre(opts?: BaseOptions): Promise<{
9
+ export default class BigBedAdapter extends BaseFeatureDataAdapter<BigBedAdapterConfig> {
10
+ configure: (opts?: BaseOptions) => Promise<{
11
11
  bigbed: BigBed;
12
12
  header: import("@gmod/bbi").BigWigHeaderWithRefNames;
13
13
  parser: BED;
14
14
  }>;
15
- configure(opts?: BaseOptions): Promise<{
15
+ configurePre(opts?: BaseOptions): Promise<{
16
16
  bigbed: BigBed;
17
- header: Awaited<ReturnType<BigBed["getHeader"]>>;
17
+ header: import("@gmod/bbi").BigWigHeaderWithRefNames;
18
18
  parser: BED;
19
19
  }>;
20
20
  getRefNames(opts?: BaseOptions): Promise<string[]>;
21
+ getRegionByteSize(regions: Region[], opts?: BaseOptions): Promise<number>;
21
22
  getRefNameAliases(opts?: BaseOptions): Promise<{
22
23
  refName: unknown;
23
- aliases: unknown[];
24
+ aliases: string[];
24
25
  override: boolean;
25
26
  }[]>;
26
27
  getData(): Promise<Feature[]>;
27
28
  getHeader(opts?: BaseOptions): Promise<{
28
29
  version: number;
29
- fileType: string;
30
+ fileType: "bigbed" | "bigwig";
30
31
  autoSql: {};
31
32
  fields: {
32
33
  [k: string]: string;
@@ -1,13 +1,20 @@
1
1
  import { BigBed } from '@gmod/bbi';
2
2
  import BED from '@gmod/bed';
3
- import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
4
- import { SimpleFeature, doesIntersect2, max, min, updateStatus, } from '@jbrowse/core/util';
3
+ import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapters/BaseAdapter';
4
+ import { SimpleFeature, doesIntersect2, downloadStatus, max, min, updateStatus, } from '@jbrowse/core/util';
5
5
  import { openLocation } from '@jbrowse/core/util/io';
6
6
  import { ObservableCreate } from '@jbrowse/core/util/rxjs';
7
+ import { withStopTokenSignal } from '@jbrowse/core/util/stopToken';
7
8
  import { firstValueFrom, toArray } from 'rxjs';
8
- import { featureData2 } from "../util.js";
9
+ import { featureData } from '../util.js';
9
10
  export default class BigBedAdapter extends BaseFeatureDataAdapter {
10
- cachedP;
11
+ // `label` on cachedSetup so "Downloading header" shows only while the first
12
+ // fetch is in flight; every later getFeatures resolves from the memo with no
13
+ // re-flash.
14
+ configure = cachedSetup({
15
+ label: 'Downloading header',
16
+ setup: opts => this.configurePre(opts),
17
+ });
11
18
  async configurePre(opts) {
12
19
  const pm = this.pluginManager;
13
20
  const bigbed = new BigBed({
@@ -23,19 +30,19 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
23
30
  parser,
24
31
  };
25
32
  }
26
- async configure(opts) {
27
- if (!this.cachedP) {
28
- this.cachedP = this.configurePre(opts).catch((e) => {
29
- this.cachedP = undefined;
30
- throw e;
31
- });
32
- }
33
- return this.cachedP;
34
- }
35
33
  async getRefNames(opts) {
36
34
  const { header } = await this.configure(opts);
37
35
  return Object.keys(header.refsByName);
38
36
  }
37
+ // Compressed download-size estimate straight from the bbi R-tree index (block
38
+ // byte lengths, no data blocks read), so a display can byte-gate an over-large
39
+ // BigBed fetch before it starts. Overrides the base no-estimate default, which
40
+ // left BigBed tracks ungated.
41
+ async getRegionByteSize(regions, opts) {
42
+ const { bigbed } = await this.configure(opts);
43
+ return bigbed.getRegionByteSizeMulti(regions, opts);
44
+ }
45
+ // allow using BigBedAdapter for aliases with chromAlias.bb file from UCSC
39
46
  async getRefNameAliases(opts) {
40
47
  const { header } = await this.configure(opts);
41
48
  const ret = await Promise.all(Object.keys(header.refsByName).map(async (refName) => (await firstValueFrom(this.getFeatures({
@@ -48,22 +55,20 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
48
55
  .map(r => r.toJSON())
49
56
  .map(r => ({
50
57
  refName: r.ucsc,
51
- aliases: [r.ncbi, r.refseq, r.genbank],
58
+ // chromAlias.bb columns are sparse: a chrom may lack an ncbi/refseq/
59
+ // genbank name, returned as empty strings that are not valid refNames
60
+ aliases: [r.ncbi, r.refseq, r.genbank].filter((alias) => !!alias),
52
61
  override: true,
53
62
  }));
54
63
  }
55
64
  async getData() {
56
65
  const refNames = await this.getRefNames();
57
- const features = [];
58
- for (const refName of refNames) {
59
- const f = await firstValueFrom(this.getFeatures({
60
- assemblyName: 'unknown',
61
- refName,
62
- start: 0,
63
- end: Number.MAX_SAFE_INTEGER,
64
- }).pipe(toArray()));
65
- features.push(f);
66
- }
66
+ const features = await Promise.all(refNames.map(refName => firstValueFrom(this.getFeatures({
67
+ assemblyName: 'unknown',
68
+ refName,
69
+ start: 0,
70
+ end: Number.MAX_SAFE_INTEGER,
71
+ }).pipe(toArray()))));
67
72
  return features.flat();
68
73
  }
69
74
  async getHeader(opts) {
@@ -83,78 +88,69 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
83
88
  return Object.fromEntries(fields.map(({ name, comment }) => [name, comment]));
84
89
  }
85
90
  async getFeaturesHelper({ query, opts, observer, allowRedispatch, originalQuery = query, }) {
86
- const { statusCallback = () => { } } = opts;
91
+ const { statusCallback } = opts;
87
92
  const scoreColumn = this.getConf('scoreColumn');
88
93
  const aggregateField = this.getConf('aggregateField');
89
94
  const disableGeneHeuristic = this.getConf('disableGeneHeuristic');
90
- const { parser, bigbed } = await updateStatus('Downloading header', statusCallback, () => this.configure(opts));
91
- const feats = await updateStatus('Downloading features', statusCallback, () => bigbed.getFeatures(query.refName, query.start, query.end, {
95
+ const { parser, bigbed } = await this.configure(opts);
96
+ // downloadStatus, not updateStatus: bbi knows the total block bytes up front
97
+ // (they come from the R-tree index) so this reports a determinate fraction
98
+ // rather than an indeterminate spinner. Matters most here — BigBed is a
99
+ // full-feature download, which is why getRegionByteSize gates it above.
100
+ const feats = await withStopTokenSignal(opts.stopToken, signal => downloadStatus('Downloading features', statusCallback, onProgress => bigbed.getFeatures(query.refName, query.start, query.end, {
92
101
  basesPerSpan: query.end - query.start,
93
- }));
102
+ onProgress,
103
+ signal,
104
+ })));
94
105
  await updateStatus('Processing features', statusCallback, async () => {
95
106
  const parentAggregation = {};
96
- const parentAggregationFlat = [];
97
- if (feats.some(f => f.uniqueId === undefined)) {
98
- throw new Error('found uniqueId undefined');
99
- }
107
+ const singletons = [];
108
+ let minAggStart = Infinity;
109
+ let maxAggEnd = -Infinity;
100
110
  for (const feat of feats) {
101
- const splitLine = [
102
- query.refName,
103
- `${feat.start}`,
104
- `${feat.end}`,
105
- ...(feat.rest?.split('\t') || []),
106
- ];
107
- const data = parser.parseLine(splitLine, {
108
- uniqueId: feat.uniqueId,
109
- });
110
- const aggr = data[aggregateField];
111
- const aggrIsNotNone = aggr && aggr !== 'none';
112
- const aggrKey = typeof aggr === 'string' ? aggr : `${aggr}`;
113
- if (aggrIsNotNone && !parentAggregation[aggrKey]) {
114
- parentAggregation[aggrKey] = [];
115
- }
116
- const { uniqueId, type, chrom, chromStart, chromEnd, description, chromStarts: chromStarts2, blockStarts: blockStarts2, blockSizes: blockSizes2, score: score2, blockCount, thickStart, thickEnd, strand, ...rest } = data;
117
- const f = featureData2({
118
- ...rest,
111
+ // empty chrom/start/end placeholders keep `rest` columns aligned to the
112
+ // autoSql schema (which begins at chrom); featureData reads back the
113
+ // numeric start/end/refName passed below, so positions 0-2 are unused
114
+ const splitLine = ['', '', '', ...(feat.rest?.split('\t') ?? [])];
115
+ const f = featureData({
119
116
  scoreColumn,
120
117
  splitLine,
121
118
  parser,
122
- uniqueId: uniqueId,
119
+ uniqueId: feat.uniqueId,
123
120
  start: feat.start,
124
121
  end: feat.end,
125
122
  refName: query.refName,
126
123
  disableGeneHeuristic,
127
124
  });
125
+ const aggr = f[aggregateField];
126
+ const aggrIsNotNone = typeof aggr === 'string' && aggr && aggr !== 'none';
128
127
  if (aggrIsNotNone) {
129
- parentAggregation[aggrKey].push(f);
130
- parentAggregationFlat.push(f);
128
+ parentAggregation[aggr] ??= [];
129
+ parentAggregation[aggr].push(f);
130
+ if (f.start < minAggStart) {
131
+ minAggStart = f.start;
132
+ }
133
+ if (f.end > maxAggEnd) {
134
+ maxAggEnd = f.end;
135
+ }
131
136
  }
132
137
  else {
133
- if (doesIntersect2(f.start, f.end, originalQuery.start, originalQuery.end)) {
134
- observer.next(new SimpleFeature({
135
- id: `${this.id}-${uniqueId}`,
136
- data: f,
137
- }));
138
- }
138
+ singletons.push(f);
139
139
  }
140
140
  }
141
- if (allowRedispatch && parentAggregationFlat.length) {
142
- let minStart = Number.POSITIVE_INFINITY;
143
- let maxEnd = Number.NEGATIVE_INFINITY;
144
- for (const feat of parentAggregationFlat) {
145
- if (feat.start < minStart) {
146
- minStart = feat.start;
147
- }
148
- if (feat.end > maxEnd) {
149
- maxEnd = feat.end;
150
- }
151
- }
152
- if (maxEnd > query.end || minStart < query.start) {
141
+ if (allowRedispatch && maxAggEnd > -Infinity) {
142
+ if (maxAggEnd > query.end || minAggStart < query.start) {
143
+ // redispatch re-fetches a superset region, so emitting singletons
144
+ // here too would double-emit them; defer emission to the recursion
153
145
  await this.getFeaturesHelper({
154
146
  query: {
155
147
  ...query,
156
- start: minStart - 500_000,
157
- end: maxEnd + 500_000,
148
+ // extend query to catch gene subfeatures outside the current view;
149
+ // 500 kbp heuristic covers most genes/transcripts. Clamped at 0 so
150
+ // a gene near the start of a contig doesn't produce a negative
151
+ // region start.
152
+ start: Math.max(0, minAggStart - 500_000),
153
+ end: maxAggEnd + 500_000,
158
154
  },
159
155
  opts,
160
156
  observer,
@@ -164,59 +160,56 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
164
160
  return;
165
161
  }
166
162
  }
167
- Object.entries(parentAggregation).map(([name, subfeatures]) => {
168
- const s = min(subfeatures.map(f => f.start));
169
- const e = max(subfeatures.map(f => f.end));
170
- if (doesIntersect2(s, e, originalQuery.start, originalQuery.end)) {
171
- const subs = subfeatures.sort((a, b) => a.uniqueId.localeCompare(b.uniqueId));
172
- if (subs.some((a, i) => subs.some((b, j) => i !== j && doesIntersect2(a.start, a.end, b.start, b.end)))) {
163
+ for (const f of singletons) {
164
+ if (doesIntersect2(f.start, f.end, originalQuery.start, originalQuery.end)) {
165
+ observer.next(new SimpleFeature({
166
+ id: `${this.id}-${f.uniqueId}`,
167
+ data: f,
168
+ }));
169
+ }
170
+ }
171
+ for (const [name, subfeatures] of Object.entries(parentAggregation)) {
172
+ const subs = subfeatures.sort((a, b) => a.uniqueId.localeCompare(b.uniqueId));
173
+ const sortedByStart = [...subs].sort((a, b) => a.start - b.start);
174
+ const hasOverlaps = sortedByStart.some((f, i) => i > 0 && sortedByStart[i - 1].end > f.start);
175
+ // overlapping subs → one gene parent; non-overlapping → one parent per sub
176
+ // (handles bacterial GFF where two genes share a name but are distinct loci)
177
+ const groups = hasOverlaps ? [subs] : subs.map(sub => [sub]);
178
+ for (const group of groups) {
179
+ // gate on each parent's own extent: a distant non-overlapping locus
180
+ // sharing this name must not ride in on a sibling that intersects
181
+ const groupStart = min(group.map(f => f.start));
182
+ const groupEnd = max(group.map(f => f.end));
183
+ if (doesIntersect2(groupStart, groupEnd, originalQuery.start, originalQuery.end)) {
173
184
  observer.next(new SimpleFeature({
174
- id: `${this.id}-${subs[0]?.uniqueId}-parent`,
185
+ id: `${this.id}-${group[0].uniqueId}-parent`,
175
186
  data: {
176
187
  type: 'gene',
177
- subfeatures: subs,
178
- strand: subs[0]?.strand || 1,
188
+ subfeatures: group,
189
+ strand: group.find(f => f.strand !== 0)?.strand ?? 1,
179
190
  name,
180
- start: s,
181
- end: e,
191
+ start: groupStart,
192
+ end: groupEnd,
182
193
  refName: query.refName,
183
194
  },
184
195
  }));
185
196
  }
186
- else {
187
- for (const sub of subs) {
188
- observer.next(new SimpleFeature({
189
- id: `${this.id}-${sub.uniqueId}-parent`,
190
- data: {
191
- type: 'gene',
192
- subfeatures: [sub],
193
- strand: subs[0]?.strand || 1,
194
- name,
195
- start: sub.start,
196
- end: sub.end,
197
- refName: query.refName,
198
- },
199
- }));
200
- }
201
- }
202
197
  }
203
- });
198
+ }
204
199
  });
205
- observer.complete();
206
200
  }
207
201
  getFeatures(query, opts = {}) {
208
202
  return ObservableCreate(async (observer) => {
209
203
  try {
210
204
  await this.getFeaturesHelper({
211
- query: {
212
- ...query,
213
- start: query.start,
214
- end: query.end,
215
- },
205
+ query,
216
206
  opts,
217
207
  observer,
218
208
  allowRedispatch: true,
219
209
  });
210
+ // complete once here, not in the helper: a redispatch recurses into the
211
+ // helper, so completing there would fire complete() twice
212
+ observer.complete();
220
213
  }
221
214
  catch (e) {
222
215
  observer.error(e);
@@ -1,25 +1,60 @@
1
+ import type { Instance } from '@jbrowse/mobx-state-tree';
2
+ export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
3
+ /**
4
+ * #config BigBedAdapter
5
+ * #trackType FeatureTrack
6
+ * #fileFormat feature | BigBed
7
+ *
8
+ * #example
9
+ * ```js
10
+ * {
11
+ * type: 'BigBedAdapter',
12
+ * uri: 'https://example.com/features.bb',
13
+ * }
14
+ * ```
15
+ */
1
16
  declare const BigBedAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
2
- bigBedLocation: {
3
- type: string;
4
- defaultValue: {
5
- uri: string;
6
- locationType: string;
17
+ readonly densityAdapter: {
18
+ readonly type: 'frozen';
19
+ readonly defaultValue: null;
20
+ readonly description: 'optional quantitative sub-adapter (e.g. a BigWigAdapter over a features-per-bin bigWig) drawn as a density band where the region is too large to fetch features; null disables it';
21
+ };
22
+ /**
23
+ * #slot
24
+ * location of the BigBed file. It carries its own index and summary zoom
25
+ * levels, so there is no separate index to configure.
26
+ */
27
+ readonly bigBedLocation: {
28
+ readonly type: "fileLocation";
29
+ readonly defaultValue: {
30
+ readonly uri: "/path/to/my.bb";
31
+ readonly locationType: "UriLocation";
7
32
  };
8
33
  };
9
- scoreColumn: {
10
- type: string;
11
- description: string;
12
- defaultValue: string;
34
+ /**
35
+ * #slot
36
+ */
37
+ readonly scoreColumn: {
38
+ readonly type: "string";
39
+ readonly description: "The column to use as a \"score\" attribute";
40
+ readonly defaultValue: "";
13
41
  };
14
- aggregateField: {
15
- type: string;
16
- description: string;
17
- defaultValue: string;
42
+ /**
43
+ * #slot
44
+ */
45
+ readonly aggregateField: {
46
+ readonly type: "string";
47
+ readonly description: "An attribute to aggregate features with";
48
+ readonly defaultValue: "geneName2";
18
49
  };
19
- disableGeneHeuristic: {
20
- type: string;
21
- description: string;
22
- defaultValue: boolean;
50
+ /**
51
+ * #slot
52
+ */
53
+ readonly disableGeneHeuristic: {
54
+ readonly type: "boolean";
55
+ readonly description: "Disable the heuristic that auto-detects BED12 features as gene/transcript structures. Useful for files that have BED12-like structure but are not genes (e.g. tandem duplications)";
56
+ readonly defaultValue: false;
23
57
  };
24
58
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
59
+ export type BigBedAdapterConfig = Instance<typeof BigBedAdapter>;
25
60
  export default BigBedAdapter;