Skip to content

Commit

Permalink
Improve the proteinInfoPanel for multiple species.
Browse files Browse the repository at this point in the history
  • Loading branch information
yjcyxky committed Mar 24, 2024
1 parent 7c52b9f commit ebb8955
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 91 deletions.
3 changes: 3 additions & 0 deletions studio/src/NodeInfoPanel/AlignmentViewer/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.alignment-viewer-container > div {
width: 100%;
}
7 changes: 4 additions & 3 deletions studio/src/NodeInfoPanel/AlignmentViewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ import { Row } from 'antd';
// @ts-ignore
import { AlignmentViewer as ReactAlignmentViewer } from 'react-alignment-viewer';
import type { AlignmentData } from '../index.t';
import './index.less';

type AlignmentViewerProps = {
data: AlignmentData[];
};

function transformDataForAlignmentViewer(dataArray: AlignmentData[]): string[] {
return dataArray.map(item => `>${item.species}|${item.geneSymbol}|${item.entrezgene}\n${item.sequence}`);
return dataArray.map(item => `>sp|${item.uniProtId}|${item.proteinName} ${item.proteinDescription} OS=${item.species} GN=${item.geneSymbol} PE=${item.score} SV=${item.sequenceVersion}\n${item.sequence}`);
}

const AlignmentViewer: React.FC<AlignmentViewerProps> = (props) => {
const [dataset, setDataset] = React.useState<string[]>(transformDataForAlignmentViewer(props.data));

return <Row className="alignment-viewer-container" style={{ width: '100%', height: '100%' }}>
<ReactAlignmentViewer data={dataset} />
return <Row className="alignment-viewer-container" style={{ marginTop: '20px', width: '100%', height: '100%' }}>
<ReactAlignmentViewer data={dataset.join('\n')} />
</Row>
}

