@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 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 +2371 -1642
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +2384 -1641
- 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 +4387 -2952
- 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 +33 -13
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +33 -31
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +60 -72
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +441 -180
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
- 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/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 +95 -38
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +221 -201
- 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 +1 -1
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddChildFeature.tsx +5 -2
- package/src/components/AddFeature.tsx +9 -3
- package/src/components/AddRefSeqAliases.tsx +9 -9
- package/src/components/CopyFeature.tsx +3 -1
- package/src/components/CreateApolloAnnotation.tsx +1 -0
- package/src/components/DeleteAssembly.tsx +1 -1
- 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 +1 -1
- package/src/components/MergeTranscripts.tsx +12 -15
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/ViewCheckResults.tsx +1 -1
- package/src/components/index.ts +1 -0
- package/src/config.ts +6 -0
- 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} +56 -47
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/session.ts +162 -116
- package/src/util/annotationFeatureUtils.ts +15 -21
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +152 -0
- package/src/util/mouseEventsUtils.ts +32 -0
|
@@ -13,16 +13,20 @@ import { alpha } from '@mui/material'
|
|
|
13
13
|
import equal from 'fast-deep-equal/es6'
|
|
14
14
|
import { getSnapshot } from 'mobx-state-tree'
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { MergeExons, SplitExon } from '../../components'
|
|
17
17
|
import { FilterTranscripts } from '../../components/FilterTranscripts'
|
|
18
|
-
import { getMinAndMaxPx, getOverlappingEdge } from '../../util'
|
|
19
|
-
import { type LinearApolloSixFrameDisplay } from '../stateModel'
|
|
20
18
|
import {
|
|
21
|
-
type LinearApolloSixFrameDisplayMouseEvents,
|
|
22
19
|
type MousePosition,
|
|
23
|
-
type
|
|
24
|
-
|
|
25
|
-
|
|
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'
|
|
26
30
|
import { type LinearApolloSixFrameDisplayRendering } from '../stateModel/rendering'
|
|
27
31
|
import { type CanvasMouseEvent } from '../types'
|
|
28
32
|
|
|
@@ -133,6 +137,7 @@ function draw(
|
|
|
133
137
|
theme,
|
|
134
138
|
highestRow,
|
|
135
139
|
filteredTranscripts,
|
|
140
|
+
selectedFeature,
|
|
136
141
|
showFeatureLabels,
|
|
137
142
|
} = stateModel
|
|
138
143
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
@@ -143,12 +148,11 @@ function draw(
|
|
|
143
148
|
const cdsHeight = rowHeight
|
|
144
149
|
const topLevelFeatureHeight = rowHeight
|
|
145
150
|
const featureLabelSpacer = showFeatureLabels ? 2 : 1
|
|
146
|
-
const textColor = theme
|
|
151
|
+
const textColor = theme.palette.text.primary
|
|
147
152
|
const { attributes, children, min, strand } = topLevelFeature
|
|
148
153
|
if (!children) {
|
|
149
154
|
return
|
|
150
155
|
}
|
|
151
|
-
const { apolloSelectedFeature } = session
|
|
152
156
|
const { apolloDataStore } = session
|
|
153
157
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
154
158
|
if (!featureTypeOntology) {
|
|
@@ -169,7 +173,7 @@ function draw(
|
|
|
169
173
|
: topLevelFeatureMinX
|
|
170
174
|
const topLevelRow = (strand == 1 ? 3 : 4) * featureLabelSpacer
|
|
171
175
|
const topLevelFeatureTop = topLevelRow * rowHeight
|
|
172
|
-
ctx.fillStyle = theme
|
|
176
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
173
177
|
ctx.fillRect(
|
|
174
178
|
topLevelFeatureStartPx,
|
|
175
179
|
topLevelFeatureTop,
|
|
@@ -177,9 +181,9 @@ function draw(
|
|
|
177
181
|
topLevelFeatureHeight,
|
|
178
182
|
)
|
|
179
183
|
|
|
180
|
-
ctx.fillStyle = isSelectedFeature(topLevelFeature,
|
|
184
|
+
ctx.fillStyle = isSelectedFeature(topLevelFeature, selectedFeature)
|
|
181
185
|
? alpha('rgb(0,0,0)', 0.7)
|
|
182
|
-
: alpha(theme
|
|
186
|
+
: alpha(theme.palette.background.paper, 0.7)
|
|
183
187
|
ctx.fillRect(
|
|
184
188
|
topLevelFeatureStartPx + 1,
|
|
185
189
|
topLevelFeatureTop + 1,
|
|
@@ -187,7 +191,7 @@ function draw(
|
|
|
187
191
|
topLevelFeatureHeight - 2,
|
|
188
192
|
)
|
|
189
193
|
|
|
190
|
-
const isSelected = isSelectedFeature(topLevelFeature,
|
|
194
|
+
const isSelected = isSelectedFeature(topLevelFeature, selectedFeature)
|
|
191
195
|
const label: Label = {
|
|
192
196
|
x: topLevelFeatureStartPx,
|
|
193
197
|
y: topLevelFeatureTop,
|
|
@@ -203,9 +207,9 @@ function draw(
|
|
|
203
207
|
}
|
|
204
208
|
|
|
205
209
|
const forwardFill =
|
|
206
|
-
theme
|
|
210
|
+
theme.palette.mode === 'dark' ? forwardFillDark : forwardFillLight
|
|
207
211
|
const backwardFill =
|
|
208
|
-
theme
|
|
212
|
+
theme.palette.mode === 'dark' ? backwardFillDark : backwardFillLight
|
|
209
213
|
const reversal = reversed ? -1 : 1
|
|
210
214
|
let topFill: CanvasPattern | null = null,
|
|
211
215
|
bottomFill: CanvasPattern | null = null
|
|
@@ -269,8 +273,8 @@ function draw(
|
|
|
269
273
|
|
|
270
274
|
const exonTop =
|
|
271
275
|
topLevelFeatureTop + (topLevelFeatureHeight - exonHeight) / 2
|
|
272
|
-
const isSelected = isSelectedFeature(exon,
|
|
273
|
-
ctx.fillStyle = theme
|
|
276
|
+
const isSelected = isSelectedFeature(exon, selectedFeature)
|
|
277
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
274
278
|
ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
|
|
275
279
|
if (widthPx > 2) {
|
|
276
280
|
ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
@@ -309,7 +313,7 @@ function draw(
|
|
|
309
313
|
}
|
|
310
314
|
}
|
|
311
315
|
|
|
312
|
-
const isSelected = isSelectedFeature(child,
|
|
316
|
+
const isSelected = isSelectedFeature(child, selectedFeature?.parent)
|
|
313
317
|
let cdsStartPx = 0
|
|
314
318
|
let cdsTop = 0
|
|
315
319
|
for (const cdsRow of cdsLocations) {
|
|
@@ -318,9 +322,9 @@ function draw(
|
|
|
318
322
|
let counter = 1
|
|
319
323
|
for (const cds of cdsRow.sort((a, b) => a.max - b.max)) {
|
|
320
324
|
if (
|
|
321
|
-
(
|
|
325
|
+
(selectedFeature &&
|
|
322
326
|
isSelected &&
|
|
323
|
-
featureTypeOntology.isTypeOf(
|
|
327
|
+
featureTypeOntology.isTypeOf(selectedFeature.type, 'CDS')) ||
|
|
324
328
|
!deepSetHas(renderedCDS, cds)
|
|
325
329
|
) {
|
|
326
330
|
const cdsWidthPx = (cds.max - cds.min) / bpPerPx
|
|
@@ -331,7 +335,7 @@ function draw(
|
|
|
331
335
|
regionNumber: displayedRegionIndex,
|
|
332
336
|
})?.offsetPx ?? 0) - offsetPx
|
|
333
337
|
cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
334
|
-
ctx.fillStyle = theme
|
|
338
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
335
339
|
const frame = getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase)
|
|
336
340
|
const frameAdjust =
|
|
337
341
|
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
@@ -345,13 +349,13 @@ function draw(
|
|
|
345
349
|
cdsHeight - 2,
|
|
346
350
|
)
|
|
347
351
|
|
|
348
|
-
const frameColor = theme
|
|
352
|
+
const frameColor = theme.palette.framesCDS.at(frame)?.main
|
|
349
353
|
const cdsColorCode = frameColor ?? 'rgb(171,71,188)'
|
|
350
354
|
ctx.fillStyle = cdsColorCode
|
|
351
355
|
ctx.fillStyle =
|
|
352
|
-
|
|
356
|
+
selectedFeature &&
|
|
353
357
|
isSelected &&
|
|
354
|
-
featureTypeOntology.isTypeOf(
|
|
358
|
+
featureTypeOntology.isTypeOf(selectedFeature.type, 'CDS')
|
|
355
359
|
? 'rgb(0,0,0)'
|
|
356
360
|
: cdsColorCode
|
|
357
361
|
ctx.fillRect(
|
|
@@ -448,10 +452,10 @@ function drawDragPreview(
|
|
|
448
452
|
const rectY = row * apolloRowHeight
|
|
449
453
|
const rectWidth = Math.abs(current.x - featureEdgePx)
|
|
450
454
|
const rectHeight = apolloRowHeight * rowCount
|
|
451
|
-
overlayCtx.strokeStyle = theme
|
|
455
|
+
overlayCtx.strokeStyle = theme.palette.info.main
|
|
452
456
|
overlayCtx.setLineDash([6])
|
|
453
457
|
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
454
|
-
overlayCtx.fillStyle = alpha(theme
|
|
458
|
+
overlayCtx.fillStyle = alpha(theme.palette.info.main, 0.2)
|
|
455
459
|
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
456
460
|
}
|
|
457
461
|
|
|
@@ -460,7 +464,7 @@ function drawHover(
|
|
|
460
464
|
ctx: CanvasRenderingContext2D,
|
|
461
465
|
) {
|
|
462
466
|
const {
|
|
463
|
-
|
|
467
|
+
hoveredFeature,
|
|
464
468
|
apolloRowHeight,
|
|
465
469
|
filteredTranscripts,
|
|
466
470
|
lgv,
|
|
@@ -468,15 +472,15 @@ function drawHover(
|
|
|
468
472
|
session,
|
|
469
473
|
showFeatureLabels,
|
|
470
474
|
} = stateModel
|
|
471
|
-
if (!
|
|
475
|
+
if (!hoveredFeature) {
|
|
472
476
|
return
|
|
473
477
|
}
|
|
478
|
+
const { feature } = hoveredFeature
|
|
474
479
|
const { apolloDataStore } = session
|
|
475
480
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
476
481
|
if (!featureTypeOntology) {
|
|
477
482
|
throw new Error('featureTypeOntology is undefined')
|
|
478
483
|
}
|
|
479
|
-
const { feature } = apolloHover
|
|
480
484
|
if (!featureTypeOntology.isTypeOf(feature.type, 'transcript')) {
|
|
481
485
|
return
|
|
482
486
|
}
|
|
@@ -548,16 +552,14 @@ function drawHover(
|
|
|
548
552
|
|
|
549
553
|
function onMouseDown(
|
|
550
554
|
stateModel: LinearApolloSixFrameDisplay,
|
|
551
|
-
currentMousePosition:
|
|
555
|
+
currentMousePosition: MousePositionWithFeature,
|
|
552
556
|
event: CanvasMouseEvent,
|
|
553
557
|
) {
|
|
554
|
-
const {
|
|
558
|
+
const { feature } = currentMousePosition
|
|
555
559
|
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
556
560
|
// don't start dragging the view if we try to drag the feature edge
|
|
557
|
-
const { cds, feature } = featureAndGlyphUnderMouse
|
|
558
561
|
const draggableFeature = getDraggableFeatureInfo(
|
|
559
562
|
currentMousePosition,
|
|
560
|
-
cds,
|
|
561
563
|
feature,
|
|
562
564
|
stateModel,
|
|
563
565
|
)
|
|
@@ -576,13 +578,11 @@ function onMouseMove(
|
|
|
576
578
|
stateModel: LinearApolloSixFrameDisplay,
|
|
577
579
|
mousePosition: MousePosition,
|
|
578
580
|
) {
|
|
579
|
-
if (
|
|
580
|
-
const {
|
|
581
|
-
stateModel.
|
|
582
|
-
const { cds, feature } = featureAndGlyphUnderMouse
|
|
581
|
+
if (isMousePositionWithFeature(mousePosition)) {
|
|
582
|
+
const { feature, bp } = mousePosition
|
|
583
|
+
stateModel.setHoveredFeature({ feature, bp })
|
|
583
584
|
const draggableFeature = getDraggableFeatureInfo(
|
|
584
585
|
mousePosition,
|
|
585
|
-
cds,
|
|
586
586
|
feature,
|
|
587
587
|
stateModel,
|
|
588
588
|
)
|
|
@@ -601,53 +601,43 @@ function onMouseUp(
|
|
|
601
601
|
if (stateModel.apolloDragging) {
|
|
602
602
|
return
|
|
603
603
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (!featureTypeOntology) {
|
|
614
|
-
throw new Error('featureTypeOntology is undefined')
|
|
615
|
-
}
|
|
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)
|
|
610
|
+
if (!featureTypeOntology) {
|
|
611
|
+
throw new Error('featureTypeOntology is undefined')
|
|
612
|
+
}
|
|
616
613
|
|
|
617
|
-
|
|
618
|
-
|
|
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
|
|
621
|
+
break
|
|
622
|
+
}
|
|
623
|
+
}
|
|
619
624
|
if (
|
|
620
|
-
featureTypeOntology.isTypeOf(
|
|
621
|
-
|
|
625
|
+
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
626
|
+
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
627
|
+
containsCDSOrExon
|
|
622
628
|
) {
|
|
623
|
-
|
|
624
|
-
|
|
629
|
+
stateModel.showFeatureDetailsWidget(feature, [
|
|
630
|
+
'ApolloTranscriptDetails',
|
|
631
|
+
'apolloTranscriptDetails',
|
|
632
|
+
])
|
|
633
|
+
} else {
|
|
634
|
+
stateModel.showFeatureDetailsWidget(feature)
|
|
625
635
|
}
|
|
626
636
|
}
|
|
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
637
|
}
|
|
647
638
|
|
|
648
639
|
function getDraggableFeatureInfo(
|
|
649
640
|
mousePosition: MousePosition,
|
|
650
|
-
cds: TranscriptPartCoding | null,
|
|
651
641
|
feature: AnnotationFeature,
|
|
652
642
|
stateModel: LinearApolloSixFrameDisplay,
|
|
653
643
|
): { feature: AnnotationFeature; edge: 'min' | 'max' } | undefined {
|
|
@@ -658,9 +648,6 @@ function getDraggableFeatureInfo(
|
|
|
658
648
|
throw new Error('featureTypeOntology is undefined')
|
|
659
649
|
}
|
|
660
650
|
const isTranscript = featureTypeOntology.isTypeOf(feature.type, 'transcript')
|
|
661
|
-
if (cds === null) {
|
|
662
|
-
return
|
|
663
|
-
}
|
|
664
651
|
const featureID: string | undefined = feature.attributes
|
|
665
652
|
.get('gff_id')
|
|
666
653
|
?.toString()
|
|
@@ -700,16 +687,24 @@ function getDraggableFeatureInfo(
|
|
|
700
687
|
}
|
|
701
688
|
}
|
|
702
689
|
// 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
|
-
|
|
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
|
+
}
|
|
713
708
|
}
|
|
714
709
|
}
|
|
715
710
|
}
|
|
@@ -721,22 +716,33 @@ function drawTooltip(
|
|
|
721
716
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
722
717
|
context: CanvasRenderingContext2D,
|
|
723
718
|
): void {
|
|
724
|
-
const {
|
|
725
|
-
|
|
726
|
-
|
|
719
|
+
const {
|
|
720
|
+
hoveredFeature,
|
|
721
|
+
apolloRowHeight,
|
|
722
|
+
filteredTranscripts,
|
|
723
|
+
lgv,
|
|
724
|
+
session,
|
|
725
|
+
showFeatureLabels,
|
|
726
|
+
theme,
|
|
727
|
+
} = display
|
|
728
|
+
if (!hoveredFeature) {
|
|
727
729
|
return
|
|
728
730
|
}
|
|
729
|
-
const {
|
|
730
|
-
|
|
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) {
|
|
731
738
|
return
|
|
732
739
|
}
|
|
740
|
+
const { attributes, strand, type } = feature
|
|
733
741
|
const position = display.getFeatureLayoutPosition(feature)
|
|
734
742
|
if (!position) {
|
|
735
743
|
return
|
|
736
744
|
}
|
|
737
|
-
const featureID: string | undefined =
|
|
738
|
-
.get('gff_id')
|
|
739
|
-
?.toString()
|
|
745
|
+
const featureID: string | undefined = attributes.get('gff_id')?.toString()
|
|
740
746
|
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
741
747
|
return
|
|
742
748
|
}
|
|
@@ -746,9 +752,20 @@ function drawTooltip(
|
|
|
746
752
|
const { refName, reversed } = displayedRegion
|
|
747
753
|
const rowHeight = apolloRowHeight
|
|
748
754
|
const cdsHeight = Math.round(0.7 * rowHeight)
|
|
755
|
+
const featureLabelSpacer = showFeatureLabels ? 2 : 1
|
|
749
756
|
let location = 'Loc: '
|
|
750
|
-
|
|
751
|
-
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
|
+
}
|
|
752
769
|
const { max, min, phase } = cds
|
|
753
770
|
location += `${min + 1}–${max}`
|
|
754
771
|
|
|
@@ -759,12 +776,12 @@ function drawTooltip(
|
|
|
759
776
|
regionNumber: layoutIndex,
|
|
760
777
|
})?.offsetPx ?? 0) - offsetPx
|
|
761
778
|
const frame = getFrame(min, max, strand ?? 1, phase)
|
|
762
|
-
const frameAdjust = frame < 0 ? -1 * frame + 5 : frame
|
|
763
|
-
const cdsTop =
|
|
779
|
+
const frameAdjust = (frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
780
|
+
const cdsTop =
|
|
781
|
+
(frameAdjust - featureLabelSpacer) * rowHeight + (rowHeight - cdsHeight) / 2
|
|
764
782
|
const cdsWidthPx = (max - min) / bpPerPx
|
|
765
783
|
|
|
766
784
|
const featureType = `Type: ${cds.type}`
|
|
767
|
-
const { attributes } = feature
|
|
768
785
|
const featureName = attributes.get('gff_name')?.find((name) => name !== '')
|
|
769
786
|
const textWidth = [
|
|
770
787
|
context.measureText(featureType).width,
|
|
@@ -772,14 +789,14 @@ function drawTooltip(
|
|
|
772
789
|
]
|
|
773
790
|
if (featureName) {
|
|
774
791
|
textWidth.push(
|
|
775
|
-
context.measureText(`Parent Type: ${
|
|
792
|
+
context.measureText(`Parent Type: ${type}`).width,
|
|
776
793
|
context.measureText(`Parent Name: ${featureName}`).width,
|
|
777
794
|
)
|
|
778
795
|
}
|
|
779
796
|
const maxWidth = Math.max(...textWidth)
|
|
780
797
|
|
|
781
798
|
startPx = startPx + cdsWidthPx + 5
|
|
782
|
-
context.fillStyle = alpha(theme
|
|
799
|
+
context.fillStyle = alpha(theme.palette.text.primary, 0.7)
|
|
783
800
|
context.fillRect(
|
|
784
801
|
startPx,
|
|
785
802
|
cdsTop,
|
|
@@ -791,12 +808,12 @@ function drawTooltip(
|
|
|
791
808
|
context.lineTo(startPx - 5, cdsTop + 5)
|
|
792
809
|
context.lineTo(startPx, cdsTop + 10)
|
|
793
810
|
context.fill()
|
|
794
|
-
context.fillStyle = theme
|
|
811
|
+
context.fillStyle = theme.palette.background.default
|
|
795
812
|
let textTop = cdsTop + 12
|
|
796
813
|
context.fillText(featureType, startPx + 2, textTop)
|
|
797
814
|
if (featureName) {
|
|
798
815
|
textTop = textTop + 12
|
|
799
|
-
context.fillText(`Parent Type: ${
|
|
816
|
+
context.fillText(`Parent Type: ${type}`, startPx + 2, textTop)
|
|
800
817
|
textTop = textTop + 12
|
|
801
818
|
context.fillText(`Parent Name: ${featureName}`, startPx + 2, textTop)
|
|
802
819
|
}
|
|
@@ -806,119 +823,121 @@ function drawTooltip(
|
|
|
806
823
|
|
|
807
824
|
function getContextMenuItems(
|
|
808
825
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
826
|
+
mousePosition: MousePositionWithFeature,
|
|
809
827
|
): MenuItem[] {
|
|
810
828
|
const {
|
|
811
|
-
apolloHover,
|
|
812
829
|
apolloInternetAccount: internetAccount,
|
|
830
|
+
hoveredFeature,
|
|
813
831
|
changeManager,
|
|
814
832
|
filteredTranscripts,
|
|
815
833
|
regions,
|
|
816
834
|
selectedFeature,
|
|
817
835
|
session,
|
|
818
836
|
} = display
|
|
837
|
+
const [region] = regions
|
|
838
|
+
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
819
839
|
const menuItems: MenuItem[] = []
|
|
820
|
-
if (!apolloHover) {
|
|
821
|
-
return menuItems
|
|
822
|
-
}
|
|
823
|
-
const { feature: sourceFeature } = apolloHover
|
|
824
840
|
const role = internetAccount ? internetAccount.role : 'admin'
|
|
825
841
|
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
|
-
)
|
|
842
|
+
if (!hoveredFeature) {
|
|
843
|
+
return menuItems
|
|
844
|
+
}
|
|
897
845
|
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
898
846
|
if (!featureTypeOntology) {
|
|
899
847
|
throw new Error('featureTypeOntology is undefined')
|
|
900
848
|
}
|
|
901
|
-
if (
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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(
|
|
864
|
+
{
|
|
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
|
+
)
|
|
917
886
|
},
|
|
918
|
-
|
|
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
|
+
},
|
|
911
|
+
},
|
|
919
912
|
)
|
|
920
|
-
}
|
|
921
|
-
|
|
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
|
+
}
|
|
922
941
|
}
|
|
923
942
|
return menuItems
|
|
924
943
|
}
|
|
@@ -933,6 +952,7 @@ export const geneGlyph: Glyph = {
|
|
|
933
952
|
drawHover,
|
|
934
953
|
drawTooltip,
|
|
935
954
|
getContextMenuItems,
|
|
955
|
+
getContextMenuItemsForFeature,
|
|
936
956
|
onMouseDown,
|
|
937
957
|
onMouseLeave,
|
|
938
958
|
onMouseMove,
|