Skip to content

Commit

Permalink
Restart implementing the use of ontology to replace hard-coded types
Browse files Browse the repository at this point in the history
Started by adding isTypeOf function in
`packages/jbrowse-plugin-apollo/src/OntologyManager/index.ts` Then in
`GeneGlyph.ts` use `isTypeOf(mrna.type, 'mRNA')` in `draw()`. Edit downstream
as required by making draw async. A quick check seems ok.

Then again in `GeneGlyph.ts` use `isTypeOf(feature.type, 'gene')` in
`featuresForRow`. This required several edits elsewhere.

A gene of type `protein_coding_gene` is correctly displayed, but the check
question marks don't move on the screen and there are issues with the table
editor.
  • Loading branch information
dariober committed Nov 6, 2024
1 parent 20ed2cf commit c5eb9db
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Alert, Avatar, Tooltip, useTheme } from '@mui/material'
import { observer } from 'mobx-react'
import React, { useEffect, useState } from 'react'
import { makeStyles } from 'tss-react/mui'

import { CheckResultI } from '@apollo-annotation/mst'
import { LinearApolloDisplay as LinearApolloDisplayI } from '../stateModel'

interface LinearApolloDisplayProps {
Expand Down Expand Up @@ -50,7 +50,6 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
apolloRowHeight,
contextMenuItems: getContextMenuItems,
cursor,
featuresHeight,
isShown,
onMouseDown,
onMouseLeave,
Expand All @@ -68,16 +67,92 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
const { classes } = useStyles()
const lgv = getContainingView(model) as unknown as LinearGenomeViewModel

const [featuresHeight, setFeatureHeight] = useState<number>()
useEffect(() => {
function getFeaturesHeight() {
const featuresHeight = model.getFeaturesHeight()
setFeatureHeight(featuresHeight)
}
getFeaturesHeight()
})

useEffect(() => {
setTheme(theme)
}, [theme, setTheme])

const { assemblyManager } = session as unknown as AbstractSessionModel

const [checkInfo, setCheckInfo] = useState<
({
checkResult: CheckResultI
top: number
left: number
height: number
} | null)[]
>([])

useEffect(() => {
async function getCheckInfo() {
const info = await Promise.all(
lgv.displayedRegions.flatMap((region, idx) => {
const assembly = assemblyManager.get(region.assemblyName)
return [...session.apolloDataStore.checkResults.values()]
.filter(
(checkResult) =>
assembly?.isValidRefName(checkResult.refSeq) &&
assembly.getCanonicalRefName(checkResult.refSeq) ===
region.refName &&
doesIntersect2(
region.start,
region.end,
checkResult.start,
checkResult.end,
),
)
.map(async (checkResult) => {
const left =
(lgv.bpToPx({
refName: region.refName,
coord: checkResult.start,
regionNumber: idx,
})?.offsetPx ?? 0) - lgv.offsetPx
const [feature] = checkResult.ids
if (!feature) {
return null
}
const { topLevelFeature } = feature

let row = 0
if (parent) {
const pos =
await model.getFeatureLayoutPosition(topLevelFeature)
if (pos?.layoutRow) {
row = pos.layoutRow
}
}
const top = row * apolloRowHeight
const height = apolloRowHeight
return { checkResult, top, left, height }
})
}),
)
setCheckInfo(info)
}
void getCheckInfo()
}, [
apolloRowHeight,
assemblyManager,
lgv,
model,
session.apolloDataStore.checkResults,
])

const [contextCoord, setContextCoord] = useState<Coord>()
const [contextMenuItems, setContextMenuItems] = useState<MenuItem[]>([])
const message = regionCannotBeRendered()
if (!isShown) {
return null
}
const { assemblyManager } = session as unknown as AbstractSessionModel
return (
<>
{lgv.bpPerPx <= 3 ? (
Expand Down Expand Up @@ -173,50 +248,21 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
style={{ cursor: cursor ?? 'default' }}
data-testid="overlayCanvas"
/>
{lgv.displayedRegions.flatMap((region, idx) => {
const assembly = assemblyManager.get(region.assemblyName)
return [...session.apolloDataStore.checkResults.values()]
.filter(
(checkResult) =>
assembly?.isValidRefName(checkResult.refSeq) &&
assembly.getCanonicalRefName(checkResult.refSeq) ===
region.refName &&
doesIntersect2(
region.start,
region.end,
checkResult.start,
checkResult.end,
),
)
.map((checkResult) => {
const left =
(lgv.bpToPx({
refName: region.refName,
coord: checkResult.start,
regionNumber: idx,
})?.offsetPx ?? 0) - lgv.offsetPx
const [feature] = checkResult.ids
if (!feature) {
return null
}
const { topLevelFeature } = feature
const row = parent
? model.getFeatureLayoutPosition(topLevelFeature)
?.layoutRow ?? 0
: 0
const top = row * apolloRowHeight
const height = apolloRowHeight
return (
<Tooltip key={checkResult._id} title={checkResult.message}>
<Avatar
className={classes.avatar}
style={{ top, left, height, width: height }}
>
<ErrorIcon />
</Avatar>
</Tooltip>
)
})
{checkInfo.map((info) => {
if (!info) {
return null
}
const { checkResult, height, left, top } = info
return (
<Tooltip key={checkResult._id} title={checkResult.message}>
<Avatar
className={classes.avatar}
style={{ top, left, height, width: height }}
>
<ErrorIcon />
</Avatar>
</Tooltip>
)
})}
<Menu
open={contextMenuItems.length > 0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ function drawBoxText(
ctx.fillText(text, textStart, y + 11, textWidth)
}

function draw(
// eslint-disable-next-line @typescript-eslint/require-await
async function draw(
ctx: CanvasRenderingContext2D,
feature: AnnotationFeature,
row: number,
Expand Down Expand Up @@ -132,7 +133,7 @@ function drawDragPreview(
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
}

function drawHover(
async function drawHover(
stateModel: LinearApolloDisplay,
ctx: CanvasRenderingContext2D,
) {
Expand All @@ -141,7 +142,7 @@ function drawHover(
return
}
const { feature } = apolloHover
const position = stateModel.getFeatureLayoutPosition(feature)
const position = await stateModel.getFeatureLayoutPosition(feature)
if (!position) {
return
}
Expand All @@ -162,16 +163,16 @@ function drawHover(
ctx.fillRect(startPx, top, widthPx, apolloRowHeight)
}

function drawTooltip(
async function drawTooltip(
display: LinearApolloDisplayMouseEvents,
context: CanvasRenderingContext2D,
): void {
): Promise<void> {
const { apolloHover, apolloRowHeight, lgv, theme } = display
if (!apolloHover) {
return
}
const { feature } = apolloHover
const position = display.getFeatureLayoutPosition(feature)
const position = await display.getFeatureLayoutPosition(feature)
if (!position) {
return
}
Expand Down Expand Up @@ -388,22 +389,24 @@ function getContextMenuItems(
return menuItems
}

function getFeatureFromLayout(
// eslint-disable-next-line @typescript-eslint/require-await
async function getFeatureFromLayout(
feature: AnnotationFeature,
_bp: number,
_row: number,
): AnnotationFeature {
): Promise<AnnotationFeature> {
return feature
}

function getRowCount(_feature: AnnotationFeature) {
return 1
}

function getRowForFeature(
// eslint-disable-next-line @typescript-eslint/require-await
async function getRowForFeature(
_feature: AnnotationFeature,
_childFeature: AnnotationFeature,
): number | undefined {
): Promise<number | undefined> {
return 0
}

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 { OntologyManager } from '../../OntologyManager'

let forwardFill: CanvasPattern | null = null
let backwardFill: CanvasPattern | null = null
Expand Down Expand Up @@ -47,14 +48,15 @@ if ('document' in window) {
}
}

function draw(
async function draw(
ctx: CanvasRenderingContext2D,
feature: AnnotationFeature,
row: number,
stateModel: LinearApolloDisplayRendering,
displayedRegionIndex: number,
): void {
): Promise<void> {
const { apolloRowHeight, lgv, session, theme } = stateModel
const { apolloDataStore } = session
const { bpPerPx, displayedRegions, offsetPx } = lgv
const displayedRegion = displayedRegions[displayedRegionIndex]
const { refName, reversed } = displayedRegion
Expand All @@ -71,7 +73,11 @@ function draw(
// Draw lines on different rows for each mRNA
let currentRow = 0
for (const [, mrna] of children) {
if (mrna.type !== 'mRNA') {
const isMrna = await apolloDataStore.ontologyManager.isTypeOf(
mrna.type,
'mRNA',
)
if (!isMrna) {
currentRow += 1
continue
}
Expand Down Expand Up @@ -106,7 +112,7 @@ function draw(
currentRow = 0
for (const [, child] of children) {
if (child.type !== 'mRNA') {
boxGlyph.draw(ctx, child, row, stateModel, displayedRegionIndex)
await boxGlyph.draw(ctx, child, row, stateModel, displayedRegionIndex)
currentRow += 1
continue
}
Expand Down Expand Up @@ -253,7 +259,7 @@ function drawDragPreview(
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
}

function drawHover(
async function drawHover(
stateModel: LinearApolloDisplay,
ctx: CanvasRenderingContext2D,
) {
Expand All @@ -262,7 +268,7 @@ function drawHover(
return
}
const { feature } = apolloHover
const position = stateModel.getFeatureLayoutPosition(feature)
const position = await stateModel.getFeatureLayoutPosition(feature)
if (!position) {
return
}
Expand All @@ -284,12 +290,14 @@ function drawHover(
ctx.fillRect(startPx, top, widthPx, apolloRowHeight * getRowCount(feature))
}

function getFeatureFromLayout(
async function getFeatureFromLayout(
feature: AnnotationFeature,
bp: number,
row: number,
): AnnotationFeature | undefined {
const featureInThisRow: AnnotationFeature[] = featuresForRow(feature)[row]
ontologyManager: OntologyManager,
): Promise<AnnotationFeature | undefined> {
const ff = await featuresForRow(feature, ontologyManager)
const featureInThisRow: AnnotationFeature[] = ff[row]
for (const f of featureInThisRow) {
if (bp >= f.min && bp <= f.max && f.parent) {
return f
Expand Down Expand Up @@ -324,8 +332,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') {
async function featuresForRow(
feature: AnnotationFeature,
ontologyManager: OntologyManager,
): Promise<AnnotationFeature[][]> {
const isGene = await ontologyManager.isTypeOf(feature.type, 'gene')
if (!isGene) {
throw new Error('Top level feature for GeneGlyph must have type "gene"')
}
const { children } = feature
Expand Down Expand Up @@ -357,11 +369,12 @@ function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
return features
}

function getRowForFeature(
async function getRowForFeature(
feature: AnnotationFeature,
childFeature: AnnotationFeature,
ontologyManager: OntologyManager,
) {
const rows = featuresForRow(feature)
const rows = await featuresForRow(feature, ontologyManager)
for (const [idx, row] of rows.entries()) {
if (row.some((feature) => feature._id === childFeature._id)) {
return idx
Expand Down
Loading

0 comments on commit c5eb9db

Please sign in to comment.