@apollo-annotation/mst 0.1.17 → 0.1.19
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/package.json +4 -3
- package/src/AnnotationFeatureModel.ts +293 -0
- package/src/ApolloAssembly.ts +12 -2
- package/src/ApolloRefSeq.ts +9 -5
- package/src/CheckResult.ts +8 -4
- package/src/index.ts +1 -1
- package/src/AnnotationFeature.ts +0 -243
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollo-annotation/mst",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsc"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@jbrowse/core": "^2.
|
|
9
|
+
"@jbrowse/core": "^2.13.1",
|
|
10
10
|
"mobx": "^6.6.1",
|
|
11
11
|
"mobx-state-tree": "^5.1.7",
|
|
12
|
+
"react-dom": "^18.2.0",
|
|
12
13
|
"rxjs": "^7.4.0",
|
|
13
14
|
"tslib": "^2.3.1"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
|
16
|
-
"typescript": "^5.
|
|
17
|
+
"typescript": "^5.5.3"
|
|
17
18
|
},
|
|
18
19
|
"publishConfig": {
|
|
19
20
|
"access": "public"
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { intersection2 } from '@jbrowse/core/util'
|
|
2
|
+
import {
|
|
3
|
+
IAnyModelType,
|
|
4
|
+
IMSTMap,
|
|
5
|
+
Instance,
|
|
6
|
+
SnapshotIn,
|
|
7
|
+
SnapshotOrInstance,
|
|
8
|
+
cast,
|
|
9
|
+
getParentOfType,
|
|
10
|
+
getSnapshot,
|
|
11
|
+
types,
|
|
12
|
+
} from 'mobx-state-tree'
|
|
13
|
+
|
|
14
|
+
import { ApolloAssembly } from '.'
|
|
15
|
+
|
|
16
|
+
const LateAnnotationFeature = types.late(
|
|
17
|
+
(): IAnyModelType => AnnotationFeatureModel,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
export const AnnotationFeatureModel = types
|
|
21
|
+
.model('AnnotationFeatureModel', {
|
|
22
|
+
_id: types.identifier,
|
|
23
|
+
/** Unique ID of the reference sequence on which this feature is located */
|
|
24
|
+
refSeq: types.string,
|
|
25
|
+
/**
|
|
26
|
+
* Type of feature. Can be any string, but is usually an ontology term,
|
|
27
|
+
* e.g. "gene" from the
|
|
28
|
+
* {@link http://sequenceontology.org/browser/current_release/term/SO:0000704 |Sequence Ontology}.
|
|
29
|
+
*/
|
|
30
|
+
type: types.string,
|
|
31
|
+
/**
|
|
32
|
+
* Coordinate of the edge of the feature that is closer to the beginning of
|
|
33
|
+
* the reference sequence. This can be thought of as the "start" of features
|
|
34
|
+
* on the positive strand. Uses interbase (0-based half-open) coordinates.
|
|
35
|
+
*/
|
|
36
|
+
min: types.number,
|
|
37
|
+
/**
|
|
38
|
+
* Coordinate of the edge of the feature that is closer to the end of the
|
|
39
|
+
* reference sequence. This can be thought of as the "end" of features on
|
|
40
|
+
* the positive strand. Uses interbase (0-based half-open) coordinates.
|
|
41
|
+
*/
|
|
42
|
+
max: types.number,
|
|
43
|
+
/**
|
|
44
|
+
* The strand on which the feature is located. `+1` for the positive (a.k.a.
|
|
45
|
+
* plus or forward) and `-1` for the negative (a.k.a minus or reverse)
|
|
46
|
+
* strand.
|
|
47
|
+
*/
|
|
48
|
+
strand: types.maybe(types.union(types.literal(1), types.literal(-1))),
|
|
49
|
+
/** Child features of this feature */
|
|
50
|
+
children: types.maybe(types.map(LateAnnotationFeature)),
|
|
51
|
+
/**
|
|
52
|
+
* Additional attributes of the feature. This could include name, source,
|
|
53
|
+
* note, dbxref, etc.
|
|
54
|
+
*/
|
|
55
|
+
attributes: types.map(types.array(types.string)),
|
|
56
|
+
})
|
|
57
|
+
.views((self) => ({
|
|
58
|
+
get length() {
|
|
59
|
+
return self.max - self.min
|
|
60
|
+
},
|
|
61
|
+
get featureId() {
|
|
62
|
+
return self.attributes.get('id')
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* Possibly different from `min` because "The GFF3 format does not enforce a
|
|
66
|
+
* rule in which features must be wholly contained within the location of
|
|
67
|
+
* their parents"
|
|
68
|
+
*/
|
|
69
|
+
get minWithChildren() {
|
|
70
|
+
let { min } = self
|
|
71
|
+
const children = self.children as Children
|
|
72
|
+
if (!children) {
|
|
73
|
+
return min
|
|
74
|
+
}
|
|
75
|
+
for (const [, child] of children) {
|
|
76
|
+
min = Math.min(min, child.min)
|
|
77
|
+
}
|
|
78
|
+
return min
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
* Possibly different from `max` because "The GFF3 format does not enforce a
|
|
82
|
+
* rule in which features must be wholly contained within the location of
|
|
83
|
+
* their parents"
|
|
84
|
+
*/
|
|
85
|
+
get maxWithChildren() {
|
|
86
|
+
let { max } = self
|
|
87
|
+
const children = self.children as Children
|
|
88
|
+
if (!children) {
|
|
89
|
+
return max
|
|
90
|
+
}
|
|
91
|
+
for (const [, child] of children) {
|
|
92
|
+
max = Math.max(max, child.max)
|
|
93
|
+
}
|
|
94
|
+
return max
|
|
95
|
+
},
|
|
96
|
+
hasDescendant(featureId: string) {
|
|
97
|
+
const children = self.children as Children
|
|
98
|
+
if (!children) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
for (const [id, child] of children) {
|
|
102
|
+
if (id === featureId) {
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
if (child.hasDescendant(featureId)) {
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
},
|
|
111
|
+
get cdsLocations(): { min: number; max: number; phase: 0 | 1 | 2 }[][] {
|
|
112
|
+
if (self.type !== 'mRNA') {
|
|
113
|
+
throw new Error(
|
|
114
|
+
'Only features of type "mRNA" or equivalent can calculate CDS locations',
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
const children = self.children as Children
|
|
118
|
+
if (!children) {
|
|
119
|
+
throw new Error('no CDS or exons in mRNA')
|
|
120
|
+
}
|
|
121
|
+
const cdsChildren = [...children.values()].filter(
|
|
122
|
+
(child) => child.type === 'CDS',
|
|
123
|
+
)
|
|
124
|
+
if (cdsChildren.length === 0) {
|
|
125
|
+
throw new Error('no CDS in mRNA')
|
|
126
|
+
}
|
|
127
|
+
const cdsLocations: { min: number; max: number; phase: 0 | 1 | 2 }[][] =
|
|
128
|
+
[]
|
|
129
|
+
for (const cds of cdsChildren) {
|
|
130
|
+
const { max: cdsMax, min: cdsMin } = cds
|
|
131
|
+
const locs: { min: number; max: number }[] = []
|
|
132
|
+
for (const [, child] of children) {
|
|
133
|
+
if (child.type !== 'exon') {
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
const [start, end] = intersection2(
|
|
137
|
+
cdsMin,
|
|
138
|
+
cdsMax,
|
|
139
|
+
child.min,
|
|
140
|
+
child.max,
|
|
141
|
+
)
|
|
142
|
+
if (start !== undefined && end !== undefined) {
|
|
143
|
+
locs.push({ min: start, max: end })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
locs.sort(({ min: a }, { min: b }) => a - b)
|
|
147
|
+
if (self.strand === -1) {
|
|
148
|
+
locs.reverse()
|
|
149
|
+
}
|
|
150
|
+
let nextPhase: 0 | 1 | 2 = 0
|
|
151
|
+
const phasedLocs = locs.map((loc) => {
|
|
152
|
+
const phase = nextPhase
|
|
153
|
+
nextPhase = ((3 - ((loc.max - loc.min - phase + 3) % 3)) % 3) as
|
|
154
|
+
| 0
|
|
155
|
+
| 1
|
|
156
|
+
| 2
|
|
157
|
+
return { ...loc, phase }
|
|
158
|
+
})
|
|
159
|
+
cdsLocations.push(phasedLocs)
|
|
160
|
+
}
|
|
161
|
+
return cdsLocations
|
|
162
|
+
},
|
|
163
|
+
}))
|
|
164
|
+
.actions((self) => ({
|
|
165
|
+
setAttributes(attributes: Map<string, string[]>) {
|
|
166
|
+
self.attributes.clear()
|
|
167
|
+
for (const [key, value] of attributes.entries()) {
|
|
168
|
+
self.attributes.set(key, value)
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
setAttribute(key: string, value: string[]) {
|
|
172
|
+
self.attributes.merge({ [key]: value })
|
|
173
|
+
},
|
|
174
|
+
setType(type: string) {
|
|
175
|
+
self.type = type
|
|
176
|
+
},
|
|
177
|
+
setRefSeq(refSeq: string) {
|
|
178
|
+
self.refSeq = refSeq
|
|
179
|
+
},
|
|
180
|
+
setMin(min: number) {
|
|
181
|
+
if (min > self.max) {
|
|
182
|
+
throw new Error(`Min "${min}" is greater than max "${self.max}"`)
|
|
183
|
+
}
|
|
184
|
+
if (self.min !== min) {
|
|
185
|
+
self.min = min
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
setMax(max: number) {
|
|
189
|
+
if (max < self.min) {
|
|
190
|
+
throw new Error(`Max "${max}" is less than Min "${self.min}"`)
|
|
191
|
+
}
|
|
192
|
+
if (self.max !== max) {
|
|
193
|
+
self.max = max
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
setStrand(strand?: 1 | -1 | undefined) {
|
|
197
|
+
self.strand = strand
|
|
198
|
+
},
|
|
199
|
+
addChild(childFeature: AnnotationFeatureSnapshot) {
|
|
200
|
+
if (self.children && self.children.size > 0) {
|
|
201
|
+
const existingChildren = getSnapshot<
|
|
202
|
+
Record<string, AnnotationFeatureSnapshot>
|
|
203
|
+
>(self.children)
|
|
204
|
+
self.children.clear()
|
|
205
|
+
for (const [, child] of Object.entries({
|
|
206
|
+
...existingChildren,
|
|
207
|
+
[childFeature._id]: childFeature,
|
|
208
|
+
}).sort(([, a], [, b]) => a.min - b.min)) {
|
|
209
|
+
self.children.put(child)
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
self.children = cast({})
|
|
213
|
+
self.children?.put(childFeature)
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
deleteChild(childFeatureId: string) {
|
|
217
|
+
self.children?.delete(childFeatureId)
|
|
218
|
+
},
|
|
219
|
+
}))
|
|
220
|
+
.actions((self) => ({
|
|
221
|
+
update({
|
|
222
|
+
children,
|
|
223
|
+
max,
|
|
224
|
+
min,
|
|
225
|
+
refSeq,
|
|
226
|
+
strand,
|
|
227
|
+
}: {
|
|
228
|
+
refSeq: string
|
|
229
|
+
min: number
|
|
230
|
+
max: number
|
|
231
|
+
strand?: 1 | -1
|
|
232
|
+
children?: SnapshotOrInstance<typeof self.children>
|
|
233
|
+
}) {
|
|
234
|
+
self.setRefSeq(refSeq)
|
|
235
|
+
self.setMin(min)
|
|
236
|
+
self.setMax(max)
|
|
237
|
+
self.setStrand(strand)
|
|
238
|
+
if (children) {
|
|
239
|
+
self.children = cast(children)
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
}))
|
|
243
|
+
// This views block has to be last to avoid:
|
|
244
|
+
// "'parent' is referenced directly or indirectly in its own type annotation."
|
|
245
|
+
.views((self) => ({
|
|
246
|
+
get parent(): AnnotationFeature | undefined {
|
|
247
|
+
let parent: AnnotationFeature | undefined
|
|
248
|
+
try {
|
|
249
|
+
parent = getParentOfType(self, AnnotationFeatureModel)
|
|
250
|
+
} catch {
|
|
251
|
+
// pass
|
|
252
|
+
}
|
|
253
|
+
return parent
|
|
254
|
+
},
|
|
255
|
+
get topLevelFeature(): AnnotationFeature {
|
|
256
|
+
let feature = self
|
|
257
|
+
let parent
|
|
258
|
+
do {
|
|
259
|
+
try {
|
|
260
|
+
parent = getParentOfType(feature, AnnotationFeatureModel)
|
|
261
|
+
feature = parent
|
|
262
|
+
} catch {
|
|
263
|
+
parent = undefined
|
|
264
|
+
}
|
|
265
|
+
} while (parent)
|
|
266
|
+
return feature as AnnotationFeature
|
|
267
|
+
},
|
|
268
|
+
get assemblyId(): string {
|
|
269
|
+
return getParentOfType(self, ApolloAssembly)._id
|
|
270
|
+
},
|
|
271
|
+
}))
|
|
272
|
+
|
|
273
|
+
export type Children = IMSTMap<typeof AnnotationFeatureModel> | undefined
|
|
274
|
+
|
|
275
|
+
// eslint disables because of
|
|
276
|
+
// https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
|
|
277
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
278
|
+
interface AnnotationFeatureRaw
|
|
279
|
+
extends Instance<typeof AnnotationFeatureModel> {}
|
|
280
|
+
// This type isn't exactly right, since "children" is actually an IMSTMap and
|
|
281
|
+
// not a Map, but it's better than typing it as any.
|
|
282
|
+
export interface AnnotationFeature
|
|
283
|
+
extends Omit<AnnotationFeatureRaw, 'children'> {
|
|
284
|
+
children?: Map<string, AnnotationFeature>
|
|
285
|
+
}
|
|
286
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
287
|
+
interface AnnotationFeatureSnapshotRaw
|
|
288
|
+
extends SnapshotIn<typeof AnnotationFeatureModel> {}
|
|
289
|
+
export interface AnnotationFeatureSnapshot
|
|
290
|
+
extends AnnotationFeatureSnapshotRaw {
|
|
291
|
+
/** Child features of this feature */
|
|
292
|
+
children?: Record<string, AnnotationFeatureSnapshot>
|
|
293
|
+
}
|
package/src/ApolloAssembly.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { Instance, SnapshotIn, types } from 'mobx-state-tree'
|
|
|
2
2
|
|
|
3
3
|
import { ApolloRefSeq } from './ApolloRefSeq'
|
|
4
4
|
|
|
5
|
+
export type BackendDriverType =
|
|
6
|
+
| 'CollaborationServerDriver'
|
|
7
|
+
| 'InMemoryFileDriver'
|
|
8
|
+
| 'DesktopFileDriver'
|
|
9
|
+
|
|
5
10
|
export const ApolloAssembly = types
|
|
6
11
|
.model('ApolloAssembly', {
|
|
7
12
|
_id: types.identifier,
|
|
@@ -30,5 +35,10 @@ export const ApolloAssembly = types
|
|
|
30
35
|
},
|
|
31
36
|
}))
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
// eslint disables because of
|
|
39
|
+
// https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
41
|
+
export interface ApolloAssemblyI extends Instance<typeof ApolloAssembly> {}
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
43
|
+
export interface ApolloAssemblySnapshot
|
|
44
|
+
extends SnapshotIn<typeof ApolloAssembly> {}
|
package/src/ApolloRefSeq.ts
CHANGED
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
} from 'mobx-state-tree'
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
AnnotationFeatureModel,
|
|
11
11
|
AnnotationFeatureSnapshot,
|
|
12
|
-
} from './
|
|
12
|
+
} from './AnnotationFeatureModel'
|
|
13
13
|
|
|
14
14
|
export const Sequence = types.model({
|
|
15
15
|
start: types.number,
|
|
@@ -28,7 +28,7 @@ export const ApolloRefSeq = types
|
|
|
28
28
|
_id: types.identifier,
|
|
29
29
|
name: types.string,
|
|
30
30
|
description: '',
|
|
31
|
-
features: types.map(
|
|
31
|
+
features: types.map(AnnotationFeatureModel),
|
|
32
32
|
sequence: types.array(Sequence),
|
|
33
33
|
})
|
|
34
34
|
.actions((self) => ({
|
|
@@ -112,5 +112,9 @@ export const ApolloRefSeq = types
|
|
|
112
112
|
},
|
|
113
113
|
}))
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
// eslint disables because of
|
|
116
|
+
// https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
|
|
117
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
118
|
+
export interface ApolloRefSeqI extends Instance<typeof ApolloRefSeq> {}
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
120
|
+
export interface ApolloRefSeqSnapshot extends SnapshotIn<typeof ApolloRefSeq> {}
|
package/src/CheckResult.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Instance, SnapshotIn, types } from 'mobx-state-tree'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { AnnotationFeatureModel } from './AnnotationFeatureModel'
|
|
4
4
|
|
|
5
5
|
export const CheckResult = types.model('CheckResult', {
|
|
6
6
|
_id: types.identifier,
|
|
7
7
|
name: types.string,
|
|
8
|
-
ids: types.array(types.safeReference(
|
|
8
|
+
ids: types.array(types.safeReference(AnnotationFeatureModel)),
|
|
9
9
|
refSeq: types.string,
|
|
10
10
|
start: types.number,
|
|
11
11
|
end: types.number,
|
|
@@ -13,5 +13,9 @@ export const CheckResult = types.model('CheckResult', {
|
|
|
13
13
|
message: types.string,
|
|
14
14
|
})
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// eslint disables because of
|
|
17
|
+
// https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
19
|
+
export interface CheckResultI extends Instance<typeof CheckResult> {}
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
21
|
+
export interface CheckResultSnapshot extends SnapshotIn<typeof CheckResult> {}
|
package/src/index.ts
CHANGED
package/src/AnnotationFeature.ts
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
4
|
-
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
5
|
-
import {
|
|
6
|
-
IAnyModelType,
|
|
7
|
-
Instance,
|
|
8
|
-
SnapshotIn,
|
|
9
|
-
SnapshotOrInstance,
|
|
10
|
-
cast,
|
|
11
|
-
getParentOfType,
|
|
12
|
-
getSnapshot,
|
|
13
|
-
types,
|
|
14
|
-
} from 'mobx-state-tree'
|
|
15
|
-
|
|
16
|
-
import { ApolloAssembly } from '.'
|
|
17
|
-
|
|
18
|
-
export const LateAnnotationFeature = types.late(
|
|
19
|
-
(): IAnyModelType => AnnotationFeature,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
const Phase = types.maybe(
|
|
23
|
-
types.union(types.literal(0), types.literal(1), types.literal(2)),
|
|
24
|
-
)
|
|
25
|
-
export const AnnotationFeature = types
|
|
26
|
-
.model('AnnotationFeature', {
|
|
27
|
-
_id: types.identifier,
|
|
28
|
-
gffId: types.maybe(types.string), // ID from attributes if exists, otherwise gffId = _id
|
|
29
|
-
/** Reference sequence name */
|
|
30
|
-
refSeq: types.string,
|
|
31
|
-
/** Feature type */
|
|
32
|
-
type: types.string,
|
|
33
|
-
/** Feature location start coordinate */
|
|
34
|
-
start: types.number,
|
|
35
|
-
/** Feature location end coordinate */
|
|
36
|
-
end: types.number,
|
|
37
|
-
/**
|
|
38
|
-
* If the feature exists in multiple places, e.g. a CDS in a canonical SO
|
|
39
|
-
* gene, this gives the coordinates of the individual starts and ends. The
|
|
40
|
-
* start of the first location and the end of the last location should match
|
|
41
|
-
* the feature's start and end.
|
|
42
|
-
*/
|
|
43
|
-
discontinuousLocations: types.maybe(
|
|
44
|
-
types.array(
|
|
45
|
-
types.model({ start: types.number, end: types.number, phase: Phase }),
|
|
46
|
-
),
|
|
47
|
-
),
|
|
48
|
-
/** The strand on which the feature is located */
|
|
49
|
-
strand: types.maybe(types.union(types.literal(1), types.literal(-1))),
|
|
50
|
-
/** The feature's score */
|
|
51
|
-
score: types.maybe(types.number),
|
|
52
|
-
/**
|
|
53
|
-
* The feature's phase, which is required for certain features, e.g. CDS in a
|
|
54
|
-
* canonical SO gene
|
|
55
|
-
*/
|
|
56
|
-
phase: Phase,
|
|
57
|
-
/** Child features of this feature */
|
|
58
|
-
children: types.maybe(types.map(types.maybe(LateAnnotationFeature))),
|
|
59
|
-
/**
|
|
60
|
-
* Additional attributes of the feature. This could include name, source,
|
|
61
|
-
* note, dbxref, etc.
|
|
62
|
-
*/
|
|
63
|
-
attributes: types.map(types.array(types.string)),
|
|
64
|
-
})
|
|
65
|
-
.views((self) => ({
|
|
66
|
-
get length() {
|
|
67
|
-
return self.end - self.start
|
|
68
|
-
},
|
|
69
|
-
get featureId() {
|
|
70
|
-
return self.attributes.get('id')
|
|
71
|
-
},
|
|
72
|
-
/**
|
|
73
|
-
* Possibly different from `start` because "The GFF3 format does not enforce
|
|
74
|
-
* a rule in which features must be wholly contained within the location of
|
|
75
|
-
* their parents"
|
|
76
|
-
*/
|
|
77
|
-
get min() {
|
|
78
|
-
let min = self.start
|
|
79
|
-
for (const [, child] of self.children ?? []) {
|
|
80
|
-
min = Math.min(min, child.min)
|
|
81
|
-
}
|
|
82
|
-
return min
|
|
83
|
-
},
|
|
84
|
-
/**
|
|
85
|
-
* Possibly different from `end` because "The GFF3 format does not enforce a
|
|
86
|
-
* rule in which features must be wholly contained within the location of
|
|
87
|
-
* their parents"
|
|
88
|
-
*/
|
|
89
|
-
get max() {
|
|
90
|
-
let max = self.end
|
|
91
|
-
for (const [, child] of self.children ?? []) {
|
|
92
|
-
max = Math.max(max, child.max)
|
|
93
|
-
}
|
|
94
|
-
return max
|
|
95
|
-
},
|
|
96
|
-
hasDescendant(featureId: string) {
|
|
97
|
-
const { children } = self
|
|
98
|
-
if (!children) {
|
|
99
|
-
return false
|
|
100
|
-
}
|
|
101
|
-
for (const [id, child] of children) {
|
|
102
|
-
if (id === featureId) {
|
|
103
|
-
return true
|
|
104
|
-
}
|
|
105
|
-
if (child.hasDescendant(featureId)) {
|
|
106
|
-
return true
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return false
|
|
110
|
-
},
|
|
111
|
-
}))
|
|
112
|
-
.actions((self) => ({
|
|
113
|
-
setAttributes(attributes: Map<string, string[]>) {
|
|
114
|
-
self.attributes.clear()
|
|
115
|
-
for (const [key, value] of attributes.entries()) {
|
|
116
|
-
self.attributes.set(key, value)
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
setAttribute(key: string, value: string[]) {
|
|
120
|
-
self.attributes.merge({ [key]: value })
|
|
121
|
-
},
|
|
122
|
-
setType(type: string) {
|
|
123
|
-
self.type = type
|
|
124
|
-
},
|
|
125
|
-
setRefSeq(refSeq: string) {
|
|
126
|
-
self.refSeq = refSeq
|
|
127
|
-
},
|
|
128
|
-
setStart(start: number) {
|
|
129
|
-
if (start > self.end) {
|
|
130
|
-
throw new Error(`Start "${start}" is greater than end "${self.end}"`)
|
|
131
|
-
}
|
|
132
|
-
if (self.start !== start) {
|
|
133
|
-
self.start = start
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
setCDSDiscontinuousLocationStart(start: number, index: number) {
|
|
137
|
-
const dl = self.discontinuousLocations
|
|
138
|
-
if (dl && dl.length > 0 && dl[index].start !== start) {
|
|
139
|
-
dl[index].start = start
|
|
140
|
-
if (index === 0) {
|
|
141
|
-
self.start = start
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
setEnd(end: number) {
|
|
146
|
-
if (end < self.start) {
|
|
147
|
-
throw new Error(`End "${end}" is less than start "${self.start}"`)
|
|
148
|
-
}
|
|
149
|
-
if (self.end !== end) {
|
|
150
|
-
self.end = end
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
setCDSDiscontinuousLocationEnd(end: number, index: number) {
|
|
154
|
-
const dl = self.discontinuousLocations
|
|
155
|
-
if (dl && dl.length > 0 && dl[index].end !== end) {
|
|
156
|
-
dl[index].end = end
|
|
157
|
-
if (index === dl.length - 1) {
|
|
158
|
-
self.end = end
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
setStrand(strand?: 1 | -1) {
|
|
163
|
-
self.strand = strand
|
|
164
|
-
},
|
|
165
|
-
addChild(childFeature: AnnotationFeatureSnapshot) {
|
|
166
|
-
if (self.children && self.children.size > 0) {
|
|
167
|
-
const existingChildren = getSnapshot(self.children) ?? {}
|
|
168
|
-
self.children.clear()
|
|
169
|
-
for (const [, child] of Object.entries({
|
|
170
|
-
...existingChildren,
|
|
171
|
-
[childFeature._id]: childFeature,
|
|
172
|
-
}).sort(([, a], [, b]) => a.start - b.start)) {
|
|
173
|
-
self.children.put(child)
|
|
174
|
-
}
|
|
175
|
-
} else {
|
|
176
|
-
self.children = cast({})
|
|
177
|
-
self.children?.put(childFeature)
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
deleteChild(childFeatureId: string) {
|
|
181
|
-
self.children?.delete(childFeatureId)
|
|
182
|
-
},
|
|
183
|
-
}))
|
|
184
|
-
.actions((self) => ({
|
|
185
|
-
update({
|
|
186
|
-
children,
|
|
187
|
-
end,
|
|
188
|
-
refSeq,
|
|
189
|
-
start,
|
|
190
|
-
strand,
|
|
191
|
-
}: {
|
|
192
|
-
refSeq: string
|
|
193
|
-
start: number
|
|
194
|
-
end: number
|
|
195
|
-
strand?: 1 | -1
|
|
196
|
-
children?: SnapshotOrInstance<typeof LateAnnotationFeature>
|
|
197
|
-
}) {
|
|
198
|
-
self.setRefSeq(refSeq)
|
|
199
|
-
self.setStart(start)
|
|
200
|
-
self.setEnd(end)
|
|
201
|
-
self.setStrand(strand)
|
|
202
|
-
if (children) {
|
|
203
|
-
self.children = cast(children)
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
}))
|
|
207
|
-
// This views block has to be last to avoid:
|
|
208
|
-
// "'parent' is referenced directly or indirectly in its own type annotation."
|
|
209
|
-
.views((self) => ({
|
|
210
|
-
get parent() {
|
|
211
|
-
let parent: AnnotationFeatureI | undefined
|
|
212
|
-
try {
|
|
213
|
-
parent = getParentOfType(self, AnnotationFeature)
|
|
214
|
-
} catch {
|
|
215
|
-
// pass
|
|
216
|
-
}
|
|
217
|
-
return parent
|
|
218
|
-
},
|
|
219
|
-
get topLevelFeature(): AnnotationFeatureI {
|
|
220
|
-
let feature = self
|
|
221
|
-
let parent
|
|
222
|
-
do {
|
|
223
|
-
try {
|
|
224
|
-
parent = getParentOfType(feature, AnnotationFeature)
|
|
225
|
-
feature = parent
|
|
226
|
-
} catch {
|
|
227
|
-
parent = undefined
|
|
228
|
-
}
|
|
229
|
-
} while (parent)
|
|
230
|
-
return feature as AnnotationFeatureI
|
|
231
|
-
},
|
|
232
|
-
get assemblyId(): string {
|
|
233
|
-
return getParentOfType(self, ApolloAssembly)._id
|
|
234
|
-
},
|
|
235
|
-
}))
|
|
236
|
-
|
|
237
|
-
export type AnnotationFeatureI = Instance<typeof AnnotationFeature>
|
|
238
|
-
type AnnotationFeatureSnapshotRaw = SnapshotIn<typeof AnnotationFeature>
|
|
239
|
-
export interface AnnotationFeatureSnapshot
|
|
240
|
-
extends AnnotationFeatureSnapshotRaw {
|
|
241
|
-
/** Child features of this feature */
|
|
242
|
-
children?: Record<string, AnnotationFeatureSnapshot>
|
|
243
|
-
}
|