Skip to content

Commit

Permalink
Merge pull request #79 from ebi-uniprot/new-aa-scores
Browse files Browse the repository at this point in the history
New aa scores
  • Loading branch information
prabh-t authored May 14, 2024
2 parents 054d5e3 + 4cb896e commit fbd6f06
Show file tree
Hide file tree
Showing 19 changed files with 245 additions and 151 deletions.
26 changes: 24 additions & 2 deletions src/styles/search/_ImpactSearchResults.scss
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,13 @@

.esm1b-score-grad {
background-color: #460556;
background-image: linear-gradient(to right, #460556 , #218c8f, #f9e725);
background-image: linear-gradient(to right, #460556, #218c8f, #f9e725);
width: 8rem;
height: 1.5rem;
}
.esm1b-score-grad-std {
background-color: red;
background-image: linear-gradient(to right, blue, lightgray, red);
width: 8rem;
height: 1.5rem;
}
Expand Down Expand Up @@ -754,4 +760,20 @@
.aa-pred{
display: grid;
grid-template-columns: 35% 20% auto;
}
}

.info {
display: inline-block;
padding-left: 4px;
padding-right: 4px;
}
.info::before {
content: 'i';
font-style: italic;
vertical-align: super;
font-size: smaller;
}
.info:hover {
cursor: help;
}

14 changes: 11 additions & 3 deletions src/ui/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useState} from "react";
import React, {createContext, useState} from "react";
import {useNavigate, Route, Routes} from "react-router-dom";
import HomePage from "./pages/home/HomePage";
import SearchResultsPage from "./pages/search/SearchResultPage";
Expand All @@ -18,13 +18,19 @@ import DownloadPage from "./pages/download/DownloadPage";
import HelpPage from "./pages/help/HelpPage";
import {FormData, initialFormData} from "../types/FormData";

export const StdColorContext = createContext(true);

