Skip to content

Commit

Permalink
Transcript widget upates (#474)
Browse files Browse the repository at this point in the history
* Add transcriptParts to feature model

* Use transcriptParts in transcript sequence panel

* Add transcript widget to right-click menu

* Use transcriptParts in transcript widget structure

* Add frame color to transcript widget structure

* Use custom framesCDS in theme

* Add header to transcript sequence
  • Loading branch information
garrettjstevens authored Nov 13, 2024
1 parent eb301ba commit 73593c4
Show file tree
Hide file tree
Showing 6 changed files with 456 additions and 685 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ export class JBrowseService {
quaternary: {
main: '#571AA3',
},
framesCDS: [
null,
{ main: 'rgb(204,121,167)' },
{ main: 'rgb(230,159,0)' },
{ main: 'rgb(240,228,66)' },
{ main: 'rgb(86,180,233)' },
{ main: 'rgb(0,114,178)' },
{ main: 'rgb(0,158,115)' },
],
},
},
ApolloPlugin: {
Expand Down
95 changes: 83 additions & 12 deletions packages/apollo-mst/src/AnnotationFeatureModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ const LateAnnotationFeature = types.late(
(): IAnyModelType => AnnotationFeatureModel,
)

export interface TranscriptPartLocation {
min: number
max: number
}

export interface TranscriptPartNonCoding extends TranscriptPartLocation {
type: 'fivePrimeUTR' | 'threePrimeUTR' | 'intron'
}

export interface TranscriptPartCoding extends TranscriptPartLocation {
type: 'CDS'
phase: 0 | 1 | 2
}

export type TranscriptPart = TranscriptPartCoding | TranscriptPartNonCoding

type TranscriptParts = TranscriptPart[]

export const AnnotationFeatureModel = types
.model('AnnotationFeatureModel', {
_id: types.identifier,
Expand Down Expand Up @@ -108,7 +126,7 @@ export const AnnotationFeatureModel = types
}
return false
},
get cdsLocations(): { min: number; max: number; phase: 0 | 1 | 2 }[][] {
get transcriptParts(): TranscriptParts[] {
if (self.type !== 'mRNA') {
throw new Error(
'Only features of type "mRNA" or equivalent can calculate CDS locations',
Expand All @@ -124,41 +142,94 @@ export const AnnotationFeatureModel = types
if (cdsChildren.length === 0) {
throw new Error('no CDS in mRNA')
}
const cdsLocations: { min: number; max: number; phase: 0 | 1 | 2 }[][] =
[]
const transcriptParts: TranscriptParts[] = []
for (const cds of cdsChildren) {
const { max: cdsMax, min: cdsMin } = cds
const locs: { min: number; max: number }[] = []
const parts: TranscriptParts = []
let hasIntersected = false
const exonLocations: TranscriptPartLocation[] = []
for (const [, child] of children) {
if (child.type !== 'exon') {
continue
if (child.type === 'exon') {
exonLocations.push({ min: child.min, max: child.max })
}
}
exonLocations.sort(({ min: a }, { min: b }) => a - b)
for (const child of exonLocations) {
const lastPart = parts.at(-1)
if (lastPart) {
parts.push({ min: lastPart.max, max: child.min, type: 'intron' })
}
const [start, end] = intersection2(
cdsMin,
cdsMax,
child.min,
child.max,
)
let utrType: 'fivePrimeUTR' | 'threePrimeUTR'
if (hasIntersected) {
utrType = self.strand === 1 ? 'threePrimeUTR' : 'fivePrimeUTR'
} else {
utrType = self.strand === 1 ? 'fivePrimeUTR' : 'threePrimeUTR'
}
if (start !== undefined && end !== undefined) {
locs.push({ min: start, max: end })
hasIntersected = true
if (start === child.min && end === child.max) {
parts.push({ min: start, max: end, phase: 0, type: 'CDS' })
} else if (start === child.min) {
parts.push(
{ min: start, max: end, phase: 0, type: 'CDS' },
{ min: end, max: child.max, type: utrType },
)
} else if (end === child.max) {
parts.push(
{ min: child.min, max: start, type: utrType },
{ min: start, max: end, phase: 0, type: 'CDS' },
)
} else {
parts.push(
{ min: child.min, max: start, type: utrType },
{ min: start, max: end, phase: 0, type: 'CDS' },
{
min: end,
max: child.max,
type:
utrType === 'fivePrimeUTR'
? 'threePrimeUTR'
: 'fivePrimeUTR',
},
)
}
} else {
parts.push({ min: child.min, max: child.max, type: utrType })
}
}
locs.sort(({ min: a }, { min: b }) => a - b)
parts.sort(({ min: a }, { min: b }) => a - b)
if (self.strand === -1) {
locs.reverse()
parts.reverse()
}
let nextPhase: 0 | 1 | 2 = 0
const phasedLocs = locs.map((loc) => {
const phasedParts = parts.map((loc) => {
if (loc.type !== 'CDS') {
return loc
}
const phase = nextPhase
nextPhase = ((3 - ((loc.max - loc.min - phase + 3) % 3)) % 3) as
| 0
| 1
| 2
return { ...loc, phase }
})
cdsLocations.push(phasedLocs)
transcriptParts.push(phasedParts)
}
return cdsLocations
return transcriptParts
},
}))
.views((self) => ({
get cdsLocations(): TranscriptPartCoding[][] {
const { transcriptParts } = self
return transcriptParts.map((transcript) =>
transcript.filter((transcriptPart) => transcriptPart.type === 'CDS'),
)
},
}))
.actions((self) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,7 @@ export const Attributes = observer(function Attributes({

return (
<>
<Typography
style={{ display: 'inline', marginLeft: '15px' }}
variant="h5"
>
Attributes
</Typography>
<Typography variant="h5">Attributes</Typography>
<Grid container direction="column" spacing={1}>
{Object.entries(attributes).map(([key, value]) => {
if (key === '') {
Expand Down
Loading

0 comments on commit 73593c4

Please sign in to comment.