@apollo-annotation/jbrowse-plugin-apollo 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +317 -227
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +316 -226
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +316 -226
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +4 -4
- package/src/FeatureDetailsWidget/model.ts +0 -2
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +20 -1
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +232 -117
- package/src/LinearApolloDisplay/stateModel/base.ts +15 -1
- package/src/LinearApolloDisplay/stateModel/layouts.ts +84 -85
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +9 -4
- package/src/LinearApolloDisplay/stateModel/rendering.ts +3 -2
- package/src/OntologyManager/index.ts +22 -0
|
@@ -11,6 +11,39 @@ import { ApolloSessionModel } from '../../session'
|
|
|
11
11
|
import { baseModelFactory } from './base'
|
|
12
12
|
import { boxGlyph, geneGlyph, genericChildGlyph } from '../glyphs'
|
|
13
13
|
|
|
14
|
+
function getRowsForFeature(
|
|
15
|
+
startingRow: number,
|
|
16
|
+
rowCount: number,
|
|
17
|
+
filledRowLocations: Map<number, [number, number][]>,
|
|
18
|
+
) {
|
|
19
|
+
const rowsForFeature = []
|
|
20
|
+
for (let i = startingRow; i < startingRow + rowCount; i++) {
|
|
21
|
+
const row = filledRowLocations.get(i)
|
|
22
|
+
if (row) {
|
|
23
|
+
rowsForFeature.push(row)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return rowsForFeature
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function canPlaceFeatureInRows(
|
|
30
|
+
rowsForFeature: [number, number][][],
|
|
31
|
+
feature: AnnotationFeature,
|
|
32
|
+
) {
|
|
33
|
+
for (const rowForFeature of rowsForFeature) {
|
|
34
|
+
for (const [rowStart, rowEnd] of rowForFeature) {
|
|
35
|
+
if (
|
|
36
|
+
doesIntersect2(feature.min, feature.max, rowStart, rowEnd) ||
|
|
37
|
+
doesIntersect2(rowStart, rowEnd, feature.min, feature.max)
|
|
38
|
+
) {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
|
|
14
47
|
export function layoutsModelFactory(
|
|
15
48
|
pluginManager: PluginManager,
|
|
16
49
|
configSchema: AnyConfigurationSchemaType,
|
|
@@ -19,48 +52,14 @@ export function layoutsModelFactory(
|
|
|
19
52
|
|
|
20
53
|
return BaseLinearApolloDisplay.named('LinearApolloDisplayLayouts')
|
|
21
54
|
.props({
|
|
22
|
-
|
|
55
|
+
cleanupBoundary: 200_000,
|
|
23
56
|
})
|
|
24
57
|
.volatile(() => ({
|
|
25
58
|
seenFeatures: observable.map<string, AnnotationFeature>(),
|
|
26
59
|
}))
|
|
27
60
|
.views((self) => ({
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
self.session as unknown as AbstractSessionModel
|
|
31
|
-
return self.lgv.displayedRegions.map((region) => {
|
|
32
|
-
const assembly = assemblyManager.get(region.assemblyName)
|
|
33
|
-
let min: number | undefined
|
|
34
|
-
let max: number | undefined
|
|
35
|
-
const { end, refName, start } = region
|
|
36
|
-
for (const [, feature] of self.seenFeatures) {
|
|
37
|
-
if (
|
|
38
|
-
refName !== assembly?.getCanonicalRefName(feature.refSeq) ||
|
|
39
|
-
!doesIntersect2(start, end, feature.min, feature.max) ||
|
|
40
|
-
feature.length > self.featuresMinMaxLimit ||
|
|
41
|
-
(self.filteredFeatureTypes.length > 0 &&
|
|
42
|
-
!self.filteredFeatureTypes.includes(feature.type))
|
|
43
|
-
) {
|
|
44
|
-
continue
|
|
45
|
-
}
|
|
46
|
-
if (min === undefined) {
|
|
47
|
-
;({ min } = feature)
|
|
48
|
-
}
|
|
49
|
-
if (max === undefined) {
|
|
50
|
-
;({ max } = feature)
|
|
51
|
-
}
|
|
52
|
-
if (feature.minWithChildren < min) {
|
|
53
|
-
;({ min } = feature)
|
|
54
|
-
}
|
|
55
|
-
if (feature.maxWithChildren > max) {
|
|
56
|
-
;({ max } = feature)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
if (min !== undefined && max !== undefined) {
|
|
60
|
-
return [min, max]
|
|
61
|
-
}
|
|
62
|
-
return
|
|
63
|
-
})
|
|
61
|
+
getAnnotationFeatureById(id: string) {
|
|
62
|
+
return self.seenFeatures.get(id)
|
|
64
63
|
},
|
|
65
64
|
getGlyph(feature: AnnotationFeature) {
|
|
66
65
|
if (this.looksLikeGene(feature)) {
|
|
@@ -91,15 +90,9 @@ export function layoutsModelFactory(
|
|
|
91
90
|
if (!grandChildren?.size) {
|
|
92
91
|
return false
|
|
93
92
|
}
|
|
94
|
-
|
|
95
|
-
featureTypeOntology.isTypeOf(grandchild.type, 'CDS'),
|
|
96
|
-
)
|
|
97
|
-
const hasExon = [...grandChildren.values()].some((grandchild) =>
|
|
93
|
+
return [...grandChildren.values()].some((grandchild) =>
|
|
98
94
|
featureTypeOntology.isTypeOf(grandchild.type, 'exon'),
|
|
99
95
|
)
|
|
100
|
-
if (hasCDS && hasExon) {
|
|
101
|
-
return true
|
|
102
|
-
}
|
|
103
96
|
}
|
|
104
97
|
}
|
|
105
98
|
return false
|
|
@@ -117,15 +110,11 @@ export function layoutsModelFactory(
|
|
|
117
110
|
get featureLayouts() {
|
|
118
111
|
const { assemblyManager } =
|
|
119
112
|
self.session as unknown as AbstractSessionModel
|
|
120
|
-
return self.lgv.displayedRegions.map((region
|
|
113
|
+
return self.lgv.displayedRegions.map((region) => {
|
|
121
114
|
const assembly = assemblyManager.get(region.assemblyName)
|
|
122
|
-
const featureLayout = new Map<number, [number,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return featureLayout
|
|
126
|
-
}
|
|
127
|
-
const [min, max] = minMax
|
|
128
|
-
const rows: boolean[][] = []
|
|
115
|
+
const featureLayout = new Map<number, [number, string][]>()
|
|
116
|
+
// Track the occupied coordinates in each row
|
|
117
|
+
const filledRowLocations = new Map<number, [number, number][]>()
|
|
129
118
|
const { end, refName, start } = region
|
|
130
119
|
for (const [id, feature] of self.seenFeatures.entries()) {
|
|
131
120
|
if (!isAlive(feature)) {
|
|
@@ -151,36 +140,24 @@ export function layoutsModelFactory(
|
|
|
151
140
|
let startingRow = 0
|
|
152
141
|
let placed = false
|
|
153
142
|
while (!placed) {
|
|
154
|
-
let rowsForFeature =
|
|
143
|
+
let rowsForFeature = getRowsForFeature(
|
|
155
144
|
startingRow,
|
|
156
|
-
|
|
145
|
+
rowCount,
|
|
146
|
+
filledRowLocations,
|
|
157
147
|
)
|
|
158
148
|
if (rowsForFeature.length < rowCount) {
|
|
159
149
|
for (let i = 0; i < rowCount - rowsForFeature.length; i++) {
|
|
160
|
-
const newRowNumber =
|
|
161
|
-
|
|
150
|
+
const newRowNumber = filledRowLocations.size
|
|
151
|
+
filledRowLocations.set(newRowNumber, [])
|
|
162
152
|
featureLayout.set(newRowNumber, [])
|
|
163
153
|
}
|
|
164
|
-
rowsForFeature =
|
|
154
|
+
rowsForFeature = getRowsForFeature(
|
|
155
|
+
startingRow,
|
|
156
|
+
rowCount,
|
|
157
|
+
filledRowLocations,
|
|
158
|
+
)
|
|
165
159
|
}
|
|
166
|
-
if (
|
|
167
|
-
rowsForFeature
|
|
168
|
-
.map((rowForFeature) => {
|
|
169
|
-
// zero-length features are allowed in the spec
|
|
170
|
-
const featureMax =
|
|
171
|
-
feature.max - feature.min === 0
|
|
172
|
-
? feature.min + 1
|
|
173
|
-
: feature.max
|
|
174
|
-
let start = feature.min - min,
|
|
175
|
-
end = featureMax - min
|
|
176
|
-
if (feature.min - min < 0) {
|
|
177
|
-
start = 0
|
|
178
|
-
end = featureMax - feature.min
|
|
179
|
-
}
|
|
180
|
-
return rowForFeature.slice(start, end).some(Boolean)
|
|
181
|
-
})
|
|
182
|
-
.some(Boolean)
|
|
183
|
-
) {
|
|
160
|
+
if (!canPlaceFeatureInRows(rowsForFeature, feature)) {
|
|
184
161
|
startingRow += 1
|
|
185
162
|
continue
|
|
186
163
|
}
|
|
@@ -189,16 +166,9 @@ export function layoutsModelFactory(
|
|
|
189
166
|
rowNum < startingRow + rowCount;
|
|
190
167
|
rowNum++
|
|
191
168
|
) {
|
|
192
|
-
|
|
193
|
-
let start = feature.min - min,
|
|
194
|
-
end = feature.max - min
|
|
195
|
-
if (feature.min - min < 0) {
|
|
196
|
-
start = 0
|
|
197
|
-
end = feature.max - feature.min
|
|
198
|
-
}
|
|
199
|
-
row.fill(true, start, end)
|
|
169
|
+
filledRowLocations.get(rowNum)?.push([feature.min, feature.max])
|
|
200
170
|
const layoutRow = featureLayout.get(rowNum)
|
|
201
|
-
layoutRow?.push([rowNum - startingRow, feature])
|
|
171
|
+
layoutRow?.push([rowNum - startingRow, feature._id])
|
|
202
172
|
}
|
|
203
173
|
placed = true
|
|
204
174
|
}
|
|
@@ -212,12 +182,17 @@ export function layoutsModelFactory(
|
|
|
212
182
|
self.session.apolloDataStore.ontologyManager
|
|
213
183
|
for (const [idx, layout] of featureLayouts.entries()) {
|
|
214
184
|
for (const [layoutRowNum, layoutRow] of layout) {
|
|
215
|
-
for (const [featureRowNum,
|
|
185
|
+
for (const [featureRowNum, layoutFeatureId] of layoutRow) {
|
|
216
186
|
if (featureRowNum !== 0) {
|
|
217
187
|
// Same top-level feature in all feature rows, so only need to
|
|
218
188
|
// check the first one
|
|
219
189
|
continue
|
|
220
190
|
}
|
|
191
|
+
const layoutFeature =
|
|
192
|
+
self.getAnnotationFeatureById(layoutFeatureId)
|
|
193
|
+
if (!layoutFeature) {
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
221
196
|
if (feature._id === layoutFeature._id) {
|
|
222
197
|
return {
|
|
223
198
|
layoutIndex: idx,
|
|
@@ -263,6 +238,30 @@ export function layoutsModelFactory(
|
|
|
263
238
|
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
264
239
|
return
|
|
265
240
|
}
|
|
241
|
+
// Clear out features that are no longer in the view and out of the cleanup boundary
|
|
242
|
+
// cleanup boundary + region boundary + cleanup boundary
|
|
243
|
+
for (const [id, feature] of self.seenFeatures.entries()) {
|
|
244
|
+
let shouldKeep = false
|
|
245
|
+
for (const region of self.regions) {
|
|
246
|
+
const extendedStart = region.start - self.cleanupBoundary
|
|
247
|
+
const extendedEnd = region.end + self.cleanupBoundary
|
|
248
|
+
if (
|
|
249
|
+
doesIntersect2(
|
|
250
|
+
extendedStart,
|
|
251
|
+
extendedEnd,
|
|
252
|
+
feature.min,
|
|
253
|
+
feature.max,
|
|
254
|
+
)
|
|
255
|
+
) {
|
|
256
|
+
shouldKeep = true
|
|
257
|
+
break
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!shouldKeep) {
|
|
261
|
+
self.deleteSeenFeature(id)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Add features that are in the current view
|
|
266
265
|
for (const region of self.regions) {
|
|
267
266
|
const assembly = (
|
|
268
267
|
self.session as unknown as ApolloSessionModel
|
|
@@ -139,13 +139,18 @@ export function mouseEventsModelIntermediateFactory(
|
|
|
139
139
|
if (!layoutRow) {
|
|
140
140
|
return mousePosition
|
|
141
141
|
}
|
|
142
|
-
const foundFeature = layoutRow.find(
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
const foundFeature = layoutRow.find((f) => {
|
|
143
|
+
const feature = self.getAnnotationFeatureById(f[1])
|
|
144
|
+
return feature && bp >= feature.min && bp <= feature.max
|
|
145
|
+
})
|
|
145
146
|
if (!foundFeature) {
|
|
146
147
|
return mousePosition
|
|
147
148
|
}
|
|
148
|
-
const [featureRow,
|
|
149
|
+
const [featureRow, topLevelFeatureId] = foundFeature
|
|
150
|
+
const topLevelFeature = self.getAnnotationFeatureById(topLevelFeatureId)
|
|
151
|
+
if (!topLevelFeature) {
|
|
152
|
+
return mousePosition
|
|
153
|
+
}
|
|
149
154
|
const glyph = self.getGlyph(topLevelFeature)
|
|
150
155
|
const { featureTypeOntology } =
|
|
151
156
|
self.session.apolloDataStore.ontologyManager
|
|
@@ -413,8 +413,9 @@ export function renderingModelFactory(
|
|
|
413
413
|
for (const [idx, featureLayout] of featureLayouts.entries()) {
|
|
414
414
|
const displayedRegion = displayedRegions[idx]
|
|
415
415
|
for (const [row, featureLayoutRow] of featureLayout.entries()) {
|
|
416
|
-
for (const [featureRow,
|
|
417
|
-
|
|
416
|
+
for (const [featureRow, featureId] of featureLayoutRow) {
|
|
417
|
+
const feature = self.getAnnotationFeatureById(featureId)
|
|
418
|
+
if (featureRow > 0 || !feature) {
|
|
418
419
|
continue
|
|
419
420
|
}
|
|
420
421
|
if (
|
|
@@ -35,6 +35,7 @@ export const OntologyRecordType = types
|
|
|
35
35
|
})
|
|
36
36
|
.volatile((_self) => ({
|
|
37
37
|
dataStore: undefined as undefined | OntologyStore,
|
|
38
|
+
startedEquivalentTypeRequests: new Set<string>(),
|
|
38
39
|
}))
|
|
39
40
|
.actions((self) => ({
|
|
40
41
|
/** does nothing, just used to access the model to force its lifecycle hooks to run */
|
|
@@ -66,6 +67,10 @@ export const OntologyRecordType = types
|
|
|
66
67
|
if (!self.dataStore) {
|
|
67
68
|
return
|
|
68
69
|
}
|
|
70
|
+
if (self.startedEquivalentTypeRequests.has(type)) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
self.startedEquivalentTypeRequests.add(type)
|
|
69
74
|
const terms = (yield self.dataStore.getTermsWithLabelOrSynonym(
|
|
70
75
|
type,
|
|
71
76
|
)) as unknown as OntologyTerm[]
|
|
@@ -75,6 +80,23 @@ export const OntologyRecordType = types
|
|
|
75
80
|
self.setEquivalentTypes(type, equivalents)
|
|
76
81
|
}),
|
|
77
82
|
}))
|
|
83
|
+
.actions((self) => ({
|
|
84
|
+
afterCreate() {
|
|
85
|
+
autorun((reaction) => {
|
|
86
|
+
if (!self.dataStore) {
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
void self.loadEquivalentTypes('gene')
|
|
90
|
+
void self.loadEquivalentTypes('transcript')
|
|
91
|
+
void self.loadEquivalentTypes('CDS')
|
|
92
|
+
void self.loadEquivalentTypes('mRNA')
|
|
93
|
+
reaction.dispose()
|
|
94
|
+
})
|
|
95
|
+
},
|
|
96
|
+
setEquivalentTypes(type: string, equivalentTypes: string[]) {
|
|
97
|
+
self.equivalentTypes.set(type, equivalentTypes)
|
|
98
|
+
},
|
|
99
|
+
}))
|
|
78
100
|
.views((self) => ({
|
|
79
101
|
isTypeOf(queryType: string, typeOf: string): boolean {
|
|
80
102
|
if (queryType === typeOf) {
|