@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 0.3.9
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 +11212 -10483
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +11251 -10509
- 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 +7726 -9014
- 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 +18 -18
- package/src/ApolloInternetAccount/model.ts +123 -70
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
- package/src/BackendDrivers/CollaborationServerDriver.ts +72 -20
- package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
- package/src/ChangeManager.ts +36 -14
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
- package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +72 -234
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +23 -131
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +279 -217
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
- package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
- package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
- package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
- package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
- package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
- package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
- package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +157 -0
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +101 -38
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +334 -262
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
- package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/components/AddAssembly.tsx +34 -38
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddChildFeature.tsx +5 -2
- package/src/components/AddFeature.tsx +30 -21
- package/src/components/AddRefSeqAliases.tsx +64 -50
- package/src/components/CopyFeature.tsx +4 -2
- package/src/components/CreateApolloAnnotation.tsx +22 -9
- package/src/components/DeleteAssembly.tsx +3 -10
- package/src/components/DownloadGFF3.tsx +2 -2
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +6 -6
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +3 -10
- package/src/components/ManageUsers.tsx +23 -22
- package/src/components/MergeTranscripts.tsx +12 -15
- package/src/components/OntologyTermAutocomplete.tsx +1 -8
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/ViewChangeLog.tsx +25 -50
- package/src/components/ViewCheckResults.tsx +2 -8
- package/src/components/index.ts +1 -0
- package/src/config.ts +6 -0
- package/src/index.ts +53 -115
- package/src/makeDisplayComponent.tsx +9 -14
- package/src/menus/index.ts +1 -0
- package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/ClientDataStore.ts +32 -14
- package/src/session/session.ts +159 -121
- package/src/util/annotationFeatureUtils.ts +15 -21
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +329 -0
- package/src/util/loadAssemblyIntoClient.ts +3 -2
- package/src/util/mouseEventsUtils.ts +32 -0
|
@@ -2,27 +2,41 @@ import {
|
|
|
2
2
|
type AnnotationFeature,
|
|
3
3
|
type TranscriptPartCoding,
|
|
4
4
|
} from '@apollo-annotation/mst'
|
|
5
|
+
import { type BaseDisplayModel } from '@jbrowse/core/pluggableElementTypes'
|
|
5
6
|
import { type MenuItem } from '@jbrowse/core/ui'
|
|
6
7
|
import {
|
|
7
8
|
type AbstractSessionModel,
|
|
9
|
+
getContainingView,
|
|
8
10
|
getFrame,
|
|
9
11
|
intersection2,
|
|
10
12
|
measureText,
|
|
11
13
|
} from '@jbrowse/core/util'
|
|
14
|
+
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
12
15
|
import { alpha } from '@mui/material'
|
|
13
16
|
import equal from 'fast-deep-equal/es6'
|
|
14
17
|
import { getSnapshot } from 'mobx-state-tree'
|
|
15
18
|
|
|
16
|
-
import {
|
|
19
|
+
import { MergeExons, SplitExon } from '../../components'
|
|
17
20
|
import { FilterTranscripts } from '../../components/FilterTranscripts'
|
|
18
|
-
import { getMinAndMaxPx, getOverlappingEdge } from '../../util'
|
|
19
|
-
import { type LinearApolloSixFrameDisplay } from '../stateModel'
|
|
20
21
|
import {
|
|
21
|
-
type LinearApolloSixFrameDisplayMouseEvents,
|
|
22
22
|
type MousePosition,
|
|
23
|
-
type
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
type MousePositionWithFeature,
|
|
24
|
+
getAdjacentExons,
|
|
25
|
+
getContextMenuItemsForFeature,
|
|
26
|
+
getMinAndMaxPx,
|
|
27
|
+
getOverlappingEdge,
|
|
28
|
+
getRelatedFeatures,
|
|
29
|
+
getStreamIcon,
|
|
30
|
+
isCDSFeature,
|
|
31
|
+
isExonFeature,
|
|
32
|
+
isMousePositionWithFeature,
|
|
33
|
+
// isTranscriptFeature,
|
|
34
|
+
isSelectedFeature,
|
|
35
|
+
navToFeatureCenter,
|
|
36
|
+
selectFeatureAndOpenWidget,
|
|
37
|
+
} from '../../util'
|
|
38
|
+
import { type LinearApolloSixFrameDisplay } from '../stateModel'
|
|
39
|
+
import { type LinearApolloSixFrameDisplayMouseEvents } from '../stateModel/mouseEvents'
|
|
26
40
|
import { type LinearApolloSixFrameDisplayRendering } from '../stateModel/rendering'
|
|
27
41
|
import { type CanvasMouseEvent } from '../types'
|
|
28
42
|
|
|
@@ -105,14 +119,14 @@ function drawTextLabels(
|
|
|
105
119
|
for (let i = labelArray.length - 1; i >= 0; --i) {
|
|
106
120
|
const label = labelArray[i]
|
|
107
121
|
ctx.fillStyle = label.color
|
|
108
|
-
const labelRowX =
|
|
122
|
+
const labelRowX = label.x + 1
|
|
109
123
|
const labelRowY = label.y + label.h
|
|
110
124
|
const textWidth = measureText(label.text, 10)
|
|
111
125
|
if (label.isSelected) {
|
|
112
|
-
ctx.clearRect(labelRowX - 5, labelRowY, textWidth + 10, label.h)
|
|
113
126
|
ctx.font = 'bold '.concat(font)
|
|
114
127
|
}
|
|
115
128
|
if (label.text) {
|
|
129
|
+
ctx.clearRect(labelRowX - 5, labelRowY, textWidth + 10, label.h)
|
|
116
130
|
ctx.fillText(label.text, labelRowX, labelRowY + 11, textWidth)
|
|
117
131
|
ctx.font = font
|
|
118
132
|
}
|
|
@@ -133,6 +147,7 @@ function draw(
|
|
|
133
147
|
theme,
|
|
134
148
|
highestRow,
|
|
135
149
|
filteredTranscripts,
|
|
150
|
+
selectedFeature,
|
|
136
151
|
showFeatureLabels,
|
|
137
152
|
} = stateModel
|
|
138
153
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
@@ -143,12 +158,11 @@ function draw(
|
|
|
143
158
|
const cdsHeight = rowHeight
|
|
144
159
|
const topLevelFeatureHeight = rowHeight
|
|
145
160
|
const featureLabelSpacer = showFeatureLabels ? 2 : 1
|
|
146
|
-
const textColor = theme
|
|
161
|
+
const textColor = theme.palette.text.primary
|
|
147
162
|
const { attributes, children, min, strand } = topLevelFeature
|
|
148
163
|
if (!children) {
|
|
149
164
|
return
|
|
150
165
|
}
|
|
151
|
-
const { apolloSelectedFeature } = session
|
|
152
166
|
const { apolloDataStore } = session
|
|
153
167
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
154
168
|
if (!featureTypeOntology) {
|
|
@@ -169,7 +183,7 @@ function draw(
|
|
|
169
183
|
: topLevelFeatureMinX
|
|
170
184
|
const topLevelRow = (strand == 1 ? 3 : 4) * featureLabelSpacer
|
|
171
185
|
const topLevelFeatureTop = topLevelRow * rowHeight
|
|
172
|
-
ctx.fillStyle = theme
|
|
186
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
173
187
|
ctx.fillRect(
|
|
174
188
|
topLevelFeatureStartPx,
|
|
175
189
|
topLevelFeatureTop,
|
|
@@ -177,9 +191,9 @@ function draw(
|
|
|
177
191
|
topLevelFeatureHeight,
|
|
178
192
|
)
|
|
179
193
|
|
|
180
|
-
ctx.fillStyle = isSelectedFeature(topLevelFeature,
|
|
194
|
+
ctx.fillStyle = isSelectedFeature(topLevelFeature, selectedFeature)
|
|
181
195
|
? alpha('rgb(0,0,0)', 0.7)
|
|
182
|
-
: alpha(theme
|
|
196
|
+
: alpha(theme.palette.background.paper, 0.7)
|
|
183
197
|
ctx.fillRect(
|
|
184
198
|
topLevelFeatureStartPx + 1,
|
|
185
199
|
topLevelFeatureTop + 1,
|
|
@@ -187,7 +201,7 @@ function draw(
|
|
|
187
201
|
topLevelFeatureHeight - 2,
|
|
188
202
|
)
|
|
189
203
|
|
|
190
|
-
const isSelected = isSelectedFeature(topLevelFeature,
|
|
204
|
+
const isSelected = isSelectedFeature(topLevelFeature, selectedFeature)
|
|
191
205
|
const label: Label = {
|
|
192
206
|
x: topLevelFeatureStartPx,
|
|
193
207
|
y: topLevelFeatureTop,
|
|
@@ -203,9 +217,9 @@ function draw(
|
|
|
203
217
|
}
|
|
204
218
|
|
|
205
219
|
const forwardFill =
|
|
206
|
-
theme
|
|
220
|
+
theme.palette.mode === 'dark' ? forwardFillDark : forwardFillLight
|
|
207
221
|
const backwardFill =
|
|
208
|
-
theme
|
|
222
|
+
theme.palette.mode === 'dark' ? backwardFillDark : backwardFillLight
|
|
209
223
|
const reversal = reversed ? -1 : 1
|
|
210
224
|
let topFill: CanvasPattern | null = null,
|
|
211
225
|
bottomFill: CanvasPattern | null = null
|
|
@@ -269,8 +283,8 @@ function draw(
|
|
|
269
283
|
|
|
270
284
|
const exonTop =
|
|
271
285
|
topLevelFeatureTop + (topLevelFeatureHeight - exonHeight) / 2
|
|
272
|
-
const isSelected = isSelectedFeature(exon,
|
|
273
|
-
ctx.fillStyle = theme
|
|
286
|
+
const isSelected = isSelectedFeature(exon, selectedFeature)
|
|
287
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
274
288
|
ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
|
|
275
289
|
if (widthPx > 2) {
|
|
276
290
|
ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
@@ -309,7 +323,7 @@ function draw(
|
|
|
309
323
|
}
|
|
310
324
|
}
|
|
311
325
|
|
|
312
|
-
const isSelected = isSelectedFeature(child,
|
|
326
|
+
const isSelected = isSelectedFeature(child, selectedFeature?.parent)
|
|
313
327
|
let cdsStartPx = 0
|
|
314
328
|
let cdsTop = 0
|
|
315
329
|
for (const cdsRow of cdsLocations) {
|
|
@@ -318,9 +332,9 @@ function draw(
|
|
|
318
332
|
let counter = 1
|
|
319
333
|
for (const cds of cdsRow.sort((a, b) => a.max - b.max)) {
|
|
320
334
|
if (
|
|
321
|
-
(
|
|
335
|
+
(selectedFeature &&
|
|
322
336
|
isSelected &&
|
|
323
|
-
featureTypeOntology.isTypeOf(
|
|
337
|
+
featureTypeOntology.isTypeOf(selectedFeature.type, 'CDS')) ||
|
|
324
338
|
!deepSetHas(renderedCDS, cds)
|
|
325
339
|
) {
|
|
326
340
|
const cdsWidthPx = (cds.max - cds.min) / bpPerPx
|
|
@@ -331,12 +345,36 @@ function draw(
|
|
|
331
345
|
regionNumber: displayedRegionIndex,
|
|
332
346
|
})?.offsetPx ?? 0) - offsetPx
|
|
333
347
|
cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
334
|
-
ctx.fillStyle = theme
|
|
348
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
335
349
|
const frame = getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase)
|
|
336
350
|
const frameAdjust =
|
|
337
351
|
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
338
352
|
cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
339
353
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
354
|
+
|
|
355
|
+
// Draw lines to connect CDS features with shared mRNA parent
|
|
356
|
+
if (counter > 1) {
|
|
357
|
+
// Mid-point for intron line "hat"
|
|
358
|
+
const midPoint: [number, number] = [
|
|
359
|
+
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
360
|
+
Math.max(
|
|
361
|
+
frame < 0 ? rowHeight * featureLabelSpacer * highestRow + 1 : 1, // Avoid render ceiling
|
|
362
|
+
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
363
|
+
),
|
|
364
|
+
]
|
|
365
|
+
ctx.strokeStyle = 'rgb(0, 128, 128)'
|
|
366
|
+
ctx.beginPath()
|
|
367
|
+
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
368
|
+
ctx.lineTo(...midPoint)
|
|
369
|
+
ctx.stroke()
|
|
370
|
+
ctx.moveTo(...midPoint)
|
|
371
|
+
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
372
|
+
ctx.stroke()
|
|
373
|
+
}
|
|
374
|
+
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
375
|
+
prevCDSTop = cdsTop + rowHeight / 2
|
|
376
|
+
counter += 1
|
|
377
|
+
|
|
340
378
|
if (cdsWidthPx > 2) {
|
|
341
379
|
ctx.clearRect(
|
|
342
380
|
cdsStartPx + 1,
|
|
@@ -345,13 +383,13 @@ function draw(
|
|
|
345
383
|
cdsHeight - 2,
|
|
346
384
|
)
|
|
347
385
|
|
|
348
|
-
const frameColor = theme
|
|
386
|
+
const frameColor = theme.palette.framesCDS.at(frame)?.main
|
|
349
387
|
const cdsColorCode = frameColor ?? 'rgb(171,71,188)'
|
|
350
388
|
ctx.fillStyle = cdsColorCode
|
|
351
389
|
ctx.fillStyle =
|
|
352
|
-
|
|
390
|
+
selectedFeature &&
|
|
353
391
|
isSelected &&
|
|
354
|
-
featureTypeOntology.isTypeOf(
|
|
392
|
+
featureTypeOntology.isTypeOf(selectedFeature.type, 'CDS')
|
|
355
393
|
? 'rgb(0,0,0)'
|
|
356
394
|
: cdsColorCode
|
|
357
395
|
ctx.fillRect(
|
|
@@ -361,31 +399,6 @@ function draw(
|
|
|
361
399
|
cdsHeight - 2,
|
|
362
400
|
)
|
|
363
401
|
|
|
364
|
-
// Draw lines to connect CDS features with shared mRNA parent
|
|
365
|
-
if (counter > 1) {
|
|
366
|
-
// Mid-point for intron line "hat"
|
|
367
|
-
const midPoint: [number, number] = [
|
|
368
|
-
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
369
|
-
Math.max(
|
|
370
|
-
frame < 0
|
|
371
|
-
? rowHeight * featureLabelSpacer * highestRow + 1
|
|
372
|
-
: 1, // Avoid render ceiling
|
|
373
|
-
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
374
|
-
),
|
|
375
|
-
]
|
|
376
|
-
ctx.strokeStyle = 'rgb(0, 128, 128)'
|
|
377
|
-
ctx.beginPath()
|
|
378
|
-
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
379
|
-
ctx.lineTo(...midPoint)
|
|
380
|
-
ctx.stroke()
|
|
381
|
-
ctx.moveTo(...midPoint)
|
|
382
|
-
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
383
|
-
ctx.stroke()
|
|
384
|
-
}
|
|
385
|
-
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
386
|
-
prevCDSTop = cdsTop + rowHeight / 2
|
|
387
|
-
counter += 1
|
|
388
|
-
|
|
389
402
|
if (topFill && bottomFill) {
|
|
390
403
|
ctx.fillStyle = topFill
|
|
391
404
|
ctx.fillRect(
|
|
@@ -448,10 +461,10 @@ function drawDragPreview(
|
|
|
448
461
|
const rectY = row * apolloRowHeight
|
|
449
462
|
const rectWidth = Math.abs(current.x - featureEdgePx)
|
|
450
463
|
const rectHeight = apolloRowHeight * rowCount
|
|
451
|
-
overlayCtx.strokeStyle = theme
|
|
464
|
+
overlayCtx.strokeStyle = theme.palette.info.main
|
|
452
465
|
overlayCtx.setLineDash([6])
|
|
453
466
|
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
454
|
-
overlayCtx.fillStyle = alpha(theme
|
|
467
|
+
overlayCtx.fillStyle = alpha(theme.palette.info.main, 0.2)
|
|
455
468
|
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
456
469
|
}
|
|
457
470
|
|
|
@@ -460,7 +473,7 @@ function drawHover(
|
|
|
460
473
|
ctx: CanvasRenderingContext2D,
|
|
461
474
|
) {
|
|
462
475
|
const {
|
|
463
|
-
|
|
476
|
+
hoveredFeature,
|
|
464
477
|
apolloRowHeight,
|
|
465
478
|
filteredTranscripts,
|
|
466
479
|
lgv,
|
|
@@ -468,15 +481,15 @@ function drawHover(
|
|
|
468
481
|
session,
|
|
469
482
|
showFeatureLabels,
|
|
470
483
|
} = stateModel
|
|
471
|
-
if (!
|
|
484
|
+
if (!hoveredFeature) {
|
|
472
485
|
return
|
|
473
486
|
}
|
|
487
|
+
const { feature } = hoveredFeature
|
|
474
488
|
const { apolloDataStore } = session
|
|
475
489
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
476
490
|
if (!featureTypeOntology) {
|
|
477
491
|
throw new Error('featureTypeOntology is undefined')
|
|
478
492
|
}
|
|
479
|
-
const { feature } = apolloHover
|
|
480
493
|
if (!featureTypeOntology.isTypeOf(feature.type, 'transcript')) {
|
|
481
494
|
return
|
|
482
495
|
}
|
|
@@ -504,43 +517,42 @@ function drawHover(
|
|
|
504
517
|
let counter = 1
|
|
505
518
|
for (const cds of cdsRow.sort((a, b) => a.max - b.max)) {
|
|
506
519
|
const cdsWidthPx = (cds.max - cds.min) / bpPerPx
|
|
520
|
+
const minX =
|
|
521
|
+
(lgv.bpToPx({
|
|
522
|
+
refName,
|
|
523
|
+
coord: cds.min,
|
|
524
|
+
regionNumber: layoutIndex,
|
|
525
|
+
})?.offsetPx ?? 0) - offsetPx
|
|
526
|
+
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
527
|
+
const frame = getFrame(cds.min, cds.max, strand ?? 1, cds.phase)
|
|
528
|
+
const frameAdjust =
|
|
529
|
+
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
530
|
+
const cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
531
|
+
if (counter > 1) {
|
|
532
|
+
// Mid-point for intron line "hat"
|
|
533
|
+
const midPoint: [number, number] = [
|
|
534
|
+
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
535
|
+
Math.max(
|
|
536
|
+
frame < 0 ? rowHeight * featureLabelSpacer * highestRow + 1 : 1, // Avoid render ceiling
|
|
537
|
+
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
538
|
+
),
|
|
539
|
+
]
|
|
540
|
+
ctx.strokeStyle = 'rgb(0, 0, 0)'
|
|
541
|
+
ctx.lineWidth = 2
|
|
542
|
+
ctx.beginPath()
|
|
543
|
+
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
544
|
+
ctx.lineTo(...midPoint)
|
|
545
|
+
ctx.stroke()
|
|
546
|
+
ctx.moveTo(...midPoint)
|
|
547
|
+
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
548
|
+
ctx.stroke()
|
|
549
|
+
}
|
|
550
|
+
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
551
|
+
prevCDSTop = cdsTop + rowHeight / 2
|
|
552
|
+
counter += 1
|
|
507
553
|
if (cdsWidthPx > 2) {
|
|
508
|
-
const minX =
|
|
509
|
-
(lgv.bpToPx({
|
|
510
|
-
refName,
|
|
511
|
-
coord: cds.min,
|
|
512
|
-
regionNumber: layoutIndex,
|
|
513
|
-
})?.offsetPx ?? 0) - offsetPx
|
|
514
|
-
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
515
|
-
const frame = getFrame(cds.min, cds.max, strand ?? 1, cds.phase)
|
|
516
|
-
const frameAdjust =
|
|
517
|
-
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
518
|
-
const cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
519
554
|
ctx.fillStyle = 'rgba(255,0,0,0.6)'
|
|
520
555
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
521
|
-
|
|
522
|
-
if (counter > 1) {
|
|
523
|
-
// Mid-point for intron line "hat"
|
|
524
|
-
const midPoint: [number, number] = [
|
|
525
|
-
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
526
|
-
Math.max(
|
|
527
|
-
frame < 0 ? rowHeight * featureLabelSpacer * highestRow + 1 : 1, // Avoid render ceiling
|
|
528
|
-
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
529
|
-
),
|
|
530
|
-
]
|
|
531
|
-
ctx.strokeStyle = 'rgb(0, 0, 0)'
|
|
532
|
-
ctx.lineWidth = 2
|
|
533
|
-
ctx.beginPath()
|
|
534
|
-
ctx.moveTo(prevCDSEndPx, prevCDSTop)
|
|
535
|
-
ctx.lineTo(...midPoint)
|
|
536
|
-
ctx.stroke()
|
|
537
|
-
ctx.moveTo(...midPoint)
|
|
538
|
-
ctx.lineTo(cdsStartPx, cdsTop + rowHeight / 2)
|
|
539
|
-
ctx.stroke()
|
|
540
|
-
}
|
|
541
|
-
prevCDSEndPx = cdsStartPx + cdsWidthPx
|
|
542
|
-
prevCDSTop = cdsTop + rowHeight / 2
|
|
543
|
-
counter += 1
|
|
544
556
|
}
|
|
545
557
|
}
|
|
546
558
|
}
|
|
@@ -548,16 +560,14 @@ function drawHover(
|
|
|
548
560
|
|
|
549
561
|
function onMouseDown(
|
|
550
562
|
stateModel: LinearApolloSixFrameDisplay,
|
|
551
|
-
currentMousePosition:
|
|
563
|
+
currentMousePosition: MousePositionWithFeature,
|
|
552
564
|
event: CanvasMouseEvent,
|
|
553
565
|
) {
|
|
554
|
-
const {
|
|
566
|
+
const { feature } = currentMousePosition
|
|
555
567
|
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
556
568
|
// don't start dragging the view if we try to drag the feature edge
|
|
557
|
-
const { cds, feature } = featureAndGlyphUnderMouse
|
|
558
569
|
const draggableFeature = getDraggableFeatureInfo(
|
|
559
570
|
currentMousePosition,
|
|
560
|
-
cds,
|
|
561
571
|
feature,
|
|
562
572
|
stateModel,
|
|
563
573
|
)
|
|
@@ -576,13 +586,11 @@ function onMouseMove(
|
|
|
576
586
|
stateModel: LinearApolloSixFrameDisplay,
|
|
577
587
|
mousePosition: MousePosition,
|
|
578
588
|
) {
|
|
579
|
-
if (
|
|
580
|
-
const {
|
|
581
|
-
stateModel.
|
|
582
|
-
const { cds, feature } = featureAndGlyphUnderMouse
|
|
589
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
590
|
+
const { feature, bp } = mousePosition
|
|
591
|
+
stateModel.setHoveredFeature({ feature, bp })
|
|
583
592
|
const draggableFeature = getDraggableFeatureInfo(
|
|
584
593
|
mousePosition,
|
|
585
|
-
cds,
|
|
586
594
|
feature,
|
|
587
595
|
stateModel,
|
|
588
596
|
)
|
|
@@ -601,53 +609,43 @@ function onMouseUp(
|
|
|
601
609
|
if (stateModel.apolloDragging) {
|
|
602
610
|
return
|
|
603
611
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (!featureTypeOntology) {
|
|
614
|
-
throw new Error('featureTypeOntology is undefined')
|
|
615
|
-
}
|
|
612
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
613
|
+
const { feature } = mousePosition
|
|
614
|
+
const { session } = stateModel
|
|
615
|
+
const { apolloDataStore } = session
|
|
616
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
617
|
+
stateModel.setSelectedFeature(feature)
|
|
618
|
+
if (!featureTypeOntology) {
|
|
619
|
+
throw new Error('featureTypeOntology is undefined')
|
|
620
|
+
}
|
|
616
621
|
|
|
617
|
-
|
|
618
|
-
|
|
622
|
+
let containsCDSOrExon = false
|
|
623
|
+
for (const [, child] of feature.children ?? []) {
|
|
624
|
+
if (
|
|
625
|
+
featureTypeOntology.isTypeOf(child.type, 'CDS') ||
|
|
626
|
+
featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
627
|
+
) {
|
|
628
|
+
containsCDSOrExon = true
|
|
629
|
+
break
|
|
630
|
+
}
|
|
631
|
+
}
|
|
619
632
|
if (
|
|
620
|
-
featureTypeOntology.isTypeOf(
|
|
621
|
-
|
|
633
|
+
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
634
|
+
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
635
|
+
containsCDSOrExon
|
|
622
636
|
) {
|
|
623
|
-
|
|
624
|
-
|
|
637
|
+
stateModel.showFeatureDetailsWidget(feature, [
|
|
638
|
+
'ApolloTranscriptDetails',
|
|
639
|
+
'apolloTranscriptDetails',
|
|
640
|
+
])
|
|
641
|
+
} else {
|
|
642
|
+
stateModel.showFeatureDetailsWidget(feature)
|
|
625
643
|
}
|
|
626
644
|
}
|
|
627
|
-
if (
|
|
628
|
-
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
629
|
-
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
630
|
-
containsCDSOrExon
|
|
631
|
-
) {
|
|
632
|
-
stateModel.showFeatureDetailsWidget(feature, [
|
|
633
|
-
'ApolloTranscriptDetails',
|
|
634
|
-
'apolloTranscriptDetails',
|
|
635
|
-
])
|
|
636
|
-
} else {
|
|
637
|
-
stateModel.showFeatureDetailsWidget(feature)
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
export function isSelectedFeature(
|
|
642
|
-
feature: AnnotationFeature,
|
|
643
|
-
selectedFeature: AnnotationFeature | undefined,
|
|
644
|
-
) {
|
|
645
|
-
return Boolean(selectedFeature && feature._id === selectedFeature._id)
|
|
646
645
|
}
|
|
647
646
|
|
|
648
647
|
function getDraggableFeatureInfo(
|
|
649
648
|
mousePosition: MousePosition,
|
|
650
|
-
cds: TranscriptPartCoding | null,
|
|
651
649
|
feature: AnnotationFeature,
|
|
652
650
|
stateModel: LinearApolloSixFrameDisplay,
|
|
653
651
|
): { feature: AnnotationFeature; edge: 'min' | 'max' } | undefined {
|
|
@@ -658,9 +656,6 @@ function getDraggableFeatureInfo(
|
|
|
658
656
|
throw new Error('featureTypeOntology is undefined')
|
|
659
657
|
}
|
|
660
658
|
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript')
|
|
661
|
-
if (cds === null) {
|
|
662
|
-
return
|
|
663
|
-
}
|
|
664
659
|
const featureID: string | undefined = feature.attributes
|
|
665
660
|
.get('gff_id')
|
|
666
661
|
?.toString()
|
|
@@ -700,16 +695,24 @@ function getDraggableFeatureInfo(
|
|
|
700
695
|
}
|
|
701
696
|
}
|
|
702
697
|
// End of special cases, let's see if we're on the edge of this CDS or exon
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
698
|
+
for (const loc of transcript.cdsLocations) {
|
|
699
|
+
for (const cds of loc) {
|
|
700
|
+
const minMax = getMinAndMaxPx(cds, refName, regionNumber, lgv)
|
|
701
|
+
if (minMax) {
|
|
702
|
+
const overlappingCDS = cdsChildren.find((child) => {
|
|
703
|
+
const [start, end] = intersection2(bp, bp + 1, child.min, child.max)
|
|
704
|
+
return start !== undefined && end !== undefined
|
|
705
|
+
})
|
|
706
|
+
if (overlappingCDS) {
|
|
707
|
+
const overlappingEdge = getOverlappingEdge(
|
|
708
|
+
overlappingCDS,
|
|
709
|
+
x,
|
|
710
|
+
minMax,
|
|
711
|
+
)
|
|
712
|
+
if (overlappingEdge) {
|
|
713
|
+
return overlappingEdge
|
|
714
|
+
}
|
|
715
|
+
}
|
|
713
716
|
}
|
|
714
717
|
}
|
|
715
718
|
}
|
|
@@ -721,22 +724,33 @@ function drawTooltip(
|
|
|
721
724
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
722
725
|
context: CanvasRenderingContext2D,
|
|
723
726
|
): void {
|
|
724
|
-
const {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
+
const {
|
|
728
|
+
hoveredFeature,
|
|
729
|
+
apolloRowHeight,
|
|
730
|
+
filteredTranscripts,
|
|
731
|
+
lgv,
|
|
732
|
+
session,
|
|
733
|
+
showFeatureLabels,
|
|
734
|
+
theme,
|
|
735
|
+
} = display
|
|
736
|
+
if (!hoveredFeature) {
|
|
727
737
|
return
|
|
728
738
|
}
|
|
729
|
-
const {
|
|
730
|
-
|
|
739
|
+
const { feature, bp } = hoveredFeature
|
|
740
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
741
|
+
if (!featureTypeOntology) {
|
|
742
|
+
throw new Error('featureTypeOntology is undefined')
|
|
743
|
+
}
|
|
744
|
+
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript')
|
|
745
|
+
if (!isTranscript) {
|
|
731
746
|
return
|
|
732
747
|
}
|
|
748
|
+
const { attributes, strand, type } = feature
|
|
733
749
|
const position = display.getFeatureLayoutPosition(feature)
|
|
734
750
|
if (!position) {
|
|
735
751
|
return
|
|
736
752
|
}
|
|
737
|
-
const featureID: string | undefined =
|
|
738
|
-
.get('gff_id')
|
|
739
|
-
?.toString()
|
|
753
|
+
const featureID: string | undefined = attributes.get('gff_id')?.toString()
|
|
740
754
|
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
741
755
|
return
|
|
742
756
|
}
|
|
@@ -746,9 +760,20 @@ function drawTooltip(
|
|
|
746
760
|
const { refName, reversed } = displayedRegion
|
|
747
761
|
const rowHeight = apolloRowHeight
|
|
748
762
|
const cdsHeight = Math.round(0.7 * rowHeight)
|
|
763
|
+
const featureLabelSpacer = showFeatureLabels ? 2 : 1
|
|
749
764
|
let location = 'Loc: '
|
|
750
|
-
|
|
751
|
-
const
|
|
765
|
+
let cds: TranscriptPartCoding | undefined = undefined
|
|
766
|
+
for (const loc of feature.cdsLocations) {
|
|
767
|
+
for (const cdsLoc of loc) {
|
|
768
|
+
if (bp >= cdsLoc.min && bp <= cdsLoc.max) {
|
|
769
|
+
cds = cdsLoc
|
|
770
|
+
break
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (!cds) {
|
|
775
|
+
return
|
|
776
|
+
}
|
|
752
777
|
const { max, min, phase } = cds
|
|
753
778
|
location += `${min + 1}–${max}`
|
|
754
779
|
|
|
@@ -759,12 +784,12 @@ function drawTooltip(
|
|
|
759
784
|
regionNumber: layoutIndex,
|
|
760
785
|
})?.offsetPx ?? 0) - offsetPx
|
|
761
786
|
const frame = getFrame(min, max, strand ?? 1, phase)
|
|
762
|
-
const frameAdjust = frame < 0 ? -1 * frame + 5 : frame
|
|
763
|
-
const cdsTop =
|
|
787
|
+
const frameAdjust = (frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
788
|
+
const cdsTop =
|
|
789
|
+
(frameAdjust - featureLabelSpacer) * rowHeight + (rowHeight - cdsHeight) / 2
|
|
764
790
|
const cdsWidthPx = (max - min) / bpPerPx
|
|
765
791
|
|
|
766
792
|
const featureType = `Type: ${cds.type}`
|
|
767
|
-
const { attributes } = feature
|
|
768
793
|
const featureName = attributes.get('gff_name')?.find((name) => name !== '')
|
|
769
794
|
const textWidth = [
|
|
770
795
|
context.measureText(featureType).width,
|
|
@@ -772,14 +797,14 @@ function drawTooltip(
|
|
|
772
797
|
]
|
|
773
798
|
if (featureName) {
|
|
774
799
|
textWidth.push(
|
|
775
|
-
context.measureText(`Parent Type: ${
|
|
800
|
+
context.measureText(`Parent Type: ${type}`).width,
|
|
776
801
|
context.measureText(`Parent Name: ${featureName}`).width,
|
|
777
802
|
)
|
|
778
803
|
}
|
|
779
804
|
const maxWidth = Math.max(...textWidth)
|
|
780
805
|
|
|
781
806
|
startPx = startPx + cdsWidthPx + 5
|
|
782
|
-
context.fillStyle = alpha(theme
|
|
807
|
+
context.fillStyle = alpha(theme.palette.text.primary, 0.7)
|
|
783
808
|
context.fillRect(
|
|
784
809
|
startPx,
|
|
785
810
|
cdsTop,
|
|
@@ -791,12 +816,12 @@ function drawTooltip(
|
|
|
791
816
|
context.lineTo(startPx - 5, cdsTop + 5)
|
|
792
817
|
context.lineTo(startPx, cdsTop + 10)
|
|
793
818
|
context.fill()
|
|
794
|
-
context.fillStyle = theme
|
|
819
|
+
context.fillStyle = theme.palette.background.default
|
|
795
820
|
let textTop = cdsTop + 12
|
|
796
821
|
context.fillText(featureType, startPx + 2, textTop)
|
|
797
822
|
if (featureName) {
|
|
798
823
|
textTop = textTop + 12
|
|
799
|
-
context.fillText(`Parent Type: ${
|
|
824
|
+
context.fillText(`Parent Type: ${type}`, startPx + 2, textTop)
|
|
800
825
|
textTop = textTop + 12
|
|
801
826
|
context.fillText(`Parent Name: ${featureName}`, startPx + 2, textTop)
|
|
802
827
|
}
|
|
@@ -806,119 +831,165 @@ function drawTooltip(
|
|
|
806
831
|
|
|
807
832
|
function getContextMenuItems(
|
|
808
833
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
834
|
+
mousePosition: MousePositionWithFeature,
|
|
809
835
|
): MenuItem[] {
|
|
810
836
|
const {
|
|
811
|
-
apolloHover,
|
|
812
837
|
apolloInternetAccount: internetAccount,
|
|
838
|
+
hoveredFeature,
|
|
813
839
|
changeManager,
|
|
814
840
|
filteredTranscripts,
|
|
815
841
|
regions,
|
|
816
842
|
selectedFeature,
|
|
817
843
|
session,
|
|
818
844
|
} = display
|
|
845
|
+
const [region] = regions
|
|
846
|
+
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
819
847
|
const menuItems: MenuItem[] = []
|
|
820
|
-
if (!apolloHover) {
|
|
821
|
-
return menuItems
|
|
822
|
-
}
|
|
823
|
-
const { feature: sourceFeature } = apolloHover
|
|
824
848
|
const role = internetAccount ? internetAccount.role : 'admin'
|
|
825
849
|
const admin = role === 'admin'
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
830
|
-
menuItems.push(
|
|
831
|
-
{
|
|
832
|
-
label: 'Add child feature',
|
|
833
|
-
disabled: readOnly,
|
|
834
|
-
onClick: () => {
|
|
835
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
836
|
-
(doneCallback) => [
|
|
837
|
-
AddChildFeature,
|
|
838
|
-
{
|
|
839
|
-
session,
|
|
840
|
-
handleClose: () => {
|
|
841
|
-
doneCallback()
|
|
842
|
-
},
|
|
843
|
-
changeManager,
|
|
844
|
-
sourceFeature,
|
|
845
|
-
sourceAssemblyId,
|
|
846
|
-
internetAccount,
|
|
847
|
-
},
|
|
848
|
-
],
|
|
849
|
-
)
|
|
850
|
-
},
|
|
851
|
-
},
|
|
852
|
-
{
|
|
853
|
-
label: 'Copy features and annotations',
|
|
854
|
-
disabled: readOnly,
|
|
855
|
-
onClick: () => {
|
|
856
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
857
|
-
(doneCallback) => [
|
|
858
|
-
CopyFeature,
|
|
859
|
-
{
|
|
860
|
-
session,
|
|
861
|
-
handleClose: () => {
|
|
862
|
-
doneCallback()
|
|
863
|
-
},
|
|
864
|
-
changeManager,
|
|
865
|
-
sourceFeature,
|
|
866
|
-
sourceAssemblyId: currentAssemblyId,
|
|
867
|
-
},
|
|
868
|
-
],
|
|
869
|
-
)
|
|
870
|
-
},
|
|
871
|
-
},
|
|
872
|
-
{
|
|
873
|
-
label: 'Delete feature',
|
|
874
|
-
disabled: !admin,
|
|
875
|
-
onClick: () => {
|
|
876
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
877
|
-
(doneCallback) => [
|
|
878
|
-
DeleteFeature,
|
|
879
|
-
{
|
|
880
|
-
session,
|
|
881
|
-
handleClose: () => {
|
|
882
|
-
doneCallback()
|
|
883
|
-
},
|
|
884
|
-
changeManager,
|
|
885
|
-
sourceFeature,
|
|
886
|
-
sourceAssemblyId: currentAssemblyId,
|
|
887
|
-
selectedFeature,
|
|
888
|
-
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
889
|
-
display.setSelectedFeature(feature)
|
|
890
|
-
},
|
|
891
|
-
},
|
|
892
|
-
],
|
|
893
|
-
)
|
|
894
|
-
},
|
|
895
|
-
},
|
|
896
|
-
)
|
|
850
|
+
if (!hoveredFeature) {
|
|
851
|
+
return menuItems
|
|
852
|
+
}
|
|
897
853
|
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
898
854
|
if (!featureTypeOntology) {
|
|
899
855
|
throw new Error('featureTypeOntology is undefined')
|
|
900
856
|
}
|
|
901
|
-
if (
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
857
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
858
|
+
const { bp, feature } = mousePosition
|
|
859
|
+
let featuresUnderClick = getRelatedFeatures(feature, bp)
|
|
860
|
+
if (isCDSFeature(feature, session)) {
|
|
861
|
+
featuresUnderClick = getRelatedFeatures(feature, bp, true)
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
for (const feature of featuresUnderClick) {
|
|
865
|
+
const featureID: string | undefined = feature.attributes
|
|
866
|
+
.get('gff_id')
|
|
867
|
+
?.toString()
|
|
868
|
+
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
869
|
+
continue
|
|
870
|
+
}
|
|
871
|
+
const contextMenuItemsForFeature = getContextMenuItemsForFeature(
|
|
872
|
+
display,
|
|
873
|
+
feature,
|
|
874
|
+
)
|
|
875
|
+
if (isExonFeature(feature, session)) {
|
|
876
|
+
const adjacentExons = getAdjacentExons(
|
|
877
|
+
feature,
|
|
878
|
+
display,
|
|
879
|
+
mousePosition,
|
|
880
|
+
session,
|
|
881
|
+
)
|
|
882
|
+
const lgv = getContainingView(
|
|
883
|
+
display as BaseDisplayModel,
|
|
884
|
+
) as unknown as LinearGenomeViewModel
|
|
885
|
+
if (adjacentExons.upstream) {
|
|
886
|
+
const exon = adjacentExons.upstream
|
|
887
|
+
contextMenuItemsForFeature.push({
|
|
888
|
+
label: 'Go to upstream exon',
|
|
889
|
+
icon: getStreamIcon(
|
|
890
|
+
feature.strand,
|
|
891
|
+
true,
|
|
892
|
+
lgv.displayedRegions.at(0)?.reversed,
|
|
893
|
+
),
|
|
894
|
+
onClick: () => {
|
|
895
|
+
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
896
|
+
selectFeatureAndOpenWidget(display, exon)
|
|
897
|
+
},
|
|
898
|
+
})
|
|
899
|
+
}
|
|
900
|
+
if (adjacentExons.downstream) {
|
|
901
|
+
const exon = adjacentExons.downstream
|
|
902
|
+
contextMenuItemsForFeature.push({
|
|
903
|
+
label: 'Go to downstream exon',
|
|
904
|
+
icon: getStreamIcon(
|
|
905
|
+
feature.strand,
|
|
906
|
+
false,
|
|
907
|
+
lgv.displayedRegions.at(0)?.reversed,
|
|
908
|
+
),
|
|
909
|
+
onClick: () => {
|
|
910
|
+
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
911
|
+
selectFeatureAndOpenWidget(display, exon)
|
|
912
|
+
},
|
|
913
|
+
})
|
|
914
|
+
}
|
|
915
|
+
contextMenuItemsForFeature.push(
|
|
916
|
+
{
|
|
917
|
+
label: 'Merge exons',
|
|
918
|
+
disabled: !admin,
|
|
919
|
+
onClick: () => {
|
|
920
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
921
|
+
(doneCallback) => [
|
|
922
|
+
MergeExons,
|
|
923
|
+
{
|
|
924
|
+
session,
|
|
925
|
+
handleClose: () => {
|
|
926
|
+
doneCallback()
|
|
927
|
+
},
|
|
928
|
+
changeManager,
|
|
929
|
+
sourceFeature: feature,
|
|
930
|
+
sourceAssemblyId: currentAssemblyId,
|
|
931
|
+
selectedFeature,
|
|
932
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
933
|
+
display.setSelectedFeature(feature)
|
|
934
|
+
},
|
|
935
|
+
},
|
|
936
|
+
],
|
|
937
|
+
)
|
|
938
|
+
},
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
label: 'Split exon',
|
|
942
|
+
disabled: !admin,
|
|
943
|
+
onClick: () => {
|
|
944
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
945
|
+
(doneCallback) => [
|
|
946
|
+
SplitExon,
|
|
947
|
+
{
|
|
948
|
+
session,
|
|
949
|
+
handleClose: () => {
|
|
950
|
+
doneCallback()
|
|
951
|
+
},
|
|
952
|
+
changeManager,
|
|
953
|
+
sourceFeature: feature,
|
|
954
|
+
sourceAssemblyId: currentAssemblyId,
|
|
955
|
+
selectedFeature,
|
|
956
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
957
|
+
display.setSelectedFeature(feature)
|
|
958
|
+
},
|
|
959
|
+
},
|
|
960
|
+
],
|
|
961
|
+
)
|
|
917
962
|
},
|
|
918
|
-
|
|
963
|
+
},
|
|
919
964
|
)
|
|
920
|
-
}
|
|
921
|
-
|
|
965
|
+
}
|
|
966
|
+
if (featureTypeOntology.isTypeOf(feature.type, 'gene')) {
|
|
967
|
+
contextMenuItemsForFeature.push({
|
|
968
|
+
label: 'Filter alternate transcripts',
|
|
969
|
+
onClick: () => {
|
|
970
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
971
|
+
(doneCallback) => [
|
|
972
|
+
FilterTranscripts,
|
|
973
|
+
{
|
|
974
|
+
handleClose: () => {
|
|
975
|
+
doneCallback()
|
|
976
|
+
},
|
|
977
|
+
sourceFeature: feature,
|
|
978
|
+
filteredTranscripts: getSnapshot(filteredTranscripts),
|
|
979
|
+
onUpdate: (forms: string[]) => {
|
|
980
|
+
display.updateFilteredTranscripts(forms)
|
|
981
|
+
},
|
|
982
|
+
},
|
|
983
|
+
],
|
|
984
|
+
)
|
|
985
|
+
},
|
|
986
|
+
})
|
|
987
|
+
}
|
|
988
|
+
menuItems.push({
|
|
989
|
+
label: feature.type,
|
|
990
|
+
subMenu: contextMenuItemsForFeature,
|
|
991
|
+
})
|
|
992
|
+
}
|
|
922
993
|
}
|
|
923
994
|
return menuItems
|
|
924
995
|
}
|
|
@@ -933,6 +1004,7 @@ export const geneGlyph: Glyph = {
|
|
|
933
1004
|
drawHover,
|
|
934
1005
|
drawTooltip,
|
|
935
1006
|
getContextMenuItems,
|
|
1007
|
+
getContextMenuItemsForFeature,
|
|
936
1008
|
onMouseDown,
|
|
937
1009
|
onMouseLeave,
|
|
938
1010
|
onMouseMove,
|