Skip to content

Commit

Permalink
Allow getting protein translations
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Mar 3, 2021
1 parent 2483e07 commit e7740e1
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 107 deletions.
172 changes: 165 additions & 7 deletions packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
Divider,
Paper,
Tooltip,
Select,
MenuItem,
} from '@material-ui/core'
import ExpandMore from '@material-ui/icons/ExpandMore'
import { makeStyles } from '@material-ui/core/styles'
Expand All @@ -16,7 +18,12 @@ import { observer } from 'mobx-react'
import clsx from 'clsx'
import isObject from 'is-object'
import { readConfObject } from '../configuration'
import { measureText, getSession } from '../util'
import {
measureText,
getSession,
defaultCodonTable,
generateCodonTable,
} from '../util'
import { Feature } from '../util/simpleFeature'
import SanitizedHTML from '../ui/SanitizedHTML'

Expand Down Expand Up @@ -207,36 +214,187 @@ interface BaseProps extends BaseCardProps {
model?: any
}

// display the stitched-together sequence of a gene's CDS, cDNA, or protein
// sequence. this is a best effort and weird genomic phenomena could lead these
// to not be 100% accurate
function SequenceFeatureDetails(props: BaseProps) {
const { model, feature } = props
const { assemblyManager, rpcManager } = getSession(model)
const { assemblyNames } = model.view
const [sequence, setSequence] = useState<string>()
const [error, setError] = useState<string>()
const [assemblyName] = assemblyNames
const hasCDS = feature.subfeatures.find((sub: any) => sub.type === 'CDS')
const [mode, setMode] = useState(hasCDS ? 'cds' : 'cdna')
const loading = !sequence
const codonTable = generateCodonTable(defaultCodonTable)

useEffect(() => {
;(async () => {
const assemblyConfig = assemblyManager.get(assemblyName)?.configuration
const adapterConfig = readConfObject(assemblyConfig, [
const assembly = await assemblyManager.waitForAssembly(assemblyName)
if (!assembly) {
setError('assembly not found')
return
}
const adapterConfig = readConfObject(assembly.configuration, [
'sequence',
'adapter',
])
const sessionId = 'getSequence'
const region = {
start: feature.start,
end: feature.end,
refName: feature.refName,
refName: assembly?.getCanonicalRefName(feature.refName),
}
const feats = await rpcManager.call(sessionId, 'CoreGetFeatures', {
adapterConfig,
region,
sessionId,
})
const [feat] = feats as Feature[]
if (!feat) {
setError('sequence not found')
}

setSequence(feat.get('seq'))
})()
}, [feature, assemblyManager, rpcManager, assemblyName])

return <div>Hello world {sequence}</div>
const text: React.ReactNode[] = []
if (sequence && feature) {
const children = feature.subfeatures
.sort((a: any, b: any) => a.start - b.start)
.map((sub: any) => {
return {
...sub,
start: sub.start - feature.start,
end: sub.end - feature.start,
}
})

const cdsColor = 'rgba(150,150,0,0.3)'
const utrColor = 'rgba(0,150,150,0.3)'
const proteinColor = 'rgba(150,0,150,0.3)'

const cds = children.filter((sub: any) => sub.type === 'CDS')
const exons = children.filter((sub: any) => sub.type === 'exon')

if (mode === 'cds') {
cds.forEach((sub: any) => {
text.push(
<div
key={`${sub.start}-${sub.end}`}
style={{
display: 'inline',
backgroundColor: cdsColor,
}}
>
{sequence.slice(sub.start, sub.end)}
</div>,
)
})
} else if (mode === 'cdna') {
// if we have CDS, it is a real gene, color the difference between the
// start and end of the CDS as UTR and the rest as CDS
if (cds.length) {
text.push(
<div
key="5prime_utr"
style={{
display: 'inline',
backgroundColor: utrColor,
}}
>
{sequence.slice(0, cds[0].start)}
</div>,
)
cds.forEach((sub: any) => {
text.push(
<div
key={`${sub.start}-${sub.end}`}
style={{
display: 'inline',
backgroundColor: cdsColor,
}}
>
{sequence.slice(sub.start, sub.end)}
</div>,
)
})

text.push(
<div
key="3prime_utr"
style={{
display: 'inline',
backgroundColor: utrColor,
}}
>
{sequence.slice(cds[cds.length - 1].end)}
</div>,
)
}
// no CDS, probably a pseudogene, color whole thing as "UTR"
else {
exons.forEach((sub: any) => {
text.push(
<div
key={`${sub.start}-${sub.end}`}
style={{
display: 'inline',
backgroundColor: utrColor,
}}
>
{sequence.slice(sub.start, sub.end)}
</div>,
)
})
}
} else if (mode === 'protein') {
let str = ''
cds.forEach((sub: any) => {
str += sequence.slice(sub.start, sub.end)
})
let protein = ''
for (let i = 0; i < str.length; i += 3) {
protein += codonTable[str.slice(i, i + 3)]
}
text.push(
<div
key={protein}
style={{
display: 'inline',
backgroundColor: proteinColor,
}}
>
{protein}
</div>,
)
}
}

return (
<div>
<Select
value={mode}
onChange={event => setMode(event.target.value as string)}
>
{hasCDS ? <MenuItem value="cds">CDS</MenuItem> : null}
{hasCDS ? <MenuItem value="protein">Protein</MenuItem> : null}
<MenuItem value="cdna">cDNA</MenuItem>
</Select>
<div style={{ display: 'inline' }}>
{error ? (
<Typography color="error">{error}</Typography>
) : (
<div style={{ fontFamily: 'monospace', wordWrap: 'break-word' }}>
{text}
</div>
)}
{loading ? <div>Loading gene sequence...</div> : null}
</div>
</div>
)
}

function CoreDetails(props: BaseProps) {
Expand Down Expand Up @@ -446,9 +604,9 @@ function Subfeature(props: BaseProps) {
<CoreDetails {...props} />
<Divider />
<Attributes attributes={feature} {...props} />
<div>
{feature.type === 'mRNA' || feature.type === 'transcript' ? (
<SequenceFeatureDetails {...props} />
</div>
) : null}
{feature.subfeatures && feature.subfeatures.length ? (
<BaseCard title="Subfeatures" defaultExpanded={false}>
{feature.subfeatures.map((subfeature: any) => (
Expand Down
100 changes: 100 additions & 0 deletions packages/core/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -871,3 +871,103 @@ export function measureText(str: string, fontSize = 10) {
.reduce((cur, acc) => acc + cur) * fontSize
)
}

export const defaultStarts = ['ATG']
export const defaultStops = ['TAA', 'TAG', 'TGA']
export const defaultCodonTable = {
TCA: 'S',
TCC: 'S',
TCG: 'S',
TCT: 'S',
TTC: 'F',
TTT: 'F',
TTA: 'L',
TTG: 'L',
TAC: 'Y',
TAT: 'Y',
TAA: '*',
TAG: '*',
TGC: 'C',
TGT: 'C',
TGA: '*',
TGG: 'W',
CTA: 'L',
CTC: 'L',
CTG: 'L',
CTT: 'L',
CCA: 'P',
CCC: 'P',
CCG: 'P',
CCT: 'P',
CAC: 'H',
CAT: 'H',
CAA: 'Q',
CAG: 'Q',
CGA: 'R',
CGC: 'R',
CGG: 'R',
CGT: 'R',
ATA: 'I',
ATC: 'I',
ATT: 'I',
ATG: 'M',
ACA: 'T',
ACC: 'T',
ACG: 'T',
ACT: 'T',
AAC: 'N',
AAT: 'N',
AAA: 'K',
AAG: 'K',
AGC: 'S',
AGT: 'S',
AGA: 'R',
AGG: 'R',
GTA: 'V',
GTC: 'V',
GTG: 'V',
GTT: 'V',
GCA: 'A',
GCC: 'A',
GCG: 'A',
GCT: 'A',
GAC: 'D',
GAT: 'D',
GAA: 'E',
GAG: 'E',
GGA: 'G',
GGC: 'G',
GGG: 'G',
GGT: 'G',
}

/**
* take CodonTable above and generate larger codon table that includes
* all permutations of upper and lower case nucleotides
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function generateCodonTable(table: any) {
const tempCodonTable: { [key: string]: string } = {}
Object.keys(table).forEach(codon => {
const aa = table[codon]
const nucs: string[][] = []
for (let i = 0; i < 3; i++) {
const nuc = codon.charAt(i)
nucs[i] = []
nucs[i][0] = nuc.toUpperCase()
nucs[i][1] = nuc.toLowerCase()
}
for (let i = 0; i < 2; i++) {
const n0 = nucs[0][i]
for (let j = 0; j < 2; j++) {
const n1 = nucs[1][j]
for (let k = 0; k < 2; k++) {
const n2 = nucs[2][k]
const triplet = n0 + n1 + n2
tempCodonTable[triplet] = aa
}
}
}
})
return tempCodonTable
}
Loading

0 comments on commit e7740e1

Please sign in to comment.