From 7cfdd1b9bd8d7d55b64e20bed4a23b8523e423e5 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Mon, 1 Jul 2024 11:17:42 -0400 Subject: [PATCH] graph branch --- example/data.json | 80 ++++++++++++------------ example/data2.json | 16 +++-- example/data3.json | 78 ++++++++++++------------ src/components/Graph/Graph.tsx | 104 +++++++++++++++++++------------- src/components/Graph/Legend.tsx | 3 +- src/components/Graph/types.ts | 46 ++++++++++++-- stories/Graph.stories.tsx | 44 +++++++++++++- 7 files changed, 232 insertions(+), 139 deletions(-) diff --git a/example/data.json b/example/data.json index 71ecd77..6a96679 100644 --- a/example/data.json +++ b/example/data.json @@ -408,164 +408,164 @@ ], "node": [ { - "cCRE": "EH38E1939823", + "id": "EH38E1939823", "category": "PLS" }, { - "cCRE": "EH38E1939855", + "id": "EH38E1939855", "category": "CA-CTCF" }, { - "cCRE": "EH38E1940335", + "id": "EH38E1940335", "category": "dELS" }, { - "cCRE": "EH38E1960374", + "id": "EH38E1960374", "category": "PLS" }, { - "cCRE": "EH38E1960377", + "id": "EH38E1960377", "category": "pELS" }, { - "cCRE": "EH38E3291096", + "id": "EH38E3291096", "category": "PLS" }, { - "cCRE": "EH38E3291121", + "id": "EH38E3291121", "category": "PLS" }, { - "cCRE": "EH38E3291122", + "id": "EH38E3291122", "category": "PLS" }, { - "cCRE": "EH38E3291174", + "id": "EH38E3291174", "category": "PLS" }, { - "cCRE": "EH38E3291218", + "id": "EH38E3291218", "category": "PLS" }, { - "cCRE": "EH38E3291222", + "id": "EH38E3291222", "category": "dELS" }, { - "cCRE": "EH38E3291226", + "id": "EH38E3291226", "category": "dELS" }, { - "cCRE": "EH38E3291232", + "id": "EH38E3291232", "category": "pELS" }, { - "cCRE": "EH38E3291244", + "id": "EH38E3291244", "category": "pELS" }, { - "cCRE": "EH38E3291249", + "id": "EH38E3291249", "category": "PLS" }, { - "cCRE": "EH38E3291263", + "id": "EH38E3291263", "category": "pELS" }, { - "cCRE": "EH38E3291271", + "id": "EH38E3291271", "category": "PLS" }, { - "cCRE": "EH38E3291279", + "id": "EH38E3291279", "category": "PLS" }, { - "cCRE": "EH38E3291318", + "id": "EH38E3291318", "category": "CA-CTCF" }, { - "cCRE": "EH38E3291346", + "id": "EH38E3291346", "category": "PLS" }, { - "cCRE": "EH38E3291358", + "id": "EH38E3291358", "category": "PLS" }, { - "cCRE": "EH38E3291364", + "id": "EH38E3291364", "category": "PLS" }, { - "cCRE": "EH38E3291374", + "id": "EH38E3291374", "category": "CA-TF" }, { - "cCRE": "EH38E3291392", + "id": "EH38E3291392", "category": "PLS" }, { - "cCRE": "EH38E3291410", + "id": "EH38E3291410", "category": "PLS" }, { - "cCRE": "EH38E3291664", + "id": "EH38E3291664", "category": "PLS" }, { - "cCRE": "EH38E3291668", + "id": "EH38E3291668", "category": "pELS" }, { - "cCRE": "EH38E3291779", + "id": "EH38E3291779", "category": "CA-CTCF" }, { - "cCRE": "EH38E3312736", + "id": "EH38E3312736", "category": "pELS" }, { - "cCRE": "EH38E3312746", + "id": "EH38E3312746", "category": "dELS" }, { - "cCRE": "EH38E3312765", + "id": "EH38E3312765", "category": "CA-TF" }, { - "cCRE": "EH38E3312774", + "id": "EH38E3312774", "category": "PLS" }, { - "cCRE": "EH38E3312787", + "id": "EH38E3312787", "category": "CA-TF" }, { - "cCRE": "EH38E4193211", + "id": "EH38E4193211", "category": "pELS" }, { - "cCRE": "EH38E4193228", + "id": "EH38E4193228", "category": "PLS" }, { - "cCRE": "EH38E4193243", + "id": "EH38E4193243", "category": "CA-H3K4me3" }, { - "cCRE": "EH38E4193273", + "id": "EH38E4193273", "category": "pELS" }, { - "cCRE": "EH38E4193467", + "id": "EH38E4193467", "category": "CA-CTCF" }, { - "cCRE": "EH38E4201343", + "id": "EH38E4201343", "category": "pELS" } ], "centered": { - "cCRE": "EH38E4193211" + "id": "EH38E4193211" } } } \ No newline at end of file diff --git a/example/data2.json b/example/data2.json index 61bb921..d8c1f96 100644 --- a/example/data2.json +++ b/example/data2.json @@ -14,19 +14,17 @@ ], "node": [ { - "cCRE": "node_1", - "category": "PLS", - "simple": "Promoter" + "id": "node_1", + "category": "PLS" + }, { - "cCRE": "node_2", - "category": "CA-CTCF", - "simple": "Chromatin Accessible + CTCF" + "id": "node_2", + "category": "CA-CTCF" }, { - "cCRE": "node_3", - "category": "CA-TF", - "simple": "Transcription Factor" + "id": "node_3", + "category": "CA-TF" } ] } diff --git a/example/data3.json b/example/data3.json index af67074..2a8936d 100644 --- a/example/data3.json +++ b/example/data3.json @@ -408,197 +408,197 @@ ], "node": [ { - "cCRE": "EH38E1939823", + "id": "EH38E1939823", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E1939855", + "id": "EH38E1939855", "category": "CA-CTCF", "simple": "Chromatin Accessible + CTCF" }, { - "cCRE": "EH38E1940335", + "id": "EH38E1940335", "category": "dELS", "simple": "Distal Enhancer" }, { - "cCRE": "EH38E1960374", + "id": "EH38E1960374", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E1960377", + "id": "EH38E1960377", "category": "pELS", "simple": "Proximal Enhancer" }, { - "cCRE": "EH38E3291096", + "id": "EH38E3291096", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291121", + "id": "EH38E3291121", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291122", + "id": "EH38E3291122", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291174", + "id": "EH38E3291174", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291218", + "id": "EH38E3291218", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291222", + "id": "EH38E3291222", "category": "dELS", "simple": "Distal Enhancer" }, { - "cCRE": "EH38E3291226", + "id": "EH38E3291226", "category": "dELS", "simple": "Distal Enhancer" }, { - "cCRE": "EH38E3291232", + "id": "EH38E3291232", "category": "pELS", "simple": "Proximal Enhancer" }, { - "cCRE": "EH38E3291244", + "id": "EH38E3291244", "category": "pELS", "simple": "Proximal Enhancer" }, { - "cCRE": "EH38E3291249", + "id": "EH38E3291249", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291263", + "id": "EH38E3291263", "category": "pELS", "simple": "Proximal Enhancer" }, { - "cCRE": "EH38E3291271", + "id": "EH38E3291271", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291279", + "id": "EH38E3291279", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291318", + "id": "EH38E3291318", "category": "CA-CTCF", "simple": "Chromatin Accessible + CTCF" }, { - "cCRE": "EH38E3291346", + "id": "EH38E3291346", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291358", + "id": "EH38E3291358", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291364", + "id": "EH38E3291364", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291374", + "id": "EH38E3291374", "category": "CA-TF", "simple": "Chromatin Accessible + Transcription Factor" }, { - "cCRE": "EH38E3291392", + "id": "EH38E3291392", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291410", + "id": "EH38E3291410", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291664", + "id": "EH38E3291664", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3291668", + "id": "EH38E3291668", "category": "pELS", "simple": "Proximal Enhancer" }, { - "cCRE": "EH38E3291779", + "id": "EH38E3291779", "category": "CA-CTCF", "simple": "Chromatin Accessible + CTCF" }, { - "cCRE": "EH38E3312736", + "id": "EH38E3312736", "category": "pELS", "simple": "Proximal Enhancer" }, { - "cCRE": "EH38E3312746", + "id": "EH38E3312746", "category": "dELS", "simple": "Distal Enhancer" }, { - "cCRE": "EH38E3312765", + "id": "EH38E3312765", "category": "CA-TF", "simple": "Chromatin Accessible + Transcription Factor" }, { - "cCRE": "EH38E3312774", + "id": "EH38E3312774", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E3312787", + "id": "EH38E3312787", "category": "CA-TF", "simple": "Chromatin Accessible + Transcription Factor" }, { - "cCRE": "EH38E4193211", + "id": "EH38E4193211", "category": "pELS", "simple": "Proximal Enhancer" }, { - "cCRE": "EH38E4193228", + "id": "EH38E4193228", "category": "PLS", "simple": "Promoter" }, { - "cCRE": "EH38E4193243", + "id": "EH38E4193243", "category": "CA-H3K4me3", "simple": "Chromatin Accessible + H3K4me3" }, { - "cCRE": "EH38E4193273", + "id": "EH38E4193273", "category": "pELS", "simple": "Proximal Enhancer" }, { - "cCRE": "EH38E4193467", + "id": "EH38E4193467", "category": "CA-CTCF", "simple": "Chromatin Accessible + CTCF" }, { - "cCRE": "EH38E4201343", + "id": "EH38E4201343", "category": "pELS", "simple": "Proximal Enhancer" } diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 571b298..96d8f6a 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -19,10 +19,10 @@ interface ToolTipData { centered?: string; } -function shortHand(str: string): string { - const simple = str as cCREClass; - return cCREConstants[simple]?.label || 'n/a'; -} +// function shortHand(str: string): string { +// const simple = str as cCREClass; +// return cCREConstants[simple]?.label || 'n/a'; +// } const download = (image: string, { name = 'img', extension = 'jpg' } = {}) => { const a = document.createElement('a'); @@ -50,16 +50,19 @@ function convertToSimple(str: string): string { case 'CA-only': return 'Chromatin Accessible'; default: - return ''; + return str; } } +const defaultScale = (n: number) => 10 * Math.log(n * 4 + 1); + const Graph: React.FC = ({ data, title, id, width = '100%', height = '100%', + scale = defaultScale, }) => { const cyRef = useRef(null); @@ -138,7 +141,7 @@ const Graph: React.FC = ({ if (data.centered) { // function to filter nodes and edges based on degree - do some check here for if centered even exists in data const filterNodesAndEdges = (degree: number) => { - const centeredNode = data.centered.cCRE; + const centeredNode = data.centered.id; let nodesToInclude = new Set([centeredNode]); let edgesToInclude: Edge[] = []; let visited = new Set([centeredNode]); @@ -157,16 +160,16 @@ const Graph: React.FC = ({ // find edges involving the current node data.edge.forEach((edge) => { const neighbors = [ - { target: edge.target, perturbed: edge.perturbed }, - { target: edge.perturbed, perturbed: edge.target }, + { to: edge.to, from: edge.from }, + { to: edge.from, from: edge.to }, ]; - neighbors.forEach(({ target, perturbed }) => { - if (perturbed === node && !visited.has(target)) { - visited.add(target); - nodesToInclude.add(target); + neighbors.forEach(({ to, from }) => { + if (from === node && !visited.has(to)) { + visited.add(to); + nodesToInclude.add(to); edgesToInclude.push(edge); - queue.push({ node: target, depth: depth + 1 }); + queue.push({ node: to, depth: depth + 1 }); } }); }); @@ -174,7 +177,7 @@ const Graph: React.FC = ({ // filter nodes to include only those found within the specified degrees of separation const filteredNodes = data.node.filter((node) => - nodesToInclude.has(node.cCRE) + nodesToInclude.has(node.id) ); return { nodes: filteredNodes, edges: edgesToInclude }; }; @@ -211,9 +214,9 @@ const Graph: React.FC = ({ }, [data]); } const simple: string[] = elements - .map((e) => e.category) - .map((elem) => convertToSimple(elem)); - const createID = (index: number): string => elements[index].cCRE; + .map((e) => e.info?.category) + .map((elem) => convertToSimple(elem !== undefined ? elem : '')); + const createID = (index: number): string => elements[index].id; useEffect(() => { if ( @@ -224,7 +227,7 @@ const Graph: React.FC = ({ ) return; - const allcCREs: string[] = elements.map((e) => e.cCRE); + const allcCREs: string[] = elements.map((e) => e.id); let connect: number[][] = []; for (let i = 0; i < elements.length; i++) { @@ -234,7 +237,7 @@ const Graph: React.FC = ({ // connect holds all the connections between nodes // connect[0] holds the target node INDICES for the FIRST node edges.forEach((e) => { - connect[allcCREs.indexOf(e.perturbed)].push(allcCREs.indexOf(e.target)); + connect[allcCREs.indexOf(e.from)].push(allcCREs.indexOf(e.to)); }); const edgeColor = (idx: number): string => { @@ -254,7 +257,7 @@ const Graph: React.FC = ({ selector: 'node', style: { label: '', - 'font-size': 15, + 'font-size': 12, }, }, { @@ -289,19 +292,39 @@ const Graph: React.FC = ({ // ADD NODES for (var i = 0; i < elements.length; i++) { if (toggles[simple[i]] !== false) { - cy.add({ - data: { id: createID(i) }, // create name - position: { - // random position - x: Math.random() * (1250 - 100) + 100, - y: Math.random() * (600 - 100) + 100, - }, - style: { - // find color based on CRE - 'background-color': chooseColor(i), - label: showLabels ? shortHand(simple[i]) : '', - }, - }); + if (data.centered && elements[i].id === data.centered.id) { + cy.add({ + data: { id: createID(i) }, // create name + position: { + // random position + x: Math.random() * (1250 - 100) + 100, + y: Math.random() * (600 - 100) + 100, + }, + style: { + // find color based on CRE + 'background-color': chooseColor(i), + label: showLabels ? createID(i) : '', + fontSize: '12px', + borderWidth: '2px', + borderColor: 'black', + }, + }); + } else { + cy.add({ + data: { id: createID(i) }, // create name + position: { + // random position + x: Math.random() * (1250 - 100) + 100, + y: Math.random() * (600 - 100) + 100, + }, + style: { + // find color based on CRE + 'background-color': chooseColor(i), + label: showLabels ? createID(i) : '', + fontSize: '12px', + }, + }); + } } } @@ -331,7 +354,7 @@ const Graph: React.FC = ({ ? 'triangle' : null, 'target-arrow-color': edgeColor(j), - width: 10 * Math.log(scales[j] * 4 + 1), + width: scale(scales[j]), }, }); edgeCount++; @@ -344,10 +367,10 @@ const Graph: React.FC = ({ cy.nodes().forEach((node: NodeSingular) => { let cre = allcCREs[idx].toString(); let s = simple[idx].toString(); - if (data.centered && cre === data.centered.cCRE) { + if (data.centered && cre === data.centered.id) { node.on('mousemove', (event) => handleMouseMove(event, { - cCRE: cre, + id: cre, type: s, centered: 'Centered Node', }) @@ -364,8 +387,6 @@ const Graph: React.FC = ({ node.on('mouseout', hideTooltip); }); - console.log(data.edge.every((e) => e.expressionImpact)); - cy.edges().forEach((edge: EdgeSingular) => { if (data.edge.every((e) => e.expressionImpact)) { edge.on('mousemove', (event) => @@ -401,15 +422,12 @@ const Graph: React.FC = ({ ]); useEffect(() => { - const simple: string[] = elements - .map((e) => e.category) - .map((elem) => convertToSimple(elem)); - if (!cyRef.current) return; let ind = 0; + cyRef.current.nodes().forEach((node) => { node.style({ - label: showLabels ? shortHand(simple[ind]) : '', + label: showLabels ? createID(ind) : '', }); ind++; }); diff --git a/src/components/Graph/Legend.tsx b/src/components/Graph/Legend.tsx index 56e0e5d..0a2312c 100644 --- a/src/components/Graph/Legend.tsx +++ b/src/components/Graph/Legend.tsx @@ -86,8 +86,7 @@ const Legend: React.FC = ({ })} )} - {/* Conditional rendering based on edgeType */} - {edgeType ? ( + {!collapsed && edgeType ? ( <>
number, } + + export interface GraphPropsWithData { + accession: string; + celltype: string; + degreeOfSeparation: number; + id: number, + } + + export type NewEdge = { + source: string; + destination: string; + distance: number; + path: string; + weights: string; + }; + + export type NewNode = { + accession: string; + ccre_group: string; + }; + + + export type OldFormat = { + data: { + node: Node[]; + edge: Edge[]; + centered: {cCRE: string}; + }; + }; + + export type ToolTipData = { + cCRE?: string; + type: string; + centered?: string; + } \ No newline at end of file diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index c2f04dd..06470f4 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -5,6 +5,7 @@ import { GraphProps } from '../src'; import data2 from '../example/data2.json'; import data3 from '../example/data3.json'; import data from '../example/data.json'; +import { Edge, Node } from '../src/components/Graph/types'; import '../src/App.css'; const meta: Meta = { @@ -15,11 +16,52 @@ export default meta; const Template: Story = (args) => ; +const convertNodeData = (data: { + data: { + node: Array<{ id: string; category: string }>; + edge: Array<{ + perturbed: string; + target: string; + effectSize: number; + expressionImpact?: string; + }>; + centered?: { id: string }; + }; +}): { nodes: Node[]; edges: Edge[]; centered?: { id: string } } => { + // Convert nodes to required format + const nodes = data.data.node.map((node) => ({ + id: node.id, + info: { category: node.category }, + })); + + // Convert edges to required format + const edges = data.data.edge.map((edge) => ({ + from: edge.perturbed, // Map 'perturbed' to 'from' + to: edge.target, // Map 'target' to 'to' + effectSize: edge.effectSize, + ...(edge.expressionImpact && { expressionImpact: edge.expressionImpact }), // Only include expressionImpact if it exists + })); + + // Create the result object + const result: { nodes: Node[]; edges: Edge[]; centered?: { id: string } } = { + nodes, + edges, + }; + + // Include centered if it exists + if (data.data.centered) { + result.centered = data.data.centered; + } + + return result; +}; + export const SampleGraph = Template.bind({}); SampleGraph.args = { data: data2.data, title: 'Sample Graph With No Centered cCRE', id: 1, + scale: (n: number) => 10 * n, }; export const PilotDataWithCentered = Template.bind({}); PilotDataWithCentered.args = { @@ -30,7 +72,7 @@ PilotDataWithCentered.args = { export const FiftyPercent = Template.bind({}); FiftyPercent.args = { - data: data.data, + data: convertNodeData(data), title: '50% Width and Height', id: 2, width: '50%',