Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explore page #286

Draft
wants to merge 12 commits into
base: staging
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api_routes/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const services = require('./services');
const external_apis = require('./external');

const samples = JSON.parse(fs.readFileSync(path.join(__dirname, './sample-query-cache.json')));
const drugDiseasePairs = JSON.parse(fs.readFileSync(path.join(__dirname, '../data/drug-disease-mappings-subset.json'))).slice(0, 1000);

router.use('/', external_apis);

Expand Down Expand Up @@ -58,6 +59,11 @@ router.route('/quick_answer')
}
});

router.route('/explore')
.post(async (req, res) => {
res.status(200).send(drugDiseasePairs);
});

router.route('/answer')
.post(async (req, res) => {
const { questionId, ara } = req.query;
Expand Down
1 change: 1 addition & 0 deletions data/drug-disease-mappings-subset.json

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions scripts/process-drug-disease-dataset.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable no-restricted-syntax */
const fs = require('fs');
const path = require('path');

const input = '../data/raw/DrugtoDiseasePrediction-subset.json';
const output = '../data/drug-disease-mappings-subset.json';
const lowerScoreLimit = 0.5;
const prettyPrintJson = false;

const rawMaps = JSON.parse(
fs.readFileSync(path.resolve(__dirname, input), 'utf-8'),
);

/**
* @typedef {{
* id: string,
* name: string,
* }} Node
*
* @typedef {{
* drug: Node,
* disease: Node,
* score: number,
* known: boolean,
* }} DrugDiseaseMapping
*
* @type {DrugDiseaseMapping[]}
*/
const arrayOfMappings = Object.entries(rawMaps.Score)
.filter(([, score]) => score >= lowerScoreLimit)
.sort((a, b) => b[1] - a[1])
.map(([id, score]) => ({
drug: {
id: rawMaps.DrugID[id],
name: rawMaps.DrugName[id],
},
disease: {
id: rawMaps.DiseaseID[id],
name: rawMaps.DiseaseName[id],
},
score,
known: rawMaps.Known[id] === '1',
}));

fs.writeFileSync(
path.resolve(__dirname, output),
JSON.stringify(arrayOfMappings, null, prettyPrintJson ? 2 : 0),
);
16 changes: 16 additions & 0 deletions src/API/explorePage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import utils from './utils';
import { api } from './baseUrlProxy';

const routes = {
async getDrugChemicalPairs() {
let response;
try {
response = await api.post('/api/explore');
} catch (error) {
return utils.handleAxiosError(error);
}
return response.data;
},
};

export default routes;
4 changes: 4 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Guide from '~/pages/Guide';
import Tutorial from '~/pages/Tutorial';
import TermsofService from '~/pages/TermsofService';
import QueryBuilder from '~/pages/queryBuilder/QueryBuilder';
import Explore from '~/pages/explore/Explore';
import Answer from '~/pages/answer/Answer';

import QuestionList from '~/pages/questionList/QuestionList';
Expand Down Expand Up @@ -72,6 +73,9 @@ export default function App() {
<Route path="/about">
<About />
</Route>
<Route path="/explore">
<Explore />
</Route>
<Route path="/guide">
<Guide />
</Route>
Expand Down
1 change: 1 addition & 0 deletions src/components/header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default function Header() {
<MuiLink href="/" style={{ cursor: 'pointer', margin: 0 }}><Logo height="48px" width="100%" style={{ paddingTop: '6px' }} /></MuiLink>
<div className="grow" />
<Link to="/">Question Builder</Link>
<Link to="/explore">Explore</Link>
<Link to="/about">About</Link>
<Link to="/guide">Guide</Link>
<Link to="/tutorial">Tutorial</Link>
Expand Down
179 changes: 179 additions & 0 deletions src/pages/explore/DrugDiseasePairs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { Button, makeStyles } from '@material-ui/core';
import { ArrowRight } from '@material-ui/icons';
import React from 'react';
import {
Grid, Row, Col,
} from 'react-bootstrap';
import { useHistory, Link } from 'react-router-dom';
import QueryBuilderContext from '~/context/queryBuilder';
import useQueryBuilder from '../queryBuilder/useQueryBuilder';
import explorePage from '~/API/explorePage';

const useStyles = makeStyles({
hover: {
'& .MuiButtonBase-root': {
visibility: 'hidden',
},
'&:hover .MuiButtonBase-root': {
visibility: 'visible',
},
},
});

const fetchPairs = explorePage.getDrugChemicalPairs;

export default function DrugDiseasePairs() {
const [pairs, setPairs] = React.useState([]);
const [isLoading, setIsLoading] = React.useState(true);

// eslint-disable-next-line no-unused-vars
const [error, setError] = React.useState(null);

const queryBuilder = useQueryBuilder(QueryBuilderContext);
const history = useHistory();

const handleStartQuery = (pair) => {
const query = {
message: {
query_graph: {
nodes: {
n0: {
name: pair.disease.name,
ids: [pair.disease.id],
},
n1: {
name: pair.drug.name,
ids: [pair.drug.id],
},
},
edges: {
e0: {
subject: 'n0',
object: 'n1',
predicates: [
'biolink:related_to',
],
},
},
},
},
};

queryBuilder.dispatch({ type: 'saveGraph', payload: query });
history.push('/');
};

const classes = useStyles();

React.useEffect(() => {
let ignore = false;

(async () => {
try {
const data = await fetchPairs();

if (ignore) return;

setPairs(data);
setIsLoading(false);
} catch (e) {
setError(e.message);
setIsLoading(false);
}
})();

return () => {
ignore = true;
};
}, []);

return (
<Grid style={{ marginBottom: '50px', marginTop: '50px' }}>
<Row>
<Col md={12}>
<small><Link to="/explore">← View all datasets</Link></small>
<h1>Drug - Disease Pairs</h1>
<p style={{ fontSize: '1.6rem' }}>
These drug-disease pairs were generated using a machine learning model to align with the nodes
in the ROBOKOP knowledge graph. They highlight potential associations between various drugs and
a broad range of diseases, suggesting possible avenues for further research. These connections
can serve as a starting point for a new query by hovering over a pair and clicking &ldquo;Start a Query&rdquo;.
</p>

<p style={{ fontSize: '1.6rem' }}>
Scores with an asterisk and underline means the drug-disease pair is already known. The score is still
predicted using the trained model.
</p>

<hr />

{isLoading ? 'Loading...' : (
<table style={{ fontSize: '1.6rem', width: '100%' }}>
<thead>
<tr style={{ borderBottom: '1px solid #eee' }}>
<th style={{ paddingBottom: '1rem' }}>
<h4 style={{ textTransform: 'uppercase' }}>Disease</h4>
<input placeholder="Search diseases" />
</th>
<th style={{ paddingBottom: '1rem' }}>
<h4 style={{ textTransform: 'uppercase' }}>Drug</h4>
<input placeholder="Search drugs" />
</th>
<th style={{ paddingBottom: '1rem' }}>
<h4 style={{ textTransform: 'uppercase' }}>Score</h4>
</th>
</tr>
</thead>
<tbody>
{
pairs.map((pair, i) => (
<tr className={classes.hover} key={i}>
<td>
{pair.disease.name}
<Chip>{pair.disease.id}</Chip>
</td>
<td>
{pair.drug.name}
<Chip>{pair.drug.id}</Chip>
</td>
<td>
{
pair.known ? (
<span style={{ textDecoration: 'underline' }}>
{pair.score.toFixed(6)}*
</span>
) : (
pair.score.toFixed(6)
)
}
</td>
<Button
variant="contained"
color="primary"
endIcon={<ArrowRight />}
onClick={() => handleStartQuery(pair)}
>
Start a query
</Button>
</tr>
))
}
</tbody>
</table>
)}
</Col>
</Row>
</Grid>
);
}

function Chip({ children }) {
return (
<span style={{
fontSize: '1.1rem', backgroundColor: '#e9e9e9', borderRadius: '4px', padding: '2px 4px', marginLeft: '1ch',
}}
>
{children}
</span>
);
}
52 changes: 52 additions & 0 deletions src/pages/explore/Explore.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import {
Grid, Row, Col,
} from 'react-bootstrap';
import {
Switch, Route, Link, useRouteMatch,
} from 'react-router-dom';
import DrugChemicalPairs from './DrugDiseasePairs';

export default function Explore() {
const match = useRouteMatch();

return (
<Switch>
<Route path={`${match.path}/drug-chemical`}>
<DrugChemicalPairs />
</Route>
<Route path={match.path}>
<Index />
</Route>
</Switch>
);
}

function Index() {
const match = useRouteMatch();

return (
<Grid style={{ marginBottom: '50px', marginTop: '50px' }}>
<Row>
<Col md={12}>
<h1>Explore</h1>
<p style={{ fontSize: '1.6rem' }}>
Click a link below to view a curated dataset that can be further explored in the ROBOKOP query builder or answer explorer.
</p>

<hr />

<Link to={`${match.url}/drug-chemical`} style={{ fontSize: '1.6rem' }}>
Drug to Disease Pairs
</Link>
<p style={{ fontSize: '1.6rem', marginTop: '0.5rem' }}>
These drug-disease pairs were generated using a machine learning model to align with the nodes
in the ROBOKOP knowledge graph. They highlight potential associations between various drugs and
a broad range of diseases, suggesting possible avenues for further research. These connections
can serve as a starting point for a new query by hovering over a pair and clicking &ldquo;Start a Query&rdquo;.
</p>
</Col>
</Row>
</Grid>
);
}
Loading