@apollo-annotation/jbrowse-plugin-apollo 0.3.6 → 0.3.8
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 +4603 -2045
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +4611 -2039
- 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 +9387 -4016
- 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 +15 -15
- package/src/ApolloInternetAccount/model.ts +48 -13
- package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
- package/src/ChangeManager.ts +42 -18
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -81
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +946 -190
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +61 -73
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +55 -211
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +562 -108
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +78 -14
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +15 -9
- package/src/LinearApolloDisplay/stateModel/base.ts +63 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +79 -292
- package/src/LinearApolloDisplay/stateModel/rendering.ts +45 -344
- 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/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 +481 -0
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +102 -40
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +382 -243
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +83 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +23 -11
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +118 -123
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +53 -63
- package/src/OntologyManager/index.ts +4 -1
- package/src/TabularEditor/HybridGrid/Feature.tsx +20 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
- package/src/components/AddAssembly.tsx +1 -1
- package/src/components/AddAssemblyAliases.tsx +114 -0
- package/src/components/AddChildFeature.tsx +7 -7
- package/src/components/AddFeature.tsx +20 -15
- package/src/components/AddRefSeqAliases.tsx +9 -9
- package/src/components/CopyFeature.tsx +4 -4
- package/src/components/CreateApolloAnnotation.tsx +335 -151
- package/src/components/DeleteAssembly.tsx +1 -1
- package/src/components/DeleteFeature.tsx +358 -11
- package/src/components/DownloadGFF3.tsx +20 -1
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +86 -0
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +1 -1
- package/src/components/MergeExons.tsx +193 -0
- package/src/components/MergeTranscripts.tsx +182 -0
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/SplitExon.tsx +134 -0
- package/src/components/ViewCheckResults.tsx +1 -1
- package/src/components/index.ts +4 -0
- package/src/config.ts +11 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
- package/src/extensions/annotationFromPileup.ts +99 -89
- package/src/index.ts +42 -105
- package/src/makeDisplayComponent.tsx +0 -1
- package/src/menus/index.ts +1 -0
- package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +60 -33
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/session.ts +163 -104
- package/src/util/annotationFeatureUtils.ts +59 -0
- package/src/util/copyToClipboard.ts +21 -0
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +201 -0
- package/src/util/index.ts +2 -0
- package/src/util/mouseEventsUtils.ts +145 -0
|
@@ -5,22 +5,28 @@ import {
|
|
|
5
5
|
import { type MenuItem } from '@jbrowse/core/ui'
|
|
6
6
|
import {
|
|
7
7
|
type AbstractSessionModel,
|
|
8
|
-
type SessionWithWidgets,
|
|
9
8
|
getFrame,
|
|
10
9
|
intersection2,
|
|
11
|
-
|
|
10
|
+
measureText,
|
|
12
11
|
} from '@jbrowse/core/util'
|
|
13
12
|
import { alpha } from '@mui/material'
|
|
14
13
|
import equal from 'fast-deep-equal/es6'
|
|
14
|
+
import { getSnapshot } from 'mobx-state-tree'
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { MergeExons, SplitExon } from '../../components'
|
|
17
|
+
import { FilterTranscripts } from '../../components/FilterTranscripts'
|
|
18
18
|
import {
|
|
19
|
-
type LinearApolloSixFrameDisplayMouseEvents,
|
|
20
19
|
type MousePosition,
|
|
21
|
-
type
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
type MousePositionWithFeature,
|
|
21
|
+
getContextMenuItemsForFeature,
|
|
22
|
+
getMinAndMaxPx,
|
|
23
|
+
getOverlappingEdge,
|
|
24
|
+
getRelatedFeatures,
|
|
25
|
+
isMousePositionWithFeature,
|
|
26
|
+
isSelectedFeature,
|
|
27
|
+
} from '../../util'
|
|
28
|
+
import { type LinearApolloSixFrameDisplay } from '../stateModel'
|
|
29
|
+
import { type LinearApolloSixFrameDisplayMouseEvents } from '../stateModel/mouseEvents'
|
|
24
30
|
import { type LinearApolloSixFrameDisplayRendering } from '../stateModel/rendering'
|
|
25
31
|
import { type CanvasMouseEvent } from '../types'
|
|
26
32
|
|
|
@@ -30,7 +36,10 @@ let forwardFillLight: CanvasPattern | null = null
|
|
|
30
36
|
let backwardFillLight: CanvasPattern | null = null
|
|
31
37
|
let forwardFillDark: CanvasPattern | null = null
|
|
32
38
|
let backwardFillDark: CanvasPattern | null = null
|
|
33
|
-
|
|
39
|
+
const canvas = globalThis.document.createElement('canvas')
|
|
40
|
+
// @ts-expect-error getContext is undefined in the web worker
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
42
|
+
if (canvas?.getContext) {
|
|
34
43
|
for (const direction of ['forward', 'backward']) {
|
|
35
44
|
for (const themeMode of ['light', 'dark']) {
|
|
36
45
|
const canvas = document.createElement('canvas')
|
|
@@ -83,6 +92,37 @@ function deepSetHas<T>(set: Set<T>, item: T): boolean {
|
|
|
83
92
|
return false
|
|
84
93
|
}
|
|
85
94
|
|
|
95
|
+
interface Label {
|
|
96
|
+
x: number
|
|
97
|
+
y: number
|
|
98
|
+
h: number
|
|
99
|
+
text: string | undefined
|
|
100
|
+
color: string
|
|
101
|
+
isSelected: boolean
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function drawTextLabels(
|
|
105
|
+
ctx: CanvasRenderingContext2D,
|
|
106
|
+
labelArray: Label[],
|
|
107
|
+
font = '10px sans-serif',
|
|
108
|
+
) {
|
|
109
|
+
for (let i = labelArray.length - 1; i >= 0; --i) {
|
|
110
|
+
const label = labelArray[i]
|
|
111
|
+
ctx.fillStyle = label.color
|
|
112
|
+
const labelRowX = Math.max(label.x + 1, 0)
|
|
113
|
+
const labelRowY = label.y + label.h
|
|
114
|
+
const textWidth = measureText(label.text, 10)
|
|
115
|
+
if (label.isSelected) {
|
|
116
|
+
ctx.clearRect(labelRowX - 5, labelRowY, textWidth + 10, label.h)
|
|
117
|
+
ctx.font = 'bold '.concat(font)
|
|
118
|
+
}
|
|
119
|
+
if (label.text) {
|
|
120
|
+
ctx.fillText(label.text, labelRowX, labelRowY + 11, textWidth)
|
|
121
|
+
ctx.font = font
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
86
126
|
function draw(
|
|
87
127
|
ctx: CanvasRenderingContext2D,
|
|
88
128
|
topLevelFeature: AnnotationFeature,
|
|
@@ -90,23 +130,35 @@ function draw(
|
|
|
90
130
|
stateModel: LinearApolloSixFrameDisplayRendering,
|
|
91
131
|
displayedRegionIndex: number,
|
|
92
132
|
): void {
|
|
93
|
-
const {
|
|
133
|
+
const {
|
|
134
|
+
apolloRowHeight,
|
|
135
|
+
lgv,
|
|
136
|
+
session,
|
|
137
|
+
theme,
|
|
138
|
+
highestRow,
|
|
139
|
+
filteredTranscripts,
|
|
140
|
+
selectedFeature,
|
|
141
|
+
showFeatureLabels,
|
|
142
|
+
} = stateModel
|
|
94
143
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
95
144
|
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
96
145
|
const { refName, reversed } = displayedRegion
|
|
97
146
|
const rowHeight = apolloRowHeight
|
|
98
|
-
const exonHeight =
|
|
99
|
-
const cdsHeight =
|
|
100
|
-
const
|
|
147
|
+
const exonHeight = rowHeight
|
|
148
|
+
const cdsHeight = rowHeight
|
|
149
|
+
const topLevelFeatureHeight = rowHeight
|
|
150
|
+
const featureLabelSpacer = showFeatureLabels ? 2 : 1
|
|
151
|
+
const textColor = theme.palette.text.primary
|
|
152
|
+
const { attributes, children, min, strand } = topLevelFeature
|
|
101
153
|
if (!children) {
|
|
102
154
|
return
|
|
103
155
|
}
|
|
104
|
-
const { apolloSelectedFeature } = session
|
|
105
156
|
const { apolloDataStore } = session
|
|
106
157
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
107
158
|
if (!featureTypeOntology) {
|
|
108
159
|
throw new Error('featureTypeOntology is undefined')
|
|
109
160
|
}
|
|
161
|
+
const labelArray: Label[] = []
|
|
110
162
|
|
|
111
163
|
// Draw background for gene
|
|
112
164
|
const topLevelFeatureMinX =
|
|
@@ -119,11 +171,9 @@ function draw(
|
|
|
119
171
|
const topLevelFeatureStartPx = reversed
|
|
120
172
|
? topLevelFeatureMinX - topLevelFeatureWidthPx
|
|
121
173
|
: topLevelFeatureMinX
|
|
122
|
-
const topLevelRow = strand == 1 ? 3 : 4
|
|
174
|
+
const topLevelRow = (strand == 1 ? 3 : 4) * featureLabelSpacer
|
|
123
175
|
const topLevelFeatureTop = topLevelRow * rowHeight
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
ctx.fillStyle = theme?.palette.text.primary ?? 'black'
|
|
176
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
127
177
|
ctx.fillRect(
|
|
128
178
|
topLevelFeatureStartPx,
|
|
129
179
|
topLevelFeatureTop,
|
|
@@ -131,10 +181,9 @@ function draw(
|
|
|
131
181
|
topLevelFeatureHeight,
|
|
132
182
|
)
|
|
133
183
|
|
|
134
|
-
ctx.fillStyle =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
: alpha(theme?.palette.background.paper ?? '#ffffff', 0.7)
|
|
184
|
+
ctx.fillStyle = isSelectedFeature(topLevelFeature, selectedFeature)
|
|
185
|
+
? alpha('rgb(0,0,0)', 0.7)
|
|
186
|
+
: alpha(theme.palette.background.paper, 0.7)
|
|
138
187
|
ctx.fillRect(
|
|
139
188
|
topLevelFeatureStartPx + 1,
|
|
140
189
|
topLevelFeatureTop + 1,
|
|
@@ -142,10 +191,25 @@ function draw(
|
|
|
142
191
|
topLevelFeatureHeight - 2,
|
|
143
192
|
)
|
|
144
193
|
|
|
194
|
+
const isSelected = isSelectedFeature(topLevelFeature, selectedFeature)
|
|
195
|
+
const label: Label = {
|
|
196
|
+
x: topLevelFeatureStartPx,
|
|
197
|
+
y: topLevelFeatureTop,
|
|
198
|
+
h: topLevelFeatureHeight,
|
|
199
|
+
text: attributes.get('gff_id')?.toString(),
|
|
200
|
+
color: textColor,
|
|
201
|
+
isSelected,
|
|
202
|
+
}
|
|
203
|
+
if (isSelected) {
|
|
204
|
+
labelArray.unshift(label)
|
|
205
|
+
} else {
|
|
206
|
+
labelArray.push(label)
|
|
207
|
+
}
|
|
208
|
+
|
|
145
209
|
const forwardFill =
|
|
146
|
-
theme
|
|
210
|
+
theme.palette.mode === 'dark' ? forwardFillDark : forwardFillLight
|
|
147
211
|
const backwardFill =
|
|
148
|
-
theme
|
|
212
|
+
theme.palette.mode === 'dark' ? backwardFillDark : backwardFillLight
|
|
149
213
|
const reversal = reversed ? -1 : 1
|
|
150
214
|
let topFill: CanvasPattern | null = null,
|
|
151
215
|
bottomFill: CanvasPattern | null = null
|
|
@@ -184,10 +248,16 @@ function draw(
|
|
|
184
248
|
) {
|
|
185
249
|
continue
|
|
186
250
|
}
|
|
187
|
-
const { children: childrenOfmRNA, cdsLocations
|
|
251
|
+
const { children: childrenOfmRNA, cdsLocations } = child
|
|
188
252
|
if (!childrenOfmRNA) {
|
|
189
253
|
continue
|
|
190
254
|
}
|
|
255
|
+
const childID: string | undefined = child.attributes
|
|
256
|
+
.get('gff_id')
|
|
257
|
+
?.toString()
|
|
258
|
+
if (childID && filteredTranscripts.includes(childID)) {
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
191
261
|
for (const [, exon] of childrenOfmRNA) {
|
|
192
262
|
if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
|
|
193
263
|
continue
|
|
@@ -203,14 +273,12 @@ function draw(
|
|
|
203
273
|
|
|
204
274
|
const exonTop =
|
|
205
275
|
topLevelFeatureTop + (topLevelFeatureHeight - exonHeight) / 2
|
|
206
|
-
|
|
276
|
+
const isSelected = isSelectedFeature(exon, selectedFeature)
|
|
277
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
207
278
|
ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
|
|
208
279
|
if (widthPx > 2) {
|
|
209
280
|
ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
210
|
-
ctx.fillStyle =
|
|
211
|
-
apolloSelectedFeature && exon._id === apolloSelectedFeature._id
|
|
212
|
-
? 'rgb(0,0,0)'
|
|
213
|
-
: alpha('#f5f500', 0.6)
|
|
281
|
+
ctx.fillStyle = isSelected ? 'rgb(0,0,0)' : alpha('#f5f500', 0.6)
|
|
214
282
|
ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
215
283
|
if (topFill && bottomFill) {
|
|
216
284
|
ctx.fillStyle = topFill
|
|
@@ -228,18 +296,35 @@ function draw(
|
|
|
228
296
|
(exonHeight - 2) / 2,
|
|
229
297
|
)
|
|
230
298
|
}
|
|
299
|
+
|
|
300
|
+
const label: Label = {
|
|
301
|
+
x: startPx,
|
|
302
|
+
y: exonTop,
|
|
303
|
+
h: exonHeight,
|
|
304
|
+
text: exon.attributes.get('gff_id')?.toString(),
|
|
305
|
+
color: textColor,
|
|
306
|
+
isSelected,
|
|
307
|
+
}
|
|
308
|
+
if (isSelected) {
|
|
309
|
+
labelArray.unshift(label)
|
|
310
|
+
} else {
|
|
311
|
+
labelArray.push(label)
|
|
312
|
+
}
|
|
231
313
|
}
|
|
232
314
|
}
|
|
233
315
|
|
|
316
|
+
const isSelected = isSelectedFeature(child, selectedFeature?.parent)
|
|
317
|
+
let cdsStartPx = 0
|
|
318
|
+
let cdsTop = 0
|
|
234
319
|
for (const cdsRow of cdsLocations) {
|
|
235
320
|
let prevCDSTop = 0
|
|
236
321
|
let prevCDSEndPx = 0
|
|
237
322
|
let counter = 1
|
|
238
323
|
for (const cds of cdsRow.sort((a, b) => a.max - b.max)) {
|
|
239
324
|
if (
|
|
240
|
-
(
|
|
241
|
-
|
|
242
|
-
|
|
325
|
+
(selectedFeature &&
|
|
326
|
+
isSelected &&
|
|
327
|
+
featureTypeOntology.isTypeOf(selectedFeature.type, 'CDS')) ||
|
|
243
328
|
!deepSetHas(renderedCDS, cds)
|
|
244
329
|
) {
|
|
245
330
|
const cdsWidthPx = (cds.max - cds.min) / bpPerPx
|
|
@@ -249,12 +334,12 @@ function draw(
|
|
|
249
334
|
coord: cds.min,
|
|
250
335
|
regionNumber: displayedRegionIndex,
|
|
251
336
|
})?.offsetPx ?? 0) - offsetPx
|
|
252
|
-
|
|
253
|
-
ctx.fillStyle = theme
|
|
337
|
+
cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
338
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
254
339
|
const frame = getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase)
|
|
255
|
-
const frameAdjust =
|
|
256
|
-
|
|
257
|
-
|
|
340
|
+
const frameAdjust =
|
|
341
|
+
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
342
|
+
cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
258
343
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
259
344
|
if (cdsWidthPx > 2) {
|
|
260
345
|
ctx.clearRect(
|
|
@@ -264,13 +349,13 @@ function draw(
|
|
|
264
349
|
cdsHeight - 2,
|
|
265
350
|
)
|
|
266
351
|
|
|
267
|
-
const frameColor = theme
|
|
352
|
+
const frameColor = theme.palette.framesCDS.at(frame)?.main
|
|
268
353
|
const cdsColorCode = frameColor ?? 'rgb(171,71,188)'
|
|
269
354
|
ctx.fillStyle = cdsColorCode
|
|
270
355
|
ctx.fillStyle =
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
356
|
+
selectedFeature &&
|
|
357
|
+
isSelected &&
|
|
358
|
+
featureTypeOntology.isTypeOf(selectedFeature.type, 'CDS')
|
|
274
359
|
? 'rgb(0,0,0)'
|
|
275
360
|
: cdsColorCode
|
|
276
361
|
ctx.fillRect(
|
|
@@ -286,7 +371,9 @@ function draw(
|
|
|
286
371
|
const midPoint: [number, number] = [
|
|
287
372
|
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
288
373
|
Math.max(
|
|
289
|
-
frame < 0
|
|
374
|
+
frame < 0
|
|
375
|
+
? rowHeight * featureLabelSpacer * highestRow + 1
|
|
376
|
+
: 1, // Avoid render ceiling
|
|
290
377
|
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
291
378
|
),
|
|
292
379
|
]
|
|
@@ -324,6 +411,22 @@ function draw(
|
|
|
324
411
|
}
|
|
325
412
|
}
|
|
326
413
|
}
|
|
414
|
+
const label: Label = {
|
|
415
|
+
x: cdsStartPx,
|
|
416
|
+
y: cdsTop,
|
|
417
|
+
h: cdsHeight,
|
|
418
|
+
text: child.attributes.get('gff_id')?.toString(),
|
|
419
|
+
color: textColor,
|
|
420
|
+
isSelected,
|
|
421
|
+
}
|
|
422
|
+
if (isSelected) {
|
|
423
|
+
labelArray.unshift(label)
|
|
424
|
+
} else {
|
|
425
|
+
labelArray.push(label)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (showFeatureLabels) {
|
|
429
|
+
drawTextLabels(ctx, labelArray)
|
|
327
430
|
}
|
|
328
431
|
}
|
|
329
432
|
|
|
@@ -349,10 +452,10 @@ function drawDragPreview(
|
|
|
349
452
|
const rectY = row * apolloRowHeight
|
|
350
453
|
const rectWidth = Math.abs(current.x - featureEdgePx)
|
|
351
454
|
const rectHeight = apolloRowHeight * rowCount
|
|
352
|
-
overlayCtx.strokeStyle = theme
|
|
455
|
+
overlayCtx.strokeStyle = theme.palette.info.main
|
|
353
456
|
overlayCtx.setLineDash([6])
|
|
354
457
|
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
355
|
-
overlayCtx.fillStyle = alpha(theme
|
|
458
|
+
overlayCtx.fillStyle = alpha(theme.palette.info.main, 0.2)
|
|
356
459
|
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
357
460
|
}
|
|
358
461
|
|
|
@@ -360,19 +463,33 @@ function drawHover(
|
|
|
360
463
|
stateModel: LinearApolloSixFrameDisplay,
|
|
361
464
|
ctx: CanvasRenderingContext2D,
|
|
362
465
|
) {
|
|
363
|
-
const {
|
|
364
|
-
|
|
466
|
+
const {
|
|
467
|
+
hoveredFeature,
|
|
468
|
+
apolloRowHeight,
|
|
469
|
+
filteredTranscripts,
|
|
470
|
+
lgv,
|
|
471
|
+
highestRow,
|
|
472
|
+
session,
|
|
473
|
+
showFeatureLabels,
|
|
474
|
+
} = stateModel
|
|
475
|
+
if (!hoveredFeature) {
|
|
365
476
|
return
|
|
366
477
|
}
|
|
478
|
+
const { feature } = hoveredFeature
|
|
367
479
|
const { apolloDataStore } = session
|
|
368
480
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
369
481
|
if (!featureTypeOntology) {
|
|
370
482
|
throw new Error('featureTypeOntology is undefined')
|
|
371
483
|
}
|
|
372
|
-
const { feature } = apolloHover
|
|
373
484
|
if (!featureTypeOntology.isTypeOf(feature.type, 'transcript')) {
|
|
374
485
|
return
|
|
375
486
|
}
|
|
487
|
+
const featureID: string | undefined = feature.attributes
|
|
488
|
+
.get('gff_id')
|
|
489
|
+
?.toString()
|
|
490
|
+
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
491
|
+
return
|
|
492
|
+
}
|
|
376
493
|
const position = stateModel.getFeatureLayoutPosition(feature)
|
|
377
494
|
if (!position) {
|
|
378
495
|
return
|
|
@@ -382,7 +499,8 @@ function drawHover(
|
|
|
382
499
|
const displayedRegion = displayedRegions[layoutIndex]
|
|
383
500
|
const { refName, reversed } = displayedRegion
|
|
384
501
|
const rowHeight = apolloRowHeight
|
|
385
|
-
const cdsHeight =
|
|
502
|
+
const cdsHeight = rowHeight
|
|
503
|
+
const featureLabelSpacer = showFeatureLabels ? 2 : 1
|
|
386
504
|
const { cdsLocations, strand } = feature
|
|
387
505
|
for (const cdsRow of cdsLocations) {
|
|
388
506
|
let prevCDSTop = 0
|
|
@@ -399,9 +517,9 @@ function drawHover(
|
|
|
399
517
|
})?.offsetPx ?? 0) - offsetPx
|
|
400
518
|
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
401
519
|
const frame = getFrame(cds.min, cds.max, strand ?? 1, cds.phase)
|
|
402
|
-
const frameAdjust =
|
|
403
|
-
|
|
404
|
-
|
|
520
|
+
const frameAdjust =
|
|
521
|
+
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
522
|
+
const cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
405
523
|
ctx.fillStyle = 'rgba(255,0,0,0.6)'
|
|
406
524
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
407
525
|
|
|
@@ -410,7 +528,7 @@ function drawHover(
|
|
|
410
528
|
const midPoint: [number, number] = [
|
|
411
529
|
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
412
530
|
Math.max(
|
|
413
|
-
frame < 0 ? rowHeight * highestRow + 1 : 1, // Avoid render ceiling
|
|
531
|
+
frame < 0 ? rowHeight * featureLabelSpacer * highestRow + 1 : 1, // Avoid render ceiling
|
|
414
532
|
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
415
533
|
),
|
|
416
534
|
]
|
|
@@ -434,16 +552,14 @@ function drawHover(
|
|
|
434
552
|
|
|
435
553
|
function onMouseDown(
|
|
436
554
|
stateModel: LinearApolloSixFrameDisplay,
|
|
437
|
-
currentMousePosition:
|
|
555
|
+
currentMousePosition: MousePositionWithFeature,
|
|
438
556
|
event: CanvasMouseEvent,
|
|
439
557
|
) {
|
|
440
|
-
const {
|
|
558
|
+
const { feature } = currentMousePosition
|
|
441
559
|
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
442
560
|
// don't start dragging the view if we try to drag the feature edge
|
|
443
|
-
const { cds, feature } = featureAndGlyphUnderMouse
|
|
444
561
|
const draggableFeature = getDraggableFeatureInfo(
|
|
445
562
|
currentMousePosition,
|
|
446
|
-
cds,
|
|
447
563
|
feature,
|
|
448
564
|
stateModel,
|
|
449
565
|
)
|
|
@@ -453,6 +569,7 @@ function onMouseDown(
|
|
|
453
569
|
currentMousePosition,
|
|
454
570
|
draggableFeature.feature,
|
|
455
571
|
draggableFeature.edge,
|
|
572
|
+
true,
|
|
456
573
|
)
|
|
457
574
|
}
|
|
458
575
|
}
|
|
@@ -461,13 +578,11 @@ function onMouseMove(
|
|
|
461
578
|
stateModel: LinearApolloSixFrameDisplay,
|
|
462
579
|
mousePosition: MousePosition,
|
|
463
580
|
) {
|
|
464
|
-
if (
|
|
465
|
-
const {
|
|
466
|
-
stateModel.
|
|
467
|
-
const { cds, feature } = featureAndGlyphUnderMouse
|
|
581
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
582
|
+
const { feature, bp } = mousePosition
|
|
583
|
+
stateModel.setHoveredFeature({ feature, bp })
|
|
468
584
|
const draggableFeature = getDraggableFeatureInfo(
|
|
469
585
|
mousePosition,
|
|
470
|
-
cds,
|
|
471
586
|
feature,
|
|
472
587
|
stateModel,
|
|
473
588
|
)
|
|
@@ -486,103 +601,112 @@ function onMouseUp(
|
|
|
486
601
|
if (stateModel.apolloDragging) {
|
|
487
602
|
return
|
|
488
603
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
604
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
605
|
+
const { feature } = mousePosition
|
|
606
|
+
const { session } = stateModel
|
|
607
|
+
const { apolloDataStore } = session
|
|
608
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
609
|
+
stateModel.setSelectedFeature(feature)
|
|
495
610
|
if (!featureTypeOntology) {
|
|
496
611
|
throw new Error('featureTypeOntology is undefined')
|
|
497
612
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
613
|
+
|
|
614
|
+
let containsCDSOrExon = false
|
|
615
|
+
for (const [, child] of feature.children ?? []) {
|
|
616
|
+
if (
|
|
617
|
+
featureTypeOntology.isTypeOf(child.type, 'CDS') ||
|
|
618
|
+
featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
619
|
+
) {
|
|
620
|
+
containsCDSOrExon = true
|
|
505
621
|
break
|
|
506
622
|
}
|
|
507
623
|
}
|
|
508
|
-
|
|
509
|
-
|
|
624
|
+
if (
|
|
625
|
+
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
626
|
+
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
627
|
+
containsCDSOrExon
|
|
628
|
+
) {
|
|
629
|
+
stateModel.showFeatureDetailsWidget(feature, [
|
|
630
|
+
'ApolloTranscriptDetails',
|
|
631
|
+
'apolloTranscriptDetails',
|
|
632
|
+
])
|
|
633
|
+
} else {
|
|
634
|
+
stateModel.showFeatureDetailsWidget(feature)
|
|
635
|
+
}
|
|
510
636
|
}
|
|
511
637
|
}
|
|
512
638
|
|
|
513
639
|
function getDraggableFeatureInfo(
|
|
514
640
|
mousePosition: MousePosition,
|
|
515
|
-
cds: TranscriptPartCoding | null,
|
|
516
641
|
feature: AnnotationFeature,
|
|
517
642
|
stateModel: LinearApolloSixFrameDisplay,
|
|
518
643
|
): { feature: AnnotationFeature; edge: 'min' | 'max' } | undefined {
|
|
519
|
-
const { session } = stateModel
|
|
644
|
+
const { filteredTranscripts, session } = stateModel
|
|
520
645
|
const { apolloDataStore } = session
|
|
521
646
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
522
647
|
if (!featureTypeOntology) {
|
|
523
648
|
throw new Error('featureTypeOntology is undefined')
|
|
524
649
|
}
|
|
525
650
|
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript')
|
|
526
|
-
|
|
651
|
+
const featureID: string | undefined = feature.attributes
|
|
652
|
+
.get('gff_id')
|
|
653
|
+
?.toString()
|
|
654
|
+
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
527
655
|
return
|
|
528
656
|
}
|
|
529
657
|
const { bp, refName, regionNumber, x } = mousePosition
|
|
530
658
|
const { lgv } = stateModel
|
|
531
|
-
const { offsetPx } = lgv
|
|
532
|
-
|
|
533
|
-
const minPxInfo = lgv.bpToPx({ refName, coord: cds.min, regionNumber })
|
|
534
|
-
const maxPxInfo = lgv.bpToPx({ refName, coord: cds.max, regionNumber })
|
|
535
|
-
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
536
|
-
return
|
|
537
|
-
}
|
|
538
|
-
const minPx = minPxInfo.offsetPx - offsetPx
|
|
539
|
-
const maxPx = maxPxInfo.offsetPx - offsetPx
|
|
540
|
-
if (Math.abs(maxPx - minPx) < 8) {
|
|
541
|
-
return
|
|
542
|
-
}
|
|
543
659
|
if (isTranscript) {
|
|
544
660
|
const transcript = feature
|
|
545
661
|
if (!transcript.children) {
|
|
546
662
|
return
|
|
547
663
|
}
|
|
548
664
|
const exonChildren: AnnotationFeature[] = []
|
|
665
|
+
const cdsChildren: AnnotationFeature[] = []
|
|
549
666
|
for (const child of transcript.children.values()) {
|
|
550
667
|
const childIsExon = featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
668
|
+
const childIsCDS = featureTypeOntology.isTypeOf(child.type, 'CDS')
|
|
551
669
|
if (childIsExon) {
|
|
552
670
|
exonChildren.push(child)
|
|
671
|
+
} else if (childIsCDS) {
|
|
672
|
+
cdsChildren.push(child)
|
|
553
673
|
}
|
|
554
674
|
}
|
|
555
|
-
|
|
556
675
|
const overlappingExon = exonChildren.find((child) => {
|
|
557
676
|
const [start, end] = intersection2(bp, bp + 1, child.min, child.max)
|
|
558
677
|
return start !== undefined && end !== undefined
|
|
559
678
|
})
|
|
560
|
-
if (
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
refName,
|
|
570
|
-
coord: overlappingExon.max,
|
|
571
|
-
regionNumber,
|
|
572
|
-
})
|
|
573
|
-
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
574
|
-
return
|
|
575
|
-
}
|
|
576
|
-
const minPx = minPxInfo.offsetPx - offsetPx
|
|
577
|
-
const maxPx = maxPxInfo.offsetPx - offsetPx
|
|
578
|
-
if (Math.abs(maxPx - minPx) < 8) {
|
|
579
|
-
return
|
|
580
|
-
}
|
|
581
|
-
if (Math.abs(minPx - x) < 4) {
|
|
582
|
-
return { feature: overlappingExon, edge: 'min' }
|
|
679
|
+
if (overlappingExon) {
|
|
680
|
+
// We are on an exon, are we on the edge of it?
|
|
681
|
+
const minMax = getMinAndMaxPx(overlappingExon, refName, regionNumber, lgv)
|
|
682
|
+
if (minMax) {
|
|
683
|
+
const overlappingEdge = getOverlappingEdge(overlappingExon, x, minMax)
|
|
684
|
+
if (overlappingEdge) {
|
|
685
|
+
return overlappingEdge
|
|
686
|
+
}
|
|
687
|
+
}
|
|
583
688
|
}
|
|
584
|
-
if
|
|
585
|
-
|
|
689
|
+
// End of special cases, let's see if we're on the edge of this CDS or exon
|
|
690
|
+
for (const loc of transcript.cdsLocations) {
|
|
691
|
+
for (const cds of loc) {
|
|
692
|
+
const minMax = getMinAndMaxPx(cds, refName, regionNumber, lgv)
|
|
693
|
+
if (minMax) {
|
|
694
|
+
const overlappingCDS = cdsChildren.find((child) => {
|
|
695
|
+
const [start, end] = intersection2(bp, bp + 1, child.min, child.max)
|
|
696
|
+
return start !== undefined && end !== undefined
|
|
697
|
+
})
|
|
698
|
+
if (overlappingCDS) {
|
|
699
|
+
const overlappingEdge = getOverlappingEdge(
|
|
700
|
+
overlappingCDS,
|
|
701
|
+
x,
|
|
702
|
+
minMax,
|
|
703
|
+
)
|
|
704
|
+
if (overlappingEdge) {
|
|
705
|
+
return overlappingEdge
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
586
710
|
}
|
|
587
711
|
}
|
|
588
712
|
return
|
|
@@ -592,27 +716,56 @@ function drawTooltip(
|
|
|
592
716
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
593
717
|
context: CanvasRenderingContext2D,
|
|
594
718
|
): void {
|
|
595
|
-
const {
|
|
596
|
-
|
|
719
|
+
const {
|
|
720
|
+
hoveredFeature,
|
|
721
|
+
apolloRowHeight,
|
|
722
|
+
filteredTranscripts,
|
|
723
|
+
lgv,
|
|
724
|
+
session,
|
|
725
|
+
showFeatureLabels,
|
|
726
|
+
theme,
|
|
727
|
+
} = display
|
|
728
|
+
if (!hoveredFeature) {
|
|
597
729
|
return
|
|
598
730
|
}
|
|
599
|
-
const {
|
|
600
|
-
|
|
731
|
+
const { feature, bp } = hoveredFeature
|
|
732
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
733
|
+
if (!featureTypeOntology) {
|
|
734
|
+
throw new Error('featureTypeOntology is undefined')
|
|
735
|
+
}
|
|
736
|
+
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript')
|
|
737
|
+
if (!isTranscript) {
|
|
601
738
|
return
|
|
602
739
|
}
|
|
740
|
+
const { attributes, strand, type } = feature
|
|
603
741
|
const position = display.getFeatureLayoutPosition(feature)
|
|
604
742
|
if (!position) {
|
|
605
743
|
return
|
|
606
744
|
}
|
|
745
|
+
const featureID: string | undefined = attributes.get('gff_id')?.toString()
|
|
746
|
+
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
747
|
+
return
|
|
748
|
+
}
|
|
607
749
|
const { layoutIndex } = position
|
|
608
750
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
609
751
|
const displayedRegion = displayedRegions[layoutIndex]
|
|
610
752
|
const { refName, reversed } = displayedRegion
|
|
611
753
|
const rowHeight = apolloRowHeight
|
|
612
754
|
const cdsHeight = Math.round(0.7 * rowHeight)
|
|
755
|
+
const featureLabelSpacer = showFeatureLabels ? 2 : 1
|
|
613
756
|
let location = 'Loc: '
|
|
614
|
-
|
|
615
|
-
const
|
|
757
|
+
let cds: TranscriptPartCoding | undefined = undefined
|
|
758
|
+
for (const loc of feature.cdsLocations) {
|
|
759
|
+
for (const cdsLoc of loc) {
|
|
760
|
+
if (bp >= cdsLoc.min && bp <= cdsLoc.max) {
|
|
761
|
+
cds = cdsLoc
|
|
762
|
+
break
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (!cds) {
|
|
767
|
+
return
|
|
768
|
+
}
|
|
616
769
|
const { max, min, phase } = cds
|
|
617
770
|
location += `${min + 1}–${max}`
|
|
618
771
|
|
|
@@ -623,12 +776,12 @@ function drawTooltip(
|
|
|
623
776
|
regionNumber: layoutIndex,
|
|
624
777
|
})?.offsetPx ?? 0) - offsetPx
|
|
625
778
|
const frame = getFrame(min, max, strand ?? 1, phase)
|
|
626
|
-
const frameAdjust = frame < 0 ? -1 * frame + 5 : frame
|
|
627
|
-
const cdsTop =
|
|
779
|
+
const frameAdjust = (frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
780
|
+
const cdsTop =
|
|
781
|
+
(frameAdjust - featureLabelSpacer) * rowHeight + (rowHeight - cdsHeight) / 2
|
|
628
782
|
const cdsWidthPx = (max - min) / bpPerPx
|
|
629
783
|
|
|
630
784
|
const featureType = `Type: ${cds.type}`
|
|
631
|
-
const { attributes } = feature
|
|
632
785
|
const featureName = attributes.get('gff_name')?.find((name) => name !== '')
|
|
633
786
|
const textWidth = [
|
|
634
787
|
context.measureText(featureType).width,
|
|
@@ -636,14 +789,14 @@ function drawTooltip(
|
|
|
636
789
|
]
|
|
637
790
|
if (featureName) {
|
|
638
791
|
textWidth.push(
|
|
639
|
-
context.measureText(`Parent Type: ${
|
|
792
|
+
context.measureText(`Parent Type: ${type}`).width,
|
|
640
793
|
context.measureText(`Parent Name: ${featureName}`).width,
|
|
641
794
|
)
|
|
642
795
|
}
|
|
643
796
|
const maxWidth = Math.max(...textWidth)
|
|
644
797
|
|
|
645
798
|
startPx = startPx + cdsWidthPx + 5
|
|
646
|
-
context.fillStyle = alpha(theme
|
|
799
|
+
context.fillStyle = alpha(theme.palette.text.primary, 0.7)
|
|
647
800
|
context.fillRect(
|
|
648
801
|
startPx,
|
|
649
802
|
cdsTop,
|
|
@@ -655,12 +808,12 @@ function drawTooltip(
|
|
|
655
808
|
context.lineTo(startPx - 5, cdsTop + 5)
|
|
656
809
|
context.lineTo(startPx, cdsTop + 10)
|
|
657
810
|
context.fill()
|
|
658
|
-
context.fillStyle = theme
|
|
811
|
+
context.fillStyle = theme.palette.background.default
|
|
659
812
|
let textTop = cdsTop + 12
|
|
660
813
|
context.fillText(featureType, startPx + 2, textTop)
|
|
661
814
|
if (featureName) {
|
|
662
815
|
textTop = textTop + 12
|
|
663
|
-
context.fillText(`Parent Type: ${
|
|
816
|
+
context.fillText(`Parent Type: ${type}`, startPx + 2, textTop)
|
|
664
817
|
textTop = textTop + 12
|
|
665
818
|
context.fillText(`Parent Name: ${featureName}`, startPx + 2, textTop)
|
|
666
819
|
}
|
|
@@ -670,136 +823,121 @@ function drawTooltip(
|
|
|
670
823
|
|
|
671
824
|
function getContextMenuItems(
|
|
672
825
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
826
|
+
mousePosition: MousePositionWithFeature,
|
|
673
827
|
): MenuItem[] {
|
|
674
828
|
const {
|
|
675
|
-
apolloHover,
|
|
676
829
|
apolloInternetAccount: internetAccount,
|
|
830
|
+
hoveredFeature,
|
|
677
831
|
changeManager,
|
|
832
|
+
filteredTranscripts,
|
|
678
833
|
regions,
|
|
679
834
|
selectedFeature,
|
|
680
835
|
session,
|
|
681
836
|
} = display
|
|
837
|
+
const [region] = regions
|
|
838
|
+
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
682
839
|
const menuItems: MenuItem[] = []
|
|
683
|
-
if (!apolloHover) {
|
|
684
|
-
return menuItems
|
|
685
|
-
}
|
|
686
|
-
const { feature: sourceFeature } = apolloHover
|
|
687
840
|
const role = internetAccount ? internetAccount.role : 'admin'
|
|
688
841
|
const admin = role === 'admin'
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
693
|
-
menuItems.push(
|
|
694
|
-
{
|
|
695
|
-
label: 'Add child feature',
|
|
696
|
-
disabled: readOnly,
|
|
697
|
-
onClick: () => {
|
|
698
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
699
|
-
(doneCallback) => [
|
|
700
|
-
AddChildFeature,
|
|
701
|
-
{
|
|
702
|
-
session,
|
|
703
|
-
handleClose: () => {
|
|
704
|
-
doneCallback()
|
|
705
|
-
},
|
|
706
|
-
changeManager,
|
|
707
|
-
sourceFeature,
|
|
708
|
-
sourceAssemblyId,
|
|
709
|
-
internetAccount,
|
|
710
|
-
},
|
|
711
|
-
],
|
|
712
|
-
)
|
|
713
|
-
},
|
|
714
|
-
},
|
|
715
|
-
{
|
|
716
|
-
label: 'Copy features and annotations',
|
|
717
|
-
disabled: readOnly,
|
|
718
|
-
onClick: () => {
|
|
719
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
720
|
-
(doneCallback) => [
|
|
721
|
-
CopyFeature,
|
|
722
|
-
{
|
|
723
|
-
session,
|
|
724
|
-
handleClose: () => {
|
|
725
|
-
doneCallback()
|
|
726
|
-
},
|
|
727
|
-
changeManager,
|
|
728
|
-
sourceFeature,
|
|
729
|
-
sourceAssemblyId: currentAssemblyId,
|
|
730
|
-
},
|
|
731
|
-
],
|
|
732
|
-
)
|
|
733
|
-
},
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
label: 'Delete feature',
|
|
737
|
-
disabled: !admin,
|
|
738
|
-
onClick: () => {
|
|
739
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
740
|
-
(doneCallback) => [
|
|
741
|
-
DeleteFeature,
|
|
742
|
-
{
|
|
743
|
-
session,
|
|
744
|
-
handleClose: () => {
|
|
745
|
-
doneCallback()
|
|
746
|
-
},
|
|
747
|
-
changeManager,
|
|
748
|
-
sourceFeature,
|
|
749
|
-
sourceAssemblyId: currentAssemblyId,
|
|
750
|
-
selectedFeature,
|
|
751
|
-
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
752
|
-
display.setSelectedFeature(feature)
|
|
753
|
-
},
|
|
754
|
-
},
|
|
755
|
-
],
|
|
756
|
-
)
|
|
757
|
-
},
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
label: 'Edit feature details',
|
|
761
|
-
onClick: () => {
|
|
762
|
-
const apolloFeatureWidget = (
|
|
763
|
-
session as unknown as SessionWithWidgets
|
|
764
|
-
).addWidget(
|
|
765
|
-
'ApolloFeatureDetailsWidget',
|
|
766
|
-
'apolloFeatureDetailsWidget',
|
|
767
|
-
{
|
|
768
|
-
feature: sourceFeature,
|
|
769
|
-
assembly: currentAssemblyId,
|
|
770
|
-
refName: region.refName,
|
|
771
|
-
},
|
|
772
|
-
)
|
|
773
|
-
;(session as unknown as SessionWithWidgets).showWidget(
|
|
774
|
-
apolloFeatureWidget,
|
|
775
|
-
)
|
|
776
|
-
},
|
|
777
|
-
},
|
|
778
|
-
)
|
|
842
|
+
if (!hoveredFeature) {
|
|
843
|
+
return menuItems
|
|
844
|
+
}
|
|
779
845
|
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
780
846
|
if (!featureTypeOntology) {
|
|
781
847
|
throw new Error('featureTypeOntology is undefined')
|
|
782
848
|
}
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
849
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
850
|
+
const { bp, feature } = mousePosition
|
|
851
|
+
for (const relatedFeature of getRelatedFeatures(feature, bp)) {
|
|
852
|
+
const featureID: string | undefined = relatedFeature.attributes
|
|
853
|
+
.get('gff_id')
|
|
854
|
+
?.toString()
|
|
855
|
+
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
856
|
+
continue
|
|
857
|
+
}
|
|
858
|
+
const contextMenuItemsForFeature = getContextMenuItemsForFeature(
|
|
859
|
+
display,
|
|
860
|
+
relatedFeature,
|
|
861
|
+
)
|
|
862
|
+
if (featureTypeOntology.isTypeOf(relatedFeature.type, 'exon')) {
|
|
863
|
+
contextMenuItemsForFeature.push(
|
|
793
864
|
{
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
865
|
+
label: 'Merge exons',
|
|
866
|
+
disabled: !admin,
|
|
867
|
+
onClick: () => {
|
|
868
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
869
|
+
(doneCallback) => [
|
|
870
|
+
MergeExons,
|
|
871
|
+
{
|
|
872
|
+
session,
|
|
873
|
+
handleClose: () => {
|
|
874
|
+
doneCallback()
|
|
875
|
+
},
|
|
876
|
+
changeManager,
|
|
877
|
+
sourceFeature: relatedFeature,
|
|
878
|
+
sourceAssemblyId: currentAssemblyId,
|
|
879
|
+
selectedFeature,
|
|
880
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
881
|
+
display.setSelectedFeature(feature)
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
],
|
|
885
|
+
)
|
|
886
|
+
},
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
label: 'Split exon',
|
|
890
|
+
disabled: !admin,
|
|
891
|
+
onClick: () => {
|
|
892
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
893
|
+
(doneCallback) => [
|
|
894
|
+
SplitExon,
|
|
895
|
+
{
|
|
896
|
+
session,
|
|
897
|
+
handleClose: () => {
|
|
898
|
+
doneCallback()
|
|
899
|
+
},
|
|
900
|
+
changeManager,
|
|
901
|
+
sourceFeature: relatedFeature,
|
|
902
|
+
sourceAssemblyId: currentAssemblyId,
|
|
903
|
+
selectedFeature,
|
|
904
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
905
|
+
display.setSelectedFeature(feature)
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
],
|
|
909
|
+
)
|
|
910
|
+
},
|
|
798
911
|
},
|
|
799
912
|
)
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
913
|
+
}
|
|
914
|
+
if (featureTypeOntology.isTypeOf(relatedFeature.type, 'gene')) {
|
|
915
|
+
contextMenuItemsForFeature.push({
|
|
916
|
+
label: 'Filter alternate transcripts',
|
|
917
|
+
onClick: () => {
|
|
918
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
919
|
+
(doneCallback) => [
|
|
920
|
+
FilterTranscripts,
|
|
921
|
+
{
|
|
922
|
+
handleClose: () => {
|
|
923
|
+
doneCallback()
|
|
924
|
+
},
|
|
925
|
+
sourceFeature: relatedFeature,
|
|
926
|
+
filteredTranscripts: getSnapshot(filteredTranscripts),
|
|
927
|
+
onUpdate: (forms: string[]) => {
|
|
928
|
+
display.updateFilteredTranscripts(forms)
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
],
|
|
932
|
+
)
|
|
933
|
+
},
|
|
934
|
+
})
|
|
935
|
+
}
|
|
936
|
+
menuItems.push({
|
|
937
|
+
label: relatedFeature.type,
|
|
938
|
+
subMenu: contextMenuItemsForFeature,
|
|
939
|
+
})
|
|
940
|
+
}
|
|
803
941
|
}
|
|
804
942
|
return menuItems
|
|
805
943
|
}
|
|
@@ -814,6 +952,7 @@ export const geneGlyph: Glyph = {
|
|
|
814
952
|
drawHover,
|
|
815
953
|
drawTooltip,
|
|
816
954
|
getContextMenuItems,
|
|
955
|
+
getContextMenuItemsForFeature,
|
|
817
956
|
onMouseDown,
|
|
818
957
|
onMouseLeave,
|
|
819
958
|
onMouseMove,
|