@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo-annotation/jbrowse-plugin-apollo",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Apollo plugin for JBrowse 2",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -48,9 +48,9 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@apollo-annotation/common": "^0.3.3",
52
- "@apollo-annotation/mst": "^0.3.3",
53
- "@apollo-annotation/shared": "^0.3.3",
51
+ "@apollo-annotation/common": "^0.3.4",
52
+ "@apollo-annotation/mst": "^0.3.4",
53
+ "@apollo-annotation/shared": "^0.3.4",
54
54
  "@emotion/react": "^11.10.6",
55
55
  "@emotion/styled": "^11.10.6",
56
56
  "@gmod/gff": "1.2.0",
@@ -9,7 +9,6 @@ import { ElementId } from '@jbrowse/core/util/types/mst'
9
9
  import { autorun } from 'mobx'
10
10
  import { Instance, SnapshotIn, addDisposer, types } from 'mobx-state-tree'
11
11
 
12
- import { ChangeManager } from '../ChangeManager'
13
12
  import { ApolloSessionModel } from '../session'
14
13
 
15
14
  export const ApolloFeatureDetailsWidgetModel = types
@@ -86,7 +85,6 @@ export const ApolloTranscriptDetailsModel = types
86
85
  ),
87
86
  assembly: types.string,
88
87
  refName: types.string,
89
- changeManager: types.frozen<ChangeManager>(),
90
88
  })
