Skip to content

Commit

Permalink
Merge pull request #9 from c3g/feat/precomputed-peaks
Browse files Browse the repository at this point in the history
feat!: configurability + precomputed peaks
  • Loading branch information
davidlougheed authored Jul 6, 2023
2 parents 4052f6e + 2569c2b commit 6fa8f66
Show file tree
Hide file tree
Showing 53 changed files with 2,724 additions and 17,214 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/data/*
!/data/.gitkeep
!/data/otherData
!/data/ucsc.other-tracks.txt
/input-files/*.csv
!/input-files/flu-infection-gene-peaks.csv
/config.js
Expand Down
3 changes: 3 additions & 0 deletions app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ if (!process.env.VARWIG_DISABLE_AUTH) {
app.use('/api/assays', (await import('./routes/assays.mjs')).default);
app.use('/api/autocomplete', (await import('./routes/autocomplete.mjs')).default);
app.use('/api/messages', (await import('./routes/messages.mjs')).default);
app.use('/api/assembly', (await import('./routes/assembly.mjs')).default);
app.use('/api/overview', (await import('./routes/overview.mjs')).default);
app.use('/api/conditions', (await import('./routes/conditions.mjs')).default);
app.use('/api/ethnicities', (await import('./routes/ethnicities.mjs')).default);
app.use('/api/peaks', (await import('./routes/peaks.mjs')).default);
app.use('/api/sessions', (await import('./routes/sessions.mjs')).default);
app.use('/api/samples', (await import('./routes/samples.mjs')).default);
Expand Down
15,441 changes: 1,619 additions & 13,822 deletions client/package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "flu-infection-client",
"version": "0.12.2",
"version": "0.13.0",
"private": true,
"dependencies": {
"@redux-devtools/extension": "^3.2.5",
Expand All @@ -13,11 +13,11 @@
"d3-scale": "^4.0.2",
"font-awesome": "^4.7.0",
"memoize-one": "^6.0.0",
"rambda": "^7.5.0",
"rambda": "^8.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^8.0.5",
"react-router-dom": "^6.11.2",
"react-redux": "^8.1.1",
"react-router-dom": "^6.14.1",
"react-table": "^7.8.0",
"reactstrap": "^8.10.1",
"redux": "^4.2.1",
Expand Down
73 changes: 45 additions & 28 deletions client/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,38 @@ import axios from 'axios'

import * as api from './api'
import * as k from './constants/ActionTypes.js'
import {ASSEMBLY, BASE_URL} from "./constants/app";
import {BASE_URL} from "./constants/app";
import {constructUCSCUrl} from "./helpers/ucsc";

export const setChrom = createAction(k.SET_CHROM);
export const setPosition = createAction(k.SET_POSITION);
export const setOverviewChrom = createAction(k.SET_OVERVIEW_CHROM);
export const setOverviewAssay = createAction(k.SET_OVERVIEW_ASSAY);
export const handleError = createAction(k.HANDLE_ERROR);

export const assays = createFetchActions(k.ASSAYS);
export const samples = createFetchActions(k.SAMPLES);
export const chroms = createFetchActions(k.CHROMS);
export const positions = createFetchActions(k.POSITIONS);
export const values = createFetchActions(k.VALUES);
export const peaks = createFetchActions(k.PEAKS);
export const overviewConfig = createFetchActions(k.OVERVIEW_CONFIG);
export const manhattanData = createFetchActions(k.MANHATTAN_DATA);
export const user = createFetchActions(k.USER);
export const messages = createFetchActions(k.MESSAGES);
export const setChrom = createAction(k.SET_CHROM);
export const setPosition = createAction(k.SET_POSITION);
export const setOverviewChrom = createAction(k.SET_OVERVIEW_CHROM);
export const setOverviewAssay = createAction(k.SET_OVERVIEW_ASSAY);
export const setUsePrecomputed = createAction(k.SET_USE_PRECOMPUTED);
export const handleError = createAction(k.HANDLE_ERROR);

export const assays = createFetchActions(k.ASSAYS);
export const samples = createFetchActions(k.SAMPLES);
export const chroms = createFetchActions(k.CHROMS);
export const positions = createFetchActions(k.POSITIONS);
export const values = createFetchActions(k.VALUES);
export const peaks = createFetchActions(k.PEAKS);
export const assembly = createFetchActions(k.ASSEMBLY);
export const conditions = createFetchActions(k.CONDITIONS);
export const ethnicities = createFetchActions(k.ETHNICITIES);
export const overviewConfig = createFetchActions(k.OVERVIEW_CONFIG);
export const manhattanData = createFetchActions(k.MANHATTAN_DATA);
export const user = createFetchActions(k.USER);
export const messages = createFetchActions(k.MESSAGES);

export const fetchAssays = createFetchFunction(api.fetchAssays, assays);
export const fetchChroms = createFetchFunction(api.fetchChroms, chroms);
// export const fetchChroms = createFetchFunction(api.fetchChroms, chroms);
export const fetchPositions = createFetchFunction(api.fetchPositions, positions);
export const cacheValues = createFetchFunction(api.cacheValues, values);
// export const cacheValues = createFetchFunction(api.cacheValues, values);
export const fetchPeaks = createFetchFunction(api.fetchPeaks, peaks);
export const fetchAssembly = createFetchFunction(api.fetchAssembly, assembly);
export const fetchConditions = createFetchFunction(api.fetchConditions, conditions);
export const fetchEthnicities = createFetchFunction(api.fetchEthnicities, ethnicities);
export const fetchOverviewConfig = createFetchFunction(api.fetchOverviewConfig, overviewConfig);
export const fetchManhattanData = createFetchFunction(api.fetchManhattanData, manhattanData);
export const fetchUser = createFetchFunction(api.fetchUser, user);
Expand All @@ -44,32 +51,42 @@ export const doSearch = () => (dispatch, getState) => {
}
};

export const mergeTracks = peak => dispatch => {
const buildUCSCHighlight = (asm, chr, start, end, color) => `${asm}.${chr}:${start}-${end}${color}`;

export const mergeTracks = peak => (dispatch, getState) => {
const assembly = getState().assembly.data?.id;

if (!assembly) {
console.error(`Could not retrieve assembly ID - got ${assembly}`);
return;
}

const session = {...peak};
const {feature, snp} = session;

const padding = 500;

const featureChrom = `chr${session.feature.chrom}`;
const snpChrom = `chr${session.snp.chrom}`;
const featureChrom = `chr${feature.chrom}`;
const snpChrom = `chr${snp.chrom}`;

api.createSession(session)
.then(sessionID => {
const snpPosition = session.snp.position;
const snpPosition = snp.position;
const displayWindow = featureChrom === snpChrom
? [Math.min(session.feature.start, snpPosition), Math.max(session.feature.end, snpPosition)]
: [session.feature.start, session.feature.end];
? [Math.min(feature.start, snpPosition), Math.max(feature.end, snpPosition)]
: [feature.start, feature.end];
const position = `${featureChrom}:${displayWindow[0]-padding}-${displayWindow[1]+padding}`;
const hubURL = `${BASE_URL}/api/ucsc/hub/${sessionID}`;
const ucscURL = constructUCSCUrl([
["db", ASSEMBLY],
["db", assembly],
["hubClear", hubURL],
// ["hubClear", permaHubURL],
["position", position],

// Highlight the SNP in red, and the feature in light yellow
["highlight", [
`${ASSEMBLY}.${featureChrom}:${session.feature.start}-${session.feature.end}#FFEECC`,
`${ASSEMBLY}.${snpChrom}:${session.snp.position}-${session.snp.position+1}#FF9F9F`,
buildUCSCHighlight(assembly, featureChrom, feature.start, feature.end, "#FFEECC"),
buildUCSCHighlight(assembly, snpChrom, snp.position, snp.position + 1, "#FF9F9F"),
].join("|")],
]);

Expand Down
28 changes: 16 additions & 12 deletions client/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,24 @@ export function fetchPeaks(params) {
return get('/peaks/query', params)
}

export function fetchChroms() {
return get('/autocomplete/chroms')
.then(chroms => {
const parse = string => +string.slice(3)
chroms.sort((a, b) => parse(a) - parse(b))
return chroms
})
}
// export function fetchChroms() {
// return get('/autocomplete/chroms')
// .then(chroms => {
// const parse = string => +string.slice(3)
// chroms.sort((a, b) => parse(a) - parse(b))
// return chroms
// })
// }

export function fetchPositions(params, cancelToken) {
return get('/autocomplete/positions', params, {cancelToken})
}

export const fetchOverviewConfig = () => get('/overview/config');
export const fetchAssembly = () => get('/assembly');
export const fetchConditions = () => get('/conditions');
export const fetchEthnicities = () => get('/ethnicities');

export const fetchOverviewConfig = () => get('/overview/config');
export const fetchManhattanData = ({chrom, assay}) => get(`/overview/assays/${assay}/topBinned/${chrom}`);


Expand All @@ -46,14 +49,15 @@ export const fetchManhattanData = ({chrom, assay}) => get(`/overview/assays/${as
* @property {string} feature.chrom
* @property {number} feature.start
* @property {number} feature.end
* @property {boolean} usePrecomputed
*/

