diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..fe3948b Binary files /dev/null and b/.DS_Store differ diff --git a/example/data.json b/example/data.json index e94bd7a..71ecd77 100644 --- a/example/data.json +++ b/example/data.json @@ -409,198 +409,159 @@ "node": [ { "cCRE": "EH38E1939823", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E1939855", - "category": "CA-CTCF", - "simple": "Chromatin Accessible + CTCF" + "category": "CA-CTCF" }, { "cCRE": "EH38E1940335", - "category": "dELS", - "simple": "Distal Enhancer" + "category": "dELS" }, { "cCRE": "EH38E1960374", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E1960377", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "cCRE": "EH38E3291096", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291121", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291122", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291174", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291218", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291222", - "category": "dELS", - "simple": "Distal Enhancer" + "category": "dELS" }, { "cCRE": "EH38E3291226", - "category": "dELS", - "simple": "Distal Enhancer" + "category": "dELS" }, { "cCRE": "EH38E3291232", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "cCRE": "EH38E3291244", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "cCRE": "EH38E3291249", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291263", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "cCRE": "EH38E3291271", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291279", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291318", - "category": "CA-CTCF", - "simple": "Chromatin Accessible + CTCF" + "category": "CA-CTCF" }, { "cCRE": "EH38E3291346", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291358", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291364", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291374", - "category": "CA-TF", - "simple": "Chromatin Accessible + Transcription Factor" + "category": "CA-TF" }, { "cCRE": "EH38E3291392", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291410", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291664", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3291668", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "cCRE": "EH38E3291779", - "category": "CA-CTCF", - "simple": "Chromatin Accessible + CTCF" + "category": "CA-CTCF" }, { "cCRE": "EH38E3312736", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "cCRE": "EH38E3312746", - "category": "dELS", - "simple": "Distal Enhancer" + "category": "dELS" }, { "cCRE": "EH38E3312765", - "category": "CA-TF", - "simple": "Chromatin Accessible + Transcription Factor" + "category": "CA-TF" }, { "cCRE": "EH38E3312774", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E3312787", - "category": "CA-TF", - "simple": "Chromatin Accessible + Transcription Factor" + "category": "CA-TF" }, { "cCRE": "EH38E4193211", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "cCRE": "EH38E4193228", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "cCRE": "EH38E4193243", - "category": "CA-H3K4me3", - "simple": "Chromatin Accessible + H3K4me3" + "category": "CA-H3K4me3" }, { "cCRE": "EH38E4193273", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "cCRE": "EH38E4193467", - "category": "CA-CTCF", - "simple": "Chromatin Accessible + CTCF" + "category": "CA-CTCF" }, { "cCRE": "EH38E4201343", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" } ], "centered": { diff --git a/example/data2.json b/example/data2.json index a38b226..61bb921 100644 --- a/example/data2.json +++ b/example/data2.json @@ -4,14 +4,12 @@ { "perturbed": "node_1", "target": "node_2", - "effectSize": 0.1134, - "expressionImpact": "lower-expression" + "effectSize": 0.1134 }, { "perturbed": "node_3", "target": "node_2", - "effectSize": 0.5, - "expressionImpact": "higher-expression" + "effectSize": 0.5 } ], "node": [ diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 4bcb69c..571b298 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -31,6 +31,29 @@ const download = (image: string, { name = 'img', extension = 'jpg' } = {}) => { a.click(); }; +function convertToSimple(str: string): string { + switch (str) { + case 'PLS': + return 'Promoter'; + case 'dELS': + return 'Distal Enhancer'; + case 'pELS': + return 'Proximal Enhancer'; + case 'CA-CTCF': + return 'Chromatin Accessible + CTCF'; + case 'CA-H3K4me3': + return 'Chromatin Accessible + H3K4me3'; + case 'CA-TF': + return 'Chromatin Accessible + Transcription Factor'; + case 'Low-DNase': + return 'Low DNase'; + case 'CA-only': + return 'Chromatin Accessible'; + default: + return ''; + } +} + const Graph: React.FC = ({ data, title, @@ -46,7 +69,7 @@ const Graph: React.FC = ({ const [scales, setScales] = useState([]); const [expressionType, setExpressions] = useState([]); const [edges, setEdges] = useState([]); - const [showLabels, setShowLabels] = useState(true); // State for label visibility + const [showLabels, setShowLabels] = useState(true); const [toggles, setToggles] = useState<{ [key: string]: boolean }>({ Promoter: true, 'Distal Enhancer': true, @@ -58,6 +81,7 @@ const Graph: React.FC = ({ 'Lower-Expression': true, 'Higher-Expression': true, }); + const [degree, setDegree] = useState(3); const toggleControls = () => { @@ -161,11 +185,13 @@ const Graph: React.FC = ({ setEdges(filteredData.edges); setScales(filteredData.edges.map((e) => e.effectSize)); setExpressions( - filteredData.edges.map((e) => - e.expressionImpact === 'higher-expression' - ? 'Higher-Expression' - : 'Lower-Expression' - ) + data.edge.map((e) => { + if (e.expressionImpact === 'higher-expression') + return 'Higher-Expression'; + if (e.expressionImpact === 'lower-expression') + return 'Lower-Expression'; + return 'Edge'; + }) ); }, [data, degree]); } else { @@ -174,14 +200,20 @@ const Graph: React.FC = ({ setEdges(data.edge); setScales(data.edge.map((e: Edge) => e.effectSize)); setExpressions( - data.edge.map((e: Edge) => - e.expressionImpact === 'higher-expression' - ? 'Higher-Expression' - : 'Lower-Expression' - ) + data.edge.map((e: Edge) => { + if (e.expressionImpact === 'higher-expression') + return 'Higher-Expression'; + if (e.expressionImpact === 'lower-expression') + return 'Lower-Expression'; + return 'Edge'; + }) ); }, [data]); } + const simple: string[] = elements + .map((e) => e.category) + .map((elem) => convertToSimple(elem)); + const createID = (index: number): string => elements[index].cCRE; useEffect(() => { if ( @@ -194,7 +226,7 @@ const Graph: React.FC = ({ const allcCREs: string[] = elements.map((e) => e.cCRE); - const connect: number[][] = []; + let connect: number[][] = []; for (let i = 0; i < elements.length; i++) { connect.push([]); // does not work with Array.fill } @@ -205,13 +237,14 @@ const Graph: React.FC = ({ connect[allcCREs.indexOf(e.perturbed)].push(allcCREs.indexOf(e.target)); }); - const createID = (index: number): string => elements[index].cCRE; - const edgeColor = (idx: number): string => - expressionType[idx] === 'Lower-Expression' ? 'black' : 'blue'; - + const edgeColor = (idx: number): string => { + if (expressionType[idx] === 'Lower-Expression') return 'black'; + if (expressionType[idx] === 'Higher-Expression') return 'blue'; + return 'grey'; + }; function chooseColor(index: number): string { - const simple = elements[index].simple as cCREClass; - return cCREConstants[simple]?.color || 'grey'; + const s = simple[index] as cCREClass; + return cCREConstants[s]?.color || 'grey'; } const cy = cytoscape({ @@ -229,7 +262,6 @@ const Graph: React.FC = ({ style: { 'line-color': '#ccc', 'curve-style': 'bezier', - 'target-arrow-shape': 'triangle', }, }, ], @@ -256,7 +288,7 @@ const Graph: React.FC = ({ // ADD NODES for (var i = 0; i < elements.length; i++) { - if (toggles[elements[i].simple] !== false) { + if (toggles[simple[i]] !== false) { cy.add({ data: { id: createID(i) }, // create name position: { @@ -267,7 +299,7 @@ const Graph: React.FC = ({ style: { // find color based on CRE 'background-color': chooseColor(i), - label: showLabels ? shortHand(elements[i].simple) : '', + label: showLabels ? shortHand(simple[i]) : '', }, }); } @@ -277,12 +309,12 @@ const Graph: React.FC = ({ let edgeCount = 0; for (var j = 0; j < elements.length; j++) { // add # of edges per node based on the target connections - if (toggles[elements[j].simple] !== false) { + if (toggles[simple[j]] !== false) { // toggle let len = connect[j].length; for (let s = 0; s < len; s++) { if ( - toggles[elements[connect[j][s]].simple] !== false && // toggle + toggles[simple[connect[j][s]]] !== false && // toggle toggles[expressionType[j]] !== false ) { cy.add({ @@ -293,6 +325,11 @@ const Graph: React.FC = ({ }, style: { 'line-color': edgeColor(j), + 'target-arrow-shape': + expressionType[j] === 'Higher-Expression' || + expressionType[j] === 'Lower-Expression' + ? 'triangle' + : null, 'target-arrow-color': edgeColor(j), width: 10 * Math.log(scales[j] * 4 + 1), }, @@ -306,12 +343,12 @@ const Graph: React.FC = ({ let idx = 0; cy.nodes().forEach((node: NodeSingular) => { let cre = allcCREs[idx].toString(); - let simple = elements[idx].simple.toString(); + let s = simple[idx].toString(); if (data.centered && cre === data.centered.cCRE) { node.on('mousemove', (event) => handleMouseMove(event, { cCRE: cre, - type: simple, + type: s, centered: 'Centered Node', }) ); @@ -319,7 +356,7 @@ const Graph: React.FC = ({ node.on('mousemove', (event) => handleMouseMove(event, { cCRE: cre, - type: simple, + type: s, }) ); } @@ -327,15 +364,25 @@ const Graph: React.FC = ({ node.on('mouseout', hideTooltip); }); + console.log(data.edge.every((e) => e.expressionImpact)); + cy.edges().forEach((edge: EdgeSingular) => { - edge.on('mousemove', (event) => - handleMouseMove(event, { - type: - edge.style('line-color').toString() === 'rgb(0,0,0)' - ? 'Lower-Expression' - : 'Higher-Expression', - }) - ); + if (data.edge.every((e) => e.expressionImpact)) { + edge.on('mousemove', (event) => + handleMouseMove(event, { + type: + edge.style('line-color').toString() === 'rgb(0,0,0)' + ? 'Lower-Expression' + : 'Higher-Expression', + }) + ); + } else { + edge.on('mousemove', (event) => + handleMouseMove(event, { + type: 'Edge', + }) + ); + } edge.on('mouseout', hideTooltip); }); organize(); @@ -354,13 +401,15 @@ const Graph: React.FC = ({ ]); useEffect(() => { - const allSIMPLE: string[] = elements.map((e) => e.simple); + 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(allSIMPLE[ind]) : '', // Update label based on `showLabels` + label: showLabels ? shortHand(simple[ind]) : '', }); ind++; }); @@ -443,7 +492,6 @@ const Graph: React.FC = ({ right: '2px', }, }; - console.log(showLabels); return (
= ({ type="number" value={degree} min={1} - max={5} + max={3} onChange={(e) => setDegree(parseInt(e.target.value))} />
@@ -508,7 +556,12 @@ const Graph: React.FC = ({ func={() => setShowLabels(!showLabels)} > - + e.expressionImpact)} + /> )} diff --git a/src/components/Graph/Legend.tsx b/src/components/Graph/Legend.tsx index 3e40add..56e0e5d 100644 --- a/src/components/Graph/Legend.tsx +++ b/src/components/Graph/Legend.tsx @@ -1,13 +1,19 @@ import React, { CSSProperties, useState } from 'react'; -import { cCREConstants } from './constants'; -import GraphButton from './GraphButton'; +import { cCREClass, cCREConstants } from './constants'; interface LegendProps { toggles: { [key: string]: boolean }; onToggle: (category: string) => void; + simpleCategories: string[]; + edgeType: boolean; } -const Legend: React.FC = ({ toggles, onToggle }) => { +const Legend: React.FC = ({ + toggles, + onToggle, + simpleCategories, + edgeType, +}) => { const [collapsed, setCollapsed] = useState(false); const buttonStyle: CSSProperties = { @@ -42,37 +48,81 @@ const Legend: React.FC = ({ toggles, onToggle }) => { boxShadow: '0 0 10px rgba(0,0,0,0.5)', }; - const d = { - width: '237px', - }; + const d = { width: '237px' }; + const lower = 'Lower-Expression'; + const higher = 'Higher-Expression'; + + const uniqueCategories = Array.from(new Set(simpleCategories)); return (
- setCollapsed(!collapsed)} - styles={buttonStyle} - > + {!collapsed && (
- {Object.entries(cCREConstants).map(([key, value]) => ( -
- onToggle(key)} - /> - onToggle(key)} - > - {key} ({value.label}) - -
- ))} + {uniqueCategories.map((category) => { + const typedCategory = category as cCREClass; + const categoryData = cCREConstants[typedCategory]; + return ( +
+ onToggle(category)} + /> + onToggle(category)} + > + {category} ({categoryData?.label || 'n/a'}) + +
+ ); + })}
)} + {/* Conditional rendering based on edgeType */} + {edgeType ? ( + <> +
+ onToggle(lower)} + /> + onToggle(lower)} + > + {lower} (Edge) + +
+
+ onToggle(higher)} + /> + onToggle(higher)} + > + {higher} (Edge) + +
+ + ) : null}
); }; diff --git a/src/components/Graph/constants.ts b/src/components/Graph/constants.ts index 95034a3..d457cd4 100644 --- a/src/components/Graph/constants.ts +++ b/src/components/Graph/constants.ts @@ -10,6 +10,7 @@ export const cCREConstants = { "Chromatin Accessible + H3K4me3": { label: "CA+H3K4me3", color: "#ffaaaa" }, "Lower-Expression": { label: "Edge", color: "#000000" }, "Higher-Expression": { label: "Edge", color: "#0000FF" }, + "Edge": { label: "Edge", color: "grey"}, }; export type cCREClass = keyof typeof cCREConstants; diff --git a/src/components/Graph/types.ts b/src/components/Graph/types.ts index 35ecc60..e0c4272 100644 --- a/src/components/Graph/types.ts +++ b/src/components/Graph/types.ts @@ -2,13 +2,12 @@ export interface Edge { perturbed: string; target: string; effectSize: number; - expressionImpact: string; + expressionImpact?: string; } export interface Node { cCRE: string; category: string; - simple: string; } export interface GraphProps {