@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
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
+
import { readConfObject } from '@jbrowse/core/configuration'
|
|
3
|
+
import { type BaseDisplayModel } from '@jbrowse/core/pluggableElementTypes'
|
|
2
4
|
import { type MenuItem } from '@jbrowse/core/ui'
|
|
3
5
|
import {
|
|
4
6
|
type AbstractSessionModel,
|
|
7
|
+
getContainingView,
|
|
5
8
|
getFrame,
|
|
6
9
|
intersection2,
|
|
7
10
|
isSessionModelWithWidgets,
|
|
8
11
|
} from '@jbrowse/core/util'
|
|
12
|
+
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
9
13
|
import { alpha } from '@mui/material'
|
|
10
14
|
|
|
11
15
|
import { type OntologyRecord } from '../../OntologyManager'
|
|
12
16
|
import { MergeExons, MergeTranscripts, SplitExon } from '../../components'
|
|
13
|
-
import { type ApolloSessionModel } from '../../session'
|
|
14
|
-
import { getMinAndMaxPx, getOverlappingEdge } from '../../util'
|
|
15
|
-
import { getFeaturesUnderClick } from '../../util/annotationFeatureUtils'
|
|
16
|
-
import { type LinearApolloDisplay } from '../stateModel'
|
|
17
17
|
import {
|
|
18
|
-
type LinearApolloDisplayMouseEvents,
|
|
19
18
|
type MousePosition,
|
|
20
|
-
type
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
type MousePositionWithFeature,
|
|
20
|
+
containsSelectedFeature,
|
|
21
|
+
getAdjacentExons,
|
|
22
|
+
getMinAndMaxPx,
|
|
23
|
+
getOverlappingEdge,
|
|
24
|
+
getStreamIcon,
|
|
25
|
+
isCDSFeature,
|
|
26
|
+
isExonFeature,
|
|
27
|
+
isMousePositionWithFeature,
|
|
28
|
+
isTranscriptFeature,
|
|
29
|
+
navToFeatureCenter,
|
|
30
|
+
selectFeatureAndOpenWidget,
|
|
31
|
+
} from '../../util'
|
|
32
|
+
import { getRelatedFeatures } from '../../util/annotationFeatureUtils'
|
|
33
|
+
import { type LinearApolloDisplay } from '../stateModel'
|
|
34
|
+
import { type LinearApolloDisplayMouseEvents } from '../stateModel/mouseEvents'
|
|
23
35
|
import { type LinearApolloDisplayRendering } from '../stateModel/rendering'
|
|
24
36
|
import { type CanvasMouseEvent } from '../types'
|
|
25
37
|
|
|
@@ -77,52 +89,85 @@ if (canvas?.getContext) {
|
|
|
77
89
|
}
|
|
78
90
|
}
|
|
79
91
|
|
|
80
|
-
function
|
|
92
|
+
function drawBackground(
|
|
81
93
|
ctx: CanvasRenderingContext2D,
|
|
82
94
|
feature: AnnotationFeature,
|
|
83
|
-
row: number,
|
|
84
95
|
stateModel: LinearApolloDisplayRendering,
|
|
85
96
|
displayedRegionIndex: number,
|
|
86
|
-
|
|
97
|
+
row: number,
|
|
98
|
+
color?: string,
|
|
99
|
+
) {
|
|
87
100
|
const { apolloRowHeight, lgv, session, theme } = stateModel
|
|
88
101
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
89
102
|
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
90
103
|
const { refName, reversed } = displayedRegion
|
|
91
|
-
const rowHeight = apolloRowHeight
|
|
92
|
-
const cdsHeight = Math.round(0.9 * rowHeight)
|
|
93
|
-
const { children, min, strand } = feature
|
|
94
|
-
if (!children) {
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
const { apolloSelectedFeature } = session
|
|
98
104
|
const { apolloDataStore } = session
|
|
99
105
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
100
106
|
if (!featureTypeOntology) {
|
|
101
107
|
throw new Error('featureTypeOntology is undefined')
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
// Draw background for gene
|
|
105
110
|
const topLevelFeatureMinX =
|
|
106
111
|
(lgv.bpToPx({
|
|
107
112
|
refName,
|
|
108
|
-
coord: min,
|
|
113
|
+
coord: feature.min,
|
|
109
114
|
regionNumber: displayedRegionIndex,
|
|
110
115
|
})?.offsetPx ?? 0) - offsetPx
|
|
111
116
|
const topLevelFeatureWidthPx = feature.length / bpPerPx
|
|
112
117
|
const topLevelFeatureStartPx = reversed
|
|
113
118
|
? topLevelFeatureMinX - topLevelFeatureWidthPx
|
|
114
119
|
: topLevelFeatureMinX
|
|
115
|
-
const topLevelFeatureTop = row *
|
|
120
|
+
const topLevelFeatureTop = row * apolloRowHeight
|
|
116
121
|
const topLevelFeatureHeight =
|
|
117
|
-
getRowCount(feature, featureTypeOntology) *
|
|
122
|
+
getRowCount(feature, featureTypeOntology) * apolloRowHeight
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
let selectedColor
|
|
125
|
+
if (color) {
|
|
126
|
+
selectedColor = color
|
|
127
|
+
} else {
|
|
128
|
+
selectedColor = readConfObject(
|
|
129
|
+
session.getPluginConfiguration(),
|
|
130
|
+
'geneBackgroundColor',
|
|
131
|
+
{ featureType: feature.type },
|
|
132
|
+
) as string
|
|
133
|
+
if (!selectedColor) {
|
|
134
|
+
selectedColor = alpha(theme.palette.background.paper, 0.6)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
ctx.fillStyle = selectedColor
|
|
120
138
|
ctx.fillRect(
|
|
121
139
|
topLevelFeatureStartPx,
|
|
122
140
|
topLevelFeatureTop,
|
|
123
141
|
topLevelFeatureWidthPx,
|
|
124
142
|
topLevelFeatureHeight,
|
|
125
143
|
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function draw(
|
|
147
|
+
ctx: CanvasRenderingContext2D,
|
|
148
|
+
feature: AnnotationFeature,
|
|
149
|
+
row: number,
|
|
150
|
+
stateModel: LinearApolloDisplayRendering,
|
|
151
|
+
displayedRegionIndex: number,
|
|
152
|
+
): void {
|
|
153
|
+
const { apolloRowHeight, lgv, selectedFeature, session, theme } = stateModel
|
|
154
|
+
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
155
|
+
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
156
|
+
const { refName, reversed } = displayedRegion
|
|
157
|
+
const rowHeight = apolloRowHeight
|
|
158
|
+
const cdsHeight = Math.round(0.9 * rowHeight)
|
|
159
|
+
const { children, strand } = feature
|
|
160
|
+
if (!children) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
const { apolloDataStore } = session
|
|
164
|
+
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
165
|
+
if (!featureTypeOntology) {
|
|
166
|
+
throw new Error('featureTypeOntology is undefined')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Draw background for gene
|
|
170
|
+
drawBackground(ctx, feature, stateModel, displayedRegionIndex, row)
|
|
126
171
|
|
|
127
172
|
// Draw lines on different rows for each transcript
|
|
128
173
|
let currentRow = 0
|
|
@@ -138,8 +183,8 @@ function draw(
|
|
|
138
183
|
if (!transcriptChildren) {
|
|
139
184
|
continue
|
|
140
185
|
}
|
|
141
|
-
|
|
142
186
|
const cdsCount = getCDSCount(transcript, featureTypeOntology)
|
|
187
|
+
|
|
143
188
|
for (const [, childFeature] of transcriptChildren) {
|
|
144
189
|
if (!featureTypeOntology.isTypeOf(childFeature.type, 'CDS')) {
|
|
145
190
|
continue
|
|
@@ -169,9 +214,9 @@ function draw(
|
|
|
169
214
|
}
|
|
170
215
|
|
|
171
216
|
const forwardFill =
|
|
172
|
-
theme
|
|
217
|
+
theme.palette.mode === 'dark' ? forwardFillDark : forwardFillLight
|
|
173
218
|
const backwardFill =
|
|
174
|
-
theme
|
|
219
|
+
theme.palette.mode === 'dark' ? backwardFillDark : backwardFillLight
|
|
175
220
|
// Draw exon and CDS for each transcript
|
|
176
221
|
currentRow = 0
|
|
177
222
|
for (const [, child] of children) {
|
|
@@ -188,7 +233,7 @@ function draw(
|
|
|
188
233
|
const cdsCount = getCDSCount(child, featureTypeOntology)
|
|
189
234
|
if (cdsCount != 0) {
|
|
190
235
|
for (const cdsRow of child.cdsLocations) {
|
|
191
|
-
const {
|
|
236
|
+
const { children: transcriptChildren } = child
|
|
192
237
|
if (!transcriptChildren) {
|
|
193
238
|
continue
|
|
194
239
|
}
|
|
@@ -217,7 +262,7 @@ function draw(
|
|
|
217
262
|
regionNumber: displayedRegionIndex,
|
|
218
263
|
})?.offsetPx ?? 0) - offsetPx
|
|
219
264
|
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
220
|
-
ctx.fillStyle = theme
|
|
265
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
221
266
|
const cdsTop =
|
|
222
267
|
(row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2
|
|
223
268
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
@@ -234,12 +279,8 @@ function draw(
|
|
|
234
279
|
child.strand ?? 1,
|
|
235
280
|
cds.phase,
|
|
236
281
|
)
|
|
237
|
-
const frameColor = theme
|
|
238
|
-
|
|
239
|
-
ctx.fillStyle =
|
|
240
|
-
apolloSelectedFeature && _id === apolloSelectedFeature._id
|
|
241
|
-
? 'rgb(0,0,0)'
|
|
242
|
-
: cdsColorCode
|
|
282
|
+
const frameColor = theme.palette.framesCDS.at(frame)?.main
|
|
283
|
+
ctx.fillStyle = frameColor ?? 'black'
|
|
243
284
|
ctx.fillRect(
|
|
244
285
|
cdsStartPx + 1,
|
|
245
286
|
cdsTop + 1,
|
|
@@ -295,6 +336,9 @@ function draw(
|
|
|
295
336
|
currentRow += 1
|
|
296
337
|
}
|
|
297
338
|
}
|
|
339
|
+
if (selectedFeature && containsSelectedFeature(feature, selectedFeature)) {
|
|
340
|
+
drawHighlight(stateModel, ctx, selectedFeature, true)
|
|
341
|
+
}
|
|
298
342
|
}
|
|
299
343
|
|
|
300
344
|
function drawExon(
|
|
@@ -308,11 +352,10 @@ function drawExon(
|
|
|
308
352
|
forwardFill: CanvasPattern | null,
|
|
309
353
|
backwardFill: CanvasPattern | null,
|
|
310
354
|
) {
|
|
311
|
-
const { apolloRowHeight, lgv,
|
|
355
|
+
const { apolloRowHeight, lgv, theme } = stateModel
|
|
312
356
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
313
357
|
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
314
358
|
const { refName, reversed } = displayedRegion
|
|
315
|
-
const { apolloSelectedFeature } = session
|
|
316
359
|
|
|
317
360
|
const minX =
|
|
318
361
|
(lgv.bpToPx({
|
|
@@ -326,14 +369,11 @@ function drawExon(
|
|
|
326
369
|
const top = (row + currentRow) * apolloRowHeight
|
|
327
370
|
const exonHeight = Math.round(0.6 * apolloRowHeight)
|
|
328
371
|
const exonTop = top + (apolloRowHeight - exonHeight) / 2
|
|
329
|
-
ctx.fillStyle = theme
|
|
372
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
330
373
|
ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
|
|
331
374
|
if (widthPx > 2) {
|
|
332
375
|
ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
333
|
-
ctx.fillStyle =
|
|
334
|
-
apolloSelectedFeature && exon._id === apolloSelectedFeature._id
|
|
335
|
-
? 'rgb(0,0,0)'
|
|
336
|
-
: 'rgb(211,211,211)'
|
|
376
|
+
ctx.fillStyle = 'rgb(211,211,211)'
|
|
337
377
|
ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
338
378
|
if (forwardFill && backwardFill && strand) {
|
|
339
379
|
const reversal = reversed ? -1 : 1
|
|
@@ -354,6 +394,21 @@ function drawExon(
|
|
|
354
394
|
}
|
|
355
395
|
}
|
|
356
396
|
|
|
397
|
+
function* range(start: number, stop: number, step = 1): Generator<number> {
|
|
398
|
+
if (start === stop) {
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
if (start < stop) {
|
|
402
|
+
for (let i = start; i < stop; i += step) {
|
|
403
|
+
yield i
|
|
404
|
+
}
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
for (let i = start; i > stop; i -= step) {
|
|
408
|
+
yield i
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
357
412
|
function drawLine(
|
|
358
413
|
ctx: CanvasRenderingContext2D,
|
|
359
414
|
stateModel: LinearApolloDisplayRendering,
|
|
@@ -372,14 +427,35 @@ function drawLine(
|
|
|
372
427
|
coord: transcript.min,
|
|
373
428
|
regionNumber: displayedRegionIndex,
|
|
374
429
|
})?.offsetPx ?? 0) - offsetPx
|
|
375
|
-
const widthPx = transcript.length / bpPerPx
|
|
430
|
+
const widthPx = Math.round(transcript.length / bpPerPx)
|
|
376
431
|
const startPx = reversed ? minX - widthPx : minX
|
|
377
432
|
const height =
|
|
378
433
|
Math.round((currentRow + 1 / 2) * apolloRowHeight) + row * apolloRowHeight
|
|
379
|
-
ctx.strokeStyle = theme
|
|
434
|
+
ctx.strokeStyle = theme.palette.text.primary
|
|
435
|
+
const { strand = 1 } = transcript
|
|
380
436
|
ctx.beginPath()
|
|
381
|
-
|
|
382
|
-
|
|
437
|
+
// If view is reversed, draw forward as reverse and vice versa
|
|
438
|
+
const effectiveStrand = strand * (reversed ? -1 : 1)
|
|
439
|
+
// Draw the transcript line, and extend it out a bit on the 3` end
|
|
440
|
+
const lineStart = startPx - (effectiveStrand === -1 ? 5 : 0)
|
|
441
|
+
const lineEnd = startPx + widthPx + (effectiveStrand === -1 ? 0 : 5)
|
|
442
|
+
ctx.moveTo(lineStart, height)
|
|
443
|
+
ctx.lineTo(lineEnd, height)
|
|
444
|
+
// Now to draw arrows every 20 pixels along the line
|
|
445
|
+
// Make the arrow range a bit shorter to avoid an arrow hanging off the 5` end
|
|
446
|
+
const arrowsStart = lineStart + (effectiveStrand === -1 ? 0 : 3)
|
|
447
|
+
const arrowsEnd = lineEnd - (effectiveStrand === -1 ? 3 : 0)
|
|
448
|
+
// Offset determines if the arrows face left or right
|
|
449
|
+
const offset = effectiveStrand === -1 ? 3 : -3
|
|
450
|
+
const arrowRange =
|
|
451
|
+
effectiveStrand === -1
|
|
452
|
+
? range(arrowsStart, arrowsEnd, 20)
|
|
453
|
+
: range(arrowsEnd, arrowsStart, 20)
|
|
454
|
+
for (const arrowLocation of arrowRange) {
|
|
455
|
+
ctx.moveTo(arrowLocation + offset, height + offset)
|
|
456
|
+
ctx.lineTo(arrowLocation, height)
|
|
457
|
+
ctx.lineTo(arrowLocation + offset, height - offset)
|
|
458
|
+
}
|
|
383
459
|
ctx.stroke()
|
|
384
460
|
}
|
|
385
461
|
|
|
@@ -405,24 +481,22 @@ function drawDragPreview(
|
|
|
405
481
|
const rectY = row * apolloRowHeight
|
|
406
482
|
const rectWidth = Math.abs(current.x - featureEdgePx)
|
|
407
483
|
const rectHeight = apolloRowHeight * rowCount
|
|
408
|
-
overlayCtx.strokeStyle = theme
|
|
484
|
+
overlayCtx.strokeStyle = theme.palette.info.main
|
|
409
485
|
overlayCtx.setLineDash([6])
|
|
410
486
|
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
411
|
-
overlayCtx.fillStyle = alpha(theme
|
|
487
|
+
overlayCtx.fillStyle = alpha(theme.palette.info.main, 0.2)
|
|
412
488
|
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
413
489
|
}
|
|
414
490
|
|
|
415
|
-
function
|
|
416
|
-
stateModel:
|
|
491
|
+
function drawHighlight(
|
|
492
|
+
stateModel: LinearApolloDisplayRendering,
|
|
417
493
|
ctx: CanvasRenderingContext2D,
|
|
494
|
+
feature: AnnotationFeature,
|
|
495
|
+
selected = false,
|
|
418
496
|
) {
|
|
419
|
-
const {
|
|
497
|
+
const { apolloRowHeight, lgv, session, theme } = stateModel
|
|
420
498
|
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
421
499
|
|
|
422
|
-
if (!apolloHover) {
|
|
423
|
-
return
|
|
424
|
-
}
|
|
425
|
-
const { feature } = apolloHover
|
|
426
500
|
const position = stateModel.getFeatureLayoutPosition(feature)
|
|
427
501
|
if (!position) {
|
|
428
502
|
return
|
|
@@ -441,7 +515,9 @@ function drawHover(
|
|
|
441
515
|
const row = layoutRow + featureRow
|
|
442
516
|
const top = row * apolloRowHeight
|
|
443
517
|
const widthPx = length / bpPerPx
|
|
444
|
-
ctx.fillStyle =
|
|
518
|
+
ctx.fillStyle = selected
|
|
519
|
+
? theme.palette.action.disabled
|
|
520
|
+
: theme.palette.action.focus
|
|
445
521
|
|
|
446
522
|
if (!featureTypeOntology) {
|
|
447
523
|
throw new Error('featureTypeOntology is undefined')
|
|
@@ -454,6 +530,18 @@ function drawHover(
|
|
|
454
530
|
)
|
|
455
531
|
}
|
|
456
532
|
|
|
533
|
+
function drawHover(
|
|
534
|
+
stateModel: LinearApolloDisplay,
|
|
535
|
+
ctx: CanvasRenderingContext2D,
|
|
536
|
+
) {
|
|
537
|
+
const { hoveredFeature } = stateModel
|
|
538
|
+
|
|
539
|
+
if (!hoveredFeature) {
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
drawHighlight(stateModel, ctx, hoveredFeature.feature)
|
|
543
|
+
}
|
|
544
|
+
|
|
457
545
|
function getFeatureFromLayout(
|
|
458
546
|
feature: AnnotationFeature,
|
|
459
547
|
bp: number,
|
|
@@ -615,13 +703,12 @@ function getRowForFeature(
|
|
|
615
703
|
|
|
616
704
|
function onMouseDown(
|
|
617
705
|
stateModel: LinearApolloDisplay,
|
|
618
|
-
currentMousePosition:
|
|
706
|
+
currentMousePosition: MousePositionWithFeature,
|
|
619
707
|
event: CanvasMouseEvent,
|
|
620
708
|
) {
|
|
621
|
-
const {
|
|
709
|
+
const { feature } = currentMousePosition
|
|
622
710
|
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
623
711
|
// don't start dragging the view if we try to drag the feature edge
|
|
624
|
-
const { feature } = featureAndGlyphUnderMouse
|
|
625
712
|
const draggableFeature = getDraggableFeatureInfo(
|
|
626
713
|
currentMousePosition,
|
|
627
714
|
feature,
|
|
@@ -642,10 +729,9 @@ function onMouseMove(
|
|
|
642
729
|
stateModel: LinearApolloDisplay,
|
|
643
730
|
mousePosition: MousePosition,
|
|
644
731
|
) {
|
|
645
|
-
if (
|
|
646
|
-
const {
|
|
647
|
-
stateModel.
|
|
648
|
-
const { feature } = featureAndGlyphUnderMouse
|
|
732
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
733
|
+
const { feature, bp } = mousePosition
|
|
734
|
+
stateModel.setHoveredFeature({ feature, bp })
|
|
649
735
|
const draggableFeature = getDraggableFeatureInfo(
|
|
650
736
|
mousePosition,
|
|
651
737
|
feature,
|
|
@@ -666,41 +752,11 @@ function onMouseUp(
|
|
|
666
752
|
if (stateModel.apolloDragging) {
|
|
667
753
|
return
|
|
668
754
|
}
|
|
669
|
-
const {
|
|
670
|
-
if (!
|
|
755
|
+
const { feature } = mousePosition
|
|
756
|
+
if (!feature) {
|
|
671
757
|
return
|
|
672
758
|
}
|
|
673
|
-
|
|
674
|
-
stateModel.setSelectedFeature(feature)
|
|
675
|
-
const { session } = stateModel
|
|
676
|
-
const { apolloDataStore } = session
|
|
677
|
-
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
678
|
-
if (!featureTypeOntology) {
|
|
679
|
-
throw new Error('featureTypeOntology is undefined')
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
let containsCDSOrExon = false
|
|
683
|
-
for (const [, child] of feature.children ?? []) {
|
|
684
|
-
if (
|
|
685
|
-
featureTypeOntology.isTypeOf(child.type, 'CDS') ||
|
|
686
|
-
featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
687
|
-
) {
|
|
688
|
-
containsCDSOrExon = true
|
|
689
|
-
break
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
if (
|
|
693
|
-
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
694
|
-
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
695
|
-
containsCDSOrExon
|
|
696
|
-
) {
|
|
697
|
-
stateModel.showFeatureDetailsWidget(feature, [
|
|
698
|
-
'ApolloTranscriptDetails',
|
|
699
|
-
'apolloTranscriptDetails',
|
|
700
|
-
])
|
|
701
|
-
} else {
|
|
702
|
-
stateModel.showFeatureDetailsWidget(feature)
|
|
703
|
-
}
|
|
759
|
+
selectFeatureAndOpenWidget(stateModel, feature)
|
|
704
760
|
}
|
|
705
761
|
|
|
706
762
|
function getDraggableFeatureInfo(
|
|
@@ -769,49 +825,13 @@ function getDraggableFeatureInfo(
|
|
|
769
825
|
return
|
|
770
826
|
}
|
|
771
827
|
|
|
772
|
-
function isTranscriptFeature(
|
|
773
|
-
feature: AnnotationFeature,
|
|
774
|
-
session: ApolloSessionModel,
|
|
775
|
-
): boolean {
|
|
776
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
777
|
-
if (!featureTypeOntology) {
|
|
778
|
-
throw new Error('featureTypeOntology is undefined')
|
|
779
|
-
}
|
|
780
|
-
return (
|
|
781
|
-
featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
782
|
-
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')
|
|
783
|
-
)
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
function isExonFeature(
|
|
787
|
-
feature: AnnotationFeature,
|
|
788
|
-
session: ApolloSessionModel,
|
|
789
|
-
): boolean {
|
|
790
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
791
|
-
if (!featureTypeOntology) {
|
|
792
|
-
throw new Error('featureTypeOntology is undefined')
|
|
793
|
-
}
|
|
794
|
-
return featureTypeOntology.isTypeOf(feature.type, 'exon')
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
function isCDSFeature(
|
|
798
|
-
feature: AnnotationFeature,
|
|
799
|
-
session: ApolloSessionModel,
|
|
800
|
-
): boolean {
|
|
801
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
802
|
-
if (!featureTypeOntology) {
|
|
803
|
-
throw new Error('featureTypeOntology is undefined')
|
|
804
|
-
}
|
|
805
|
-
return featureTypeOntology.isTypeOf(feature.type, 'CDS')
|
|
806
|
-
}
|
|
807
|
-
|
|
808
828
|
function getContextMenuItems(
|
|
809
829
|
display: LinearApolloDisplayMouseEvents,
|
|
810
|
-
mousePosition:
|
|
830
|
+
mousePosition: MousePositionWithFeature,
|
|
811
831
|
): MenuItem[] {
|
|
812
832
|
const {
|
|
813
833
|
apolloInternetAccount: internetAccount,
|
|
814
|
-
|
|
834
|
+
hoveredFeature,
|
|
815
835
|
changeManager,
|
|
816
836
|
regions,
|
|
817
837
|
selectedFeature,
|
|
@@ -822,53 +842,120 @@ function getContextMenuItems(
|
|
|
822
842
|
const menuItems: MenuItem[] = []
|
|
823
843
|
const role = internetAccount ? internetAccount.role : 'admin'
|
|
824
844
|
const admin = role === 'admin'
|
|
825
|
-
if (!
|
|
845
|
+
if (!hoveredFeature) {
|
|
826
846
|
return menuItems
|
|
827
847
|
}
|
|
828
848
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
featuresUnderClick =
|
|
832
|
-
|
|
849
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
850
|
+
const { bp, feature } = mousePosition
|
|
851
|
+
let featuresUnderClick = getRelatedFeatures(feature, bp)
|
|
852
|
+
if (isCDSFeature(feature, session)) {
|
|
853
|
+
featuresUnderClick = getRelatedFeatures(feature, bp, true)
|
|
854
|
+
}
|
|
833
855
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
856
|
+
for (const feature of featuresUnderClick) {
|
|
857
|
+
const contextMenuItemsForFeature = boxGlyph.getContextMenuItemsForFeature(
|
|
858
|
+
display,
|
|
859
|
+
feature,
|
|
860
|
+
)
|
|
861
|
+
if (isExonFeature(feature, session)) {
|
|
862
|
+
const adjacentExons = getAdjacentExons(
|
|
863
|
+
feature,
|
|
864
|
+
display,
|
|
865
|
+
mousePosition,
|
|
866
|
+
session,
|
|
867
|
+
)
|
|
868
|
+
const lgv = getContainingView(
|
|
869
|
+
display as BaseDisplayModel,
|
|
870
|
+
) as unknown as LinearGenomeViewModel
|
|
871
|
+
if (adjacentExons.upstream) {
|
|
872
|
+
const exon = adjacentExons.upstream
|
|
873
|
+
contextMenuItemsForFeature.push({
|
|
874
|
+
label: 'Go to upstream exon',
|
|
875
|
+
icon: getStreamIcon(
|
|
876
|
+
feature.strand,
|
|
877
|
+
true,
|
|
878
|
+
lgv.displayedRegions.at(0)?.reversed,
|
|
879
|
+
),
|
|
880
|
+
onClick: () => {
|
|
881
|
+
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
882
|
+
selectFeatureAndOpenWidget(display, exon)
|
|
883
|
+
},
|
|
884
|
+
})
|
|
885
|
+
}
|
|
886
|
+
if (adjacentExons.downstream) {
|
|
887
|
+
const exon = adjacentExons.downstream
|
|
888
|
+
contextMenuItemsForFeature.push({
|
|
889
|
+
label: 'Go to downstream exon',
|
|
890
|
+
icon: getStreamIcon(
|
|
891
|
+
feature.strand,
|
|
892
|
+
false,
|
|
893
|
+
lgv.displayedRegions.at(0)?.reversed,
|
|
894
|
+
),
|
|
895
|
+
onClick: () => {
|
|
896
|
+
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
897
|
+
selectFeatureAndOpenWidget(display, exon)
|
|
898
|
+
},
|
|
899
|
+
})
|
|
900
|
+
}
|
|
901
|
+
contextMenuItemsForFeature.push(
|
|
902
|
+
{
|
|
903
|
+
label: 'Merge exons',
|
|
904
|
+
disabled: !admin,
|
|
905
|
+
onClick: () => {
|
|
906
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
907
|
+
(doneCallback) => [
|
|
908
|
+
MergeExons,
|
|
909
|
+
{
|
|
910
|
+
session,
|
|
911
|
+
handleClose: () => {
|
|
912
|
+
doneCallback()
|
|
913
|
+
},
|
|
914
|
+
changeManager,
|
|
915
|
+
sourceFeature: feature,
|
|
916
|
+
sourceAssemblyId: currentAssemblyId,
|
|
917
|
+
selectedFeature,
|
|
918
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
919
|
+
display.setSelectedFeature(feature)
|
|
920
|
+
},
|
|
852
921
|
},
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
922
|
+
],
|
|
923
|
+
)
|
|
924
|
+
},
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
label: 'Split exon',
|
|
928
|
+
disabled: !admin,
|
|
929
|
+
onClick: () => {
|
|
930
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
931
|
+
(doneCallback) => [
|
|
932
|
+
SplitExon,
|
|
933
|
+
{
|
|
934
|
+
session,
|
|
935
|
+
handleClose: () => {
|
|
936
|
+
doneCallback()
|
|
937
|
+
},
|
|
938
|
+
changeManager,
|
|
939
|
+
sourceFeature: feature,
|
|
940
|
+
sourceAssemblyId: currentAssemblyId,
|
|
941
|
+
selectedFeature,
|
|
942
|
+
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
943
|
+
display.setSelectedFeature(feature)
|
|
944
|
+
},
|
|
859
945
|
},
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
946
|
+
],
|
|
947
|
+
)
|
|
948
|
+
},
|
|
863
949
|
},
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
950
|
+
)
|
|
951
|
+
}
|
|
952
|
+
if (isTranscriptFeature(feature, session)) {
|
|
953
|
+
contextMenuItemsForFeature.push({
|
|
954
|
+
label: 'Merge transcript',
|
|
868
955
|
onClick: () => {
|
|
869
956
|
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
870
957
|
(doneCallback) => [
|
|
871
|
-
|
|
958
|
+
MergeTranscripts,
|
|
872
959
|
{
|
|
873
960
|
session,
|
|
874
961
|
handleClose: () => {
|
|
@@ -885,56 +972,31 @@ function getContextMenuItems(
|
|
|
885
972
|
],
|
|
886
973
|
)
|
|
887
974
|
},
|
|
888
|
-
},
|
|
889
|
-
)
|
|
890
|
-
}
|
|
891
|
-
if (isTranscriptFeature(feature, session)) {
|
|
892
|
-
contextMenuItemsForFeature.push({
|
|
893
|
-
label: 'Merge transcript',
|
|
894
|
-
onClick: () => {
|
|
895
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
896
|
-
(doneCallback) => [
|
|
897
|
-
MergeTranscripts,
|
|
898
|
-
{
|
|
899
|
-
session,
|
|
900
|
-
handleClose: () => {
|
|
901
|
-
doneCallback()
|
|
902
|
-
},
|
|
903
|
-
changeManager,
|
|
904
|
-
sourceFeature: feature,
|
|
905
|
-
sourceAssemblyId: currentAssemblyId,
|
|
906
|
-
selectedFeature,
|
|
907
|
-
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
908
|
-
display.setSelectedFeature(feature)
|
|
909
|
-
},
|
|
910
|
-
},
|
|
911
|
-
],
|
|
912
|
-
)
|
|
913
|
-
},
|
|
914
|
-
})
|
|
915
|
-
if (isSessionModelWithWidgets(session)) {
|
|
916
|
-
contextMenuItemsForFeature.push({
|
|
917
|
-
label: 'Open transcript details',
|
|
918
|
-
onClick: () => {
|
|
919
|
-
const apolloTranscriptWidget = session.addWidget(
|
|
920
|
-
'ApolloTranscriptDetails',
|
|
921
|
-
'apolloTranscriptDetails',
|
|
922
|
-
{
|
|
923
|
-
feature,
|
|
924
|
-
assembly: currentAssemblyId,
|
|
925
|
-
changeManager,
|
|
926
|
-
refName: region.refName,
|
|
927
|
-
},
|
|
928
|
-
)
|
|
929
|
-
session.showWidget(apolloTranscriptWidget)
|
|
930
|
-
},
|
|
931
975
|
})
|
|
976
|
+
if (isSessionModelWithWidgets(session)) {
|
|
977
|
+
contextMenuItemsForFeature.push({
|
|
978
|
+
label: 'Open transcript details',
|
|
979
|
+
onClick: () => {
|
|
980
|
+
const apolloTranscriptWidget = session.addWidget(
|
|
981
|
+
'ApolloTranscriptDetails',
|
|
982
|
+
'apolloTranscriptDetails',
|
|
983
|
+
{
|
|
984
|
+
feature,
|
|
985
|
+
assembly: currentAssemblyId,
|
|
986
|
+
changeManager,
|
|
987
|
+
refName: region.refName,
|
|
988
|
+
},
|
|
989
|
+
)
|
|
990
|
+
session.showWidget(apolloTranscriptWidget)
|
|
991
|
+
},
|
|
992
|
+
})
|
|
993
|
+
}
|
|
932
994
|
}
|
|
995
|
+
menuItems.push({
|
|
996
|
+
label: feature.type,
|
|
997
|
+
subMenu: contextMenuItemsForFeature,
|
|
998
|
+
})
|
|
933
999
|
}
|
|
934
|
-
menuItems.push({
|
|
935
|
-
label: feature.type,
|
|
936
|
-
subMenu: contextMenuItemsForFeature,
|
|
937
|
-
})
|
|
938
1000
|
}
|
|
939
1001
|
return menuItems
|
|
940
1002
|
}
|