export default function App() {
const [stdColor, setStdColor] = useState(true);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState<FormData>(initialFormData);
const [page, setPage] = useState<Page>(firstPage(0));
const [searchResults, setSearchResults] = useState<MappingRecord[][][]>([]);
const navigate = useNavigate();
const toggleStdColor = () => {
setStdColor(stdColor ? false : true);
};

// MappingRecord 3d array -> [][][] list of mappings/genes/isoforms
// mappings : [
// ...
Expand Down Expand Up @@ -152,7 +158,7 @@ export default function App() {
.finally(() => setLoading(false));
}

return (
return (<StdColorContext.Provider value={stdColor}>
<Routes>
<Route
path={HOME}
Expand All @@ -172,14 +178,16 @@ export default function App() {
formData={formData}
fetchNextPage={fetchPage}
loading={loading}
toggleStdColor={toggleStdColor}
/>} />
<Route path={QUERY} element={<QueryPage />} />
<Route path={QUERY} element={<QueryPage toggleStdColor={toggleStdColor} />} />
<Route path={API_ERROR} element={<APIErrorPage />} />
<Route path={ABOUT} element={<AboutPage />} />
<Route path={RELEASE} element={<ReleasePage />} />
<Route path={CONTACT} element={<ContactPage />} />
<Route path={DOWNLOAD} element={<DownloadPage searchResults={searchResults}/>} />
<Route path={HELP} element={<HelpPage />} />
</Routes>
</StdColorContext.Provider>
);
}
10 changes: 10 additions & 0 deletions src/ui/components/common/Common.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function Info(props: {text?: string}) {
if (props.text === null)
return <></>
return <div className="info" title={props.text}></div>
}

export const pubmedRef = (id: number) => {
return <sup><a href={`http://www.ncbi.nlm.nih.gov/pubmed/${id}`} target="_blank"
rel="noreferrer" title={`Source: PubMed ID ${id}`}>ref</a></sup>
}
2 changes: 1 addition & 1 deletion src/ui/components/function/FunctionalDataRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function FunctionalDataRow(props: FunctionalDataRowProps) {
<td colSpan={TOTAL_COLS} className="expanded-row">
<div className="significances-groups">
<div className="column">
<h5><img src={ProteinIcon} className="click-icon" alt="protein icon" title="Functional information" /> Reference Function</h5>
<h5><img src={ProteinIcon} className="click-icon" alt="protein icon" title="Functional information" /> Functional information</h5>
<ResidueRegionTable functionalData={functionalData} record={record} />
<ProteinFunctionTable comments={functionalData.comments} />
<ProteinInformationTable apiData={functionalData} />
Expand Down
67 changes: 28 additions & 39 deletions src/ui/components/function/ResidueRegionTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {StringVoidFun} from "../../../constants/CommonTypes";
import {aminoAcid3to1Letter, formatRange, getKeyValue} from "../../../utills/Util";
import {FunctionalResponse, Pocket, Foldx, P2PInteraction, ProteinFeature} from "../../../types/FunctionalResponse";
import {MappingRecord} from "../../../utills/Convertor";
import {Prediction} from "./prediction/Prediction";
import {Prediction, PUBMED_ID} from "./prediction/Prediction";
import {pubmedRef} from "../common/Common";

interface ResidueRegionTableProps {
functionalData: FunctionalResponse
Expand Down Expand Up @@ -42,8 +43,8 @@ function ResidueRegionTable(props: ResidueRegionTableProps) {
<th>Region Containing Variant Position</th>
</tr>
<tr>
<td>{getResidues(residues, props.record, props.functionalData.foldxs, oneLetterVariantAA, expendedRowKey, toggleRow)}</td>
<td>{getRegions(regions, props.functionalData.accession, props.functionalData.pockets, props.functionalData.interactions, expendedRowKey, toggleRow)}</td>
<td style={{verticalAlign: 'top' }}>{getResidues(residues, props.record, props.functionalData.foldxs, oneLetterVariantAA, expendedRowKey, toggleRow)}</td>
<td style={{verticalAlign: 'top' }}>{getRegions(regions, props.functionalData.accession, props.functionalData.pockets, props.functionalData.interactions, expendedRowKey, toggleRow)}</td>
</tr>
</tbody>
</table>
Expand All @@ -52,14 +53,16 @@ function ResidueRegionTable(props: ResidueRegionTableProps) {
}

function getResidues(regions: Array<ProteinFeature>, record: MappingRecord, foldxs: Array<Foldx>, oneLetterVariantAA: string | null, expendedRowKey: string, toggleRow: StringVoidFun) {
let counter = 0;
let foldxs_ = oneLetterVariantAA ? foldxs.filter(foldx => foldx.mutatedType.toLowerCase() === oneLetterVariantAA) : foldxs
return <>
<b>Annotations from UniProt</b>
{regions.length === 0 && <div>
No functional data for the variant position
</div>
}
{
regions.forEach((region) => {
counter = counter + 1;
let key = 'residue-' + counter;
return getFeatureList(region, key, expendedRowKey, toggleRow);
regions.map((region, idx) => {
return getFeatureList(region, `residue-${idx}`, expendedRowKey, toggleRow);
})
}
<AminoAcidModel refAA={record.refAA!} variantAA={record.variantAA!}/>
Expand All @@ -68,40 +71,26 @@ function getResidues(regions: Array<ProteinFeature>, record: MappingRecord, fold
}

function getRegions(regions: Array<ProteinFeature>, accession: string, pockets: Array<Pocket>, interactions: Array<P2PInteraction>, expendedRowKey: string, toggleRow: StringVoidFun) {
let regionsList: Array<JSX.Element> = [];
let counter = 0;

if (regions.length === 0) {
return (<>
<label style={{textAlign: 'center', fontWeight: 'bold'}}>
No functional data for the region
</label>
<br/><br/>
<b>Predictions</b>
<br/>(Source: PubMed ID <a href="https://pubmed.ncbi.nlm.nih.gov/36690744" target="_blank"
rel="noreferrer">15980494</a>)<br/>
<Pockets pockets={pockets} expendedRowKey={expendedRowKey} toggleRow={toggleRow}/>
<Interfaces accession={accession} interactions={interactions} expendedRowKey={expendedRowKey}
toggleRow={toggleRow}/>
</>);
}
regions.forEach((region) => {
counter = counter + 1;
let key = 'region-' + counter;
var list = getFeatureList(region, key, expendedRowKey, toggleRow);
regionsList.push(list);
});
return <>
<b>Curated observations from UniProt</b>
{regionsList}
<br/><br/>
<b>Predictions</b>
<br/>(Source: PubMed ID <a href="https://pubmed.ncbi.nlm.nih.gov/36690744" target="_blank"
rel="noreferrer">15980494</a>)<br/>
return (<>
<b>Annotations from UniProt</b>
{regions.length === 0 && <div>
No functional data for the region
</div>
}
{
regions.map((region, idx) => {
return getFeatureList(region, `region-${idx}`, expendedRowKey, toggleRow);
})
}
{
(pockets.length > 0 || interactions.length >0) && <>
<b>Structure predictions</b>{pubmedRef(PUBMED_ID.INTERFACES)}
</>
}
<Pockets pockets={pockets} expendedRowKey={expendedRowKey} toggleRow={toggleRow}/>
<Interfaces accession={accession} interactions={interactions} expendedRowKey={expendedRowKey}
toggleRow={toggleRow}/>
</>
</>);
}

function getFeatureList(feature: ProteinFeature, key: string, expendedRowKey: string, toggleRow: StringVoidFun) {
Expand Down
24 changes: 13 additions & 11 deletions src/ui/components/function/prediction/AlphaMissensePred.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import {AMScore} from "../../../../types/MappingResponse";
import {getPredRef, PredAttr, PUBMED_ID} from "./Prediction";
import {PredAttr, PUBMED_ID} from "./Prediction";
import Spaces from "../../../elements/Spaces";
import {STD_BENIGN_COLOR, STD_PATHOGENIC_COLOR, STD_UNCERTAIN_COLOR} from "./PredConstants";
import {pubmedRef} from "../../common/Common";

export const AM_SCORE_ATTR: {[key: string]: PredAttr} = {
PATHOGENIC: { color: '#ed1e24', title: 'pathogenic' },
AMBIGUOUS: { color: '#a8a9ad', title: 'ambiguous' },
BENIGN: { color: '#3853a4', title: 'benign' }
BENIGN: { color: '#3853a4', stdColor: STD_BENIGN_COLOR, title: 'benign' },
AMBIGUOUS: { color: '#a8a9ad', stdColor: STD_UNCERTAIN_COLOR, title: 'ambiguous' },
PATHOGENIC: { color: '#ed1e24', stdColor: STD_PATHOGENIC_COLOR, title: 'pathogenic' }
}

export const AlphaMissensePred = (props: { am?: AMScore }) => {
export const AlphaMissensePred = (props: { am?: AMScore, stdColor: boolean }) => {
if (props.am === undefined || props.am === null) {
return <></>
}
return <div className="aa-pred">
<div>AlphaMissense {getPredRef(PUBMED_ID.AM)}</div>
<div>AlphaMissense {pubmedRef(PUBMED_ID.AM)}</div>
<div>{props.am.amPathogenicity}</div>
<div><AlphaMissensePredIcon amScore={props.am} /></div>
<div><AlphaMissensePredIcon {...props} /></div>
</div>
}

function AlphaMissensePredIcon(props: { amScore?: AMScore }) {
if (props.amScore) {
let cls = props.amScore.amClass as keyof PredAttr
function AlphaMissensePredIcon(props: { am?: AMScore, stdColor: boolean }) {
if (props.am) {
let cls = props.am.amClass as keyof PredAttr
return <>
<i className="bi bi-circle-fill" style={{color: AM_SCORE_ATTR[cls].color}}></i>
<i className="bi bi-circle-fill" style={{color: (props.stdColor ? AM_SCORE_ATTR[cls].stdColor : AM_SCORE_ATTR[cls].color)}}></i>
<Spaces/>{AM_SCORE_ATTR[cls].title}
</>
}
Expand Down
24 changes: 14 additions & 10 deletions src/ui/components/function/prediction/ConservPred.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import tinygradient from "tinygradient";
import {ConservScore} from "../../../../types/MappingResponse";
import {getPredRef, PredAttr, PUBMED_ID} from "./Prediction";
import {PredAttr, PUBMED_ID} from "./Prediction";
import Spaces from "../../../elements/Spaces";
import {Info, pubmedRef} from "../../common/Common";

export const CONSERV_SCORE_ATTR: PredAttr[] = [
{color: '#732faf', title: 'very low' },
{color: '#194888', title: 'low' },
{color: '#277777', title: 'fairly low' },
{color: '#72cb5d', title: 'moderate' },
{color: '#bab518', title: 'fairly high' },
{color: '#c46307', title: 'high' },
{color: '#9d0101', title: 'very high' }
{color: '#732faf', stdColor: '', title: 'very low' },
{color: '#194888', stdColor: '', title: 'low' },
{color: '#277777', stdColor: '', title: 'fairly low' },
{color: '#72cb5d', stdColor: '', title: 'moderate' },
{color: '#bab518', stdColor: '', title: 'fairly high' },
{color: '#c46307', stdColor: '', title: 'high' },
{color: '#9d0101', stdColor: '', title: 'very high' }
]

export const CONSERV_COLOUR_GRADIENT = tinygradient(CONSERV_SCORE_ATTR.map(s => s.color));
Expand All @@ -20,8 +21,10 @@ export const ConservPred = (props: { conserv?: ConservScore }) => {
return <></>
}
return <div className="aa-pred">
<div>Conservation {getPredRef(PUBMED_ID.CONSERV)}</div>
<div>{props.conserv.score}</div>
<div>Conservation {pubmedRef(PUBMED_ID.CONSERV)}</div>
<div>{props.conserv.score}
<Info text="Inter species conservation based on UniRef90 alignments using the ScoreCons algorithm. 0=no conservation across alignment, 1=total conservation." />
</div>
<div><ConservPredIcon conservScore={props.conserv}/></div>
</div>
}
Expand All @@ -33,6 +36,7 @@ function ConservPredIcon(props: { conservScore?: ConservScore }) {
return <>
<i className="bi bi-circle-fill" style={{color: colorAtPos }}></i>
<Spaces/>{scoreAttr.title}
<Info text="High conservation = 0.85-1.0" />
</>
}
return <></>
Expand Down
53 changes: 34 additions & 19 deletions src/ui/components/function/prediction/EsmPred.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,58 @@
import tinygradient from "tinygradient";
import {ESMScore} from "../../../../types/MappingResponse";
import {getPredRef, PredAttr, PUBMED_ID} from "./Prediction";
import {
PredAttr,
PUBMED_ID
} from "./Prediction";
import Spaces from "../../../elements/Spaces";
import {STD_BENIGN_COLOR, STD_COLOR_GRADIENT, STD_PATHOGENIC_COLOR, STD_UNCERTAIN_COLOR} from "./PredConstants";
import {Info, pubmedRef} from "../../common/Common";

// James: A score of -7.5 appears to be used for pathogenicity in ESM1b. I am not sure we can say that 0--7.5 is benign though.
// Perhaps just leave those blank for now and just label the ones -7.5 and below.
// likely pathogenic (yellow) -25 <------> 0 likely benign (blue)
export const ESM_SCORE_ATTR: PredAttr[] = [
{color: '#460556', title: 'pathogenic' },
{color: '#218c8f', title: '' },
{color: '#f9e725', title: '' } // maybe benign
{color: '#460556', stdColor: STD_BENIGN_COLOR , title: 'benign', info: '-5 to 0 likely benign' },
{color: '#218c8f', stdColor: STD_UNCERTAIN_COLOR, title: 'uncertain', info: '-10 to -5 uncertain significance' },
{color: '#f9e725', stdColor: STD_PATHOGENIC_COLOR, title: 'pathogenic', info: '-25 to -10 likely pathogenic' }
]

export const ESM_MAX_SCORE = -25
export const ESM_PATHOGENIC_SCORE = -7.5

export const ESM_COLOUR_GRADIENT = tinygradient(ESM_SCORE_ATTR.map(s => s.color));
export const ESM_COLOR_GRADIENT = tinygradient(ESM_SCORE_ATTR.map(s => s.color));

export const EsmPred = (props: { esm?: ESMScore }) => {
export const EsmPred = (props: { esm?: ESMScore, stdColor: boolean }) => {
if (props.esm === undefined || props.esm === null) {
return <></>
}
return <div className="aa-pred">
<div>ESM-1b {getPredRef(PUBMED_ID.ESM)}</div>
<div>ESM-1b {pubmedRef(PUBMED_ID.ESM)}</div>
<div>{props.esm.score}</div>
<div><EsmPredIcon esmScore={props.esm}/></div>
<div><EsmPredIcon {...props}/></div>
</div>
}

function EsmPredIcon(props: {esmScore?: ESMScore}) {
if (props.esmScore) {
let title = '';
if (props.esmScore.score < ESM_PATHOGENIC_SCORE) {
title = ESM_SCORE_ATTR[0].title
}
const colorAtPos = ESM_COLOUR_GRADIENT.rgbAt(props.esmScore.score/ ESM_MAX_SCORE).toHexString()
function EsmPredIcon(props: {esm?: ESMScore, stdColor: boolean }) {
if (props.esm) {
const colorGrad = props.stdColor ? STD_COLOR_GRADIENT : ESM_COLOR_GRADIENT;
const colorAtPos = colorGrad.rgbAt(props.esm.score/ ESM_MAX_SCORE).toHexString()
const esmAttr = esmScoreAttr(props.esm.score)
return <>
<i className="bi bi-circle-fill" style={{color: colorAtPos}}></i>
<Spaces/>{title}
<Spaces/>{esmAttr && <>
{esmAttr.title}
<Info text={esmAttr.info}/>
</>}
</>
}
return <></>
}

function esmScoreAttr(score: number) {
if (score >= -5 && score < 0) {
return ESM_SCORE_ATTR[0]
} else if (score >= -10 && score < -5) {
return ESM_SCORE_ATTR[1]
} else if (score >= -25 && score < -10) {
return ESM_SCORE_ATTR[2]
}
return null
}
Loading

0 comments on commit fbd6f06

Please sign in to comment.