Expand Down
2 changes: 1 addition & 1 deletion studio/src/NodeInfoPanel/ComposedProteinPanel/index.less
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.composed-protein-panel {
.ant-tabs-tab {
text-align: left;
width: 80px;
width: 90px;
padding: 8px 0 !important;
}
}
93 changes: 56 additions & 37 deletions studio/src/NodeInfoPanel/ComposedProteinPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ type ComposedProteinPanel = {

const fetchProteinInfoByGeneInfo = async (geneInfo: GeneInfo): Promise<UniProtEntry> => {
const uniprotId = geneInfo.uniprot ? geneInfo.uniprot['Swiss-Prot'] : null;
if (!uniprotId) {
const uniprotIds = geneInfo.uniprot ? (geneInfo.uniprot.TrEMBL || []) : [];
if (!uniprotId && uniprotIds.length === 0) {
return {} as UniProtEntry;
}

return fetchProteinInfo(uniprotId);
if (uniprotId) {
return fetchProteinInfo(uniprotId);
} else {
return fetchProteinInfo(uniprotIds[0]);
}
}

const ComposedProteinPanel: React.FC<ComposedProteinPanel> = (props) => {
Expand All @@ -37,6 +42,8 @@ const ComposedProteinPanel: React.FC<ComposedProteinPanel> = (props) => {
}>>({});

useEffect(() => {
// Get the geneInfo and proteinInfo from the mygene.info and uniprot API.
// If possible, we would like to get all the geneInfo and proteinInfo from the homologene.
const init = async () => {
if (!geneInfo) {
return;
Expand All @@ -60,6 +67,8 @@ const ComposedProteinPanel: React.FC<ComposedProteinPanel> = (props) => {
return isExpectedSpecies(`${taxid}`)
})

console.log("All expected species and genes: ", remaingGenes);

const geneInfos = remaingGenes.map(([taxid, entrezgene]) => {
if (taxid === geneInfo.taxid) {
return geneInfo;
Expand All @@ -80,40 +89,42 @@ const ComposedProteinPanel: React.FC<ComposedProteinPanel> = (props) => {
return expectedOrder.indexOf(a.taxid.toString()) - expectedOrder.indexOf(b.taxid.toString());
});
setAllGeneInfos(orderedGeneInfos);
}).catch((error) => {
console.error(error);
setAllGeneInfos([]);
});

const proteinInfos = remaingGenes.map(([taxid, entrezgene]) => {
if (taxid === geneInfo.taxid) {
return proteinInfo;
}
return fetchProteinInfoByGeneInfo(geneInfo);
});

Promise.all(proteinInfos).then((proteinInfos) => {
const oProteinInfos = proteinInfos.map((proteinInfo, index) => {
return proteinInfo;
});

const proteinInfoMap: Record<string, {
proteinInfo: UniProtEntry;
geneInfo: GeneInfo;
}> = {};
oProteinInfos.forEach((proteinInfo, index) => {
const genePair = remaingGenes[index];
const taxid = genePair[0].toString();
const geneInfo = allGeneInfos.find((geneInfo) => geneInfo.taxid.toString() === taxid);
proteinInfoMap[taxid] = {
proteinInfo,
geneInfo: geneInfo?.geneInfo || {} as GeneInfo
const proteinInfos = remaingGenes.map(([taxid, entrezgene]) => {
if (taxid === geneInfo.taxid) {
return proteinInfo;
}
const gInfo = orderedGeneInfos.find((geneInfo) => geneInfo.taxid === taxid)?.geneInfo || {} as GeneInfo;
return fetchProteinInfoByGeneInfo(gInfo);
});

setAllProteinInfos(proteinInfoMap);
Promise.all(proteinInfos).then((proteinInfos) => {
const oProteinInfos = proteinInfos.map((proteinInfo, index) => {
return proteinInfo;
});

const proteinInfoMap: Record<string, {
proteinInfo: UniProtEntry;
geneInfo: GeneInfo;
}> = {};
oProteinInfos.forEach((proteinInfo, index) => {
const genePair = remaingGenes[index];
const taxid = genePair[0].toString();
const geneInfo = orderedGeneInfos.find((geneInfo) => geneInfo.taxid.toString() === taxid);
proteinInfoMap[taxid] = {
proteinInfo,
// TODO: It might cause that we get the wrong fasta data at the AlignmentViewer.
geneInfo: geneInfo?.geneInfo || {} as GeneInfo
}
});

setAllProteinInfos(proteinInfoMap);
}).catch((error) => {
console.error(error);
});
}).catch((error) => {
console.error(error);
setAllGeneInfos([]);
});
}
}
Expand All @@ -136,14 +147,22 @@ const ComposedProteinPanel: React.FC<ComposedProteinPanel> = (props) => {
if (oItems.length === 0) {
return;
} else {
const alignmentData = Object.keys(allProteinInfos).map((taxid) => {
let alignmentData: any[] = [];
Object.keys(allProteinInfos).forEach((taxid) => {
const proteinInfo = allProteinInfos[taxid].proteinInfo;
const geneInfo = allProteinInfos[taxid].geneInfo;
return {
sequence: proteinInfo.sequence.value,
species: guessSpecies(taxid),
geneSymbol: geneInfo.symbol,
entrezgene: geneInfo.entrezgene
if (proteinInfo?.sequence?.value) {
alignmentData.push({
sequenceVersion: proteinInfo.entryAudit?.sequenceVersion || 0,
score: proteinInfo.annotationScore || 0,
proteinName: proteinInfo.uniProtkbId,
proteinDescription: proteinInfo.proteinDescription?.recommendedName?.fullName?.value || '',
uniProtId: proteinInfo.primaryAccession,
sequence: proteinInfo.sequence.value,
species: guessSpecies(taxid),
geneSymbol: geneInfo.symbol,
entrezgene: geneInfo.entrezgene
})
}
});
oItems.push({
Expand All @@ -154,7 +173,7 @@ const ComposedProteinPanel: React.FC<ComposedProteinPanel> = (props) => {
}

setItems(oItems);
}, [allGeneInfos]);
}, [allGeneInfos, allProteinInfos]);

return (items.length === 0 ?
<Empty description="No information available." /> :
Expand Down
60 changes: 32 additions & 28 deletions studio/src/NodeInfoPanel/ProteinInfoPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isProteinCoding, fetchProteinInfo } from "./utils";
import type { DescriptionsProps } from 'antd';
import { MolStarViewer } from "..";
import { guessSpecies } from "@/components/util";
import { isEmpty } from "lodash";

import './index.less';

Expand All @@ -30,9 +31,12 @@ function PubMedLinks(text: string) {
}

export const getBiologyBackground = (proteinInfo: UniProtEntry): React.ReactNode => {
const background = proteinInfo.comments.filter((comment) => comment.commentType === 'FUNCTION');
if (background.length === 0) {
return <Empty description="No biology background found" />;
const background = proteinInfo?.comments?.filter((comment) => comment.commentType === 'FUNCTION');
if (!background || (background && background.length === 0)) {
return <div>
<h2>Biology Background</h2>
<Empty description="No biology background found" style={{ minHeight: '200px' }} />
</div>
} else {
return (
<div>
Expand Down Expand Up @@ -85,6 +89,7 @@ export const PdbInfo: React.FC<{ proteinInfo: UniProtEntry }> = ({ proteinInfo }
};
});
setPdbData(pdbData);
setCurrentPdbId(pdbData[0]?.id);

const alphafoldData = proteinInfo.uniProtKBCrossReferences.filter(
(ref) => ref.database === 'AlphaFoldDB'
Expand All @@ -101,12 +106,12 @@ export const PdbInfo: React.FC<{ proteinInfo: UniProtEntry }> = ({ proteinInfo }
};
});

setPdbData([...pdbData, ...alphafoldPdbData]);
const oPdbData = [...pdbData, ...alphafoldPdbData];
setPdbData(oPdbData);
setCurrentPdbId(oPdbData[0]?.id);
}).catch((err) => {
console.error(err);
});

setCurrentPdbId(pdbData[0]?.id);
}, [proteinInfo]);

return (
Expand Down Expand Up @@ -210,28 +215,27 @@ export const ProteinInfoPanel: React.FC<ProteinInfoPanelProps> = (props) => {
}, [geneInfo]);

return (
Object.keys(proteinInfo).length === 0 ? <Empty description="No protein info found" /> :
<Row className="protein-info-panel">
<Col className="general-information">
{/* @ts-ignore */}
<Descriptions title="General Information" bordered items={generalInfo} column={2} />
</Col>
<Col className="biology-background">
{proteinInfo ? (
getBiologyBackground(proteinInfo)
) : (
<Empty description="No protein info found" />
)}
</Col>
<Col className="protein-snp">

</Col>
<Col className="protein-structure">
<h2>Sequence</h2>
<p>{proteinInfo.sequence.value}</p>
<PdbInfo proteinInfo={proteinInfo} />
</Col>
</Row>
<Row className="protein-info-panel">
<Col className="general-information">
{/* @ts-ignore */}
<Descriptions title="General Information" bordered items={generalInfo} column={2} />
</Col>
<Col className="biology-background">
{getBiologyBackground(proteinInfo)}
</Col>
<Col className="protein-snp">

</Col>
<Col className="protein-structure">
<h2>Sequence</h2>
{isEmpty(proteinInfo) ? <Empty description="No reviewed sequence found" /> :
<>
<p>{proteinInfo.sequence.value}</p>
<PdbInfo proteinInfo={proteinInfo} />
</>
}
</Col>
</Row>
);
}

Expand Down
5 changes: 5 additions & 0 deletions studio/src/NodeInfoPanel/index.t.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ export interface CrossReferenceProperty {
}

export interface AlignmentData {
proteinDescription: string;
proteinName: string;
sequenceVersion: number;
score: number;
uniProtId: string;
sequence: string;
species: string;
geneSymbol: string;
Expand Down
2 changes: 1 addition & 1 deletion studio/src/NodeInfoPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const NodeInfoPanel: React.FC<{ node?: GraphNode, hiddenItems?: string[] }> = ({
return <Tabs
className="plugins4kg tabs-nav-right"
items={items && items.length > 0 ? items : defaultItems}
></Tabs>
/>
}

export default NodeInfoPanel;
8 changes: 6 additions & 2 deletions studio/src/components/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,18 @@ export const expectedOrder: string[] = [
'9606',
'10090',
'10116',
'9541'
'9541',
'9544',
'9598'
]

export const expectedSpecies: Record<string, string[]> = {
'9606': ['Human', 'Human'],
'10090': ['Mouse', 'Mouse'],
'10116': ['Rattus norvegicus', 'Rat'],
'9541': ['Macaca fascicularis', 'Macaca']
'9541': ['Macaca fascicularis', 'M.fascicularis'],
'9544': ['Macaca mulatta', 'M.mulatta'],
'9598': ['Chimpanzee', 'Chimpanzee']
}

export const guessSpecies = (taxid: string) => {
Expand Down
5 changes: 5 additions & 0 deletions studio/src/pages/KnowledgeTable/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
margin-right: 5px;
}
}

.ant-spin-nested-loading, .ant-spin-container {
width: 100%;
height: 100%;
}
}

.knowledge-drawer {
Expand Down
40 changes: 21 additions & 19 deletions studio/src/pages/KnowledgeTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { history } from 'umi';
import { Table, Row, Tag, Space, message, Popover, Button, Empty, Tooltip, Drawer } from 'antd';
import { Table, Row, Tag, Space, message, Popover, Button, Empty, Tooltip, Drawer, Spin } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { useAuth0 } from "@auth0/auth0-react";
Expand Down Expand Up @@ -491,24 +491,26 @@ const KnowledgeTable: React.FC = (props) => {
return record.relid || `${JSON.stringify(record)}`;
};

return (total === 0) ? (
<>
<Empty description={
<>
<p>No Knowledges for Your Query {nodeId ? `(${nodeId} ${nodeName ? nodeName : ''})` : ''}</p>
<Button type="primary" onClick={() => history.push('/')}>
Go Back to Home Page
</Button>
</>
}
style={{
height: '100%',
flexDirection: 'column',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}} />
</>
return (total == 0) ? (
<Row className='knowledge-table-container'>
<Spin spinning={loading}>
<Empty description={
<>
<p>No Knowledges for Your Query {nodeId ? `(${nodeId} ${nodeName ? nodeName : ''})` : ''}</p>
<Button type="primary" onClick={() => history.push('/')}>
Go Back to Home Page
</Button>
</>
}
style={{
height: '100%',
flexDirection: 'column',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}} />
</Spin>
</Row>
) : (
<Row className="knowledge-table-container">
<div className='button-container'>
Expand Down

0 comments on commit ebb8955

Please sign in to comment.