Skip to content

Commit

Permalink
Merge pull request #1171 from MetabolicAtlas/feat/redo-IP
Browse files Browse the repository at this point in the history
feat: redo interaction partners
  • Loading branch information
MalinAhlberg committed Oct 27, 2022
2 parents 172df77 + cb3bb03 commit c88112f
Show file tree
Hide file tree
Showing 28 changed files with 1,145 additions and 1,556 deletions.
6 changes: 6 additions & 0 deletions api/src/endpoints/neo4j.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getRelatedMetabolites,
getRandomComponents,
getInteractionPartners,
getInteractionPartnersExpansion,
getMapsListing,
mapSearch,
search,
Expand All @@ -36,6 +37,7 @@ const fetchWith = async (req, res, queryHandler) => {
searchTerm,
componentTypes,
isForAllCompartments,
expanded,
} = req.query;

try {
Expand All @@ -46,6 +48,7 @@ const fetchWith = async (req, res, queryHandler) => {
limit,
full,
searchTerm,
expanded,
};

if (componentTypes) {
Expand Down Expand Up @@ -115,6 +118,9 @@ neo4jRoutes.get('/random-components', async (req, res) =>
neo4jRoutes.get('/interaction-partners/:id', async (req, res) =>
fetchWith(req, res, getInteractionPartners)
);
neo4jRoutes.get('/interaction-partners-expansion/:id', async (req, res) => {
fetchWith(req, res, getInteractionPartnersExpansion);
});

neo4jRoutes.get('/maps/listing', async (req, res) =>
fetchWith(req, res, getMapsListing)
Expand Down
6 changes: 5 additions & 1 deletion api/src/neo4j/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import {
} from 'neo4j/queries/relatedReactions';
import getRelatedMetabolites from 'neo4j/queries/relatedMetabolites';
import getRandomComponents from 'neo4j/queries/randomComponents';
import getInteractionPartners from 'neo4j/queries/interactionPartners';
import {
getInteractionPartners,
getInteractionPartnersExpansion,
} from 'neo4j/queries/interactionPartners';
import { getMapsListing, mapSearch } from 'neo4j/queries/map';
import get3dNetwork from 'neo4j/queries/3d-network';
import {
Expand Down Expand Up @@ -46,6 +49,7 @@ export {
getRelatedMetabolites,
getRandomComponents,
getInteractionPartners,
getInteractionPartnersExpansion,
getMapsListing,
mapSearch,
search,
Expand Down
128 changes: 126 additions & 2 deletions api/src/neo4j/queries/interactionPartners.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import querySingleResult from 'neo4j/queryHandlers/single';
import parseParams from 'neo4j/shared/helper';
import populateWithLayout from 'workers/3d-network';

const getInteractionPartners = async ({ id, model, version }) => {
const [m, v] = parseParams(model, version);
Expand Down Expand Up @@ -49,7 +50,130 @@ WITH apoc.map.mergeList(apoc.coll.flatten(
)) as reaction, component
RETURN { component: component, reactions: COLLECT(reaction)}
`;
return querySingleResult(statement);
const result = await querySingleResult(statement);
let links = [];
let nodes = [];
let unique = new Set();

const addLink = (s, t) => {
const link = `${s}-${t}`;
const inverseLink = `${t}-${s}`;
if (!unique.has(link) && !unique.has(inverseLink)) {
links.push({ s, t });
unique.add(link);
}
};

result.reactions.forEach(reaction => {
const { genes, metabolites } = reaction;

// loop through metabolites, add them to nodes
// and add links to the main node
metabolites.forEach(metabolite => {
if (!unique.has(metabolite.id)) {
nodes.push({
g: 'm',
id: metabolite.id,
n: metabolite.name || metabolite.name,
});
unique.add(metabolite.id);

if (id !== metabolite.id) {
addLink(id, metabolite.id);
}
}
});

// loop through genes, add them to nodes
// and add links to the main node
genes.forEach(gene => {
if (!unique.has(gene.id)) {
nodes.push({ g: 'e', id: gene.id, n: gene.name || gene.id });
unique.add(gene.id);

if (id !== gene.id) {
addLink(id, gene.id);
}
}

// loop through metabolites for each gene
// and add links to the gene
metabolites.forEach(metabolite => addLink(gene.id, metabolite.id));
});
});
const network = await populateWithLayout({
nodes,
links,
dim: 2,
mainNodeID: id,
reCenter: true,
});
return { result, network };
};

const getInteractionPartnersExpansion = async ({
id,
model,
version,
expanded,
}) => {
const { network, result } = await getInteractionPartners({
id,
model,
version,
});
let unique = new Set();
let expandedNodes = {};

const expandedNetworks = await Promise.all(
expanded.map(nodeId =>
getInteractionPartners({
id: nodeId,
model,
version,
})
)
);

for (let i = 0; i < expandedNetworks.length; i++) {
const expandedNetwork = expandedNetworks[i];
const nodeId = expanded[i];

expandedNodes[nodeId] = expandedNetwork.result.component.name;
const addLink = (s, t) => {
const link = `${s}-${t}`;
const inverseLink = `${t}-${s}`;
if (!unique.has(link) && !unique.has(inverseLink)) {
network.links.push({ s, t });
unique.add(link);
}
};

expandedNetwork.network.links.forEach(link => addLink(link.s, link.t));
expandedNetwork.network.nodes.forEach(node => {
if (!network.nodes.map(n => n.id).includes(node.id)) {
network.nodes.push({
g: node.g,
id: node.id,
n: node.n,
});
}
});

result.reactions = [
...result.reactions,
...expandedNetwork.result.reactions,
];
}

const newNetwork = await populateWithLayout({
...network,
dim: 2,
mainNodeID: expanded[expanded.length - 1],
reCenter: true,
});

return { result, network: newNetwork, expandedNodes };
};

export default getInteractionPartners;
export { getInteractionPartners, getInteractionPartnersExpansion };
30 changes: 25 additions & 5 deletions api/src/utils/3d-network.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This file uses `require` and `module.exports` as opposed to
// the import/export instances that are used elsewhere.
// The reason for this is that it intended to be used in a worker
// thread (`/api/src/workers/3d-network.js`) so it needs to used
// thread (`/api/src/workers/3d-network.js`) so it needs to use
// the syntax that is default to node.js

const createGraph = require('ngraph.graph');
Expand All @@ -10,7 +10,13 @@ const createLayout = require('ngraph.forcelayout');
const SCALE = 5;
const MAX_ITERATIONS = 1000;

module.exports = ({ nodes, links }) => {
module.exports = ({
nodes,
links,
dim = 3,
mainNodeID = null,
reCenter = false,
}) => {
const g = createGraph();

for (let node of nodes) {
Expand All @@ -24,7 +30,7 @@ module.exports = ({ nodes, links }) => {
}

const startTime = Date.now();
const layout = createLayout(g, { dimensions: 3 });
const layout = createLayout(g, { dimensions: dim });

let iterations = MAX_ITERATIONS;
const elementsCount = nodes.length + links.length;
Expand All @@ -43,6 +49,7 @@ module.exports = ({ nodes, links }) => {
}

const nodesWithPos = [];
let mainNode;

g.forEachNode(node => {
const { x, y, z } = layout.getNodePosition(node.id);
Expand All @@ -52,13 +59,26 @@ module.exports = ({ nodes, links }) => {
Math.round(z * SCALE),
];

nodesWithPos.push({
const nodeWithPos = {
id: node.id,
pos,
...node.data,
});
};
nodesWithPos.push(nodeWithPos);

if (node.id === mainNodeID) {
mainNode = nodeWithPos;
}
});

if (mainNode !== undefined && reCenter) {
// re-center all of the nodes based on the main node
nodesWithPos.forEach(node => {
node.pos[0] -= mainNode.pos[0];
node.pos[1] -= mainNode.pos[1];
});
}

return {
nodes: nodesWithPos,
links,
Expand Down
4 changes: 2 additions & 2 deletions api/test/interactionPartners.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('interaction partners', () => {
`${API_BASE}/interaction-partners/ENSG00000120697?model=HumanGem&version=${HUMAN_GEM_VERSION}`
);

const { reactions } = await res.json();
expect(reactions.length).toBeGreaterThan(0);
const { result } = await res.json();
expect(result.reactions.length).toBeGreaterThan(0);
});
});
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"format": "prettier --write src"
},
"dependencies": {
"@metabolicatlas/3d-network-viewer": "^0.1.22",
"@metabolicatlas/3d-network-viewer": "^0.1.28",
"@panzoom/panzoom": "^4.4.3",
"@vueuse/head": "^0.7.9",
"axios": "^0.24.0",
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/api/interactionPartners.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import axios from 'axios';
const fetchInteractionPartners = async ({ id, model, version }) => {
const params = { model, version };
const { data } = await axios.get(`/interaction-partners/${id}`, { params });
return data;
return { result: data.result, network: data.network };
};

export default { fetchInteractionPartners };
const fetchInteractionPartnersExpansion = async ({ id, model, version, expanded }) => {
const params = { model, version, expanded };
const { data } = await axios.get(`/interaction-partners-expansion/${id}`, { params });
return { result: data.result, network: data.network, expandedNodes: data.expandedNodes };
};

export default { fetchInteractionPartners, fetchInteractionPartnersExpansion };
Binary file removed frontend/src/assets/interPart-cover.jpg
Binary file not shown.
Binary file added frontend/src/assets/interPart.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed frontend/src/assets/interPart.mp4
Binary file not shown.
Binary file modified frontend/src/assets/interaction.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit c88112f

Please sign in to comment.