Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,24 @@ import {
isSessionModelWithWidgets,
} from '@jbrowse/core/util'
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
import SkipNextRoundedIcon from '@mui/icons-material/SkipNextRounded'
import SkipPreviousRoundedIcon from '@mui/icons-material/SkipPreviousRounded'
import { alpha } from '@mui/material'

import { type OntologyRecord } from '../../OntologyManager'
import { MergeExons, MergeTranscripts, SplitExon } from '../../components'
import { type ApolloSessionModel } from '../../session'
import {
type MousePosition,
type MousePositionWithFeature,
containsSelectedFeature,
getAdjacentExons,
getMinAndMaxPx,
getOverlappingEdge,
getStreamIcon,
isCDSFeature,
isExonFeature,
isMousePositionWithFeature,
isTranscriptFeature,
navToFeatureCenter,
selectFeatureAndOpenWidget,
} from '../../util'
import { getRelatedFeatures } from '../../util/annotationFeatureUtils'
import { type LinearApolloDisplay } from '../stateModel'
Expand Down Expand Up @@ -696,45 +699,6 @@ function getRowForFeature(
return
}

function selectFeatureAndOpenWidget(
stateModel: LinearApolloDisplayMouseEvents,
feature: AnnotationFeature,
) {
if (stateModel.apolloDragging) {
return
}
stateModel.setSelectedFeature(feature)
const { session } = stateModel
const { apolloDataStore } = session
const { featureTypeOntology } = apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}

let containsCDSOrExon = false
for (const [, child] of feature.children ?? []) {
if (
featureTypeOntology.isTypeOf(child.type, 'CDS') ||
featureTypeOntology.isTypeOf(child.type, 'exon')
) {
containsCDSOrExon = true
break
}
}
if (
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
containsCDSOrExon
) {
stateModel.showFeatureDetailsWidget(feature, [
'ApolloTranscriptDetails',
'apolloTranscriptDetails',
])
} else {
stateModel.showFeatureDetailsWidget(feature)
}
}

function onMouseDown(
stateModel: LinearApolloDisplay,
currentMousePosition: MousePositionWithFeature,
Expand Down Expand Up @@ -859,131 +823,6 @@ function getDraggableFeatureInfo(
return
}

function isTranscriptFeature(
feature: AnnotationFeature,
session: ApolloSessionModel,
): boolean {
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}
return (
featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')
)
}

function isExonFeature(
feature: AnnotationFeature,
session: ApolloSessionModel,
): boolean {
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}
return featureTypeOntology.isTypeOf(feature.type, 'exon')
}

function isCDSFeature(
feature: AnnotationFeature,
session: ApolloSessionModel,
): boolean {
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}
return featureTypeOntology.isTypeOf(feature.type, 'CDS')
}

interface AdjacentExons {
upstream: AnnotationFeature | undefined
downstream: AnnotationFeature | undefined
}

function getAdjacentExons(
currentExon: AnnotationFeature,
display: LinearApolloDisplayMouseEvents,
mousePosition: MousePositionWithFeature,
session: ApolloSessionModel,
): AdjacentExons {
const lgv = getContainingView(
display as BaseDisplayModel,
) as unknown as LinearGenomeViewModel

// Genomic coords of current view
const viewGenomicLeft = mousePosition.bp - lgv.bpPerPx * mousePosition.x
const viewGenomicRight = viewGenomicLeft + lgv.coarseTotalBp
if (!currentExon.parent) {
return { upstream: undefined, downstream: undefined }
}
const transcript = currentExon.parent
if (!transcript.children) {
throw new Error(`Error getting children of ${transcript._id}`)
}
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}

let exons = []
for (const [, child] of transcript.children) {
if (featureTypeOntology.isTypeOf(child.type, 'exon')) {
exons.push(child)
}
}
const adjacentExons: AdjacentExons = {
upstream: undefined,
downstream: undefined,
}
exons = exons.sort((a, b) => (a.min < b.min ? -1 : 1))
for (const exon of exons) {
if (exon.min > viewGenomicRight) {
adjacentExons.downstream = exon
break
}
}
exons = exons.sort((a, b) => (a.min > b.min ? -1 : 1))
for (const exon of exons) {
if (exon.max < viewGenomicLeft) {
adjacentExons.upstream = exon
break
}
}
if (transcript.strand === -1) {
const newUpstream = adjacentExons.downstream
adjacentExons.downstream = adjacentExons.upstream
adjacentExons.upstream = newUpstream
}
return adjacentExons
}

function getStreamIcon(
strand: 1 | -1 | undefined,
isUpstream: boolean,
isFlipped: boolean | undefined,
) {
// This is the icon you would use for strand=1, downstream, straight
// (non-flipped) view
let icon = SkipNextRoundedIcon

if (strand === -1) {
icon = SkipPreviousRoundedIcon
}
if (isUpstream) {
icon =
icon === SkipPreviousRoundedIcon
? SkipNextRoundedIcon
: SkipPreviousRoundedIcon
}
if (isFlipped) {
icon =
icon === SkipPreviousRoundedIcon
? SkipNextRoundedIcon
: SkipPreviousRoundedIcon
}
return icon
}