91
89
  .volatile(() => ({
92
90
  tryReload: undefined as string | undefined,
@@ -9,7 +9,13 @@ import {
9
9
  } from '@jbrowse/core/util'
10
10
  import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
11
11
  import ErrorIcon from '@mui/icons-material/Error'
12
- import { Alert, Avatar, Tooltip, useTheme } from '@mui/material'
12
+ import {
13
+ Alert,
14
+ Avatar,
15
+ CircularProgress,
16
+ Tooltip,
17
+ useTheme,
18
+ } from '@mui/material'
13
19
  import { observer } from 'mobx-react'
14
20
  import React, { useEffect, useState } from 'react'
15
21
  import { makeStyles } from 'tss-react/mui'
@@ -39,6 +45,13 @@ const useStyles = makeStyles()((theme) => ({
39
45
  color: theme.palette.warning.light,
40
46
  backgroundColor: theme.palette.warning.contrastText,
41
47
  },
48
+ loading: {
49
+ position: 'absolute',
50
+ right: theme.spacing(3),
51
+ zIndex: 10,
52
+ pointerEvents: 'none',
53
+ textAlign: 'right',
54
+ },
42
55
  }))
43
56
 
44
57
  export const LinearApolloDisplay = observer(function LinearApolloDisplay(
@@ -47,6 +60,7 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
47
60
  const theme = useTheme()
48
61
  const { model } = props
49
62
  const {
63
+ loading,
50
64
  apolloRowHeight,
51
65
  contextMenuItems: getContextMenuItems,
52
66
  cursor,
@@ -128,6 +142,11 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
128
142
  }
129
143
  }}
130
144
  >
145
+ {loading ? (
146
+ <div className={classes.loading}>
147
+ <CircularProgress size="18px" />
148
+ </div>
149
+ ) : null}
131
150
  {message ? (
132
151
  <Alert severity="warning" classes={{ message: classes.ellipses }}>
133
152
  <Tooltip title={message}>
@@ -74,7 +74,6 @@ function draw(
74
74
  const displayedRegion = displayedRegions[displayedRegionIndex]
75
75
  const { refName, reversed } = displayedRegion
76
76
  const rowHeight = apolloRowHeight
77
- const exonHeight = Math.round(0.6 * rowHeight)
78
77
  const cdsHeight = Math.round(0.9 * rowHeight)
79
78
  const { children, min, strand } = feature
80
79
  if (!children) {
@@ -121,29 +120,36 @@ function draw(
121
120
  currentRow += 1
122
121
  continue
123
122
  }
124
- const { children: childrenOfTranscript, min } = transcript
125
- if (!childrenOfTranscript) {
123
+ const { children: transcriptChildren } = transcript
124
+ if (!transcriptChildren) {
126
125
  continue
127
126
  }
128
- for (const [, cds] of childrenOfTranscript) {
129
- if (!featureTypeOntology.isTypeOf(cds.type, 'CDS')) {
127
+
128
+ const cdsCount = getCDSCount(transcript, featureTypeOntology)
129
+ for (const [, childFeature] of transcriptChildren) {
130
+ if (!featureTypeOntology.isTypeOf(childFeature.type, 'CDS')) {
130
131
  continue
131
132
  }
132
- const minX =
133
- (lgv.bpToPx({
134
- refName,
135
- coord: min,
136
- regionNumber: displayedRegionIndex,
137
- })?.offsetPx ?? 0) - offsetPx
138
- const widthPx = transcript.length / bpPerPx
139
- const startPx = reversed ? minX - widthPx : minX
140
- const height =
141
- Math.round((currentRow + 1 / 2) * rowHeight) + row * rowHeight
142
- ctx.strokeStyle = theme?.palette.text.primary ?? 'black'
143
- ctx.beginPath()
144
- ctx.moveTo(startPx, height)
145
- ctx.lineTo(startPx + widthPx, height)
146
- ctx.stroke()
133
+ drawLine(
134
+ ctx,
135
+ stateModel,
136
+ displayedRegionIndex,
137
+ row,
138
+ transcript,
139
+ currentRow,
140
+ )
141
+ currentRow += 1
142
+ }
143
+
144
+ if (cdsCount === 0) {
145
+ drawLine(
146
+ ctx,
147
+ stateModel,
148
+ displayedRegionIndex,
149
+ row,
150
+ transcript,
151
+ currentRow,
152
+ )
147
153
  currentRow += 1
148
154
  }
149
155
  }
@@ -160,120 +166,205 @@ function draw(
160
166
  currentRow += 1
161
167
  continue
162
168
  }
163
- for (const cdsRow of child.cdsLocations) {
164
- const { _id, children: childrenOfTranscript } = child
165
- if (!childrenOfTranscript) {
166
- continue
167
- }
168
- for (const [, exon] of childrenOfTranscript) {
169
- if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
169
+ const cdsCount = getCDSCount(child, featureTypeOntology)
170
+ if (cdsCount != 0) {
171
+ for (const cdsRow of child.cdsLocations) {
172
+ const { _id, children: transcriptChildren } = child
173
+ if (!transcriptChildren) {
170
174
  continue
171
175
  }
172
- const minX =
173
- (lgv.bpToPx({
174
- refName,
175
- coord: exon.min,
176
- regionNumber: displayedRegionIndex,
177
- })?.offsetPx ?? 0) - offsetPx
178
- const widthPx = exon.length / bpPerPx
179
- const startPx = reversed ? minX - widthPx : minX
180
-
181
- const top = (row + currentRow) * rowHeight
182
- const exonTop = top + (rowHeight - exonHeight) / 2
183
- ctx.fillStyle = theme?.palette.text.primary ?? 'black'
184
- ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
185
- if (widthPx > 2) {
186
- ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
187
- ctx.fillStyle =
188
- apolloSelectedFeature && exon._id === apolloSelectedFeature._id
189
- ? 'rgb(0,0,0)'
190
- : 'rgb(211,211,211)'
191
- ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
192
- if (forwardFill && backwardFill && strand) {
193
- const reversal = reversed ? -1 : 1
194
- const [topFill, bottomFill] =
195
- strand * reversal === 1
196
- ? [forwardFill, backwardFill]
197
- : [backwardFill, forwardFill]
198
- ctx.fillStyle = topFill
199
- ctx.fillRect(
200
- startPx + 1,
201
- exonTop + 1,
202
- widthPx - 2,
203
- (exonHeight - 2) / 2,
204
- )
205
- ctx.fillStyle = bottomFill
206
- ctx.fillRect(
207
- startPx + 1,
208
- exonTop + 1 + (exonHeight - 2) / 2,
209
- widthPx - 2,
210
- (exonHeight - 2) / 2,
211
- )
176
+ for (const [, exon] of transcriptChildren) {
177
+ if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
178
+ continue
212
179
  }
213
- }
214
- }
215
- for (const cds of cdsRow) {
216
- const cdsWidthPx = (cds.max - cds.min) / bpPerPx
217
- const minX =
218
- (lgv.bpToPx({
219
- refName,
220
- coord: cds.min,
221
- regionNumber: displayedRegionIndex,
222
- })?.offsetPx ?? 0) - offsetPx
223
- const cdsStartPx = reversed ? minX - cdsWidthPx : minX
224
- ctx.fillStyle = theme?.palette.text.primary ?? 'black'
225
- const cdsTop =
226
- (row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2
227
- ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
228
- if (cdsWidthPx > 2) {
229
- ctx.clearRect(
230
- cdsStartPx + 1,
231
- cdsTop + 1,
232
- cdsWidthPx - 2,
233
- cdsHeight - 2,
234
- )
235
- const frame = getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase)
236
- const frameColor = theme?.palette.framesCDS.at(frame)?.main
237
- const cdsColorCode = frameColor ?? 'rgb(171,71,188)'
238
- ctx.fillStyle =
239
- apolloSelectedFeature && _id === apolloSelectedFeature._id
240
- ? 'rgb(0,0,0)'
241
- : cdsColorCode
242
- ctx.fillStyle = cdsColorCode
243
- ctx.fillRect(
244
- cdsStartPx + 1,
245
- cdsTop + 1,
246
- cdsWidthPx - 2,
247
- cdsHeight - 2,
180
+ drawExon(
181
+ ctx,
182
+ stateModel,
183
+ displayedRegionIndex,
184
+ row,
185
+ exon,
186
+ currentRow,
187
+ strand,
188
+ forwardFill,
189
+ backwardFill,
248
190
  )
249
- if (forwardFill && backwardFill && strand) {
250
- const reversal = reversed ? -1 : 1
251
- const [topFill, bottomFill] =
252
- strand * reversal === 1
253
- ? [forwardFill, backwardFill]
254
- : [backwardFill, forwardFill]
255
- ctx.fillStyle = topFill
256
- ctx.fillRect(
191
+ }
192
+ for (const cds of cdsRow) {
193
+ const cdsWidthPx = (cds.max - cds.min) / bpPerPx
194
+ const minX =
195
+ (lgv.bpToPx({
196
+ refName,
197
+ coord: cds.min,
198
+ regionNumber: displayedRegionIndex,
199
+ })?.offsetPx ?? 0) - offsetPx
200
+ const cdsStartPx = reversed ? minX - cdsWidthPx : minX
201
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black'
202
+ const cdsTop =
203
+ (row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2
204
+ ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
205
+ if (cdsWidthPx > 2) {
206
+ ctx.clearRect(
257
207
  cdsStartPx + 1,
258
208
  cdsTop + 1,
259
209
  cdsWidthPx - 2,
260
- (cdsHeight - 2) / 2,
210
+ cdsHeight - 2,
261
211
  )
262
- ctx.fillStyle = bottomFill
212
+ const frame = getFrame(
213
+ cds.min,
214
+ cds.max,
215
+ child.strand ?? 1,
216
+ cds.phase,
217
+ )
218
+ const frameColor = theme?.palette.framesCDS.at(frame)?.main
219
+ const cdsColorCode = frameColor ?? 'rgb(171,71,188)'
220
+ ctx.fillStyle =
221
+ apolloSelectedFeature && _id === apolloSelectedFeature._id
222
+ ? 'rgb(0,0,0)'
223
+ : cdsColorCode
224
+ ctx.fillStyle = cdsColorCode
263
225
  ctx.fillRect(
264
226
  cdsStartPx + 1,
265
- cdsTop + (cdsHeight - 2) / 2,
227
+ cdsTop + 1,
266
228
  cdsWidthPx - 2,
267
- (cdsHeight - 2) / 2,
229
+ cdsHeight - 2,
268
230
  )
231
+ if (forwardFill && backwardFill && strand) {
232
+ const reversal = reversed ? -1 : 1
233
+ const [topFill, bottomFill] =
234
+ strand * reversal === 1
235
+ ? [forwardFill, backwardFill]
236
+ : [backwardFill, forwardFill]
237
+ ctx.fillStyle = topFill
238
+ ctx.fillRect(
239
+ cdsStartPx + 1,
240
+ cdsTop + 1,
241
+ cdsWidthPx - 2,
242
+ (cdsHeight - 2) / 2,
243
+ )
244
+ ctx.fillStyle = bottomFill
245
+ ctx.fillRect(
246
+ cdsStartPx + 1,
247
+ cdsTop + (cdsHeight - 2) / 2,
248
+ cdsWidthPx - 2,
249
+ (cdsHeight - 2) / 2,
250
+ )
251
+ }
269
252
  }
270
253
  }
254
+ currentRow += 1
255
+ }
256
+ }
257
+
258
+ const { children: transcriptChildren } = child
259
+ // Draw exons for non-coding genes
260
+ if (cdsCount === 0 && transcriptChildren) {
261
+ for (const [, exon] of transcriptChildren) {
262
+ if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
263
+ continue
264
+ }
265
+ drawExon(
266
+ ctx,
267
+ stateModel,
268
+ displayedRegionIndex,
269
+ row,
270
+ exon,
271
+ currentRow,
272
+ strand,
273
+ forwardFill,
274
+ backwardFill,
275
+ )
271
276
  }
272
277
  currentRow += 1
273
278
  }
274
279
  }
275
280
  }
276
281
 
282
+ function drawExon(
283
+ ctx: CanvasRenderingContext2D,
284
+ stateModel: LinearApolloDisplayRendering,
285
+ displayedRegionIndex: number,
286
+ row: number,
287
+ exon: AnnotationFeature,
288
+ currentRow: number,
289
+ strand: number | undefined,
290
+ forwardFill: CanvasPattern | null,
291
+ backwardFill: CanvasPattern | null,
292
+ ) {
293
+ const { apolloRowHeight, lgv, session, theme } = stateModel
294
+ const { bpPerPx, displayedRegions, offsetPx } = lgv
295
+ const displayedRegion = displayedRegions[displayedRegionIndex]
296
+ const { refName, reversed } = displayedRegion
297
+ const { apolloSelectedFeature } = session
298
+
299
+ const minX =
300
+ (lgv.bpToPx({
301
+ refName,
302
+ coord: exon.min,
303
+ regionNumber: displayedRegionIndex,
304
+ })?.offsetPx ?? 0) - offsetPx
305
+ const widthPx = exon.length / bpPerPx
306
+ const startPx = reversed ? minX - widthPx : minX
307
+
308
+ const top = (row + currentRow) * apolloRowHeight
309
+ const exonHeight = Math.round(0.6 * apolloRowHeight)
310
+ const exonTop = top + (apolloRowHeight - exonHeight) / 2
311
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black'
312
+ ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
313
+ if (widthPx > 2) {
314
+ ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
315
+ ctx.fillStyle =
316
+ apolloSelectedFeature && exon._id === apolloSelectedFeature._id
317
+ ? 'rgb(0,0,0)'
318
+ : 'rgb(211,211,211)'
319
+ ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
320
+ if (forwardFill && backwardFill && strand) {
321
+ const reversal = reversed ? -1 : 1
322
+ const [topFill, bottomFill] =
323
+ strand * reversal === 1
324
+ ? [forwardFill, backwardFill]
325
+ : [backwardFill, forwardFill]
326
+ ctx.fillStyle = topFill
327
+ ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, (exonHeight - 2) / 2)
328
+ ctx.fillStyle = bottomFill
329
+ ctx.fillRect(
330
+ startPx + 1,
331
+ exonTop + 1 + (exonHeight - 2) / 2,
332
+ widthPx - 2,
333
+ (exonHeight - 2) / 2,
334
+ )
335
+ }
336
+ }
337
+ }
338
+
339
+ function drawLine(
340
+ ctx: CanvasRenderingContext2D,
341
+ stateModel: LinearApolloDisplayRendering,
342
+ displayedRegionIndex: number,
343
+ row: number,
344
+ transcript: AnnotationFeature,
345
+ currentRow: number,
346
+ ) {
347
+ const { apolloRowHeight, lgv, theme } = stateModel
348
+ const { bpPerPx, displayedRegions, offsetPx } = lgv
349
+ const displayedRegion = displayedRegions[displayedRegionIndex]
350
+ const { refName, reversed } = displayedRegion
351
+ const minX =
352
+ (lgv.bpToPx({
353
+ refName,
354
+ coord: transcript.min,
355
+ regionNumber: displayedRegionIndex,
356
+ })?.offsetPx ?? 0) - offsetPx
357
+ const widthPx = transcript.length / bpPerPx
358
+ const startPx = reversed ? minX - widthPx : minX
359
+ const height =
360
+ Math.round((currentRow + 1 / 2) * apolloRowHeight) + row * apolloRowHeight
361
+ ctx.strokeStyle = theme?.palette.text.primary ?? 'black'
362
+ ctx.beginPath()
363
+ ctx.moveTo(startPx, height)
364
+ ctx.lineTo(startPx + widthPx, height)
365
+ ctx.stroke()
366
+ }
367
+
277
368
  function drawDragPreview(
278
369
  stateModel: LinearApolloDisplay,
279
370
  overlayCtx: CanvasRenderingContext2D,
@@ -384,6 +475,26 @@ function getFeatureFromLayout(
384
475
  return feature
385
476
  }
386
477
 
478
+ function getCDSCount(
479
+ feature: AnnotationFeature,
480
+ featureTypeOntology: OntologyRecord,
481
+ ): number {
482
+ const { children, type } = feature
483
+ if (!children) {
484
+ return 0
485
+ }
486
+ const isMrna = featureTypeOntology.isTypeOf(type, 'mRNA')
487
+ let cdsCount = 0
488
+ if (isMrna) {
489
+ for (const [, child] of children) {
490
+ if (featureTypeOntology.isTypeOf(child.type, 'CDS')) {
491
+ cdsCount += 1
492
+ }
493
+ }
494
+ }
495
+ return cdsCount
496
+ }
497
+
387
498
  function getRowCount(
388
499
  feature: AnnotationFeature,
389
500
  featureTypeOntology: OntologyRecord,
@@ -397,12 +508,13 @@ function getRowCount(
397
508
  let rowCount = 0
398
509
  if (isTranscript) {
399
510
  for (const [, child] of children) {
400
- const isCds = featureTypeOntology.isTypeOf(child.type, 'CDS')
401
- if (isCds) {
511
+ if (featureTypeOntology.isTypeOf(child.type, 'CDS')) {
402
512
  rowCount += 1
403
513
  }
404
514
  }
405
- return rowCount
515
+
516
+ // return 1 if there are no CDSs for non coding genes
517
+ return rowCount === 0 ? 1 : rowCount
406
518
  }
407
519
  for (const [, child] of children) {
408
520
  rowCount += getRowCount(child, featureTypeOntology)
@@ -449,6 +561,9 @@ function featuresForRow(
449
561
  for (const cds of cdss) {
450
562
  features.push([cds, ...exons, child, feature])
451
563
  }
564
+ if (cdss.length === 0) {
565
+ features.push([...exons, child, feature])
566
+ }
452
567
  }
453
568
  return features
454
569
  }
@@ -44,6 +44,7 @@ export function baseModelFactory(
44
44
  ),
45
45
  ),
46
46
  filteredFeatureTypes: types.array(types.string),
47
+ loadingState: false,
47
48
  })
48
49
  .views((self) => {
49
50
  const { configuration, renderProps: superRenderProps } = self
@@ -76,6 +77,9 @@ export function baseModelFactory(
76
77
  }
77
78
  return 300
78
79
  },
80
+ get loading() {
81
+ return self.loadingState
82
+ },
79
83
  }))
80
84
  .views((self) => ({
81
85
  get rendererTypeName() {
@@ -167,6 +171,9 @@ export function baseModelFactory(
167
171
  updateFilteredFeatureTypes(types: string[]) {
168
172
  self.filteredFeatureTypes = cast(types)
169
173
  },
174
+ setLoading(loading: boolean) {
175
+ self.loadingState = loading
176
+ },
170
177
  }))
171
178
  .views((self) => {
172
179
  const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self
@@ -244,9 +251,16 @@ export function baseModelFactory(
244
251
  if (!self.lgv.initialized || self.regionCannotBeRendered()) {
245
252
  return
246
253
  }
254
+ self.setLoading(true)
247
255
  void (
248
256
  self.session as unknown as ApolloSessionModel
249
- ).apolloDataStore.loadFeatures(self.regions)
257
+ ).apolloDataStore
258
+ .loadFeatures(self.regions)
259
+ .then(() => {
260
+ setTimeout(() => {
261
+ self.setLoading(false)
262
+ }, 1000)
263
+ })
250
264
  if (self.lgv.bpPerPx <= 3) {
251
265
  void (
252
266
  self.session as unknown as ApolloSessionModel