Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback ontology loading #482

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
17 changes: 13 additions & 4 deletions packages/apollo-mst/src/AnnotationFeatureModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { intersection2 } from '@jbrowse/core/util'
import { getSession, intersection2 } from '@jbrowse/core/util'
import {
IAnyModelType,
IMSTMap,
Expand Down Expand Up @@ -127,7 +127,14 @@ export const AnnotationFeatureModel = types
return false
},
get transcriptParts(): TranscriptParts[] {
if (self.type !== 'mRNA') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
const session = getSession(self) as any
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { apolloDataStore } = session
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const { featureTypeOntology } = apolloDataStore.ontologyManager
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
if (!featureTypeOntology.isTypeOf(self.type, 'mRNA')) {
throw new Error(
'Only features of type "mRNA" or equivalent can calculate CDS locations',
)
Expand All @@ -137,7 +144,8 @@ export const AnnotationFeatureModel = types
throw new Error('no CDS or exons in mRNA')
}
const cdsChildren = [...children.values()].filter(
(child) => child.type === 'CDS',
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
(child) => featureTypeOntology.isTypeOf(child.type, 'CDS'),
)
if (cdsChildren.length === 0) {
throw new Error('no CDS in mRNA')
Expand All @@ -149,7 +157,8 @@ export const AnnotationFeatureModel = types
let hasIntersected = false
const exonLocations: TranscriptPartLocation[] = []
for (const [, child] of children) {
if (child.type === 'exon') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
if (featureTypeOntology.isTypeOf(child.type, 'exon')) {
exonLocations.push({ min: child.min, max: child.max })
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,11 @@ export const TranscriptSequence = observer(function TranscriptSequence({
if (!refSeq) {
return null
}
if (feature.type !== 'mRNA') {
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}
if (featureTypeOntology.isTypeOf(feature.type, 'mRNA')) {
return null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CanvasMouseEvent } from '../types'
import { Glyph } from './Glyph'
import { boxGlyph } from './BoxGlyph'
import { LinearApolloDisplayRendering } from '../stateModel/rendering'
import { OntologyRecord } from '../../OntologyManager'

let forwardFillLight: CanvasPattern | null = null
let backwardFillLight: CanvasPattern | null = null
Expand Down Expand Up @@ -80,6 +81,11 @@ function draw(
return
}
const { apolloSelectedFeature } = session
const { apolloDataStore } = session
const { featureTypeOntology } = apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}

// Draw background for gene
const topLevelFeatureMinX =
Expand All @@ -93,7 +99,8 @@ function draw(
? topLevelFeatureMinX - topLevelFeatureWidthPx
: topLevelFeatureMinX
const topLevelFeatureTop = row * rowHeight
const topLevelFeatureHeight = getRowCount(feature) * rowHeight
const topLevelFeatureHeight =
getRowCount(feature, featureTypeOntology) * rowHeight

ctx.fillStyle = alpha(theme?.palette.background.paper ?? '#ffffff', 0.6)
ctx.fillRect(
Expand All @@ -106,7 +113,8 @@ function draw(
// Draw lines on different rows for each mRNA
let currentRow = 0
for (const [, mrna] of children) {
if (mrna.type !== 'mRNA') {
const isMrna = featureTypeOntology.isTypeOf(mrna.type, 'mRNA')
if (!isMrna) {
currentRow += 1
continue
}
Expand All @@ -115,7 +123,7 @@ function draw(
continue
}
for (const [, cds] of childrenOfmRNA) {
if (cds.type !== 'CDS') {
if (!featureTypeOntology.isTypeOf(cds.type, 'CDS')) {
continue
}
const minX =
Expand Down Expand Up @@ -144,7 +152,7 @@ function draw(
// Draw exon and CDS for each mRNA
currentRow = 0
for (const [, child] of children) {
if (child.type !== 'mRNA') {
if (!featureTypeOntology.isTypeOf(child.type, 'mRNA')) {
boxGlyph.draw(ctx, child, row, stateModel, displayedRegionIndex)
currentRow += 1
continue
Expand All @@ -155,7 +163,7 @@ function draw(
continue
}
for (const [, exon] of childrenOfmRNA) {
if (exon.type !== 'exon') {
if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
continue
}
const minX =
Expand Down Expand Up @@ -296,7 +304,9 @@ function drawHover(
stateModel: LinearApolloDisplay,
ctx: CanvasRenderingContext2D,
) {
const { apolloHover, apolloRowHeight, lgv, theme } = stateModel
const { apolloHover, apolloRowHeight, lgv, session, theme } = stateModel
const { featureTypeOntology } = session.apolloDataStore.ontologyManager

if (!apolloHover) {
return
}
Expand All @@ -320,16 +330,26 @@ function drawHover(
const top = row * apolloRowHeight
const widthPx = length / bpPerPx
ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,04)'
ctx.fillRect(startPx, top, widthPx, apolloRowHeight * getRowCount(feature))

if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}
ctx.fillRect(
startPx,
top,
widthPx,
apolloRowHeight * getRowCount(feature, featureTypeOntology),
)
}

function getFeatureFromLayout(
feature: AnnotationFeature,
bp: number,
row: number,
featureTypeOntology: OntologyRecord,
): AnnotationFeature | undefined {
const featureInThisRow: AnnotationFeature[] =
featuresForRow(feature)[row] || []
featuresForRow(feature, featureTypeOntology)[row] || []
for (const f of featureInThisRow) {
let featureObj
if (bp >= f.min && bp <= f.max && f.parent) {
Expand All @@ -339,9 +359,9 @@ function getFeatureFromLayout(
continue
}
if (
featureObj.type === 'CDS' &&
featureTypeOntology.isTypeOf(featureObj.type, 'CDS') &&
featureObj.parent &&
featureObj.parent.type === 'mRNA'
featureTypeOntology.isTypeOf(featureObj.parent.type, 'mRNA')
) {
const { cdsLocations } = featureObj.parent
for (const cdsLoc of cdsLocations) {
Expand All @@ -361,22 +381,28 @@ function getFeatureFromLayout(
return feature
}

function getRowCount(feature: AnnotationFeature, _bpPerPx?: number): number {
function getRowCount(
feature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
_bpPerPx?: number,
): number {
const { children, type } = feature
if (!children) {
return 1
}
const isMrna = featureTypeOntology.isTypeOf(type, 'mRNA')
let rowCount = 0
if (type === 'mRNA') {
if (isMrna) {
for (const [, child] of children) {
if (child.type === 'CDS') {
const isCds = featureTypeOntology.isTypeOf(child.type, 'CDS')
if (isCds) {
rowCount += 1
}
}
return rowCount
}
for (const [, child] of children) {
rowCount += getRowCount(child)
rowCount += getRowCount(child, featureTypeOntology)
}
return rowCount
}
Expand All @@ -387,8 +413,12 @@ function getRowCount(feature: AnnotationFeature, _bpPerPx?: number): number {
* If the row contains an mRNA, the order is CDS -\> exon -\> mRNA -\> gene
* If the row does not contain an mRNA, the order is subfeature -\> gene
*/
function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
if (feature.type !== 'gene') {
function featuresForRow(
feature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
): AnnotationFeature[][] {
const isGene = featureTypeOntology.isTypeOf(feature.type, 'gene')
if (!isGene) {
throw new Error('Top level feature for GeneGlyph must have type "gene"')
}
const { children } = feature
Expand All @@ -397,7 +427,7 @@ function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
}
const features: AnnotationFeature[][] = []
for (const [, child] of children) {
if (child.type !== 'mRNA') {
if (!featureTypeOntology.isTypeOf(child.type, 'mRNA')) {
features.push([child, feature])
continue
}
Expand All @@ -407,9 +437,9 @@ function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
const cdss: AnnotationFeature[] = []
const exons: AnnotationFeature[] = []
for (const [, grandchild] of child.children) {
if (grandchild.type === 'CDS') {
if (featureTypeOntology.isTypeOf(grandchild.type, 'CDS')) {
cdss.push(grandchild)
} else if (grandchild.type === 'exon') {
} else if (featureTypeOntology.isTypeOf(grandchild.type, 'exon')) {
exons.push(grandchild)
}
}
Expand All @@ -423,8 +453,9 @@ function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
function getRowForFeature(
feature: AnnotationFeature,
childFeature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
) {
const rows = featuresForRow(feature)
const rows = featuresForRow(feature, featureTypeOntology)
for (const [idx, row] of rows.entries()) {
if (row.some((feature) => feature._id === childFeature._id)) {
return idx
Expand Down Expand Up @@ -496,7 +527,16 @@ function getDraggableFeatureInfo(
feature: AnnotationFeature,
stateModel: LinearApolloDisplay,
): { feature: AnnotationFeature; edge: 'min' | 'max' } | undefined {
if (feature.type === 'gene' || feature.type === 'mRNA') {
const { session } = stateModel
const { apolloDataStore } = session
const { featureTypeOntology } = apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}
const isGene = featureTypeOntology.isTypeOf(feature.type, 'gene')
const isMrna = featureTypeOntology.isTypeOf(feature.type, 'mRNA')
const isCds = featureTypeOntology.isTypeOf(feature.type, 'CDS')
if (isGene || isMrna) {
return
}
const { bp, refName, regionNumber, x } = mousePosition
Expand All @@ -519,14 +559,19 @@ function getDraggableFeatureInfo(
if (Math.abs(maxPx - x) < 4) {
return { feature, edge: 'max' }
}
if (feature.type === 'CDS') {
if (isCds) {
const mRNA = feature.parent
if (!mRNA?.children) {
return
}
const exonChildren = [...mRNA.children.values()].filter(
(child) => child.type === 'exon',
)
const exonChildren: AnnotationFeature[] = []
for (const child of mRNA.children.values()) {
const childIsExon = featureTypeOntology.isTypeOf(child.type, 'exon')
if (childIsExon) {
exonChildren.push(child)
}
}

const overlappingExon = exonChildren.find((child) => {
const [start, end] = intersection2(bp, bp + 1, child.min, child.max)
return start !== undefined && end !== undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import {
} from '../stateModel/mouseEvents'
import { LinearApolloDisplayRendering } from '../stateModel/rendering'
import { CanvasMouseEvent } from '../types'
import { OntologyRecord } from '../../OntologyManager'

export interface Glyph {
/** @returns number of layout rows used by this glyph with this feature and zoom level */
getRowCount(feature: AnnotationFeature, bpPerPx: number): number
getRowCount(
feature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
bpPerPx: number,
): number
/** draw the feature's primary rendering on the canvas */
draw(
ctx: CanvasRenderingContext2D,
Expand All @@ -24,10 +29,12 @@ export interface Glyph {
feature: AnnotationFeature,
bp: number,
row: number,
featureTypeOntology: OntologyRecord,
): AnnotationFeature | undefined
getRowForFeature(
feature: AnnotationFeature,
childFeature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
): number | undefined

drawHover(
Expand Down

This file was deleted.

Loading
Loading