/**
* @param {ValuesOptions} params
*/
export function cacheValues(params) {
return post('/tracks/values', params)
}
// export function cacheValues(params) {
// return post(`/tracks/values?precomputed=${params.usePrecomputed ? '1' : '0'}`, params)
// }

export function createSession(params) {
return post('/sessions/create', params)
Expand Down
101 changes: 43 additions & 58 deletions client/src/components/App.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {Component, useEffect, useState} from 'react';
import React, {useCallback, useEffect, useState} from 'react';
import {Navigate, Outlet, Route, Routes, useLocation, useNavigate} from "react-router-dom";
import {useDispatch, useSelector} from "react-redux";

Expand All @@ -8,8 +8,6 @@ import PeakResults from './PeakResults'
import HelpModal from "./HelpModal";
import TermsModal from "./TermsModal";

import {ETHNICITY_BOX_COLOR} from "../constants/app";
import {saveUser} from "../actions";
import ContactModal from "./ContactModal";
import AboutPage from "./pages/AboutPage";
import ProtectedPageContainer from "./pages/ProtectedPageContainer";
Expand All @@ -18,6 +16,8 @@ import ExplorePage from "./pages/ExplorePage";
import DatasetsPage from "./pages/DatasetsPage";
import FAQPage from "./pages/FAQPage";

import {saveUser} from "../actions";


const RoutedApp = () => {
const location = useLocation();
Expand All @@ -33,21 +33,22 @@ const RoutedApp = () => {
const chrom = useSelector(state => state.ui.chrom);
const position = useSelector(state => state.ui.position);

const toggleHelp = () => setHelpModal(!helpModal);
const toggleContact = () => setContactModal(!contactModal);
const toggleTerms = () => setTermsModal(!termsModal);
const toggleHelp = useCallback(() => setHelpModal(!helpModal), [helpModal]);
const toggleContact = useCallback(() => setContactModal(!contactModal), [contactModal]);
const toggleTerms = useCallback(() => setTermsModal(!termsModal), [termsModal]);

const navigateAbout = () => navigate("/about");
const navigateDatasets = () => navigate("/datasets");
const navigateOverview = () => navigate("/overview"); // TODO: remember chrom and assay
const navigateExplore = () => {
const navigateAbout = useCallback(() => navigate("/about"), [navigate]);
const navigateDatasets = useCallback(() => navigate("/datasets"), [navigate]);
// TODO: remember chrom and assay:
const navigateOverview = useCallback(() => navigate("/overview"), [navigate]);
const navigateExplore = useCallback(() => {
if (location.pathname.startsWith("/explore")) return;
if (chrom && position) {
navigate(`/explore/locus/${chrom}/${position}`);
} else {
navigate("/explore");
}
}
}, [location.pathname, chrom, position, navigate]);
const navigateFAQ = () => navigate("/faq");

useEffect(() => {
Expand All @@ -61,17 +62,19 @@ const RoutedApp = () => {
}
}, [userData]);

const termsOnAgree = useCallback(institution => {
if (userData.isLoaded) {
dispatch(saveUser({consentedToTerms: true, institution}));
}
}, [userData, dispatch]);

return (
<div className="RoutedApp">
<TermsModal
isOpen={termsModal}
toggle={toggleTerms}
showAgree={userData.data && !userData.data.consentedToTerms}
onAgree={institution => {
if (userData.isLoaded) {
dispatch(saveUser({consentedToTerms: true, institution}));
}
}}
onAgree={termsOnAgree}
/>

<ContactModal isOpen={contactModal} toggle={toggleContact} />
Expand All @@ -93,48 +96,30 @@ const RoutedApp = () => {
};


class App extends Component {
render() {
return (
<div className='App'>
<svg height={0} style={{position: "absolute"}}>
<pattern id="diagonal" patternUnits="userSpaceOnUse" width={9} height={9} patternTransform="rotate(45 0 0)">
<rect x={0} y={0} width={9} height={9} fill="#FFFFFF" />
<line x1={3} y1={0} x2={3} y2={9} style={{
stroke: ETHNICITY_BOX_COLOR.AF,
strokeWidth: 6,
}} />
<line x1={7.5} y1={0} x2={7.5} y2={9} style={{
stroke: ETHNICITY_BOX_COLOR.EU,
strokeWidth: 3,
}} />
</pattern>
</svg>

<Routes>
<Route path="/" element={<RoutedApp />}>
<Route index={true} element={<Navigate to="/about" replace={true} />} />
<Route path="about" element={<AboutPage />} />
<Route path="datasets" element={<DatasetsPage />} />
<Route path="overview" element={<ProtectedPageContainer>
<OverviewPage />
</ProtectedPageContainer>} />
<Route path="explore" element={<ProtectedPageContainer>
<ExplorePage />
</ProtectedPageContainer>}>
<Route index={true} element={<PeakResults />} />
<Route path="locus/:chrom/:position/:assay" element={<PeakResults />} />
<Route path="locus/:chrom/:position" element={<PeakResults />} />
</Route>
<Route path="faq" element={<FAQPage />} />
<Route path="auth-failure" element={<div />} />
</Route>
<Route path="*" element={<Navigate to="/" />}/>
</Routes>
</div>
)
}
}
const App = () => (
<div className='App'>
<Routes>
<Route path="/" element={<RoutedApp />}>
<Route index={true} element={<Navigate to="/about" replace={true} />} />
<Route path="about" element={<AboutPage />} />
<Route path="datasets" element={<DatasetsPage />} />
<Route path="overview" element={<ProtectedPageContainer>
<OverviewPage />
</ProtectedPageContainer>} />
<Route path="explore" element={<ProtectedPageContainer>
<ExplorePage />
</ProtectedPageContainer>}>
<Route index={true} element={<PeakResults />} />
<Route path="locus/:chrom/:position/:assay" element={<PeakResults />} />
<Route path="locus/:chrom/:position" element={<PeakResults />} />
</Route>
<Route path="faq" element={<FAQPage />} />
<Route path="auth-failure" element={<div />} />
</Route>
<Route path="*" element={<Navigate to="/" />}/>
</Routes>
</div>
);


export default App;
Loading

0 comments on commit 6fa8f66

Please sign in to comment.