function getContextMenuItems(
display: LinearApolloDisplayMouseEvents,
mousePosition: MousePositionWithFeature,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import {
type AnnotationFeature,
type TranscriptPartCoding,
} from '@apollo-annotation/mst'
import { type BaseDisplayModel } from '@jbrowse/core/pluggableElementTypes'
import { type MenuItem } from '@jbrowse/core/ui'
import {
type AbstractSessionModel,
getContainingView,
getFrame,
intersection2,
measureText,
} from '@jbrowse/core/util'
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
import { alpha } from '@mui/material'
import equal from 'fast-deep-equal/es6'
import { getSnapshot } from 'mobx-state-tree'
Expand All @@ -18,12 +21,19 @@ import { FilterTranscripts } from '../../components/FilterTranscripts'
import {
type MousePosition,
type MousePositionWithFeature,
getAdjacentExons,
getContextMenuItemsForFeature,
getMinAndMaxPx,
getOverlappingEdge,
getRelatedFeatures,
getStreamIcon,
isCDSFeature,
isExonFeature,
isMousePositionWithFeature,
// isTranscriptFeature,
isSelectedFeature,
navToFeatureCenter,
selectFeatureAndOpenWidget,
} from '../../util'
import { type LinearApolloSixFrameDisplay } from '../stateModel'
import { type LinearApolloSixFrameDisplayMouseEvents } from '../stateModel/mouseEvents'
Expand Down Expand Up @@ -848,18 +858,62 @@ function getContextMenuItems(
}
if (isMousePositionWithFeature(mousePosition)) {
const { bp, feature } = mousePosition
for (const relatedFeature of getRelatedFeatures(feature, bp)) {
const featureID: string | undefined = relatedFeature.attributes
let featuresUnderClick = getRelatedFeatures(feature, bp)
if (isCDSFeature(feature, session)) {
featuresUnderClick = getRelatedFeatures(feature, bp, true)
}

for (const feature of featuresUnderClick) {
const featureID: string | undefined = feature.attributes
.get('gff_id')
?.toString()
if (featureID && filteredTranscripts.includes(featureID)) {
continue
}
const contextMenuItemsForFeature = getContextMenuItemsForFeature(
display,
relatedFeature,
feature,
)
if (featureTypeOntology.isTypeOf(relatedFeature.type, 'exon')) {
if (isExonFeature(feature, session)) {
const adjacentExons = getAdjacentExons(
feature,
display,
mousePosition,
session,
)
const lgv = getContainingView(
display as BaseDisplayModel,
) as unknown as LinearGenomeViewModel
if (adjacentExons.upstream) {
const exon = adjacentExons.upstream
contextMenuItemsForFeature.push({
label: 'Go to upstream exon',
icon: getStreamIcon(
feature.strand,
true,
lgv.displayedRegions.at(0)?.reversed,
),
onClick: () => {
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
selectFeatureAndOpenWidget(display, exon)
},
})
}
if (adjacentExons.downstream) {
const exon = adjacentExons.downstream
contextMenuItemsForFeature.push({
label: 'Go to downstream exon',
icon: getStreamIcon(
feature.strand,
false,
lgv.displayedRegions.at(0)?.reversed,
),
onClick: () => {
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
selectFeatureAndOpenWidget(display, exon)
},
})
}
contextMenuItemsForFeature.push(
{
label: 'Merge exons',
Expand All @@ -874,7 +928,7 @@ function getContextMenuItems(
doneCallback()
},
changeManager,
sourceFeature: relatedFeature,
sourceFeature: feature,
sourceAssemblyId: currentAssemblyId,
selectedFeature,
setSelectedFeature: (feature?: AnnotationFeature) => {
Expand All @@ -898,7 +952,7 @@ function getContextMenuItems(
doneCallback()
},
changeManager,
sourceFeature: relatedFeature,
sourceFeature: feature,
sourceAssemblyId: currentAssemblyId,
selectedFeature,
setSelectedFeature: (feature?: AnnotationFeature) => {
Expand All @@ -911,7 +965,7 @@ function getContextMenuItems(
},
)
}
if (featureTypeOntology.isTypeOf(relatedFeature.type, 'gene')) {
if (featureTypeOntology.isTypeOf(feature.type, 'gene')) {
contextMenuItemsForFeature.push({
label: 'Filter alternate transcripts',
onClick: () => {
Expand All @@ -922,7 +976,7 @@ function getContextMenuItems(
handleClose: () => {
doneCallback()
},
sourceFeature: relatedFeature,
sourceFeature: feature,
filteredTranscripts: getSnapshot(filteredTranscripts),
onUpdate: (forms: string[]) => {
display.updateFilteredTranscripts(forms)
Expand All @@ -934,7 +988,7 @@ function getContextMenuItems(
})
}
menuItems.push({
label: relatedFeature.type,
label: feature.type,
subMenu: contextMenuItemsForFeature,
})
}
Expand Down
Loading