@jbrowse/plugin-gtf 3.0.0 → 3.0.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.
@@ -2,7 +2,8 @@ import IntervalTree from '@flatten-js/interval-tree';
2
2
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
3
3
  import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
4
4
  import type { Feature } from '@jbrowse/core/util';
5
- import type { NoAssemblyRegion } from '@jbrowse/core/util/types';
5
+ import type { Region } from '@jbrowse/core/util/types';
6
+ import type { Observer } from 'rxjs';
6
7
  type StatusCallback = (arg: string) => void;
7
8
  export default class GtfAdapter extends BaseFeatureDataAdapter {
8
9
  calculatedIntervalTreeMap: Record<string, IntervalTree>;
@@ -14,7 +15,14 @@ export default class GtfAdapter extends BaseFeatureDataAdapter {
14
15
  private loadData;
15
16
  getRefNames(opts?: BaseOptions): Promise<string[]>;
16
17
  getHeader(opts?: BaseOptions): Promise<string>;
17
- getFeatures(query: NoAssemblyRegion, opts?: BaseOptions): import("rxjs").Observable<Feature>;
18
+ getFeatures(query: Region, opts?: BaseOptions): import("rxjs").Observable<Feature>;
19
+ getFeaturesHelper({ query, opts, observer, allowRedispatch, originalQuery, }: {
20
+ query: Region;
21
+ opts: BaseOptions;
22
+ observer: Observer<Feature>;
23
+ allowRedispatch: boolean;
24
+ originalQuery?: Region;
25
+ }): Promise<void>;
18
26
  freeResources(): void;
19
27
  }
20
28
  export {};
@@ -57,11 +57,8 @@ class GtfAdapter extends BaseAdapter_1.BaseFeatureDataAdapter {
57
57
  const intervalTree = new interval_tree_1.default();
58
58
  (0, gtf_nostream_1.parseStringSync)(lines)
59
59
  .flat()
60
- .map((f, i) => new util_1.SimpleFeature({
61
- data: (0, util_2.featureData)(f),
62
- id: `${this.id}-${refName}-${i}`,
63
- }))
64
- .forEach(obj => intervalTree.insert([obj.get('start'), obj.get('end')], obj));
60
+ .map((f, i) => (0, util_2.featureData)(f, `${this.id}-${refName}-${i}`))
61
+ .forEach(obj => intervalTree.insert([obj.start, obj.end], obj));
65
62
  this.calculatedIntervalTreeMap[refName] = intervalTree;
66
63
  }
67
64
  return this.calculatedIntervalTreeMap[refName];
@@ -91,20 +88,101 @@ class GtfAdapter extends BaseAdapter_1.BaseFeatureDataAdapter {
91
88
  }
92
89
  getFeatures(query, opts = {}) {
93
90
  return (0, rxjs_1.ObservableCreate)(async (observer) => {
94
- var _a;
95
91
  try {
96
- const { start, end, refName } = query;
97
- const { intervalTreeMap } = await this.loadData(opts);
98
- (_a = intervalTreeMap[refName]) === null || _a === void 0 ? void 0 : _a.call(intervalTreeMap, opts.statusCallback).search([start, end]).forEach(f => {
99
- observer.next(f);
92
+ await this.getFeaturesHelper({
93
+ query,
94
+ opts,
95
+ observer,
96
+ allowRedispatch: true,
100
97
  });
101
- observer.complete();
102
98
  }
103
99
  catch (e) {
104
100
  observer.error(e);
105
101
  }
106
102
  }, opts.stopToken);
107
103
  }
104
+ async getFeaturesHelper({ query, opts, observer, allowRedispatch, originalQuery = query, }) {
105
+ var _a;
106
+ const aggregateField = this.getConf('aggregateField');
107
+ const { start, end, refName } = query;
108
+ const { intervalTreeMap } = await this.loadData(opts);
109
+ const feats = (_a = intervalTreeMap[refName]) === null || _a === void 0 ? void 0 : _a.call(intervalTreeMap, opts.statusCallback).search([
110
+ start,
111
+ end,
112
+ ]);
113
+ if (feats) {
114
+ if (allowRedispatch && feats.length) {
115
+ let minStart = Number.POSITIVE_INFINITY;
116
+ let maxEnd = Number.NEGATIVE_INFINITY;
117
+ let hasAnyAggregateField = false;
118
+ for (const feat of feats) {
119
+ if (feat.start < minStart) {
120
+ minStart = feat.start;
121
+ }
122
+ if (feat.end > maxEnd) {
123
+ maxEnd = feat.end;
124
+ }
125
+ if (feat[aggregateField]) {
126
+ hasAnyAggregateField = true;
127
+ }
128
+ }
129
+ if (hasAnyAggregateField &&
130
+ (maxEnd > query.end || minStart < query.start)) {
131
+ await this.getFeaturesHelper({
132
+ query: {
133
+ ...query,
134
+ start: minStart,
135
+ end: maxEnd,
136
+ },
137
+ opts,
138
+ observer,
139
+ allowRedispatch: false,
140
+ originalQuery: query,
141
+ });
142
+ return;
143
+ }
144
+ }
145
+ const parentAggregation = {};
146
+ if (feats.some(f => f.uniqueId === undefined)) {
147
+ throw new Error('found uniqueId undefined');
148
+ }
149
+ for (const feat of feats) {
150
+ const aggr = feat[aggregateField];
151
+ if (!parentAggregation[aggr]) {
152
+ parentAggregation[aggr] = [];
153
+ }
154
+ if (aggr) {
155
+ parentAggregation[aggr].push(feat);
156
+ }
157
+ else {
158
+ observer.next(new util_1.SimpleFeature({
159
+ id: feat.uniqueId,
160
+ data: feat,
161
+ }));
162
+ }
163
+ }
164
+ Object.entries(parentAggregation).map(([name, subfeatures]) => {
165
+ const s = (0, util_1.min)(subfeatures.map(f => f.start));
166
+ const e = (0, util_1.max)(subfeatures.map(f => f.end));
167
+ if ((0, util_1.doesIntersect2)(s, e, originalQuery.start, originalQuery.end)) {
168
+ const { uniqueId, strand } = subfeatures[0];
169
+ observer.next(new util_1.SimpleFeature({
170
+ id: `${uniqueId}-parent`,
171
+ data: {
172
+ type: 'gene',
173
+ subfeatures,
174
+ strand,
175
+ name,
176
+ start: s,
177
+ end: e,
178
+ refName: query.refName,
179
+ },
180
+ }));
181
+ }
182
+ });
183
+ }
184
+ observer.complete();
185
+ }
108
186
  freeResources() { }
109
187
  }
110
188
  exports.default = GtfAdapter;
@@ -6,5 +6,9 @@ declare const GtfAdapter: import("@jbrowse/core/configuration/configurationSchem
6
6
  locationType: string;
7
7
  };
8
8
  };
9
+ aggregateField: {
10
+ type: string;
11
+ defaultValue: string;
12
+ };
9
13
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
10
14
  export default GtfAdapter;
@@ -7,5 +7,9 @@ const GtfAdapter = (0, configuration_1.ConfigurationSchema)('GtfAdapter', {
7
7
  type: 'fileLocation',
8
8
  defaultValue: { uri: '/path/to/my.gtf', locationType: 'UriLocation' },
9
9
  },
10
+ aggregateField: {
11
+ type: 'string',
12
+ defaultValue: 'gene_name',
13
+ },
10
14
  }, { explicitlyTyped: true });
