Unverified Commit 90ae4398 authored by Andrey Azov's avatar Andrey Azov Committed by GitHub
Browse files

Gene overview image for EV (#253)

parent 9c97e7a5
Pipeline #62182 passed with stages
in 6 minutes and 38 seconds
This diff is collapsed.
......@@ -49,13 +49,17 @@
}
},
"dependencies": {
"@apollo/react-hooks": "3.1.3",
"@sentry/browser": "5.12.4",
"apollo-boost": "0.4.7",
"classnames": "2.2.6",
"connected-react-router": "6.7.0",
"core-js": "3.6.4",
"d3": "5.15.0",
"dotenv": "8.2.0",
"ensembl-genome-browser": "https://raw.githubusercontent.com/Ensembl/ensembl-genome-browser-assets/master/assets-80f51620ed443c640cdfd6b5aebd505b.tar.gz",
"graphql": "14.6.0",
"graphql-tag": "2.10.3",
"koa-proxy": "1.0.0-alpha.3",
"lodash": "4.17.15",
"query-string": "6.11.0",
......
import React, { useEffect } from 'react';
import ApolloClient from 'apollo-boost';
import { connect } from 'react-redux';
import { Link, useParams } from 'react-router-dom';
import { ApolloProvider } from '@apollo/react-hooks';
import { BreakpointWidth } from 'src/global/globalConfig';
import * as urlHelper from 'src/shared/helpers/urlHelper';
......@@ -47,6 +49,10 @@ export type EntityViewerParams = {
entityId?: string;
};
const client = new ApolloClient({
uri: 'http://193.62.55.158:30466'
});
const EntityViewer = (props: Props) => {
const params: EntityViewerParams = useParams(); // NOTE: will likely cause a problem when server-side rendering
......@@ -55,24 +61,26 @@ const EntityViewer = (props: Props) => {
}, [params.genomeId, params.entityId]);
return (
<div className={styles.entityViewer}>
<EntityViewerAppBar />
{params.entityId ? (
<StandardAppLayout
mainContent={<GeneView />}
sidebarContent={<GeneViewSideBar />}
sidebarNavigation={<GeneViewSidebarTabs />}
sidebarToolstripContent={<EntityViewerSidebarToolstrip />}
topbarContent={<EntityViewerTopbar />}
isSidebarOpen={props.isSidebarOpen}
onSidebarToggle={props.toggleSidebar}
isDrawerOpen={false}
viewportWidth={props.viewportWidth}
/>
) : (
<ExampleLinks {...props} />
)}
</div>
<ApolloProvider client={client}>
<div className={styles.entityViewer}>
<EntityViewerAppBar />
{params.entityId ? (
<StandardAppLayout
mainContent={<GeneView />}
sidebarContent={<GeneViewSideBar />}
sidebarNavigation={<GeneViewSidebarTabs />}
sidebarToolstripContent={<EntityViewerSidebarToolstrip />}
topbarContent={<EntityViewerTopbar />}
isSidebarOpen={props.isSidebarOpen}
onSidebarToggle={props.toggleSidebar}
isDrawerOpen={false}
viewportWidth={props.viewportWidth}
/>
) : (
<ExampleLinks {...props} />
)}
</div>
</ApolloProvider>
);
};
......
......@@ -3,9 +3,9 @@
.geneView {
display: grid;
background-color: $black;
grid-template-columns: 1fr 5fr 2fr;
grid-template-columns: 228px 902px 1fr;
grid-template-rows: auto;
padding: 15px;
padding-top: 75px;
height: 100%; // may need to change this later
width: 100%;
}
......
import React from 'react';
import { connect } from 'react-redux';
import { useQuery } from '@apollo/react-hooks';
import { gql } from 'apollo-boost';
import { getEntityViewerActiveEnsObject } from 'src/content/app/entity-viewer/state/general/entityViewerGeneralSelectors';
import GeneOverviewImage from './components/gene-overview-image/GeneOverviewImage';
import { Gene } from 'src/content/app/entity-viewer/types/gene';
import { RootState } from 'src/store';
import styles from './GeneView.scss';
const GeneView = () => (
<div className={styles.geneView}>
<div className={styles.featureImage}>This is the feature image...</div>
<div className={styles.viewInLinks}>View in GB</div>
type GeneViewProps = {
geneId: string | null;
};
<div className={styles.geneViewTabs}>
These are the Entity Viewer tabs...
</div>
<div className={styles.geneViewTable}>
This is the Entity Viewer table...
type GeneViewWithDataProps = {
gene: Gene;
};
const QUERY = gql`
query Gene($id: String!) {
gene(byId: { id: $id }) {
id
slice {
location {
start
end
}
region {
strand {
code
}
}
}
transcripts {
slice {
location {
start
end
}
}
exons {
slice {
location {
start
end
}
}
}
}
}
}
`;
const GeneView = (props: GeneViewProps) => {
const { data } = useQuery<{ gene: Gene }>(QUERY, {
variables: { id: props.geneId },
skip: !props.geneId
});
// TODO decide about the loader and possibly about error handling
if (!data) {
return null;
}
return <GeneViewWithData gene={data.gene} />;
};
const GeneViewWithData = (props: GeneViewWithDataProps) => {
return (
<div className={styles.geneView}>
<div className={styles.featureImage}>
<GeneOverviewImage gene={props.gene} />
</div>
<div className={styles.viewInLinks}>View in GB</div>
<div className={styles.geneViewTabs}>
These are the Entity Viewer tabs...
</div>
<div className={styles.geneViewTable}>
This is the Entity Viewer table...
</div>
</div>
</div>
);
);
};
const mapStateToProps = (state: RootState) => ({
// FIXME: this will have to be superseded with a proper way we get ids
geneId: getEntityViewerActiveEnsObject(state)?.stable_id || null
});
export default GeneView;
export default connect(mapStateToProps)(GeneView);
@import 'src/styles/common';
.container {
color: white;
display: grid;
grid-template-columns: 210px 18px 695px 27px 180px;
}
.containerSVG {
grid-column: 3/4;
background-color: $light-grey;
height: 18px;
}
.transcript {
rect {
stroke: $dark-grey;
fill: $dark-grey;
opacity: 0.3;
}
}
.geneId {
grid-column: 1/2;
grid-row: 2/3;
text-align: right;
font-size: 13px;
font-weight: $bold;
}
.directionIndicator {
grid-column: 3/4;
display: flex;
justify-content: space-between;
padding-bottom: 6px;
font-size: 12px;
}
.strand {
grid-column: 5/6;
grid-row: 1/2;
font-size: 12px;
}
.numberOfTranscripts {
grid-column: 5/6;
grid-row: 2/3;
.transcriptsCount {
font-weight: $bold;
margin-right: 0.4em;
}
}
.ruler {
grid-column: 3/4;
grid-row: 3/4;
padding-top: 10px;
}
import React from 'react';
import { scaleLinear } from 'd3';
import { getFeatureCoordinates } from 'src/content/app/entity-viewer/shared/helpers/entity-helpers';
import UnsplicedTranscript from 'src/content/app/entity-viewer/gene-view/components/unspliced-transcript/UnsplicedTranscript';
import BasePairsRuler from 'src/content/app/entity-viewer/gene-view/components/base-pairs-ruler/BasePairsRuler';
import { Gene } from 'src/content/app/entity-viewer/types/gene';
import { Strand } from 'src/content/app/entity-viewer/types/strand';
import styles from './GeneOverviewImage.scss';
type GeneOverviewImageProps = {
gene: Gene;
};
const GeneOverviewImage = (props: GeneOverviewImageProps) => {
const { start: geneStart, end: geneEnd } = getFeatureCoordinates(props.gene); // FIXME: use gene length further on
const length = geneEnd - geneStart;
return (
<div className={styles.container}>
<GeneId {...props} />
<DirectionIndicator />
<GeneImage {...props} />
<StrandIndicator {...props} />
<NumberOfTranscripts {...props} />
<div className={styles.ruler}>
<BasePairsRuler length={length} width={695} standalone={true} />
</div>
</div>
);
};
export const GeneImage = (props: GeneOverviewImageProps) => {
const width = 695;
const { start: geneStart, end: geneEnd } = getFeatureCoordinates(props.gene);
// FIXME: use the "length" property of the gene when it is added to payload;
// (it will help with drawing genes of circular chromosomes)
const scale = scaleLinear()
.domain([geneStart, geneEnd])
.range([0, width]);
const renderedTranscripts = props.gene.transcripts.map(
(transcript, index) => {
const {
start: transcriptStart,
end: transcriptEnd
} = getFeatureCoordinates(transcript);
const startX = scale(transcriptStart);
const endX = scale(transcriptEnd);
const y = 10;
const width = Math.floor(endX - startX);
return (
<g key={index} transform={`translate(${startX} ${y})`}>
<UnsplicedTranscript
transcript={transcript}
width={width}
classNames={{
transcript: styles.transcript
}}
/>
</g>
);
}
);
return (
<svg className={styles.containerSVG} width={width}>
{renderedTranscripts}
</svg>
);
};
const GeneId = (props: GeneOverviewImageProps) => (
<div className={styles.geneId}>{props.gene.id}</div>
);
const DirectionIndicator = () => {
return (
<div className={styles.directionIndicator}>
<span>5'</span>
<span>3'</span>
</div>
);
};
// FIXME translating response into display name (forward strand, reverse strand) should be a shared function
const StrandIndicator = (props: GeneOverviewImageProps) => {
const {
gene: {
slice: {
region: {
strand: { code: strandCode }
}
}
}
} = props;
const strandDisplayName =
strandCode === Strand.FORWARD ? 'forward strand' : 'reverse strand';
return <div className={styles.strand}>{strandDisplayName}</div>;
};
const NumberOfTranscripts = (props: GeneOverviewImageProps) => {
return (
<div className={styles.numberOfTranscripts}>
<span className={styles.transcriptsCount}>
{props.gene.transcripts.length}
</span>{' '}
transcripts
</div>
);
};
export default GeneOverviewImage;
import React from 'react';
import { connect } from 'react-redux';
import { getEntityViewerActiveEnsObject } from 'src/content/app/entity-viewer/state/general/entityViewerGeneralSelectors';
import ScaleSwitcher from './scale-switcher/ScaleSwitcher';
import FeatureSummaryStrip from 'src/shared/components/feature-summary-strip/FeatureSummaryStrip';
import { RootState } from 'src/store';
import { EnsObject } from 'src/shared/state/ens-object/ensObjectTypes';
import { getEntityViewerActiveEnsObject } from 'src/content/app/entity-viewer/state/general/entityViewerGeneralSelectors';
import styles from './EntityViewerTopbar.scss';
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment