Skip to content

Commit

Permalink
[Platform]: fix comparative genomics (#649)
Browse files Browse the repository at this point in the history
* refactor: migrate to TS

* fix: visualisation

* feat: add export and download

* feat: add tooltip
  • Loading branch information
carcruz authored Feb 14, 2025
1 parent 081cbb1 commit 673ddd8
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 41 deletions.
15 changes: 11 additions & 4 deletions packages/sections/src/target/ComparativeGenomics/Body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faStar as faStarSolid } from "@fortawesome/free-solid-svg-icons";
import { faStar } from "@fortawesome/free-regular-svg-icons";
import { SectionItem, Link, Tooltip, OtTable } from "ui";
import Visualisation from "./Visualisation";
import ComparativeGenomicsPlot from "./ComparativeGenomicsPlot";

import { definition } from ".";
import Description from "./Description";
Expand Down Expand Up @@ -140,7 +140,7 @@ function Body({ id: ensemblId, label: symbol, entity, viewMode = VIEW_MODES.defa
const classes = useStyles();
const variables = { ensemblId };
const request = useQuery(COMP_GENOMICS_QUERY, { variables });

const columns = getColumns(classes);
return (
<SectionItem
entity={entity}
Expand All @@ -149,13 +149,20 @@ function Body({ id: ensemblId, label: symbol, entity, viewMode = VIEW_MODES.defa
defaultView={VIEW.chart}
renderDescription={() => <Description symbol={symbol} />}
renderChart={() => (
<Visualisation homologues={request.data?.target.homologues} viewMode={viewMode} />
<ComparativeGenomicsPlot
homologues={request.data?.target.homologues}
viewMode={viewMode}
loading={request.loading}
query={COMP_GENOMICS_QUERY.loc.source.body}
variables={variables}
columns={columns}
/>
)}
renderBody={() => (
<OtTable
showGlobalFilter
dataDownloader
columns={getColumns(classes)}
columns={columns}
rows={request.data?.target.homologues}
query={COMP_GENOMICS_QUERY.loc.source.body}
variables={variables}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useTheme } from "@mui/styles";
import { Theme } from "@mui/material";

function ChimpanzeeIcon() {
const theme = useTheme();
const theme: Theme = useTheme();
return (
<svg height="13" viewBox="0 0 1000 1032">
<path
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import * as d3 from "d3";
import { useRef, useEffect } from "react";
import { useMeasure } from "@uidotdev/usehooks";
import { Box, Typography, useTheme } from "@mui/material";
import { Box, Skeleton, Typography, useTheme } from "@mui/material";
import { grey } from "@mui/material/colors";
import { Link } from "ui";

const VIEW_MODES = {
default: "default",
mouseOrthologMaxIdentityPercentage: "mouseOrthologMaxIdentityPercentage",
paralogMaxIdentityPercentage: "paralogMaxIdentityPercentage",
};
import { Link, DataDownloader } from "ui";

const content = {
mouseOrthologMaxIdentityPercentage:
Expand All @@ -24,6 +18,21 @@ const contentURL = {
"https://platform-docs.opentargets.org/target-prioritisation#paralogues",
};

function getTooltipContent(homolog) {
return `<div>
${getPropText(homolog, "homologyType", "Type")}
${getPropText(homolog, "targetGeneSymbol", "Homolog")}
${getPropText(homolog, "queryPercentageIdentity", "Query Percentage")}
${getPropText(homolog, "targetPercentageIdentity", "Target Percentage")}
</div>`;
}

function getPropText(homolog, prop, label) {
return `<div>
<b> ${label}:</b> ${homolog[prop]}
</div>`;
}

const yAxisValues = [
"6239",
"7227",
Expand All @@ -40,10 +49,21 @@ const yAxisValues = [
"9606",
];

function Wrapper({ homologues, query, variables, viewMode }) {
const TOOLTIP_ID = "tolltip-template-comp-genomic";
const dotDefaultRadious = 6;
const dotDefaultOpacity = 0.7;

function Wrapper({ homologues, viewMode, loading, query, variables, columns }) {
const [ref, { width }] = useMeasure();
if (loading)
return (
<Box>
<Skeleton height={400} />
</Box>
);
return (
<Box sx={{ display: "flex", flexDirection: "column" }}>
<ChartControls data={homologues} query={query} variables={variables} columns={columns} />
{viewMode !== "default" && (
<Typography sx={{ width: "85%", my: 2, ml: 4 }} variant="body2">
{content[viewMode]}
Expand All @@ -52,13 +72,37 @@ function Wrapper({ homologues, query, variables, viewMode }) {
</Link>
</Typography>
)}
<Box sx={{ width: "95%", margin: "0 auto" }} ref={ref}>
<Box sx={{ width: "95%", margin: "0 auto", position: "relative" }} ref={ref}>
<div style={{ height: 0 }} id={TOOLTIP_ID}></div>
<Visualisation homologues={homologues} width={width} viewMode={viewMode} />
</Box>
</Box>
);
}

function ChartControls({ data, query, variables, columns }) {
return (
<Box
sx={{
borderColor: grey[300],
borderRadius: 1,
display: "flex",
justifyContent: "flex-end",
gap: 1,
mb: 2,
}}
>
<DataDownloader
btnLabel="Export"
rows={data}
query={query}
variables={variables}
columns={columns}
/>
</Box>
);
}

const labels = {
6239: "Caenorhabditis elegans (Nematode, N2)",
7227: "Drosophila melanogaster (Fruit fly)",
Expand Down Expand Up @@ -91,6 +135,7 @@ function Visualisation({ homologues, width, viewMode }) {
.range([height - marginBottom * 2, marginTop]);

useEffect(() => {
if (!homologues) return;
const chartWidth = (width - marginRight) * 0.4;

// Declare the x (horizontal position) scale.
Expand All @@ -103,6 +148,64 @@ function Visualisation({ homologues, width, viewMode }) {
// Create the SVG container.
const svg = d3.create("svg").attr("width", width).attr("height", height);

const tooltip = d3
.select(`#${TOOLTIP_ID}`)
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("position", "absolute")
.style("background-color", "white")
.style("border", `1px solid ${grey[400]}`)
.style("border-radius", "3px")
.style("padding", "5px")
.style("width", "175px")
.style("font-family", theme.typography.body2.fontFamily)
.style("font-size", theme.typography.body2.fontSize)
.style("box-shadow", theme.boxShadow.default);

const mouseover = function (d) {
tooltip.style("opacity", 1);
const classId = `.${d.targetGeneId}_${d.speciesId}`;
d3.select(this)
.transition()
.duration(300)
.attr("fill-opacity", 1)
.attr("r", dotDefaultRadious * 2);
d3.selectAll(classId)
.transition()
.duration(300)
.attr("fill-opacity", 1)
.attr("r", dotDefaultRadious * 2);
};
const mousemove = function (d) {
const mouseX = d3.mouse(this)[0];
const mouseY = d3.mouse(this)[1];
const bottom = mouseY > height / 2 ? `${height - mouseY + 8}px` : "initial";
const left = mouseX < width / 2 ? `${mouseX + 8}px` : "initial";
const right = mouseX > width / 2 ? `${width - mouseX + 8}px` : "initial";
const top = mouseY < height / 2 ? `${mouseY + 8}px` : "initial";
tooltip
.style("top", top)
.style("right", right)
.style("bottom", bottom)
.style("left", left)
.html(getTooltipContent(d));
};
const mouseleave = function (d) {
tooltip.style("opacity", 0);
const classId = `.${d.targetGeneId}_${d.speciesId}`;
d3.selectAll(classId)
.transition()
.duration(300)
.attr("fill-opacity", dotDefaultOpacity)
.attr("r", dotDefaultRadious);
d3.select(this)
.transition()
.duration(300)
.attr("fill-opacity", dotDefaultOpacity)
.attr("r", dotDefaultRadious);
};

// Add the x-axis.
svg
.append("g")
Expand Down Expand Up @@ -190,10 +293,19 @@ function Visualisation({ homologues, width, viewMode }) {
.attr("cy", function (d) {
return y(d.speciesId);
})
.attr("r", 6)
.attr("fill-opacity", 0.7)
.attr("r", dotDefaultRadious)
.attr("class", function (d) {
return `${d.targetGeneId}_${d.speciesId}`;
})
.attr("id", function (d, i) {
return i;
})
.attr("fill-opacity", dotDefaultOpacity)
.attr("fill", theme.palette.primary.main)
.attr("stroke", theme.palette.primary.dark);
.attr("stroke", theme.palette.primary.dark)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);

targetContainer
.selectAll(".target")
Expand All @@ -206,17 +318,23 @@ function Visualisation({ homologues, width, viewMode }) {
.attr("cy", function (d) {
return y(d.speciesId);
})
.attr("r", 6)
.attr("r", dotDefaultRadious)
.attr("class", function (d) {
return `${d.targetGeneId}_${d.speciesId}`;
})
.attr("fill-opacity", 0.7)
.attr("fill", theme.palette.primary.main)
.attr("stroke", theme.palette.primary.dark);
.attr("stroke", theme.palette.primary.dark)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);

if (viewMode === "mouseOrthologMaxIdentityPercentage") {
queryContainer.selectAll("circle").attr("fill", grey[300]).attr("stroke", grey[300]);
targetContainer.selectAll("circle").attr("fill", grey[300]).attr("stroke", grey[300]);

queryContainer
.selectAll("circle")
// .transition(300)
.attr("fill", d =>
d.queryPercentageIdentity > 80 && d.speciesId == "10090"
? theme.palette.primary.main
Expand All @@ -241,24 +359,25 @@ function Visualisation({ homologues, width, viewMode }) {
}
if (viewMode === "paralogMaxIdentityPercentage") {
queryContainer.selectAll("circle").attr("fill", grey[300]).attr("stroke", grey[300]);
targetContainer.selectAll("circle").attr("fill", grey[300]).attr("stroke", grey[300]);

targetContainer
queryContainer
.selectAll("circle")
// .transition(300)
.attr("fill", d =>
d.targetPercentageIdentity > 60 && d.speciesId == "9606"
d.queryPercentageIdentity > 60 && d.speciesId == "9606"
? theme.palette.primary.main
: grey[300]
)
.attr("stroke", d =>
d.targetPercentageIdentity > 60 && d.speciesId == "9606"
d.queryPercentageIdentity > 60 && d.speciesId == "9606"
? theme.palette.primary.dark
: grey[300]
);
targetContainer
queryContainer
.append("line")
.attr("x1", targetScale(60))
.attr("x2", targetScale(60))
.attr("x1", queryScale(60))
.attr("x2", queryScale(60))
.attr("y1", marginTop)
.attr("y2", height - marginBottom)
.attr("stroke", "#e3a772");
Expand All @@ -270,9 +389,13 @@ function Visualisation({ homologues, width, viewMode }) {

// Append the SVG element.
containerReference.current.append(svg.node());
return () => svg.remove();
return () => {
tooltip.remove();
svg.remove();
};
}, [homologues, width, viewMode]);

return <div ref={containerReference}></div>;
}

export default Wrapper;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Link } from "ui";

function Description({ symbol }) {
function Description({ symbol }: { symbol: string }) {
return (
<>
Homology for <strong>{symbol}</strong> across selected species. Source:{" "}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useTheme } from "@mui/styles";
import { Theme } from "@mui/material";

function DogIcon() {
const theme = useTheme();
const theme: Theme = useTheme();
return (
<svg height="13" viewBox="0 0 988 1027">
<path
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useTheme } from "@mui/styles";
import { Theme } from "@mui/material";

function FlyIcon() {
const theme = useTheme();
const theme: Theme = useTheme();
return (
<svg height="13" viewBox="0 0 1027 872">
<path
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useTheme } from "@mui/styles";
import { Theme } from "@mui/material";

function FrogIcon() {
const theme = useTheme();
const theme: Theme = useTheme();
return (
<svg height="13" viewBox="0 0 982 992">
<path
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useTheme } from "@mui/styles";
import { Theme } from "@mui/material";

function GuineaPigIcon() {
const theme = useTheme();
const theme: Theme = useTheme();
return (
<svg height="13" viewBox="0 0 1028 591">
<path
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useTheme } from "@mui/styles";
import { Theme } from "@mui/material";

function HumanIcon() {
const theme = useTheme();
const theme: Theme = useTheme();
return (
<svg height="13" viewBox="0 0 563 1032">
<path
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useTheme } from "@mui/styles";
import { Theme } from "@mui/material";

function MacaqueIcon() {
const theme = useTheme();
const theme: Theme = useTheme();
return (
<svg height="13" viewBox="0 0 1000 1036">
<path
Expand Down
Loading

0 comments on commit 673ddd8

Please sign in to comment.