11
15
  exports.default = GtfAdapter;
package/dist/util.d.ts CHANGED
@@ -10,4 +10,4 @@ export interface FeatureLoc {
10
10
  derived_features: unknown;
11
11
  attributes: Record<string, unknown[]>;
12
12
  }
13
- export declare function featureData(data: FeatureLoc): Record<string, unknown>;
13
+ export declare function featureData(data: FeatureLoc, id?: string): Record<string, unknown>;
package/dist/util.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.featureData = featureData;
4
- function featureData(data) {
4
+ function featureData(data, id) {
5
5
  const f = { ...data };
6
6
  f.start -= 1;
7
7
  f.strand = { '+': 1, '-': -1, '.': 0, '?': undefined }[data.strand];
@@ -52,5 +52,8 @@ function featureData(data) {
52
52
  if (f.transcript_id) {
53
53
  f.name = f.transcript_id;
54
54
  }
55
+ if (id !== undefined) {
56
+ f.uniqueId = id;
57
+ }
55
58
  return f;
56
59
  }
@@ -2,7 +2,8 @@ import IntervalTree from '@flatten-js/interval-tree';
2
2
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
3
3
  import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
4
4
  import type { Feature } from '@jbrowse/core/util';
5
- import type { NoAssemblyRegion } from '@jbrowse/core/util/types';
5
+ import type { Region } from '@jbrowse/core/util/types';
6
+ import type { Observer } from 'rxjs';
6
7
  type StatusCallback = (arg: string) => void;
7
8
  export default class GtfAdapter extends BaseFeatureDataAdapter {
8
9
  calculatedIntervalTreeMap: Record<string, IntervalTree>;
@@ -14,7 +15,14 @@ export default class GtfAdapter extends BaseFeatureDataAdapter {
14
15
  private loadData;
15
16
  getRefNames(opts?: BaseOptions): Promise<string[]>;
16
17
  getHeader(opts?: BaseOptions): Promise<string>;
17
- getFeatures(query: NoAssemblyRegion, opts?: BaseOptions): import("rxjs").Observable<Feature>;
18
+ getFeatures(query: Region, opts?: BaseOptions): import("rxjs").Observable<Feature>;
19
+ getFeaturesHelper({ query, opts, observer, allowRedispatch, originalQuery, }: {
20
+ query: Region;
21
+ opts: BaseOptions;
22
+ observer: Observer<Feature>;
23
+ allowRedispatch: boolean;
24
+ originalQuery?: Region;
25
+ }): Promise<void>;
18
26
  freeResources(): void;
19
27
  }
20
28
  export {};
@@ -1,6 +1,6 @@
1
1
  import IntervalTree from '@flatten-js/interval-tree';
2
2
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
3
- import { SimpleFeature, fetchAndMaybeUnzip } from '@jbrowse/core/util';
3
+ import { SimpleFeature, doesIntersect2, fetchAndMaybeUnzip, max, min, } from '@jbrowse/core/util';
4
4
  import { openLocation } from '@jbrowse/core/util/io';
5
5
  import { ObservableCreate } from '@jbrowse/core/util/rxjs';
6
6
  import { parseStringSync } from 'gtf-nostream';
@@ -52,11 +52,8 @@ export default class GtfAdapter extends BaseFeatureDataAdapter {
52
52
  const intervalTree = new IntervalTree();
53
53
  parseStringSync(lines)
54
54
  .flat()
55
- .map((f, i) => new SimpleFeature({
56
- data: featureData(f),
57
- id: `${this.id}-${refName}-${i}`,
58
- }))
59
- .forEach(obj => intervalTree.insert([obj.get('start'), obj.get('end')], obj));
55
+ .map((f, i) => featureData(f, `${this.id}-${refName}-${i}`))
56
+ .forEach(obj => intervalTree.insert([obj.start, obj.end], obj));
60
57
  this.calculatedIntervalTreeMap[refName] = intervalTree;
61
58
  }
62
59
  return this.calculatedIntervalTreeMap[refName];
@@ -86,19 +83,100 @@ export default class GtfAdapter extends BaseFeatureDataAdapter {
86
83
  }
87
84
  getFeatures(query, opts = {}) {
88
85
  return ObservableCreate(async (observer) => {
89
- var _a;
90
86
  try {
91
- const { start, end, refName } = query;
92
- const { intervalTreeMap } = await this.loadData(opts);
93
- (_a = intervalTreeMap[refName]) === null || _a === void 0 ? void 0 : _a.call(intervalTreeMap, opts.statusCallback).search([start, end]).forEach(f => {
94
- observer.next(f);
87
+ await this.getFeaturesHelper({
88
+ query,
89
+ opts,
90
+ observer,
91
+ allowRedispatch: true,
95
92
  });
96
- observer.complete();
97
93
  }
98
94
  catch (e) {
99
95
  observer.error(e);
100
96
  }
101
97
  }, opts.stopToken);
102
98
  }
99
+ async getFeaturesHelper({ query, opts, observer, allowRedispatch, originalQuery = query, }) {
100
+ var _a;
101
+ const aggregateField = this.getConf('aggregateField');
102
+ const { start, end, refName } = query;
103
+ const { intervalTreeMap } = await this.loadData(opts);
104
+ const feats = (_a = intervalTreeMap[refName]) === null || _a === void 0 ? void 0 : _a.call(intervalTreeMap, opts.statusCallback).search([
105
+ start,
106
+ end,
107
+ ]);
108
+ if (feats) {
109
+ if (allowRedispatch && feats.length) {
110
+ let minStart = Number.POSITIVE_INFINITY;
111
+ let maxEnd = Number.NEGATIVE_INFINITY;
112
+ let hasAnyAggregateField = false;
113
+ for (const feat of feats) {
114
+ if (feat.start < minStart) {
115
+ minStart = feat.start;
116
+ }
117
+ if (feat.end > maxEnd) {
118
+ maxEnd = feat.end;
119
+ }
120
+ if (feat[aggregateField]) {
121
+ hasAnyAggregateField = true;
122
+ }
123
+ }
124
+ if (hasAnyAggregateField &&
125
+ (maxEnd > query.end || minStart < query.start)) {
126
+ await this.getFeaturesHelper({
127
+ query: {
128
+ ...query,
129
+ start: minStart,
130
+ end: maxEnd,
131
+ },
132
+ opts,
133
+ observer,
134
+ allowRedispatch: false,
135
+ originalQuery: query,
136
+ });
137
+ return;
138
+ }
139
+ }
140
+ const parentAggregation = {};
141
+ if (feats.some(f => f.uniqueId === undefined)) {
142
+ throw new Error('found uniqueId undefined');
143
+ }
144
+ for (const feat of feats) {
145
+ const aggr = feat[aggregateField];
146
+ if (!parentAggregation[aggr]) {
147
+ parentAggregation[aggr] = [];
148
+ }
149
+ if (aggr) {
150
+ parentAggregation[aggr].push(feat);
151
+ }
152
+ else {
153
+ observer.next(new SimpleFeature({
154
+ id: feat.uniqueId,
155
+ data: feat,
156
+ }));
157
+ }
158
+ }
159
+ Object.entries(parentAggregation).map(([name, subfeatures]) => {
160
+ const s = min(subfeatures.map(f => f.start));
161
+ const e = max(subfeatures.map(f => f.end));
162
+ if (doesIntersect2(s, e, originalQuery.start, originalQuery.end)) {
163
+ const { uniqueId, strand } = subfeatures[0];
164
+ observer.next(new SimpleFeature({
165
+ id: `${uniqueId}-parent`,
166
+ data: {
167
+ type: 'gene',
168
+ subfeatures,
169
+ strand,
170
+ name,
171
+ start: s,
172
+ end: e,
173
+ refName: query.refName,
174
+ },
175
+ }));
176
+ }
177
+ });
178
+ }
179
+ observer.complete();
180
+ }
103
181
  freeResources() { }
104
182
  }
@@ -6,5 +6,9 @@ declare const GtfAdapter: import("@jbrowse/core/configuration/configurationSchem
6
6
  locationType: string;
7
7
  };
8
8
  };
9
+ aggregateField: {
10
+ type: string;
11
+ defaultValue: string;
12
+ };
9
13
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
10
14
  export default GtfAdapter;
@@ -5,5 +5,9 @@ const GtfAdapter = ConfigurationSchema('GtfAdapter', {
5
5
  type: 'fileLocation',
6
6
  defaultValue: { uri: '/path/to/my.gtf', locationType: 'UriLocation' },
7
7
  },
8
+ aggregateField: {
9
+ type: 'string',
10
+ defaultValue: 'gene_name',
11
+ },
8
12
  }, { explicitlyTyped: true });
9
13
  export default GtfAdapter;
package/esm/util.d.ts CHANGED
@@ -10,4 +10,4 @@ export interface FeatureLoc {
10
10
  derived_features: unknown;
11
11
  attributes: Record<string, unknown[]>;
12
12
  }
13
- export declare function featureData(data: FeatureLoc): Record<string, unknown>;
13
+ export declare function featureData(data: FeatureLoc, id?: string): Record<string, unknown>;
package/esm/util.js CHANGED
@@ -1,4 +1,4 @@
1
- export function featureData(data) {
1
+ export function featureData(data, id) {
2
2
  const f = { ...data };
3
3
  f.start -= 1;
4
4
  f.strand = { '+': 1, '-': -1, '.': 0, '?': undefined }[data.strand];
@@ -49,5 +49,8 @@ export function featureData(data) {
49
49
  if (f.transcript_id) {
50
50
  f.name = f.transcript_id;
51
51
  }
52
+ if (id !== undefined) {
53
+ f.uniqueId = id;
54
+ }
52
55
  return f;
53
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-gtf",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "JBrowse 2 gtf feature adapter",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -38,23 +38,23 @@
38
38
  "dependencies": {
39
39
  "@flatten-js/interval-tree": "^1.0.15",
40
40
  "@gmod/bgzf-filehandle": "^2.0.1",
41
- "gtf-nostream": "^1.0.0"
42
- },
43
- "peerDependencies": {
44
- "@jbrowse/core": "^2.0.0",
45
- "@jbrowse/plugin-linear-genome-view": "^2.0.0",
41
+ "@jbrowse/core": "^3.0.2",
42
+ "@jbrowse/plugin-linear-genome-view": "^3.0.2",
46
43
  "@mui/material": "^6.0.0",
44
+ "gtf-nostream": "^1.0.0",
47
45
  "mobx": "^6.0.0",
48
46
  "mobx-react": "^9.0.0",
49
47
  "mobx-state-tree": "^5.0.0",
50
- "react": ">=16.8.0",
51
48
  "rxjs": "^7.0.0"
52
49
  },
50
+ "peerDependencies": {
51
+ "react": ">=18.0.0"
52
+ },
53
53
  "publishConfig": {
54
54
  "access": "public"
55
55
  },
56
56
  "distModule": "esm/index.js",
57
57
  "srcModule": "src/index.ts",
58
58
  "module": "esm/index.js",
59
- "gitHead": "2c6897f1fa732b1db5b094d1dca197e333e95319"
59
+ "gitHead": "c01a35edcb2612e94661af8793f09c95c0b13c75"
60
60
  }