From 76f09e72654834e1fd5b5253b08f0b8aff8e1214 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Tue, 11 Jun 2024 14:39:11 -0400 Subject: [PATCH 01/31] Graph Component --- example/data.json | 122 +++++--- example/data2.json | 13 +- package-lock.json | 6 +- src/components/Graph/Graph.css | 57 ++++ src/components/Graph/Graph.tsx | 433 ++++++--------------------- src/components/Graph/Legend.tsx | 139 ++------- src/components/Graph/ScaleLegend.tsx | 61 ++-- src/components/Graph/constants.ts | 29 +- src/components/Graph/types.ts | 13 +- stories/Graph.stories.tsx | 38 +-- 10 files changed, 300 insertions(+), 611 deletions(-) create mode 100644 src/components/Graph/Graph.css diff --git a/example/data.json b/example/data.json index 71ecd77..af67074 100644 --- a/example/data.json +++ b/example/data.json @@ -409,163 +409,199 @@ "node": [ { "cCRE": "EH38E1939823", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E1939855", - "category": "CA-CTCF" + "category": "CA-CTCF", + "simple": "Chromatin Accessible + CTCF" }, { "cCRE": "EH38E1940335", - "category": "dELS" + "category": "dELS", + "simple": "Distal Enhancer" }, { "cCRE": "EH38E1960374", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E1960377", - "category": "pELS" + "category": "pELS", + "simple": "Proximal Enhancer" }, { "cCRE": "EH38E3291096", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291121", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291122", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291174", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291218", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291222", - "category": "dELS" + "category": "dELS", + "simple": "Distal Enhancer" }, { "cCRE": "EH38E3291226", - "category": "dELS" + "category": "dELS", + "simple": "Distal Enhancer" }, { "cCRE": "EH38E3291232", - "category": "pELS" + "category": "pELS", + "simple": "Proximal Enhancer" }, { "cCRE": "EH38E3291244", - "category": "pELS" + "category": "pELS", + "simple": "Proximal Enhancer" }, { "cCRE": "EH38E3291249", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291263", - "category": "pELS" + "category": "pELS", + "simple": "Proximal Enhancer" }, { "cCRE": "EH38E3291271", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291279", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291318", - "category": "CA-CTCF" + "category": "CA-CTCF", + "simple": "Chromatin Accessible + CTCF" }, { "cCRE": "EH38E3291346", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291358", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291364", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291374", - "category": "CA-TF" + "category": "CA-TF", + "simple": "Chromatin Accessible + Transcription Factor" }, { "cCRE": "EH38E3291392", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291410", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291664", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3291668", - "category": "pELS" + "category": "pELS", + "simple": "Proximal Enhancer" }, { "cCRE": "EH38E3291779", - "category": "CA-CTCF" + "category": "CA-CTCF", + "simple": "Chromatin Accessible + CTCF" }, { "cCRE": "EH38E3312736", - "category": "pELS" + "category": "pELS", + "simple": "Proximal Enhancer" }, { "cCRE": "EH38E3312746", - "category": "dELS" + "category": "dELS", + "simple": "Distal Enhancer" }, { "cCRE": "EH38E3312765", - "category": "CA-TF" + "category": "CA-TF", + "simple": "Chromatin Accessible + Transcription Factor" }, { "cCRE": "EH38E3312774", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E3312787", - "category": "CA-TF" + "category": "CA-TF", + "simple": "Chromatin Accessible + Transcription Factor" }, { "cCRE": "EH38E4193211", - "category": "pELS" + "category": "pELS", + "simple": "Proximal Enhancer" }, { "cCRE": "EH38E4193228", - "category": "PLS" + "category": "PLS", + "simple": "Promoter" }, { "cCRE": "EH38E4193243", - "category": "CA-H3K4me3" + "category": "CA-H3K4me3", + "simple": "Chromatin Accessible + H3K4me3" }, { "cCRE": "EH38E4193273", - "category": "pELS" + "category": "pELS", + "simple": "Proximal Enhancer" }, { "cCRE": "EH38E4193467", - "category": "CA-CTCF" + "category": "CA-CTCF", + "simple": "Chromatin Accessible + CTCF" }, { "cCRE": "EH38E4201343", - "category": "pELS" + "category": "pELS", + "simple": "Proximal Enhancer" } - ], - "centered": { - "cCRE": "EH38E4193211" - } + ] } } \ No newline at end of file diff --git a/example/data2.json b/example/data2.json index 61bb921..b94a07b 100644 --- a/example/data2.json +++ b/example/data2.json @@ -4,12 +4,8 @@ { "perturbed": "node_1", "target": "node_2", - "effectSize": 0.1134 - }, - { - "perturbed": "node_3", - "target": "node_2", - "effectSize": 0.5 + "effectSize": 0.1134, + "expressionImpact": "lower-expression" } ], "node": [ @@ -22,11 +18,6 @@ "cCRE": "node_2", "category": "CA-CTCF", "simple": "Chromatin Accessible + CTCF" - }, - { - "cCRE": "node_3", - "category": "CA-TF", - "simple": "Transcription Factor" } ] } diff --git a/package-lock.json b/package-lock.json index 652ff0c..ffccd63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@visx/tooltip": "^3.3.0", "cytoscape": "^3.29.2", "cytoscape-cose-bilkent": "^4.1.0", - "html2canvas": "^1.4.1", "use-react-screenshot": "^4.0.0" }, "devDependencies": { @@ -8667,6 +8666,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "peer": true, "engines": { "node": ">= 0.6.0" } @@ -9784,6 +9784,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "peer": true, "dependencies": { "utrie": "^1.0.2" } @@ -13091,6 +13092,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "peer": true, "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" @@ -20328,6 +20330,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "peer": true, "dependencies": { "utrie": "^1.0.2" } @@ -21171,6 +21174,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "peer": true, "dependencies": { "base64-arraybuffer": "^1.0.2" } diff --git a/src/components/Graph/Graph.css b/src/components/Graph/Graph.css new file mode 100644 index 0000000..485049c --- /dev/null +++ b/src/components/Graph/Graph.css @@ -0,0 +1,57 @@ +body { + font-family: helvetica; + font-size: 14px; + } + + #cy { + width: 100%; + height: 90%; + z-index: 999; + } + + h1 { + opacity: 0.5; + font-size: 1em; + margin: 0; + } + + .c { + width: "100%"; + height: "100vh"; + position: "relative" + } + + button { + margin: 2px; + background-color: #0095ff; + border: 0px; + border-radius: 3px; + box-shadow: rgba(255, 255, 255, .4) 0 1px 0 0 inset; + box-sizing: border-box; + color: #fff; + cursor: pointer; + font-family: -apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif; + font-size: 12px; + outline: none; + padding: 7px .8em; + position: relative; + text-align: center; + text-decoration: none; + user-select: none; + -webkit-user-select: none; + white-space: nowrap; + } + +button:hover, +button:focus { + background-color: #07c; +} + +button:focus { + box-shadow: 0 0 0 4px rgba(0, 149, 255, .15); +} + +button:active { + background-color: #0064bd; + box-shadow: none; +} diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 42ce929..b41c287 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -3,25 +3,17 @@ import cytoscape, { Core, EdgeSingular, NodeSingular } from 'cytoscape'; import coseBilkent from 'cytoscape-cose-bilkent'; import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip'; import { useScreenshot } from 'use-react-screenshot'; -import { cCREConstants, cCREClass, buttonStyle } from './constants'; +import { cCREConstants, cCREClass } from './constants'; +import './Graph.css'; import { GraphProps, Node, Edge } from './types'; import Legend from './Legend'; import ScaleLegend from './ScaleLegend'; -import GraphButton from './GraphButton'; -import KeyboardDoubleArrowRightIcon from '@mui/icons-material/KeyboardDoubleArrowRight'; -import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrowLeft'; cytoscape.use(coseBilkent); interface ToolTipData { cCRE?: string; type: string; - centered?: string; -} - -function shortHand(str: string): string { - const simple = str as cCREClass; - return cCREConstants[simple]?.label || 'n/a'; } const download = (image: string, { name = 'img', extension = 'jpg' } = {}) => { @@ -31,45 +23,14 @@ 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, - id, - width = '100%', - height = '100%', -}) => { +const Graph: React.FC = ({ data, title }) => { const cyRef = useRef(null); // state hooks - const [showControls, setShowControls] = useState(true); const [elements, setElements] = useState([]); const [scales, setScales] = useState([]); const [expressionType, setExpressions] = useState([]); const [edges, setEdges] = useState([]); - const [showLabels, setShowLabels] = useState(true); const [toggles, setToggles] = useState<{ [key: string]: boolean }>({ Promoter: true, 'Distal Enhancer': true, @@ -80,18 +41,11 @@ const Graph: React.FC = ({ 'Chromatin Accessible + CTCF': true, 'Lower-Expression': true, 'Higher-Expression': true, - 'Low DNase': true, }); - const [degree, setDegree] = useState(3); - - const toggleControls = () => { - setShowControls(!showControls); - }; - // DOWNLOAD SCREENSHOT const ref = useRef(null); - const [_, takeScreenShot] = useScreenshot(); + const [image, takeScreenShot] = useScreenshot(); const downloadScreenshot = () => { if (ref.current && typeof takeScreenShot === 'function') { @@ -115,7 +69,7 @@ const Graph: React.FC = ({ scroll: true, }); - const handleMouseMove = (event: cytoscape.EventObject, datum: any) => { + const handleMouseOver = (event: cytoscape.EventObject, datum: any) => { if (!containerRef.current) { console.error('Container ref is not set'); return; @@ -133,88 +87,19 @@ const Graph: React.FC = ({ }); }; - // each graph needs a unique id - let k = 'cy-' + id; - - 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; - let nodesToInclude = new Set([centeredNode]); - let edgesToInclude: Edge[] = []; - let visited = new Set([centeredNode]); - - // queue to manage BFS traversal - let queue: { node: string; depth: number }[] = [ - { node: centeredNode, depth: 0 }, - ]; - - while (queue.length > 0) { - const { node, depth } = queue.shift()!; - - // if the curr depth = to the degree, skip further expansion - if (depth >= degree) continue; - - // find edges involving the current node - data.edge.forEach((edge) => { - const neighbors = [ - { target: edge.target, perturbed: edge.perturbed }, - { target: edge.perturbed, perturbed: edge.target }, - ]; - - neighbors.forEach(({ target, perturbed }) => { - if (perturbed === node && !visited.has(target)) { - visited.add(target); - nodesToInclude.add(target); - edgesToInclude.push(edge); - queue.push({ node: target, depth: depth + 1 }); - } - }); - }); - } - - // filter nodes to include only those found within the specified degrees of separation - const filteredNodes = data.node.filter((node) => - nodesToInclude.has(node.cCRE) - ); - return { nodes: filteredNodes, edges: edgesToInclude }; - }; - - useEffect(() => { - const filteredData = filterNodesAndEdges(degree); - setElements(filteredData.nodes); - setEdges(filteredData.edges); - setScales(filteredData.edges.map((e) => e.effectSize)); - setExpressions( - 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 { - useEffect(() => { - setElements(data.node); - setEdges(data.edge); - setScales(data.edge.map((e: Edge) => e.effectSize)); - setExpressions( - 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(() => { + setElements(data.data.node); + setEdges(data.data.edge); + setScales(data.data.edge.map((e: Edge) => e.effectSize)); + setExpressions( + data.data.edge.map((e: Edge) => + e.expressionImpact === 'higher-expression' + ? 'Higher-Expression' + : 'Lower-Expression' + ) + ); + }, [data]); + console.log(scales); useEffect(() => { if ( @@ -227,7 +112,7 @@ const Graph: React.FC = ({ const allcCREs: string[] = elements.map((e) => e.cCRE); - let connect: number[][] = []; + const connect: number[][] = []; for (let i = 0; i < elements.length; i++) { connect.push([]); // does not work with Array.fill } @@ -238,23 +123,27 @@ const Graph: React.FC = ({ connect[allcCREs.indexOf(e.perturbed)].push(allcCREs.indexOf(e.target)); }); - const edgeColor = (idx: number): string => { - if (expressionType[idx] === 'Lower-Expression') return 'black'; - if (expressionType[idx] === 'Higher-Expression') return 'blue'; - return 'grey'; - }; + const createID = (index: number): string => elements[index].cCRE; + const edgeColor = (idx: number): string => + expressionType[idx] === 'Lower-Expression' ? 'black' : 'blue'; + function chooseColor(index: number): string { - const s = simple[index] as cCREClass; - return cCREConstants[s]?.color || 'grey'; + const simple = elements[index].simple as cCREClass; + return cCREConstants[simple]?.color || 'grey'; + } + + function shortHand(str: string): string { + const simple = str as cCREClass; + return cCREConstants[simple]?.label || 'n/a'; } const cy = cytoscape({ - container: document.getElementById(k), + container: document.getElementById('cy'), style: [ { selector: 'node', style: { - label: '', + label: 'data(id)', 'font-size': 15, }, }, @@ -263,6 +152,7 @@ const Graph: React.FC = ({ style: { 'line-color': '#ccc', 'curve-style': 'bezier', + 'target-arrow-shape': 'triangle', }, }, ], @@ -289,7 +179,7 @@ const Graph: React.FC = ({ // ADD NODES for (var i = 0; i < elements.length; i++) { - if (toggles[simple[i]] !== false) { + if (toggles[elements[i].simple] !== false) { cy.add({ data: { id: createID(i) }, // create name position: { @@ -300,7 +190,7 @@ const Graph: React.FC = ({ style: { // find color based on CRE 'background-color': chooseColor(i), - label: showLabels ? shortHand(simple[i]) : '', + label: shortHand(elements[i].simple), }, }); } @@ -310,12 +200,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[simple[j]] !== false) { + if (toggles[elements[j].simple] !== false) { // toggle let len = connect[j].length; for (let s = 0; s < len; s++) { if ( - toggles[simple[connect[j][s]]] !== false && // toggle + toggles[elements[connect[j][s]].simple] !== false && // toggle toggles[expressionType[j]] !== false ) { cy.add({ @@ -326,11 +216,6 @@ 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), }, @@ -344,49 +229,28 @@ const Graph: React.FC = ({ let idx = 0; cy.nodes().forEach((node: NodeSingular) => { let cre = allcCREs[idx].toString(); - let s = simple[idx].toString(); - if (data.centered && cre === data.centered.cCRE) { - node.on('mousemove', (event) => - handleMouseMove(event, { - cCRE: cre, - type: s, - centered: 'Centered Node', - }) - ); - } else { - node.on('mousemove', (event) => - handleMouseMove(event, { - cCRE: cre, - type: s, - }) - ); - } + let simple = elements[idx].simple.toString(); + node.on('mousemove', (event) => + handleMouseOver(event, { + cCRE: cre, + type: simple, + }) + ); idx++; node.on('mouseout', hideTooltip); }); - - console.log(data.edge.every((e) => e.expressionImpact)); - + console.log(allcCREs); cy.edges().forEach((edge: EdgeSingular) => { - 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('mousemove', (event) => + handleMouseOver(event, { + type: + edge.style('line-color').toString() === 'rgb(0,0,0)' + ? 'Lower-Expression' + : 'Higher-Expression', + }) + ); edge.on('mouseout', hideTooltip); }); - organize(); return () => { cy.destroy(); @@ -401,21 +265,6 @@ const Graph: React.FC = ({ hideTooltip, ]); - 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]) : '', - }); - ind++; - }); - }, [showLabels]); - // RANDOMIZE const randomize = () => { const cy = cyRef.current; @@ -453,148 +302,59 @@ const Graph: React.FC = ({ })); }; - const downloadStyle = { - ...buttonStyle, - top: '0px', - right: '5px', - }; - - const randomizeStyle = { - ...buttonStyle, - top: '45px', - right: '5px', - }; - - const organizeStyle = { - ...buttonStyle, - top: '90px', - right: '5px', - }; - - const toggleControlsStyle = { - ...buttonStyle, - top: '0px', - padding: '3px', - backgroundColor: 'white', - color: '#0095ff', - }; - - const labelStyle = { - ...buttonStyle, - top: '135px', - right: '5px', - }; - - const r = { - collapsed: { - right: '175px', - }, - uncollapsed: { - right: '2px', - }, - }; + // organize on any toggle change + useEffect(() => { + if (cyRef.current) { + organize(); + } + }, [toggles]); return ( -
-
+
- {data.centered ? ( -
- - setDegree(parseInt(e.target.value))} - /> -
- ) : null} - - {showControls && ( -
- - - - - setShowLabels(!showLabels)} - > - - e.expressionImpact)} - /> - -
- )} + Download Screenshot + - -
+ Organize + +
+
+

{title}

+
{tooltipOpen && tooltipData && ( @@ -607,22 +367,21 @@ const Graph: React.FC = ({ fontSize: '12px', }} key={Math.random()} - top={tooltipTop} - left={tooltipLeft} + top={tooltipTop || 0} + left={tooltipLeft || 0} > {tooltipData.cCRE ? ( -
+
cCRE: {tooltipData.cCRE}
Type: {tooltipData.type} - {tooltipData.centered ?
Centered Node
: null}
) : ( -
- Type: {tooltipData.type} -
+
Type: {tooltipData.type}
)} )} + +
); }; diff --git a/src/components/Graph/Legend.tsx b/src/components/Graph/Legend.tsx index 56e0e5d..cbc84c0 100644 --- a/src/components/Graph/Legend.tsx +++ b/src/components/Graph/Legend.tsx @@ -1,128 +1,49 @@ -import React, { CSSProperties, useState } from 'react'; -import { cCREClass, cCREConstants } from './constants'; +import React, { useState } from 'react'; +import { cCREConstants } from './constants'; +// import '../styles/Legend.css'; interface LegendProps { toggles: { [key: string]: boolean }; onToggle: (category: string) => void; - simpleCategories: string[]; - edgeType: boolean; } -const Legend: React.FC = ({ - toggles, - onToggle, - simpleCategories, - edgeType, -}) => { +const Legend: React.FC = ({ toggles, onToggle }) => { const [collapsed, setCollapsed] = useState(false); - const buttonStyle: CSSProperties = { - zIndex: 1000, - margin: '2px', - backgroundColor: '#0095ff', - border: '0px', - borderRadius: '3px', - color: '#fff', - cursor: 'pointer', - fontFamily: - '-apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif', - fontSize: '12px', - outline: 'none', - padding: '7px .8em', - textAlign: 'center', - textDecoration: 'none', - userSelect: 'none', - WebkitUserSelect: 'none', - whiteSpace: 'nowrap', - transition: 'background-color 0.3s, color 0.3s', - }; - - const divStyle: CSSProperties = { - position: 'absolute', - bottom: '10px', - right: '3px', - zIndex: 1000, - backgroundColor: 'white', - padding: '10px', - borderRadius: '5px', - boxShadow: '0 0 10px rgba(0,0,0,0.5)', - }; - - const d = { width: '237px' }; - const lower = 'Lower-Expression'; - const higher = 'Higher-Expression'; - - const uniqueCategories = Array.from(new Set(simpleCategories)); - return ( -
- {!collapsed && (
- {uniqueCategories.map((category) => { - const typedCategory = category as cCREClass; - const categoryData = cCREConstants[typedCategory]; - return ( -
- onToggle(category)} - /> - onToggle(category)} - > - {category} ({categoryData?.label || 'n/a'}) - -
- ); - })} + {Object.entries(cCREConstants).map(([key, value]) => ( +
+ onToggle(key)} + /> + + {key} ({value.label}) + +
+ ))}
)} - {/* 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/ScaleLegend.tsx b/src/components/Graph/ScaleLegend.tsx index f630e6c..5a22542 100644 --- a/src/components/Graph/ScaleLegend.tsx +++ b/src/components/Graph/ScaleLegend.tsx @@ -1,5 +1,5 @@ -import React, { CSSProperties, useState } from 'react'; -import GraphButton from './GraphButton'; +import React, { useState } from 'react'; +// import '../styles/ScaleLegend.css'; interface ScaleProps { scales: number[]; @@ -17,44 +17,19 @@ const ScaleLegend: React.FC = ({ scales }) => { const calculateWidth = (weight: number) => 10 * Math.log(weight * 4 + 1); // my scaling - const buttonStyle: CSSProperties = { - zIndex: 1000, - margin: '2px', - backgroundColor: '#0095ff', - border: '0px', - borderRadius: '3px', - color: '#fff', - cursor: 'pointer', - fontFamily: - '-apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif', - fontSize: '12px', - outline: 'none', - padding: '7px .8em', - textAlign: 'center', - textDecoration: 'none', - userSelect: 'none', - WebkitUserSelect: 'none', - whiteSpace: 'nowrap', - transition: 'background-color 0.3s, color 0.3s', - }; - - const divStyle: CSSProperties = { - position: 'absolute', - top: '200px', - right: '10px', - zIndex: 1000, - backgroundColor: 'white', - padding: '10px', - borderRadius: '5px', - border: '1px solid #ccc', - boxShadow: '0 0 10px rgba(0,0,0,0.5)', - }; - - const d = { - width: '230px', - }; return ( -
+
{!collapsed && ( <>

@@ -108,11 +83,9 @@ const ScaleLegend: React.FC = ({ scales }) => {

)} - setCollapsed(!collapsed)} - styles={buttonStyle} - > +
); }; diff --git a/src/components/Graph/constants.ts b/src/components/Graph/constants.ts index e6003f3..383615b 100644 --- a/src/components/Graph/constants.ts +++ b/src/components/Graph/constants.ts @@ -1,5 +1,3 @@ -import { CSSProperties } from "react"; - export const cCREConstants = { "Promoter": { label: "Pr", color: "#FF0000" }, "Distal Enhancer": { label: "D.E.", color: "#FFCD00" }, @@ -10,32 +8,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"}, - "Low DNase": {label: "Low DNase", color: "#e1e1e1"} }; export type cCREClass = keyof typeof cCREConstants; - - export const buttonStyle: CSSProperties = { - position: 'absolute', - zIndex: 1000, - margin: '2px', - border: '0px', - backgroundColor: '#0095ff', - borderRadius: '3px', - boxShadow: 'rgba(255, 255, 255, .4) 0 1px 0 0 inset', - boxSizing: 'border-box', - color: '#fff', - cursor: 'pointer', - fontFamily: - '-apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif', - fontSize: '12px', - outline: 'none', - padding: '7px .8em', - textAlign: 'center', - textDecoration: 'none', - userSelect: 'none', - WebkitUserSelect: 'none', - whiteSpace: 'nowrap', - transition: 'background-color 0.3s, color 0.3s', - }; \ No newline at end of file + \ No newline at end of file diff --git a/src/components/Graph/types.ts b/src/components/Graph/types.ts index e0c4272..e915ec1 100644 --- a/src/components/Graph/types.ts +++ b/src/components/Graph/types.ts @@ -2,20 +2,19 @@ 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 { data: { - edge: Edge[], node: Node[], centered: {cCRE: string} - }, - title?: string, - id: number | string, - width?: string, - height?: string, + data: any; edge: Edge[], node: Node[] +}, +title?: string } + \ No newline at end of file diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index 6e89c50..2e7aae4 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -1,14 +1,13 @@ import React from 'react'; import { Meta, Story } from '@storybook/react'; -import { Graph } from '../src'; -import { GraphProps } from '../src'; -import data2 from '../example/data2.json'; -import data3 from '../example/data3.json'; +import { Graph } from '../src/components/Graph'; +import { GraphProps } from '../src/components/Graph/types'; import data from '../example/data.json'; +import data2 from '../example/data2.json'; import '../src/App.css'; const meta: Meta = { - title: 'Graph', + title: 'Components/Graph', component: Graph, }; export default meta; @@ -16,30 +15,7 @@ export default meta; const Template: Story = (args) => ; export const SampleGraph = Template.bind({}); -SampleGraph.args = { - data: data2.data, - title: 'Sample Graph With No Centered cCRE', - id: 1, -}; -export const PilotDataWithCentered = Template.bind({}); -PilotDataWithCentered.args = { - data: data.data, - title: 'cCRE Impact With Pilot Data With Centered cCRE', - id: 'hello', -}; +export const GraphWithPilotData = Template.bind({}); -export const FiftyPercent = Template.bind({}); -FiftyPercent.args = { - data: data.data, - title: '50% Width and Height', - id: 2, - width: '50%', - height: '50%', -}; - -export const PilotDataWithoutCentered = Template.bind({}); -PilotDataWithoutCentered.args = { - data: data3.data, - title: 'cCRE Impact With Pilot Data Without Centered cCRE', - id: 'hi', -}; +SampleGraph.args = { data: data2, title: 'Sample Graph' }; +GraphWithPilotData.args = { data: data, title: 'cCRE Impact With Pilot Data' }; From 05fe0a01b3d78dbac3599965441d89451e6e5b58 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Tue, 11 Jun 2024 15:15:00 -0400 Subject: [PATCH 02/31] img to _ --- src/components/Graph/Graph.tsx | 2 +- stories/Graph.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index b41c287..437aada 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -45,7 +45,7 @@ const Graph: React.FC = ({ data, title }) => { // DOWNLOAD SCREENSHOT const ref = useRef(null); - const [image, takeScreenShot] = useScreenshot(); + const [_, takeScreenShot] = useScreenshot(); const downloadScreenshot = () => { if (ref.current && typeof takeScreenShot === 'function') { diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index 2e7aae4..c75e756 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Meta, Story } from '@storybook/react'; import { Graph } from '../src/components/Graph'; -import { GraphProps } from '../src/components/Graph/types'; +import { GraphProps } from '../src/components/Graph'; import data from '../example/data.json'; import data2 from '../example/data2.json'; import '../src/App.css'; From 62c709c4407b5a3f13d873bbc686ea8a5c90bb09 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Wed, 12 Jun 2024 13:48:58 -0400 Subject: [PATCH 03/31] control buttons --- src/components/Graph/Graph.tsx | 103 ++++++++++++++++++++++----------- src/components/Graph/types.ts | 6 +- stories/Graph.stories.tsx | 20 +++++-- 3 files changed, 88 insertions(+), 41 deletions(-) diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 437aada..d99ed07 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -23,10 +23,16 @@ const download = (image: string, { name = 'img', extension = 'jpg' } = {}) => { a.click(); }; -const Graph: React.FC = ({ data, title }) => { +const Graph: React.FC = ({ + data, + title, + width = '100%', + height = '100%', +}) => { const cyRef = useRef(null); // state hooks + const [showControls, setShowControls] = useState(true); const [elements, setElements] = useState([]); const [scales, setScales] = useState([]); const [expressionType, setExpressions] = useState([]); @@ -43,6 +49,10 @@ const Graph: React.FC = ({ data, title }) => { 'Higher-Expression': true, }); + const toggleControls = () => { + setShowControls(!showControls); + }; + // DOWNLOAD SCREENSHOT const ref = useRef(null); const [_, takeScreenShot] = useScreenshot(); @@ -99,7 +109,6 @@ const Graph: React.FC = ({ data, title }) => { ) ); }, [data]); - console.log(scales); useEffect(() => { if ( @@ -239,7 +248,7 @@ const Graph: React.FC = ({ data, title }) => { idx++; node.on('mouseout', hideTooltip); }); - console.log(allcCREs); + cy.edges().forEach((edge: EdgeSingular) => { edge.on('mousemove', (event) => handleMouseOver(event, { @@ -310,43 +319,71 @@ const Graph: React.FC = ({ data, title }) => { }, [toggles]); return ( -
- +
+ {showControls && ( + <> + + + + + + + + + )} - +

{title}

@@ -380,8 +417,6 @@ const Graph: React.FC = ({ data, title }) => { )} )} - -
); }; diff --git a/src/components/Graph/types.ts b/src/components/Graph/types.ts index e915ec1..fac4420 100644 --- a/src/components/Graph/types.ts +++ b/src/components/Graph/types.ts @@ -14,7 +14,9 @@ export interface Edge { export interface GraphProps { data: { data: any; edge: Edge[], node: Node[] -}, -title?: string + }, + title?: string, + width?: string, + height?: string, } \ No newline at end of file diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index c75e756..93cd9ce 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Meta, Story } from '@storybook/react'; -import { Graph } from '../src/components/Graph'; -import { GraphProps } from '../src/components/Graph'; -import data from '../example/data.json'; +import { Graph } from '../src'; +import { GraphProps } from '../src'; import data2 from '../example/data2.json'; +import data from '../example/data.json'; import '../src/App.css'; const meta: Meta = { @@ -15,7 +15,17 @@ export default meta; const Template: Story = (args) => ; export const SampleGraph = Template.bind({}); +SampleGraph.args = { data: data2, title: 'Sample Graph' }; export const GraphWithPilotData = Template.bind({}); +GraphWithPilotData.args = { + data: data, + title: 'cCRE Impact With Pilot Data', +}; -SampleGraph.args = { data: data2, title: 'Sample Graph' }; -GraphWithPilotData.args = { data: data, title: 'cCRE Impact With Pilot Data' }; +export const FiftyPercent = Template.bind({}); +FiftyPercent.args = { + data: data, + title: '50% Width and Height', + width: '50%', + height: '50%', +}; From 72827f5eafaf36c7fa3b5f40d8d0a7c5fb76c776 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 13 Jun 2024 10:52:51 -0400 Subject: [PATCH 04/31] working seperate graphs --- src/components/Graph/Graph.tsx | 19 +++++++++++-------- src/components/Graph/types.ts | 1 + stories/Graph.stories.tsx | 4 +++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index d99ed07..5e36974 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -26,6 +26,7 @@ const download = (image: string, { name = 'img', extension = 'jpg' } = {}) => { const Graph: React.FC = ({ data, title, + id, width = '100%', height = '100%', }) => { @@ -97,6 +98,9 @@ const Graph: React.FC = ({ }); }; + // each graph needs a unique id + let k = 'cy-' + id; + useEffect(() => { setElements(data.data.node); setEdges(data.data.edge); @@ -147,7 +151,7 @@ const Graph: React.FC = ({ } const cy = cytoscape({ - container: document.getElementById('cy'), + container: document.getElementById(k), style: [ { selector: 'node', @@ -365,7 +369,6 @@ const Graph: React.FC = ({ > Organize - @@ -384,13 +387,13 @@ const Graph: React.FC = ({ {showControls ? 'Hide Controls' : 'Show Controls'} -
-
-

{title}

+
+
+

{title}

@@ -404,8 +407,8 @@ const Graph: React.FC = ({ fontSize: '12px', }} key={Math.random()} - top={tooltipTop || 0} - left={tooltipLeft || 0} + top={tooltipTop} + left={tooltipLeft} > {tooltipData.cCRE ? (
diff --git a/src/components/Graph/types.ts b/src/components/Graph/types.ts index fac4420..d6aa115 100644 --- a/src/components/Graph/types.ts +++ b/src/components/Graph/types.ts @@ -16,6 +16,7 @@ export interface Edge { data: any; edge: Edge[], node: Node[] }, title?: string, + id: number | string, width?: string, height?: string, } diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index 93cd9ce..19b65f0 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -15,17 +15,19 @@ export default meta; const Template: Story = (args) => ; export const SampleGraph = Template.bind({}); -SampleGraph.args = { data: data2, title: 'Sample Graph' }; +SampleGraph.args = { data: data2, title: 'Sample Graph', id: 1 }; export const GraphWithPilotData = Template.bind({}); GraphWithPilotData.args = { data: data, title: 'cCRE Impact With Pilot Data', + id: 'hello', }; export const FiftyPercent = Template.bind({}); FiftyPercent.args = { data: data, title: '50% Width and Height', + id: 2, width: '50%', height: '50%', }; From 185fee49be96b04a5a9e8a8fc963524699674109 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 13 Jun 2024 15:44:09 -0400 Subject: [PATCH 05/31] fix CSS scope --- src/components/Graph/Graph.css | 57 ----------- src/components/Graph/Graph.tsx | 145 +++++++++++++++++---------- src/components/Graph/Legend.tsx | 32 +++++- src/components/Graph/ScaleLegend.tsx | 32 +++++- src/components/Graph/constants.ts | 27 ++++- src/components/Graph/types.ts | 1 - 6 files changed, 171 insertions(+), 123 deletions(-) delete mode 100644 src/components/Graph/Graph.css diff --git a/src/components/Graph/Graph.css b/src/components/Graph/Graph.css deleted file mode 100644 index 485049c..0000000 --- a/src/components/Graph/Graph.css +++ /dev/null @@ -1,57 +0,0 @@ -body { - font-family: helvetica; - font-size: 14px; - } - - #cy { - width: 100%; - height: 90%; - z-index: 999; - } - - h1 { - opacity: 0.5; - font-size: 1em; - margin: 0; - } - - .c { - width: "100%"; - height: "100vh"; - position: "relative" - } - - button { - margin: 2px; - background-color: #0095ff; - border: 0px; - border-radius: 3px; - box-shadow: rgba(255, 255, 255, .4) 0 1px 0 0 inset; - box-sizing: border-box; - color: #fff; - cursor: pointer; - font-family: -apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif; - font-size: 12px; - outline: none; - padding: 7px .8em; - position: relative; - text-align: center; - text-decoration: none; - user-select: none; - -webkit-user-select: none; - white-space: nowrap; - } - -button:hover, -button:focus { - background-color: #07c; -} - -button:focus { - box-shadow: 0 0 0 4px rgba(0, 149, 255, .15); -} - -button:active { - background-color: #0064bd; - box-shadow: none; -} diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 5e36974..3719fc2 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -1,13 +1,14 @@ -import React, { useRef, useEffect, useState } from 'react'; +import React, { useRef, useEffect, useState, CSSProperties } from 'react'; import cytoscape, { Core, EdgeSingular, NodeSingular } from 'cytoscape'; import coseBilkent from 'cytoscape-cose-bilkent'; import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip'; import { useScreenshot } from 'use-react-screenshot'; -import { cCREConstants, cCREClass } from './constants'; -import './Graph.css'; +import { cCREConstants, cCREClass, buttonStyle } from './constants'; +// import './Graph.css'; import { GraphProps, Node, Edge } from './types'; import Legend from './Legend'; import ScaleLegend from './ScaleLegend'; +import GraphButton from './GraphButton'; cytoscape.use(coseBilkent); @@ -322,6 +323,47 @@ const Graph: React.FC = ({ } }, [toggles]); + const downloadStyle = { + ...buttonStyle, + top: '5px', + right: '5px', + }; + + const randomizeStyle = { + ...buttonStyle, + top: '50px', + right: '5px', + }; + + const organizeStyle = { + ...buttonStyle, + top: '95px', + right: '5px', + }; + + const toggleControlsStyle = { + ...buttonStyle, + top: '20px', + left: '0px', + marginTop: '15px', + }; + + function mouseover(this: any) { + const c = document.getElementById(this.id()); + if (c === null) { + return null; + } + c.style.backgroundColor = '#07c'; + } + + function mouseout() { + const c = document.getElementById('b1'); + if (c === null) { + return null; + } + c.style.backgroundColor = '#0095ff'; + } + return (
= ({ height: height, position: 'relative', overflow: 'hidden', + fontSize: '14px', + fontFamily: 'helvetica', }} > {showControls && ( <> - - - - + + + + + )} - - -
-
-

{title}

+
+

{title}

{tooltipOpen && tooltipData && ( @@ -411,12 +444,14 @@ const Graph: React.FC = ({ left={tooltipLeft} > {tooltipData.cCRE ? ( -
+
cCRE: {tooltipData.cCRE}
Type: {tooltipData.type}
) : ( -
Type: {tooltipData.type}
+
+ Type: {tooltipData.type} +
)} )} diff --git a/src/components/Graph/Legend.tsx b/src/components/Graph/Legend.tsx index cbc84c0..1108958 100644 --- a/src/components/Graph/Legend.tsx +++ b/src/components/Graph/Legend.tsx @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; +import React, { CSSProperties, useState } from 'react'; import { cCREConstants } from './constants'; +import GraphButton from './GraphButton'; // import '../styles/Legend.css'; interface LegendProps { @@ -10,6 +11,27 @@ interface LegendProps { const Legend: React.FC = ({ toggles, onToggle }) => { const [collapsed, setCollapsed] = useState(false); + const buttonStyle: CSSProperties = { + zIndex: 1000, + margin: '2px', + backgroundColor: '#0095ff', + border: '0px', + borderRadius: '3px', + color: '#fff', + cursor: 'pointer', + fontFamily: + '-apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif', + fontSize: '12px', + outline: 'none', + padding: '7px .8em', + textAlign: 'center', + textDecoration: 'none', + userSelect: 'none', + WebkitUserSelect: 'none', + whiteSpace: 'nowrap', + transition: 'background-color 0.3s, color 0.3s', + }; + return (
= ({ toggles, onToggle }) => { boxShadow: '0 0 10px rgba(0,0,0,0.5)', }} > - + setCollapsed(!collapsed)} + styles={buttonStyle} + > {!collapsed && (
diff --git a/src/components/Graph/ScaleLegend.tsx b/src/components/Graph/ScaleLegend.tsx index 5a22542..f07351a 100644 --- a/src/components/Graph/ScaleLegend.tsx +++ b/src/components/Graph/ScaleLegend.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -// import '../styles/ScaleLegend.css'; +import React, { CSSProperties, useState } from 'react'; +import GraphButton from './GraphButton'; interface ScaleProps { scales: number[]; @@ -17,6 +17,26 @@ const ScaleLegend: React.FC = ({ scales }) => { const calculateWidth = (weight: number) => 10 * Math.log(weight * 4 + 1); // my scaling + const buttonStyle: CSSProperties = { + zIndex: 1000, + margin: '2px', + backgroundColor: '#0095ff', + border: '0px', + borderRadius: '3px', + color: '#fff', + cursor: 'pointer', + fontFamily: + '-apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif', + fontSize: '12px', + outline: 'none', + padding: '7px .8em', + textAlign: 'center', + textDecoration: 'none', + userSelect: 'none', + WebkitUserSelect: 'none', + whiteSpace: 'nowrap', + transition: 'background-color 0.3s, color 0.3s', + }; return (
= ({ scales }) => {
)} - + setCollapsed(!collapsed)} + styles={buttonStyle} + >
); }; diff --git a/src/components/Graph/constants.ts b/src/components/Graph/constants.ts index 383615b..95034a3 100644 --- a/src/components/Graph/constants.ts +++ b/src/components/Graph/constants.ts @@ -1,3 +1,5 @@ +import { CSSProperties } from "react"; + export const cCREConstants = { "Promoter": { label: "Pr", color: "#FF0000" }, "Distal Enhancer": { label: "D.E.", color: "#FFCD00" }, @@ -11,4 +13,27 @@ export const cCREConstants = { }; export type cCREClass = keyof typeof cCREConstants; - \ No newline at end of file + + export const buttonStyle: CSSProperties = { + position: 'absolute', + zIndex: 1000, + margin: '2px', + border: '0px', + backgroundColor: '#0095ff', + borderRadius: '3px', + boxShadow: 'rgba(255, 255, 255, .4) 0 1px 0 0 inset', + boxSizing: 'border-box', + color: '#fff', + cursor: 'pointer', + fontFamily: + '-apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif', + fontSize: '12px', + outline: 'none', + padding: '7px .8em', + textAlign: 'center', + textDecoration: 'none', + userSelect: 'none', + WebkitUserSelect: 'none', + whiteSpace: 'nowrap', + transition: 'background-color 0.3s, color 0.3s', + }; \ No newline at end of file diff --git a/src/components/Graph/types.ts b/src/components/Graph/types.ts index d6aa115..5f990cd 100644 --- a/src/components/Graph/types.ts +++ b/src/components/Graph/types.ts @@ -20,4 +20,3 @@ export interface Edge { width?: string, height?: string, } - \ No newline at end of file From c4da4ce26cd4834c902cc04da25d50938b4d3d96 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 13 Jun 2024 15:49:07 -0400 Subject: [PATCH 06/31] typing fixes --- src/components/Graph/Graph.tsx | 20 ++------------------ src/components/Graph/GraphButton.tsx | 2 +- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 3719fc2..397f9fe 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useEffect, useState, CSSProperties } from 'react'; +import React, { useRef, useEffect, useState } from 'react'; import cytoscape, { Core, EdgeSingular, NodeSingular } from 'cytoscape'; import coseBilkent from 'cytoscape-cose-bilkent'; import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip'; @@ -348,22 +348,6 @@ const Graph: React.FC = ({ marginTop: '15px', }; - function mouseover(this: any) { - const c = document.getElementById(this.id()); - if (c === null) { - return null; - } - c.style.backgroundColor = '#07c'; - } - - function mouseout() { - const c = document.getElementById('b1'); - if (c === null) { - return null; - } - c.style.backgroundColor = '#0095ff'; - } - return (
= ({ Date: Thu, 13 Jun 2024 16:25:38 -0400 Subject: [PATCH 07/31] collapse --- src/components/Graph/Graph.tsx | 36 ++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 397f9fe..6afb5b4 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -9,6 +9,8 @@ import { GraphProps, Node, Edge } from './types'; import Legend from './Legend'; import ScaleLegend from './ScaleLegend'; import GraphButton from './GraphButton'; +import KeyboardDoubleArrowRightIcon from '@mui/icons-material/KeyboardDoubleArrowRight'; +import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrowLeft'; cytoscape.use(coseBilkent); @@ -343,9 +345,19 @@ const Graph: React.FC = ({ const toggleControlsStyle = { ...buttonStyle, - top: '20px', - left: '0px', - marginTop: '15px', + top: '5px', + padding: '3px', + backgroundColor: 'white', + color: '#0095ff', + }; + + const r = { + collapsed: { + right: '175px', + }, + uncollapsed: { + right: '2px', + }, }; return ( @@ -383,11 +395,19 @@ const Graph: React.FC = ({ )} - +
Date: Fri, 14 Jun 2024 13:09:18 -0400 Subject: [PATCH 08/31] styling and data fixes --- src/components/Graph/Graph.tsx | 17 ++++++++++------ src/components/Graph/Legend.tsx | 29 +++++++++++++++------------- src/components/Graph/ScaleLegend.tsx | 29 ++++++++++++++++------------ src/components/Graph/types.ts | 2 +- stories/Graph.stories.tsx | 6 +++--- 5 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 6afb5b4..846250c 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -105,11 +105,11 @@ const Graph: React.FC = ({ let k = 'cy-' + id; useEffect(() => { - setElements(data.data.node); - setEdges(data.data.edge); - setScales(data.data.edge.map((e: Edge) => e.effectSize)); + setElements(data.node); + setEdges(data.edge); + setScales(data.edge.map((e: Edge) => e.effectSize)); setExpressions( - data.data.edge.map((e: Edge) => + data.edge.map((e: Edge) => e.expressionImpact === 'higher-expression' ? 'Higher-Expression' : 'Lower-Expression' @@ -372,7 +372,12 @@ const Graph: React.FC = ({ }} > {showControls && ( - <> +
= ({ - +
)} {!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 { From 1917ab318113f4fc6e83721bbe0d19ee0cdd48f1 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 27 Jun 2024 14:44:33 -0400 Subject: [PATCH 12/31] changes --- src/components/Graph/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Graph/constants.ts b/src/components/Graph/constants.ts index d457cd4..fcda092 100644 --- a/src/components/Graph/constants.ts +++ b/src/components/Graph/constants.ts @@ -11,6 +11,7 @@ export const cCREConstants = { "Lower-Expression": { label: "Edge", color: "#000000" }, "Higher-Expression": { label: "Edge", color: "#0000FF" }, "Edge": { label: "Edge", color: "grey"}, + "Low DNase": { label: "Low DNase", color: "#e1e1e1"}, }; export type cCREClass = keyof typeof cCREConstants; From 3ccc32f745fda20dd4c70a94f9e83356f0a35014 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Mon, 1 Jul 2024 11:17:42 -0400 Subject: [PATCH 13/31] 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%', From 764d9a3ae321c69fbf36e99f1dfe9e069493dc5b Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Mon, 1 Jul 2024 12:58:29 -0400 Subject: [PATCH 14/31] s --- .DS_Store | Bin 8196 -> 8196 bytes stories/Graph.stories.tsx | 14 +++++--------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.DS_Store b/.DS_Store index fe3948b8f730f84acd944e400f4ab2a909775c51..fbbbe54f33fef3304ea8de858a96b83231b9fb34 100644 GIT binary patch delta 48 zcmZp1XmOa}&nUDpU^hRb&}JTi)nl|h#whan%tpL|+OR#opm z7%+eU51L9nhAf6+pxV62g5tuQaFv@c2qv)Vaxzo`4J=~FW6*;c=$VtBoRpKF1T;zj eh#eUi82KQ^Pb@Up%r5bbWplI0S|(Dp>H+{OyFm;9 diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index 06470f4..c5c8630 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -9,7 +9,7 @@ import { Edge, Node } from '../src/components/Graph/types'; import '../src/App.css'; const meta: Meta = { - title: 'Components/Graph', + title: 'Graph', component: Graph, }; export default meta; @@ -28,27 +28,23 @@ const convertNodeData = (data: { 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' + from: edge.perturbed, + to: edge.target, effectSize: edge.effectSize, - ...(edge.expressionImpact && { expressionImpact: edge.expressionImpact }), // Only include expressionImpact if it exists + ...(edge.expressionImpact && { expressionImpact: edge.expressionImpact }), })); - // 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; } @@ -72,7 +68,7 @@ PilotDataWithCentered.args = { export const FiftyPercent = Template.bind({}); FiftyPercent.args = { - data: convertNodeData(data), + data: data.data, title: '50% Width and Height', id: 2, width: '50%', From 28645261605a29624e6d9e32294e1517f465f629 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Mon, 1 Jul 2024 14:37:56 -0400 Subject: [PATCH 15/31] getLabel --- example/data.json | 232 +++++++++--------- example/data2.json | 8 +- example/data3.json | 349 ++++++++++++--------------- src/components/Graph/Graph.tsx | 24 +- src/components/Graph/ScaleLegend.tsx | 20 +- src/components/Graph/types.ts | 32 +-- stories/Graph.stories.tsx | 52 +--- 7 files changed, 312 insertions(+), 405 deletions(-) diff --git a/example/data.json b/example/data.json index 6a96679..823bca1 100644 --- a/example/data.json +++ b/example/data.json @@ -2,405 +2,405 @@ "data": { "edge": [ { - "perturbed": "EH38E3291096", - "target": "EH38E1939823", + "from": "EH38E3291096", + "to": "EH38E1939823", "effectSize": 0.1134, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E1939823", - "target": "EH38E1939823", + "from": "EH38E1939823", + "to": "EH38E1939823", "effectSize": 0.1933, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291121", - "target": "EH38E1939823", + "from": "EH38E3291121", + "to": "EH38E1939823", "effectSize": 0.0545, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291122", - "target": "EH38E1939823", + "from": "EH38E3291122", + "to": "EH38E1939823", "effectSize": 0.0665, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E1939823", + "from": "EH38E3291358", + "to": "EH38E1939823", "effectSize": 0.0674, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291271", - "target": "EH38E3291279", + "from": "EH38E3291271", + "to": "EH38E3291279", "effectSize": 0.0381, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291279", - "target": "EH38E3291279", + "from": "EH38E3291279", + "to": "EH38E3291279", "effectSize": 0.1667, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E3291279", + "from": "EH38E3291358", + "to": "EH38E3291279", "effectSize": 0.0478, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291174", - "target": "EH38E3291410", + "from": "EH38E3291174", + "to": "EH38E3291410", "effectSize": 0.0504, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4193273", - "target": "EH38E3291410", + "from": "EH38E4193273", + "to": "EH38E3291410", "effectSize": 0.0507, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E3291410", + "from": "EH38E3291358", + "to": "EH38E3291410", "effectSize": 0.0895, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291392", - "target": "EH38E3291410", + "from": "EH38E3291392", + "to": "EH38E3291410", "effectSize": 0.0352, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291410", - "target": "EH38E3291410", + "from": "EH38E3291410", + "to": "EH38E3291410", "effectSize": 0.2538, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291174", - "target": "EH38E4193228", + "from": "EH38E3291174", + "to": "EH38E4193228", "effectSize": 0.0211, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291218", - "target": "EH38E4193228", + "from": "EH38E3291218", + "to": "EH38E4193228", "effectSize": 0.0477, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291222", - "target": "EH38E4193228", + "from": "EH38E3291222", + "to": "EH38E4193228", "effectSize": 0.0627, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291226", - "target": "EH38E4193228", + "from": "EH38E3291226", + "to": "EH38E4193228", "effectSize": 0.0448, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4193211", - "target": "EH38E4193228", + "from": "EH38E4193211", + "to": "EH38E4193228", "effectSize": 0.0454, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291232", - "target": "EH38E4193228", + "from": "EH38E3291232", + "to": "EH38E4193228", "effectSize": 0.0311, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291249", - "target": "EH38E4193228", + "from": "EH38E3291249", + "to": "EH38E4193228", "effectSize": 0.0593, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3291263", - "target": "EH38E4193228", + "from": "EH38E3291263", + "to": "EH38E4193228", "effectSize": 0.1136, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291271", - "target": "EH38E4193228", + "from": "EH38E3291271", + "to": "EH38E4193228", "effectSize": 0.4097, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291279", - "target": "EH38E4193228", + "from": "EH38E3291279", + "to": "EH38E4193228", "effectSize": 0.1077, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4193243", - "target": "EH38E4193228", + "from": "EH38E4193243", + "to": "EH38E4193228", "effectSize": 0.026, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3291318", - "target": "EH38E4193228", + "from": "EH38E3291318", + "to": "EH38E4193228", "effectSize": 0.0297, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E4193273", - "target": "EH38E4193228", + "from": "EH38E4193273", + "to": "EH38E4193228", "effectSize": 0.0405, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E4193228", + "from": "EH38E3291358", + "to": "EH38E4193228", "effectSize": 0.0918, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291779", - "target": "EH38E4193228", + "from": "EH38E3291779", + "to": "EH38E4193228", "effectSize": 0.0263, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E4193467", - "target": "EH38E4193228", + "from": "EH38E4193467", + "to": "EH38E4193228", "effectSize": 0.0374, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E1960374", - "target": "EH38E1960374", + "from": "EH38E1960374", + "to": "EH38E1960374", "effectSize": 0.1853, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3312736", - "target": "EH38E1960374", + "from": "EH38E3312736", + "to": "EH38E1960374", "effectSize": 0.0698, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E1960377", - "target": "EH38E1960374", + "from": "EH38E1960377", + "to": "EH38E1960374", "effectSize": 0.1479, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4201343", - "target": "EH38E1960374", + "from": "EH38E4201343", + "to": "EH38E1960374", "effectSize": 0.1042, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E1960374", - "target": "EH38E3312774", + "from": "EH38E1960374", + "to": "EH38E3312774", "effectSize": 0.143, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3312736", - "target": "EH38E3312774", + "from": "EH38E3312736", + "to": "EH38E3312774", "effectSize": 0.0864, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E1960377", - "target": "EH38E3312774", + "from": "EH38E1960377", + "to": "EH38E3312774", "effectSize": 0.1641, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E4201343", - "target": "EH38E3312774", + "from": "EH38E4201343", + "to": "EH38E3312774", "effectSize": 0.0652, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3312746", - "target": "EH38E3312774", + "from": "EH38E3312746", + "to": "EH38E3312774", "effectSize": 0.0835, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3312765", - "target": "EH38E3312774", + "from": "EH38E3312765", + "to": "EH38E3312774", "effectSize": 0.0386, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3312774", - "target": "EH38E3312774", + "from": "EH38E3312774", + "to": "EH38E3312774", "effectSize": 0.3507, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3312787", - "target": "EH38E3312774", + "from": "EH38E3312787", + "to": "EH38E3312774", "effectSize": 0.0959, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E1940335", - "target": "EH38E3291664", + "from": "EH38E1940335", + "to": "EH38E3291664", "effectSize": 0.0701, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291664", - "target": "EH38E3291664", + "from": "EH38E3291664", + "to": "EH38E3291664", "effectSize": 0.3563, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291668", - "target": "EH38E3291664", + "from": "EH38E3291668", + "to": "EH38E3291664", "effectSize": 0.1181, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291318", - "target": "EH38E3291358", + "from": "EH38E3291318", + "to": "EH38E3291358", "effectSize": 0.0369, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291346", - "target": "EH38E3291358", + "from": "EH38E3291346", + "to": "EH38E3291358", "effectSize": 0.108, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4193273", - "target": "EH38E3291358", + "from": "EH38E4193273", + "to": "EH38E3291358", "effectSize": 0.2005, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E3291358", + "from": "EH38E3291358", + "to": "EH38E3291358", "effectSize": 0.441, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291364", - "target": "EH38E3291358", + "from": "EH38E3291364", + "to": "EH38E3291358", "effectSize": 0.0862, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291374", - "target": "EH38E3291358", + "from": "EH38E3291374", + "to": "EH38E3291358", "effectSize": 0.0278, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3291664", - "target": "EH38E3291358", + "from": "EH38E3291664", + "to": "EH38E3291358", "effectSize": 0.0291, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E1939855", - "target": "EH38E3291249", + "from": "EH38E1939855", + "to": "EH38E3291249", "effectSize": 0.0684, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E4193211", - "target": "EH38E3291249", + "from": "EH38E4193211", + "to": "EH38E3291249", "effectSize": 0.0354, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291232", - "target": "EH38E3291249", + "from": "EH38E3291232", + "to": "EH38E3291249", "effectSize": 0.0767, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291244", - "target": "EH38E3291249", + "from": "EH38E3291244", + "to": "EH38E3291249", "effectSize": 0.0883, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291249", - "target": "EH38E3291249", + "from": "EH38E3291249", + "to": "EH38E3291249", "effectSize": 0.1514, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E3291249", + "from": "EH38E3291358", + "to": "EH38E3291249", "effectSize": 0.0378, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291664", - "target": "EH38E3291249", + "from": "EH38E3291664", + "to": "EH38E3291249", "effectSize": 0.0276, "expressionImpact": "higher-expression" diff --git a/example/data2.json b/example/data2.json index d8c1f96..6d20376 100644 --- a/example/data2.json +++ b/example/data2.json @@ -2,13 +2,13 @@ "data": { "edge": [ { - "perturbed": "node_1", - "target": "node_2", + "from": "node_1", + "to": "node_2", "effectSize": 0.1134 }, { - "perturbed": "node_3", - "target": "node_2", + "from": "node_3", + "to": "node_2", "effectSize": 0.5 } ], diff --git a/example/data3.json b/example/data3.json index 2a8936d..210b340 100644 --- a/example/data3.json +++ b/example/data3.json @@ -2,405 +2,405 @@ "data": { "edge": [ { - "perturbed": "EH38E3291096", - "target": "EH38E1939823", + "from": "EH38E3291096", + "to": "EH38E1939823", "effectSize": 0.1134, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E1939823", - "target": "EH38E1939823", + "from": "EH38E1939823", + "to": "EH38E1939823", "effectSize": 0.1933, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291121", - "target": "EH38E1939823", + "from": "EH38E3291121", + "to": "EH38E1939823", "effectSize": 0.0545, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291122", - "target": "EH38E1939823", + "from": "EH38E3291122", + "to": "EH38E1939823", "effectSize": 0.0665, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E1939823", + "from": "EH38E3291358", + "to": "EH38E1939823", "effectSize": 0.0674, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291271", - "target": "EH38E3291279", + "from": "EH38E3291271", + "to": "EH38E3291279", "effectSize": 0.0381, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291279", - "target": "EH38E3291279", + "from": "EH38E3291279", + "to": "EH38E3291279", "effectSize": 0.1667, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E3291279", + "from": "EH38E3291358", + "to": "EH38E3291279", "effectSize": 0.0478, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291174", - "target": "EH38E3291410", + "from": "EH38E3291174", + "to": "EH38E3291410", "effectSize": 0.0504, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4193273", - "target": "EH38E3291410", + "from": "EH38E4193273", + "to": "EH38E3291410", "effectSize": 0.0507, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E3291410", + "from": "EH38E3291358", + "to": "EH38E3291410", "effectSize": 0.0895, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291392", - "target": "EH38E3291410", + "from": "EH38E3291392", + "to": "EH38E3291410", "effectSize": 0.0352, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291410", - "target": "EH38E3291410", + "from": "EH38E3291410", + "to": "EH38E3291410", "effectSize": 0.2538, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291174", - "target": "EH38E4193228", + "from": "EH38E3291174", + "to": "EH38E4193228", "effectSize": 0.0211, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291218", - "target": "EH38E4193228", + "from": "EH38E3291218", + "to": "EH38E4193228", "effectSize": 0.0477, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291222", - "target": "EH38E4193228", + "from": "EH38E3291222", + "to": "EH38E4193228", "effectSize": 0.0627, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291226", - "target": "EH38E4193228", + "from": "EH38E3291226", + "to": "EH38E4193228", "effectSize": 0.0448, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4193211", - "target": "EH38E4193228", + "from": "EH38E4193211", + "to": "EH38E4193228", "effectSize": 0.0454, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291232", - "target": "EH38E4193228", + "from": "EH38E3291232", + "to": "EH38E4193228", "effectSize": 0.0311, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291249", - "target": "EH38E4193228", + "from": "EH38E3291249", + "to": "EH38E4193228", "effectSize": 0.0593, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3291263", - "target": "EH38E4193228", + "from": "EH38E3291263", + "to": "EH38E4193228", "effectSize": 0.1136, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291271", - "target": "EH38E4193228", + "from": "EH38E3291271", + "to": "EH38E4193228", "effectSize": 0.4097, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291279", - "target": "EH38E4193228", + "from": "EH38E3291279", + "to": "EH38E4193228", "effectSize": 0.1077, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4193243", - "target": "EH38E4193228", + "from": "EH38E4193243", + "to": "EH38E4193228", "effectSize": 0.026, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3291318", - "target": "EH38E4193228", + "from": "EH38E3291318", + "to": "EH38E4193228", "effectSize": 0.0297, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E4193273", - "target": "EH38E4193228", + "from": "EH38E4193273", + "to": "EH38E4193228", "effectSize": 0.0405, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E4193228", + "from": "EH38E3291358", + "to": "EH38E4193228", "effectSize": 0.0918, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291779", - "target": "EH38E4193228", + "from": "EH38E3291779", + "to": "EH38E4193228", "effectSize": 0.0263, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E4193467", - "target": "EH38E4193228", + "from": "EH38E4193467", + "to": "EH38E4193228", "effectSize": 0.0374, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E1960374", - "target": "EH38E1960374", + "from": "EH38E1960374", + "to": "EH38E1960374", "effectSize": 0.1853, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3312736", - "target": "EH38E1960374", + "from": "EH38E3312736", + "to": "EH38E1960374", "effectSize": 0.0698, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E1960377", - "target": "EH38E1960374", + "from": "EH38E1960377", + "to": "EH38E1960374", "effectSize": 0.1479, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4201343", - "target": "EH38E1960374", + "from": "EH38E4201343", + "to": "EH38E1960374", "effectSize": 0.1042, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E1960374", - "target": "EH38E3312774", + "from": "EH38E1960374", + "to": "EH38E3312774", "effectSize": 0.143, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3312736", - "target": "EH38E3312774", + "from": "EH38E3312736", + "to": "EH38E3312774", "effectSize": 0.0864, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E1960377", - "target": "EH38E3312774", + "from": "EH38E1960377", + "to": "EH38E3312774", "effectSize": 0.1641, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E4201343", - "target": "EH38E3312774", + "from": "EH38E4201343", + "to": "EH38E3312774", "effectSize": 0.0652, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3312746", - "target": "EH38E3312774", + "from": "EH38E3312746", + "to": "EH38E3312774", "effectSize": 0.0835, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3312765", - "target": "EH38E3312774", + "from": "EH38E3312765", + "to": "EH38E3312774", "effectSize": 0.0386, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3312774", - "target": "EH38E3312774", + "from": "EH38E3312774", + "to": "EH38E3312774", "effectSize": 0.3507, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3312787", - "target": "EH38E3312774", + "from": "EH38E3312787", + "to": "EH38E3312774", "effectSize": 0.0959, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E1940335", - "target": "EH38E3291664", + "from": "EH38E1940335", + "to": "EH38E3291664", "effectSize": 0.0701, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291664", - "target": "EH38E3291664", + "from": "EH38E3291664", + "to": "EH38E3291664", "effectSize": 0.3563, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291668", - "target": "EH38E3291664", + "from": "EH38E3291668", + "to": "EH38E3291664", "effectSize": 0.1181, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291318", - "target": "EH38E3291358", + "from": "EH38E3291318", + "to": "EH38E3291358", "effectSize": 0.0369, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291346", - "target": "EH38E3291358", + "from": "EH38E3291346", + "to": "EH38E3291358", "effectSize": 0.108, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E4193273", - "target": "EH38E3291358", + "from": "EH38E4193273", + "to": "EH38E3291358", "effectSize": 0.2005, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E3291358", + "from": "EH38E3291358", + "to": "EH38E3291358", "effectSize": 0.441, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291364", - "target": "EH38E3291358", + "from": "EH38E3291364", + "to": "EH38E3291358", "effectSize": 0.0862, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291374", - "target": "EH38E3291358", + "from": "EH38E3291374", + "to": "EH38E3291358", "effectSize": 0.0278, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E3291664", - "target": "EH38E3291358", + "from": "EH38E3291664", + "to": "EH38E3291358", "effectSize": 0.0291, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E1939855", - "target": "EH38E3291249", + "from": "EH38E1939855", + "to": "EH38E3291249", "effectSize": 0.0684, "expressionImpact": "higher-expression" }, { - "perturbed": "EH38E4193211", - "target": "EH38E3291249", + "from": "EH38E4193211", + "to": "EH38E3291249", "effectSize": 0.0354, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291232", - "target": "EH38E3291249", + "from": "EH38E3291232", + "to": "EH38E3291249", "effectSize": 0.0767, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291244", - "target": "EH38E3291249", + "from": "EH38E3291244", + "to": "EH38E3291249", "effectSize": 0.0883, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291249", - "target": "EH38E3291249", + "from": "EH38E3291249", + "to": "EH38E3291249", "effectSize": 0.1514, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291358", - "target": "EH38E3291249", + "from": "EH38E3291358", + "to": "EH38E3291249", "effectSize": 0.0378, "expressionImpact": "lower-expression" }, { - "perturbed": "EH38E3291664", - "target": "EH38E3291249", + "from": "EH38E3291664", + "to": "EH38E3291249", "effectSize": 0.0276, "expressionImpact": "higher-expression" @@ -409,198 +409,159 @@ "node": [ { "id": "EH38E1939823", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E1939855", - "category": "CA-CTCF", - "simple": "Chromatin Accessible + CTCF" + "category": "CA-CTCF" }, { "id": "EH38E1940335", - "category": "dELS", - "simple": "Distal Enhancer" + "category": "dELS" }, { "id": "EH38E1960374", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E1960377", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "id": "EH38E3291096", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291121", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291122", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291174", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291218", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291222", - "category": "dELS", - "simple": "Distal Enhancer" + "category": "dELS" }, { "id": "EH38E3291226", - "category": "dELS", - "simple": "Distal Enhancer" + "category": "dELS" }, { "id": "EH38E3291232", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "id": "EH38E3291244", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "id": "EH38E3291249", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291263", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "id": "EH38E3291271", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291279", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291318", - "category": "CA-CTCF", - "simple": "Chromatin Accessible + CTCF" + "category": "CA-CTCF" }, { "id": "EH38E3291346", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291358", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291364", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291374", - "category": "CA-TF", - "simple": "Chromatin Accessible + Transcription Factor" + "category": "CA-TF" }, { "id": "EH38E3291392", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291410", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291664", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3291668", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "id": "EH38E3291779", - "category": "CA-CTCF", - "simple": "Chromatin Accessible + CTCF" + "category": "CA-CTCF" }, { "id": "EH38E3312736", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "id": "EH38E3312746", - "category": "dELS", - "simple": "Distal Enhancer" + "category": "dELS" }, { "id": "EH38E3312765", - "category": "CA-TF", - "simple": "Chromatin Accessible + Transcription Factor" + "category": "CA-TF" }, { "id": "EH38E3312774", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E3312787", - "category": "CA-TF", - "simple": "Chromatin Accessible + Transcription Factor" + "category": "CA-TF" }, { "id": "EH38E4193211", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "id": "EH38E4193228", - "category": "PLS", - "simple": "Promoter" + "category": "PLS" }, { "id": "EH38E4193243", - "category": "CA-H3K4me3", - "simple": "Chromatin Accessible + H3K4me3" + "category": "CA-H3K4me3" }, { "id": "EH38E4193273", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" }, { "id": "EH38E4193467", - "category": "CA-CTCF", - "simple": "Chromatin Accessible + CTCF" + "category": "CA-CTCF" }, { "id": "EH38E4201343", - "category": "pELS", - "simple": "Proximal Enhancer" + "category": "pELS" } ] } diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 96d8f6a..75f9e61 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -4,7 +4,7 @@ import coseBilkent from 'cytoscape-cose-bilkent'; import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip'; import { useScreenshot } from 'use-react-screenshot'; import { cCREConstants, cCREClass, buttonStyle } from './constants'; -import { GraphProps, Node, Edge } from './types'; +import { GraphProps, Node, Edge, ToolTipData } from './types'; import Legend from './Legend'; import ScaleLegend from './ScaleLegend'; import GraphButton from './GraphButton'; @@ -13,12 +13,6 @@ import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrow cytoscape.use(coseBilkent); -interface ToolTipData { - cCRE?: string; - type: string; - centered?: string; -} - // function shortHand(str: string): string { // const simple = str as cCREClass; // return cCREConstants[simple]?.label || 'n/a'; @@ -63,6 +57,7 @@ const Graph: React.FC = ({ width = '100%', height = '100%', scale = defaultScale, + getLabel, }) => { const cyRef = useRef(null); @@ -214,10 +209,9 @@ const Graph: React.FC = ({ }, [data]); } const simple: string[] = elements - .map((e) => e.info?.category) - .map((elem) => convertToSimple(elem !== undefined ? elem : '')); + .map((e) => e.category) + .map((elem) => convertToSimple(elem)); const createID = (index: number): string => elements[index].id; - useEffect(() => { if ( elements.length === 0 || @@ -303,7 +297,7 @@ const Graph: React.FC = ({ style: { // find color based on CRE 'background-color': chooseColor(i), - label: showLabels ? createID(i) : '', + label: showLabels ? elements[i].id : '', fontSize: '12px', borderWidth: '2px', borderColor: 'black', @@ -320,7 +314,11 @@ const Graph: React.FC = ({ style: { // find color based on CRE 'background-color': chooseColor(i), - label: showLabels ? createID(i) : '', + label: showLabels + ? getLabel + ? getLabel(elements[i]) + : elements[i].id + : '', fontSize: '12px', }, }); @@ -580,7 +578,7 @@ const Graph: React.FC = ({ simpleCategories={simple} edgeType={data.edge.every((e) => e.expressionImpact)} /> - +
)} diff --git a/src/components/Graph/ScaleLegend.tsx b/src/components/Graph/ScaleLegend.tsx index f630e6c..23db8ad 100644 --- a/src/components/Graph/ScaleLegend.tsx +++ b/src/components/Graph/ScaleLegend.tsx @@ -3,9 +3,10 @@ import GraphButton from './GraphButton'; interface ScaleProps { scales: number[]; + width: (n: number) => number; } -const ScaleLegend: React.FC = ({ scales }) => { +const ScaleLegend: React.FC = ({ scales, width }) => { const [collapsed, setCollapsed] = useState(false); if (scales.length === 0) return null; @@ -15,7 +16,9 @@ const ScaleLegend: React.FC = ({ scales }) => { const mid1 = sorted[Math.floor(sorted.length / 4)]; const mid2 = sorted[Math.floor((sorted.length * 3) / 4)]; - const calculateWidth = (weight: number) => 10 * Math.log(weight * 4 + 1); // my scaling + const scaleFunctionStr = width.toString(); + const scaleFormula = + scaleFunctionStr.match(/=>\s*(.*)/)?.[1]?.trim() || scaleFunctionStr; const buttonStyle: CSSProperties = { zIndex: 1000, @@ -48,6 +51,7 @@ const ScaleLegend: React.FC = ({ scales }) => { borderRadius: '5px', border: '1px solid #ccc', boxShadow: '0 0 10px rgba(0,0,0,0.5)', + fontSize: '13px', }; const d = { @@ -57,14 +61,12 @@ const ScaleLegend: React.FC = ({ scales }) => {
{!collapsed && ( <> -

- Edge Weight Scale: (log10 * 4) + 1 -

+

Edge Weight Scale: {scaleFormula}

= ({ scales }) => {
= ({ scales }) => {
= ({ scales }) => {
number, + getLabel?: (node: Node) => string, } - 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; diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index c5c8630..2ef671b 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -16,61 +16,25 @@ 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 } } => { - const nodes = data.data.node.map((node) => ({ - id: node.id, - info: { category: node.category }, - })); - - const edges = data.data.edge.map((edge) => ({ - from: edge.perturbed, - to: edge.target, - effectSize: edge.effectSize, - ...(edge.expressionImpact && { expressionImpact: edge.expressionImpact }), - })); - - const result: { nodes: Node[]; edges: Edge[]; centered?: { id: string } } = { - nodes, - edges, - }; - - 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, + id: 'Sample', scale: (n: number) => 10 * n, }; export const PilotDataWithCentered = Template.bind({}); PilotDataWithCentered.args = { data: data.data, title: 'cCRE Impact With Pilot Data With Centered cCRE', - id: 'hello', + id: 'PilotWithCentered', }; export const FiftyPercent = Template.bind({}); FiftyPercent.args = { data: data.data, title: '50% Width and Height', - id: 2, + id: '50Percent', width: '50%', height: '50%', }; @@ -79,5 +43,13 @@ export const PilotDataWithoutCentered = Template.bind({}); PilotDataWithoutCentered.args = { data: data3.data, title: 'cCRE Impact With Pilot Data Without Centered cCRE', - id: 'hi', + id: 'PilotNoCentered', +}; + +export const DifferentLabel = Template.bind({}); +DifferentLabel.args = { + data: data3.data, + title: 'Different Label', + id: 'diffLabel', + getLabel: (node: Node) => node.category, }; From bd05ab898c910e139df6474a99dfd3b64df898a4 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Mon, 1 Jul 2024 14:38:02 -0400 Subject: [PATCH 16/31] Update package-lock.json --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 652ff0c..6620401 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@weng-lab/psychscreen-ui-components", - "version": "0.8.8", + "version": "0.8.9", "lockfileVersion": 3, "requires": true, "packages": { From 2c6473b32ca45653f505eced401bb94529726074 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Wed, 3 Jul 2024 13:25:55 -0400 Subject: [PATCH 17/31] getColor & legend fixes --- example/data.json | 116 ++++++++++++++--------------- example/data3.json | 116 ++++++++++++++--------------- src/components/Graph/Graph.tsx | 62 ++++++---------- src/components/Graph/Legend.tsx | 125 +++++++++++++++++++++----------- src/components/Graph/types.ts | 10 ++- stories/Graph.stories.tsx | 63 ++++++++++++++++ 6 files changed, 290 insertions(+), 202 deletions(-) diff --git a/example/data.json b/example/data.json index 823bca1..946775a 100644 --- a/example/data.json +++ b/example/data.json @@ -5,404 +5,404 @@ "from": "EH38E3291096", "to": "EH38E1939823", "effectSize": 0.1134, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E1939823", "to": "EH38E1939823", "effectSize": 0.1933, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291121", "to": "EH38E1939823", "effectSize": 0.0545, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291122", "to": "EH38E1939823", "effectSize": 0.0665, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E1939823", "effectSize": 0.0674, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291271", "to": "EH38E3291279", "effectSize": 0.0381, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291279", "to": "EH38E3291279", "effectSize": 0.1667, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E3291279", "effectSize": 0.0478, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291174", "to": "EH38E3291410", "effectSize": 0.0504, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4193273", "to": "EH38E3291410", "effectSize": 0.0507, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E3291410", "effectSize": 0.0895, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291392", "to": "EH38E3291410", "effectSize": 0.0352, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291410", "to": "EH38E3291410", "effectSize": 0.2538, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291174", "to": "EH38E4193228", "effectSize": 0.0211, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291218", "to": "EH38E4193228", "effectSize": 0.0477, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291222", "to": "EH38E4193228", "effectSize": 0.0627, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291226", "to": "EH38E4193228", "effectSize": 0.0448, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4193211", "to": "EH38E4193228", "effectSize": 0.0454, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291232", "to": "EH38E4193228", "effectSize": 0.0311, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291249", "to": "EH38E4193228", "effectSize": 0.0593, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3291263", "to": "EH38E4193228", "effectSize": 0.1136, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291271", "to": "EH38E4193228", "effectSize": 0.4097, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291279", "to": "EH38E4193228", "effectSize": 0.1077, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4193243", "to": "EH38E4193228", "effectSize": 0.026, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3291318", "to": "EH38E4193228", "effectSize": 0.0297, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E4193273", "to": "EH38E4193228", "effectSize": 0.0405, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E4193228", "effectSize": 0.0918, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291779", "to": "EH38E4193228", "effectSize": 0.0263, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E4193467", "to": "EH38E4193228", "effectSize": 0.0374, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E1960374", "to": "EH38E1960374", "effectSize": 0.1853, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3312736", "to": "EH38E1960374", "effectSize": 0.0698, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E1960377", "to": "EH38E1960374", "effectSize": 0.1479, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4201343", "to": "EH38E1960374", "effectSize": 0.1042, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E1960374", "to": "EH38E3312774", "effectSize": 0.143, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3312736", "to": "EH38E3312774", "effectSize": 0.0864, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E1960377", "to": "EH38E3312774", "effectSize": 0.1641, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E4201343", "to": "EH38E3312774", "effectSize": 0.0652, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3312746", "to": "EH38E3312774", "effectSize": 0.0835, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3312765", "to": "EH38E3312774", "effectSize": 0.0386, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3312774", "to": "EH38E3312774", "effectSize": 0.3507, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3312787", "to": "EH38E3312774", "effectSize": 0.0959, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E1940335", "to": "EH38E3291664", "effectSize": 0.0701, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291664", "to": "EH38E3291664", "effectSize": 0.3563, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291668", "to": "EH38E3291664", "effectSize": 0.1181, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291318", "to": "EH38E3291358", "effectSize": 0.0369, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291346", "to": "EH38E3291358", "effectSize": 0.108, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4193273", "to": "EH38E3291358", "effectSize": 0.2005, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E3291358", "effectSize": 0.441, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291364", "to": "EH38E3291358", "effectSize": 0.0862, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291374", "to": "EH38E3291358", "effectSize": 0.0278, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3291664", "to": "EH38E3291358", "effectSize": 0.0291, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E1939855", "to": "EH38E3291249", "effectSize": 0.0684, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E4193211", "to": "EH38E3291249", "effectSize": 0.0354, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291232", "to": "EH38E3291249", "effectSize": 0.0767, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291244", "to": "EH38E3291249", "effectSize": 0.0883, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291249", "to": "EH38E3291249", "effectSize": 0.1514, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E3291249", "effectSize": 0.0378, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291664", "to": "EH38E3291249", "effectSize": 0.0276, - "expressionImpact": "higher-expression" + "category": "higher-expression" } ], diff --git a/example/data3.json b/example/data3.json index 210b340..85b8275 100644 --- a/example/data3.json +++ b/example/data3.json @@ -5,404 +5,404 @@ "from": "EH38E3291096", "to": "EH38E1939823", "effectSize": 0.1134, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E1939823", "to": "EH38E1939823", "effectSize": 0.1933, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291121", "to": "EH38E1939823", "effectSize": 0.0545, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291122", "to": "EH38E1939823", "effectSize": 0.0665, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E1939823", "effectSize": 0.0674, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291271", "to": "EH38E3291279", "effectSize": 0.0381, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291279", "to": "EH38E3291279", "effectSize": 0.1667, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E3291279", "effectSize": 0.0478, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291174", "to": "EH38E3291410", "effectSize": 0.0504, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4193273", "to": "EH38E3291410", "effectSize": 0.0507, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E3291410", "effectSize": 0.0895, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291392", "to": "EH38E3291410", "effectSize": 0.0352, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291410", "to": "EH38E3291410", "effectSize": 0.2538, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291174", "to": "EH38E4193228", "effectSize": 0.0211, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291218", "to": "EH38E4193228", "effectSize": 0.0477, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291222", "to": "EH38E4193228", "effectSize": 0.0627, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291226", "to": "EH38E4193228", "effectSize": 0.0448, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4193211", "to": "EH38E4193228", "effectSize": 0.0454, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291232", "to": "EH38E4193228", "effectSize": 0.0311, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291249", "to": "EH38E4193228", "effectSize": 0.0593, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3291263", "to": "EH38E4193228", "effectSize": 0.1136, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291271", "to": "EH38E4193228", "effectSize": 0.4097, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291279", "to": "EH38E4193228", "effectSize": 0.1077, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4193243", "to": "EH38E4193228", "effectSize": 0.026, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3291318", "to": "EH38E4193228", "effectSize": 0.0297, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E4193273", "to": "EH38E4193228", "effectSize": 0.0405, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E4193228", "effectSize": 0.0918, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291779", "to": "EH38E4193228", "effectSize": 0.0263, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E4193467", "to": "EH38E4193228", "effectSize": 0.0374, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E1960374", "to": "EH38E1960374", "effectSize": 0.1853, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3312736", "to": "EH38E1960374", "effectSize": 0.0698, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E1960377", "to": "EH38E1960374", "effectSize": 0.1479, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4201343", "to": "EH38E1960374", "effectSize": 0.1042, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E1960374", "to": "EH38E3312774", "effectSize": 0.143, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3312736", "to": "EH38E3312774", "effectSize": 0.0864, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E1960377", "to": "EH38E3312774", "effectSize": 0.1641, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E4201343", "to": "EH38E3312774", "effectSize": 0.0652, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3312746", "to": "EH38E3312774", "effectSize": 0.0835, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3312765", "to": "EH38E3312774", "effectSize": 0.0386, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3312774", "to": "EH38E3312774", "effectSize": 0.3507, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3312787", "to": "EH38E3312774", "effectSize": 0.0959, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E1940335", "to": "EH38E3291664", "effectSize": 0.0701, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291664", "to": "EH38E3291664", "effectSize": 0.3563, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291668", "to": "EH38E3291664", "effectSize": 0.1181, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291318", "to": "EH38E3291358", "effectSize": 0.0369, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291346", "to": "EH38E3291358", "effectSize": 0.108, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E4193273", "to": "EH38E3291358", "effectSize": 0.2005, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E3291358", "effectSize": 0.441, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291364", "to": "EH38E3291358", "effectSize": 0.0862, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291374", "to": "EH38E3291358", "effectSize": 0.0278, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E3291664", "to": "EH38E3291358", "effectSize": 0.0291, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E1939855", "to": "EH38E3291249", "effectSize": 0.0684, - "expressionImpact": "higher-expression" + "category": "higher-expression" }, { "from": "EH38E4193211", "to": "EH38E3291249", "effectSize": 0.0354, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291232", "to": "EH38E3291249", "effectSize": 0.0767, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291244", "to": "EH38E3291249", "effectSize": 0.0883, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291249", "to": "EH38E3291249", "effectSize": 0.1514, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291358", "to": "EH38E3291249", "effectSize": 0.0378, - "expressionImpact": "lower-expression" + "category": "lower-expression" }, { "from": "EH38E3291664", "to": "EH38E3291249", "effectSize": 0.0276, - "expressionImpact": "higher-expression" + "category": "higher-expression" } ], diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 75f9e61..5836e1b 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -3,7 +3,7 @@ import cytoscape, { Core, EdgeSingular, NodeSingular } from 'cytoscape'; import coseBilkent from 'cytoscape-cose-bilkent'; import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip'; import { useScreenshot } from 'use-react-screenshot'; -import { cCREConstants, cCREClass, buttonStyle } from './constants'; +import { buttonStyle } from './constants'; import { GraphProps, Node, Edge, ToolTipData } from './types'; import Legend from './Legend'; import ScaleLegend from './ScaleLegend'; @@ -13,11 +13,6 @@ import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrow cytoscape.use(coseBilkent); -// 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'); a.href = image; @@ -58,6 +53,7 @@ const Graph: React.FC = ({ height = '100%', scale = defaultScale, getLabel, + getColor, }) => { const cyRef = useRef(null); @@ -76,8 +72,8 @@ const Graph: React.FC = ({ 'Chromatin Accessible + Transcription Factor': true, 'Chromatin Accessible + H3K4me3': true, 'Chromatin Accessible + CTCF': true, - 'Lower-Expression': true, - 'Higher-Expression': true, + 'lower-expression': true, + 'higher-expression': true, }); const [degree, setDegree] = useState(3); @@ -184,11 +180,8 @@ const Graph: React.FC = ({ setScales(filteredData.edges.map((e) => e.effectSize)); setExpressions( data.edge.map((e) => { - if (e.expressionImpact === 'higher-expression') - return 'Higher-Expression'; - if (e.expressionImpact === 'lower-expression') - return 'Lower-Expression'; - return 'Edge'; + if (e.category !== undefined) return e.category; + return ''; }) ); }, [data, degree]); @@ -198,12 +191,9 @@ const Graph: React.FC = ({ setEdges(data.edge); setScales(data.edge.map((e: Edge) => e.effectSize)); setExpressions( - data.edge.map((e: Edge) => { - if (e.expressionImpact === 'higher-expression') - return 'Higher-Expression'; - if (e.expressionImpact === 'lower-expression') - return 'Lower-Expression'; - return 'Edge'; + data.edge.map((e) => { + if (e.category !== undefined) return e.category; + return ''; }) ); }, [data]); @@ -211,6 +201,7 @@ const Graph: React.FC = ({ const simple: string[] = elements .map((e) => e.category) .map((elem) => convertToSimple(elem)); + const createID = (index: number): string => elements[index].id; useEffect(() => { if ( @@ -234,16 +225,6 @@ const Graph: React.FC = ({ connect[allcCREs.indexOf(e.from)].push(allcCREs.indexOf(e.to)); }); - 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 s = simple[index] as cCREClass; - return cCREConstants[s]?.color || 'grey'; - } - const cy = cytoscape({ container: document.getElementById(k), style: [ @@ -296,7 +277,7 @@ const Graph: React.FC = ({ }, style: { // find color based on CRE - 'background-color': chooseColor(i), + 'background-color': getColor ? getColor(elements[i]) : 'grey', label: showLabels ? elements[i].id : '', fontSize: '12px', borderWidth: '2px', @@ -313,7 +294,7 @@ const Graph: React.FC = ({ }, style: { // find color based on CRE - 'background-color': chooseColor(i), + 'background-color': getColor ? getColor(elements[i]) : 'grey', label: showLabels ? getLabel ? getLabel(elements[i]) @@ -345,13 +326,10 @@ const Graph: React.FC = ({ target: createID(connect[j][s]), }, style: { - 'line-color': edgeColor(j), + 'line-color': getColor ? getColor(edges[j]) : 'grey', 'target-arrow-shape': - expressionType[j] === 'Higher-Expression' || - expressionType[j] === 'Lower-Expression' - ? 'triangle' - : null, - 'target-arrow-color': edgeColor(j), + expressionType[j] !== 'Edge' ? 'triangle' : null, + 'target-arrow-color': getColor ? getColor(edges[j]) : 'grey', width: scale(scales[j]), }, }); @@ -360,7 +338,6 @@ const Graph: React.FC = ({ } } } - let idx = 0; cy.nodes().forEach((node: NodeSingular) => { let cre = allcCREs[idx].toString(); @@ -368,7 +345,7 @@ const Graph: React.FC = ({ if (data.centered && cre === data.centered.id) { node.on('mousemove', (event) => handleMouseMove(event, { - id: cre, + cCRE: cre, type: s, centered: 'Centered Node', }) @@ -386,7 +363,7 @@ const Graph: React.FC = ({ }); cy.edges().forEach((edge: EdgeSingular) => { - if (data.edge.every((e) => e.expressionImpact)) { + if (data.edge.every((e) => e.category)) { edge.on('mousemove', (event) => handleMouseMove(event, { type: @@ -576,7 +553,10 @@ const Graph: React.FC = ({ toggles={toggles} onToggle={handleToggle} simpleCategories={simple} - edgeType={data.edge.every((e) => e.expressionImpact)} + edgeType={data.edge.every((e) => e.category)} + colorFunc={getColor} + elements={elements} + edges={edges} />
diff --git a/src/components/Graph/Legend.tsx b/src/components/Graph/Legend.tsx index 0a2312c..63694de 100644 --- a/src/components/Graph/Legend.tsx +++ b/src/components/Graph/Legend.tsx @@ -1,11 +1,41 @@ import React, { CSSProperties, useState } from 'react'; -import { cCREClass, cCREConstants } from './constants'; +import { Node, Edge } from './types'; interface LegendProps { toggles: { [key: string]: boolean }; onToggle: (category: string) => void; simpleCategories: string[]; edgeType: boolean; + colorFunc?: (node: Node | Edge) => string; + elements: Node[]; + edges: Edge[]; +} + +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'; + case 'lower-expression': + return 'Lower-Expression'; + case 'higher-expression': + return 'Higher-Expression'; + default: + return str; + } } const Legend: React.FC = ({ @@ -13,6 +43,9 @@ const Legend: React.FC = ({ onToggle, simpleCategories, edgeType, + colorFunc, + elements, + edges, }) => { const [collapsed, setCollapsed] = useState(false); @@ -49,9 +82,8 @@ const Legend: React.FC = ({ }; const d = { width: '237px' }; - const lower = 'Lower-Expression'; - const higher = 'Higher-Expression'; + const edgeTypes = Array.from(new Set(edges.map((e) => e.category))); const uniqueCategories = Array.from(new Set(simpleCategories)); return ( @@ -63,8 +95,15 @@ const Legend: React.FC = ({ {!collapsed && (
{uniqueCategories.map((category) => { - const typedCategory = category as cCREClass; - const categoryData = cCREConstants[typedCategory]; + let n = 'grey'; + let cat = ''; + elements.forEach((node) => { + if (colorFunc && convertToSimple(node.category) === category) { + n = colorFunc(node); + cat = node.category; + } + }); + return (
= ({ /> onToggle(category)} > - {category} ({categoryData?.label || 'n/a'}) + {category} ({cat})
); })}
)} - {!collapsed && edgeType ? ( - <> -
- onToggle(lower)} - /> - onToggle(lower)} - > - {lower} (Edge) - -
-
- onToggle(higher)} - /> - onToggle(higher)} - > - {higher} (Edge) - -
- + {!collapsed && edgeType && edgeTypes.every((e) => e !== undefined) ? ( +
+ {edgeTypes.map((category) => { + if (category === undefined) { + return; + } + let n = 'grey'; + edges.forEach((edge) => { + if ( + colorFunc && + edge.category !== undefined && + edge.category === category + ) { + n = colorFunc(edge); + } + }); + + return ( +
+ onToggle(category)} + /> + onToggle(category)} + > + {convertToSimple(category)} + +
+ ); + })} +
) : null}
); diff --git a/src/components/Graph/types.ts b/src/components/Graph/types.ts index 4ed0081..6b5467c 100644 --- a/src/components/Graph/types.ts +++ b/src/components/Graph/types.ts @@ -2,25 +2,29 @@ export interface Edge { from: string; to: string; effectSize: number; - expressionImpact?: string; + category?: string; } export interface Node { id: string; category: string; - info?:{}; + color?: string; + info?:{ + [key: string]: any + }; } export interface GraphProps { data: { edge: Edge[], node: Node[], centered: {id: string} }, - title?: string, id: number | string, + title?: string, width?: string, height?: string, scale?: (n: number) => number, getLabel?: (node: Node) => string, + getColor?: (node: Node | Edge) => string, } diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index 2ef671b..43bab6b 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -8,6 +8,56 @@ import data from '../example/data.json'; import { Edge, Node } from '../src/components/Graph/types'; import '../src/App.css'; +function setColor(node: Node | Edge): string { + switch (node.category) { + case 'PLS': + return '#FF0000'; + case 'dELS': + return '#FFCD00'; + case 'pELS': + return '#FFA700'; + case 'CA-CTCF': + return '#00B0F0'; + case 'CA-H3K4me3': + return '#ffaaaa'; + case 'CA-TF': + return '#be28e5'; + case 'Low-DNase': + return '#e1e1e1'; + case 'lower-expression': + return 'black'; + case 'higher-expression': + return 'blue'; + default: + return 'grey'; + } +} + +function setColor2(node: Node | Edge): string { + switch (node.category) { + case 'PLS': + return 'red'; + case 'dELS': + return 'orange'; + case 'pELS': + return 'yellow'; + case 'CA-CTCF': + return 'green'; + case 'CA-H3K4me3': + return 'blue'; + case 'CA-TF': + return 'purple'; + case 'Low-DNase': + return 'pink'; + case 'lower-expression': + return 'black'; + case 'higher-expression': + return 'purple'; + default: + return 'grey'; + } +} + const meta: Meta = { title: 'Graph', component: Graph, @@ -22,12 +72,14 @@ SampleGraph.args = { title: 'Sample Graph With No Centered cCRE', id: 'Sample', scale: (n: number) => 10 * n, + getColor: setColor, }; export const PilotDataWithCentered = Template.bind({}); PilotDataWithCentered.args = { data: data.data, title: 'cCRE Impact With Pilot Data With Centered cCRE', id: 'PilotWithCentered', + getColor: setColor, }; export const FiftyPercent = Template.bind({}); @@ -37,6 +89,7 @@ FiftyPercent.args = { id: '50Percent', width: '50%', height: '50%', + getColor: setColor, }; export const PilotDataWithoutCentered = Template.bind({}); @@ -44,6 +97,7 @@ PilotDataWithoutCentered.args = { data: data3.data, title: 'cCRE Impact With Pilot Data Without Centered cCRE', id: 'PilotNoCentered', + getColor: setColor, }; export const DifferentLabel = Template.bind({}); @@ -52,4 +106,13 @@ DifferentLabel.args = { title: 'Different Label', id: 'diffLabel', getLabel: (node: Node) => node.category, + getColor: setColor, +}; + +export const DifferentColor = Template.bind({}); +DifferentColor.args = { + data: data3.data, + title: 'Different Color', + id: 'diffColor', + getColor: setColor2, }; From 5380012d0458bc44f6b90f4ff535362455b99c13 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Mon, 8 Jul 2024 17:00:56 -0400 Subject: [PATCH 18/31] fixes --- src/components/Graph/Graph.tsx | 37 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 5836e1b..58c122f 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -61,7 +61,7 @@ const Graph: React.FC = ({ const [showControls, setShowControls] = useState(true); const [elements, setElements] = useState([]); const [scales, setScales] = useState([]); - const [expressionType, setExpressions] = useState([]); + const [edgeTypes, setEdgeTypes] = useState([]); const [edges, setEdges] = useState([]); const [showLabels, setShowLabels] = useState(true); const [toggles, setToggles] = useState<{ [key: string]: boolean }>({ @@ -178,7 +178,7 @@ const Graph: React.FC = ({ setElements(filteredData.nodes); setEdges(filteredData.edges); setScales(filteredData.edges.map((e) => e.effectSize)); - setExpressions( + setEdgeTypes( data.edge.map((e) => { if (e.category !== undefined) return e.category; return ''; @@ -190,9 +190,10 @@ const Graph: React.FC = ({ setElements(data.node); setEdges(data.edge); setScales(data.edge.map((e: Edge) => e.effectSize)); - setExpressions( + setEdgeTypes( data.edge.map((e) => { - if (e.category !== undefined) return e.category; + if (e.category === 'lower-expression') return 'Lower-Expression'; + if (e.category === 'higher-expression') return 'Higher-Expression'; return ''; }) ); @@ -207,7 +208,7 @@ const Graph: React.FC = ({ if ( elements.length === 0 || scales.length === 0 || - expressionType.length === 0 || + edgeTypes.length === 0 || edges.length === 0 ) return; @@ -317,7 +318,7 @@ const Graph: React.FC = ({ for (let s = 0; s < len; s++) { if ( toggles[simple[connect[j][s]]] !== false && // toggle - toggles[expressionType[j]] !== false + toggles[edgeTypes[j]] !== false ) { cy.add({ data: { @@ -326,10 +327,18 @@ const Graph: React.FC = ({ target: createID(connect[j][s]), }, style: { - 'line-color': getColor ? getColor(edges[j]) : 'grey', + 'line-color': getColor + ? edges[edgeCount].category + ? getColor(edges[edgeCount]) + : 'grey' + : 'grey', 'target-arrow-shape': - expressionType[j] !== 'Edge' ? 'triangle' : null, - 'target-arrow-color': getColor ? getColor(edges[j]) : 'grey', + edgeTypes[j] !== 'Edge' ? 'triangle' : null, + 'target-arrow-color': getColor + ? edges[edgeCount].category + ? getColor(edges[edgeCount]) + : 'grey' + : 'grey', width: scale(scales[j]), }, }); @@ -386,15 +395,7 @@ const Graph: React.FC = ({ return () => { cy.destroy(); }; - }, [ - elements, - scales, - expressionType, - edges, - toggles, - showTooltip, - hideTooltip, - ]); + }, [elements, scales, edgeTypes, edges, toggles, showTooltip, hideTooltip]); useEffect(() => { if (!cyRef.current) return; From 11560f33ed19d2c6db50139fbf5480208ef71ef9 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Tue, 9 Jul 2024 21:43:56 -0400 Subject: [PATCH 19/31] working dynamic graph --- .vscode/settings.json | 3 + example/data2.json | 6 +- src/components/Graph/Graph.tsx | 120 +++++++++++------------ src/components/Graph/Legend.tsx | 60 +++++------- src/components/Graph/constants.ts | 50 ++++++---- src/components/Graph/types.ts | 3 +- stories/Graph.stories.tsx | 156 ++++++++++++++++++++++-------- 7 files changed, 239 insertions(+), 159 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b096a18 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "php.validate.run": "onType" +} \ No newline at end of file diff --git a/example/data2.json b/example/data2.json index 6d20376..1636fb6 100644 --- a/example/data2.json +++ b/example/data2.json @@ -15,16 +15,16 @@ "node": [ { "id": "node_1", - "category": "PLS" + "category": "R" }, { "id": "node_2", - "category": "CA-CTCF" + "category": "B" }, { "id": "node_3", - "category": "CA-TF" + "category": "P" } ] } diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 58c122f..b648dd3 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -20,29 +20,6 @@ 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 str; - } -} - const defaultScale = (n: number) => 10 * Math.log(n * 4 + 1); const Graph: React.FC = ({ @@ -54,6 +31,7 @@ const Graph: React.FC = ({ scale = defaultScale, getLabel, getColor, + legendToggle, }) => { const cyRef = useRef(null); @@ -64,20 +42,39 @@ const Graph: React.FC = ({ const [edgeTypes, setEdgeTypes] = useState([]); const [edges, setEdges] = useState([]); const [showLabels, setShowLabels] = useState(true); - const [toggles, setToggles] = useState<{ [key: string]: boolean }>({ - Promoter: true, - 'Distal Enhancer': true, - 'Proximal Enhancer': true, - 'Transcription Factor': true, - 'Chromatin Accessible + Transcription Factor': true, - 'Chromatin Accessible + H3K4me3': true, - 'Chromatin Accessible + CTCF': true, - 'lower-expression': true, - 'higher-expression': true, - }); - + const [toggles, setToggles] = useState<{ [key: string]: boolean }>({}); const [degree, setDegree] = useState(3); + // unique categories for legend toggles + const uniqueCategories = new Set(); + + if (legendToggle !== undefined) { + data.node.forEach((node) => { + uniqueCategories.add(legendToggle(node)); + }); + + data.edge.forEach((edge) => { + if (edge.category) { + uniqueCategories.add(legendToggle(edge)); + } + }); + } else { + data.node.forEach((node) => { + uniqueCategories.add(node.category); + }); + + data.edge.forEach((edge) => { + if (edge.category) { + uniqueCategories.add(edge.category); + } + }); + } + + const initialToggles: { [key: string]: boolean } = {}; + uniqueCategories.forEach((category) => { + initialToggles[category] = true; + }); + const toggleControls = () => { setShowControls(!showControls); }; @@ -176,14 +173,16 @@ const Graph: React.FC = ({ useEffect(() => { const filteredData = filterNodesAndEdges(degree); setElements(filteredData.nodes); - setEdges(filteredData.edges); + setEdges(data.edge); setScales(filteredData.edges.map((e) => e.effectSize)); setEdgeTypes( data.edge.map((e) => { + if (legendToggle) return legendToggle(e); if (e.category !== undefined) return e.category; return ''; }) ); + setToggles(initialToggles); }, [data, degree]); } else { useEffect(() => { @@ -192,17 +191,20 @@ const Graph: React.FC = ({ setScales(data.edge.map((e: Edge) => e.effectSize)); setEdgeTypes( data.edge.map((e) => { - if (e.category === 'lower-expression') return 'Lower-Expression'; - if (e.category === 'higher-expression') return 'Higher-Expression'; + if (legendToggle) return legendToggle(e); + if (e.category !== undefined) return e.category; return ''; }) ); + setToggles(initialToggles); }, [data]); } - const simple: string[] = elements - .map((e) => e.category) - .map((elem) => convertToSimple(elem)); - + const simple: string[] = elements.map((e) => { + if (legendToggle) return legendToggle(e); + else { + return e.category; + } + }); const createID = (index: number): string => elements[index].id; useEffect(() => { if ( @@ -223,9 +225,13 @@ 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.from)].push(allcCREs.indexOf(e.to)); - }); + const fromIndex = allcCREs.indexOf(e.from); + const toIndex = allcCREs.indexOf(e.to); + if (fromIndex !== -1 && toIndex !== -1) { + connect[fromIndex].push(toIndex); + } + }); const cy = cytoscape({ container: document.getElementById(k), style: [ @@ -313,7 +319,6 @@ const Graph: React.FC = ({ for (var j = 0; j < elements.length; j++) { // add # of edges per node based on the target connections if (toggles[simple[j]] !== false) { - // toggle let len = connect[j].length; for (let s = 0; s < len; s++) { if ( @@ -327,18 +332,10 @@ const Graph: React.FC = ({ target: createID(connect[j][s]), }, style: { - 'line-color': getColor - ? edges[edgeCount].category - ? getColor(edges[edgeCount]) - : 'grey' - : 'grey', + 'line-color': getColor ? getColor(edges[j]) : 'grey', 'target-arrow-shape': edgeTypes[j] !== 'Edge' ? 'triangle' : null, - 'target-arrow-color': getColor - ? edges[edgeCount].category - ? getColor(edges[edgeCount]) - : 'grey' - : 'grey', + 'target-arrow-color': getColor ? getColor(edges[j]) : 'grey', width: scale(scales[j]), }, }); @@ -349,12 +346,12 @@ const Graph: React.FC = ({ } let idx = 0; cy.nodes().forEach((node: NodeSingular) => { - let cre = allcCREs[idx].toString(); + let ID = allcCREs[idx].toString(); let s = simple[idx].toString(); - if (data.centered && cre === data.centered.id) { + if (data.centered && ID === data.centered.id) { node.on('mousemove', (event) => handleMouseMove(event, { - cCRE: cre, + id: ID, type: s, centered: 'Centered Node', }) @@ -362,7 +359,7 @@ const Graph: React.FC = ({ } else { node.on('mousemove', (event) => handleMouseMove(event, { - cCRE: cre, + id: ID, type: s, }) ); @@ -555,6 +552,7 @@ const Graph: React.FC = ({ onToggle={handleToggle} simpleCategories={simple} edgeType={data.edge.every((e) => e.category)} + legendToggle={legendToggle} colorFunc={getColor} elements={elements} edges={edges} @@ -606,9 +604,9 @@ const Graph: React.FC = ({ top={tooltipTop} left={tooltipLeft} > - {tooltipData.cCRE ? ( + {tooltipData.id ? (
- cCRE: {tooltipData.cCRE}
+ ID: {tooltipData.id}
Type: {tooltipData.type} {tooltipData.centered ?
Centered Node
: null}
diff --git a/src/components/Graph/Legend.tsx b/src/components/Graph/Legend.tsx index 63694de..84c3813 100644 --- a/src/components/Graph/Legend.tsx +++ b/src/components/Graph/Legend.tsx @@ -9,33 +9,7 @@ interface LegendProps { colorFunc?: (node: Node | Edge) => string; elements: Node[]; edges: Edge[]; -} - -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'; - case 'lower-expression': - return 'Lower-Expression'; - case 'higher-expression': - return 'Higher-Expression'; - default: - return str; - } + legendToggle?: (node: Node | Edge) => string; } const Legend: React.FC = ({ @@ -46,6 +20,7 @@ const Legend: React.FC = ({ colorFunc, elements, edges, + legendToggle, }) => { const [collapsed, setCollapsed] = useState(false); @@ -82,10 +57,20 @@ const Legend: React.FC = ({ }; const d = { width: '237px' }; + const edgeTypes = Array.from( + new Set( + edges.map((e) => { + if (legendToggle) return legendToggle(e); + else if (e.category) { + return e.category; + } else { + return; + } + }) + ) + ); - const edgeTypes = Array.from(new Set(edges.map((e) => e.category))); const uniqueCategories = Array.from(new Set(simpleCategories)); - return (
); })}
)} - {!collapsed && edgeType && edgeTypes.every((e) => e !== undefined) ? ( + {!collapsed && edgeType && edgeTypes !== null ? (
{edgeTypes.map((category) => { if (category === undefined) { @@ -135,8 +125,8 @@ const Legend: React.FC = ({ edges.forEach((edge) => { if ( colorFunc && - edge.category !== undefined && - edge.category === category + legendToggle && + legendToggle(edge) === category ) { n = colorFunc(edge); } @@ -156,7 +146,7 @@ const Legend: React.FC = ({ }} onClick={() => onToggle(category)} > - {convertToSimple(category)} + {category}
); diff --git a/src/components/Graph/constants.ts b/src/components/Graph/constants.ts index fcda092..87067bb 100644 --- a/src/components/Graph/constants.ts +++ b/src/components/Graph/constants.ts @@ -1,20 +1,4 @@ import { CSSProperties } from "react"; - -export const cCREConstants = { - "Promoter": { label: "Pr", color: "#FF0000" }, - "Distal Enhancer": { label: "D.E.", color: "#FFCD00" }, - "Proximal Enhancer": { label: "P.E.", color: "#FFA700" }, - "Transcription Factor": { label: "TF", color: "#d876ec" }, - "Chromatin Accessible + Transcription Factor": { label: "CA+TF", color: "#be28e5" }, - "Chromatin Accessible + CTCF": { label: "CA+CTCF", color: "#00B0F0" }, - "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"}, - "Low DNase": { label: "Low DNase", color: "#e1e1e1"}, - }; - - export type cCREClass = keyof typeof cCREConstants; export const buttonStyle: CSSProperties = { position: 'absolute', @@ -38,4 +22,36 @@ export const cCREConstants = { WebkitUserSelect: 'none', whiteSpace: 'nowrap', transition: 'background-color 0.3s, color 0.3s', - }; \ No newline at end of file + }; + + export const downloadStyle = { + ...buttonStyle, + top: '0px', + right: '5px', + }; + + export const randomizeStyle = { + ...buttonStyle, + top: '45px', + right: '5px', + }; + + export const organizeStyle = { + ...buttonStyle, + top: '90px', + right: '5px', + }; + + export const toggleControlsStyle = { + ...buttonStyle, + top: '0px', + padding: '3px', + backgroundColor: 'white', + color: '#0095ff', + }; + + export const labelStyle = { + ...buttonStyle, + top: '135px', + right: '5px', + }; diff --git a/src/components/Graph/types.ts b/src/components/Graph/types.ts index 6b5467c..a6e97c0 100644 --- a/src/components/Graph/types.ts +++ b/src/components/Graph/types.ts @@ -25,11 +25,12 @@ export interface Edge { scale?: (n: number) => number, getLabel?: (node: Node) => string, getColor?: (node: Node | Edge) => string, + legendToggle?: (node: Node | Edge) => string, } export type ToolTipData = { - cCRE?: string; + id?: string; type: string; centered?: string; } \ No newline at end of file diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index 43bab6b..2d4def2 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -9,55 +9,121 @@ import { Edge, Node } from '../src/components/Graph/types'; import '../src/App.css'; function setColor(node: Node | Edge): string { - switch (node.category) { - case 'PLS': - return '#FF0000'; - case 'dELS': - return '#FFCD00'; - case 'pELS': - return '#FFA700'; - case 'CA-CTCF': - return '#00B0F0'; - case 'CA-H3K4me3': - return '#ffaaaa'; - case 'CA-TF': - return '#be28e5'; - case 'Low-DNase': - return '#e1e1e1'; - case 'lower-expression': - return 'black'; - case 'higher-expression': - return 'blue'; - default: - return 'grey'; + if (node.category !== undefined) { + switch (node.category) { + case 'PLS': + return '#FF0000'; + case 'dELS': + return '#FFCD00'; + case 'pELS': + return '#FFA700'; + case 'CA-CTCF': + return '#00B0F0'; + case 'CA-H3K4me3': + return '#ffaaaa'; + case 'CA-TF': + return '#be28e5'; + case 'Low-DNase': + return '#e1e1e1'; + case 'lower-expression': + return 'black'; + case 'higher-expression': + return 'blue'; + default: + return 'grey'; + } } + return 'grey'; } function setColor2(node: Node | Edge): string { + if (node.category !== undefined) { + switch (node.category) { + case 'PLS': + return 'red'; + case 'dELS': + return 'orange'; + case 'pELS': + return 'yellow'; + case 'CA-CTCF': + return 'green'; + case 'CA-H3K4me3': + return 'blue'; + case 'CA-TF': + return 'purple'; + case 'Low-DNase': + return 'pink'; + case 'lower-expression': + return 'black'; + case 'higher-expression': + return 'purple'; + default: + return 'grey'; + } + } + return 'grey'; +} + +function setColor3(node: Node | Edge): string { + if (node && node.category !== undefined) { + switch (node.category) { + case 'R': + return 'red'; + case 'P': + return 'purple'; + case 'B': + return 'blue'; + default: + return 'grey'; + } + } else { + return 'grey'; + } +} + +function convertToSimple(node: Node | Edge): string { + if (node.category) { + switch (node.category) { + 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'; + case 'lower-expression': + return 'Lower-Expression'; + case 'higher-expression': + return 'Higher-Expression'; + default: + return node.category; + } + } + return ''; +} + +function convertToSimple2(node: Node | Edge): string { switch (node.category) { - case 'PLS': - return 'red'; - case 'dELS': - return 'orange'; - case 'pELS': - return 'yellow'; - case 'CA-CTCF': - return 'green'; - case 'CA-H3K4me3': - return 'blue'; - case 'CA-TF': - return 'purple'; - case 'Low-DNase': - return 'pink'; - case 'lower-expression': - return 'black'; - case 'higher-expression': - return 'purple'; + case 'R': + return 'red nodes'; + case 'B': + return 'blue nodes'; + case 'P': + return 'purple nodes'; default: - return 'grey'; + if (node.category) return node.category; + return ''; } } - const meta: Meta = { title: 'Graph', component: Graph, @@ -72,7 +138,8 @@ SampleGraph.args = { title: 'Sample Graph With No Centered cCRE', id: 'Sample', scale: (n: number) => 10 * n, - getColor: setColor, + getColor: setColor3, + legendToggle: convertToSimple2, }; export const PilotDataWithCentered = Template.bind({}); PilotDataWithCentered.args = { @@ -80,6 +147,7 @@ PilotDataWithCentered.args = { title: 'cCRE Impact With Pilot Data With Centered cCRE', id: 'PilotWithCentered', getColor: setColor, + legendToggle: convertToSimple, }; export const FiftyPercent = Template.bind({}); @@ -90,6 +158,7 @@ FiftyPercent.args = { width: '50%', height: '50%', getColor: setColor, + legendToggle: convertToSimple, }; export const PilotDataWithoutCentered = Template.bind({}); @@ -98,6 +167,7 @@ PilotDataWithoutCentered.args = { title: 'cCRE Impact With Pilot Data Without Centered cCRE', id: 'PilotNoCentered', getColor: setColor, + legendToggle: convertToSimple, }; export const DifferentLabel = Template.bind({}); @@ -107,6 +177,7 @@ DifferentLabel.args = { id: 'diffLabel', getLabel: (node: Node) => node.category, getColor: setColor, + legendToggle: convertToSimple, }; export const DifferentColor = Template.bind({}); @@ -115,4 +186,5 @@ DifferentColor.args = { title: 'Different Color', id: 'diffColor', getColor: setColor2, + legendToggle: convertToSimple, }; From ce877b8b2f475fab017d8e5c010e2aa2ed4d6f7d Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 11 Jul 2024 09:03:59 -0400 Subject: [PATCH 20/31] side control panel --- src/components/Graph/ControlPanel.tsx | 131 ++++++++++++++++ src/components/Graph/Graph.tsx | 143 ++++++------------ src/components/Graph/Legend.tsx | 168 ++++++++++----------- src/components/Graph/ScaleLegend.tsx | 207 +++++++++++++++----------- src/components/Graph/constants.ts | 57 ------- stories/Graph.stories.tsx | 9 ++ 6 files changed, 384 insertions(+), 331 deletions(-) create mode 100644 src/components/Graph/ControlPanel.tsx delete mode 100644 src/components/Graph/constants.ts diff --git a/src/components/Graph/ControlPanel.tsx b/src/components/Graph/ControlPanel.tsx new file mode 100644 index 0000000..b8121eb --- /dev/null +++ b/src/components/Graph/ControlPanel.tsx @@ -0,0 +1,131 @@ +import React, { CSSProperties, useState } from 'react'; +import { Node, Edge } from './types'; +import Button from '@mui/material/Button'; +import Legend from './Legend'; +import ScaleLegend from './ScaleLegend'; +import KeyboardDoubleArrowRightIcon from '@mui/icons-material/KeyboardDoubleArrowRight'; +import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrowLeft'; + +interface ControlPanelProps { + toggles: { [key: string]: boolean }; + onToggle: (category: string) => void; + simpleCategories: string[]; + edgeType: boolean; + colorFunc?: (node: Node | Edge) => string; + elements: Node[]; + edges: Edge[]; + scales: number[]; + scaleWidth: (n: number) => number; + downloadScreenshot: () => void; + randomize: () => void; + organize: () => void; + toggleLabels: () => void; + legendToggle?: (node: Node | Edge) => string; +} + +const ControlPanel: React.FC = ({ + toggles, + onToggle, + simpleCategories, + edgeType, + colorFunc, + elements, + edges, + scales, + scaleWidth, + downloadScreenshot, + randomize, + organize, + toggleLabels, + legendToggle, +}) => { + const [collapsed, setCollapsed] = useState(false); + + const panelStyle: CSSProperties = { + position: 'absolute', + top: '0', + right: '0', + height: '92vh', + width: collapsed ? '40px' : '250px', + backgroundColor: 'white', + transition: 'width 0.3s', + zIndex: 1000, + overflowY: 'auto', + border: '1px solid grey', + }; + + const buttonStyle: CSSProperties = { + left: collapsed ? '-12px' : '0px', + width: collapsed ? '0px' : '100%', + height: '40px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer', + }; + return ( +
+ + {!collapsed && ( + <> + + + + + + + + )} +
+ ); +}; + +export default ControlPanel; diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index b648dd3..0dce63f 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -3,13 +3,10 @@ import cytoscape, { Core, EdgeSingular, NodeSingular } from 'cytoscape'; import coseBilkent from 'cytoscape-cose-bilkent'; import { useTooltip, useTooltipInPortal, defaultStyles } from '@visx/tooltip'; import { useScreenshot } from 'use-react-screenshot'; -import { buttonStyle } from './constants'; import { GraphProps, Node, Edge, ToolTipData } from './types'; -import Legend from './Legend'; -import ScaleLegend from './ScaleLegend'; -import GraphButton from './GraphButton'; -import KeyboardDoubleArrowRightIcon from '@mui/icons-material/KeyboardDoubleArrowRight'; -import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrowLeft'; + +import ControlPanel from './ControlPanel'; +import { Typography } from '@mui/material'; cytoscape.use(coseBilkent); @@ -36,7 +33,7 @@ const Graph: React.FC = ({ const cyRef = useRef(null); // state hooks - const [showControls, setShowControls] = useState(true); + const [showControls] = useState(true); const [elements, setElements] = useState([]); const [scales, setScales] = useState([]); const [edgeTypes, setEdgeTypes] = useState([]); @@ -75,10 +72,6 @@ const Graph: React.FC = ({ initialToggles[category] = true; }); - const toggleControls = () => { - setShowControls(!showControls); - }; - // DOWNLOAD SCREENSHOT const ref = useRef(null); const [_, takeScreenShot] = useScreenshot(); @@ -289,6 +282,7 @@ const Graph: React.FC = ({ fontSize: '12px', borderWidth: '2px', borderColor: 'black', + fontFamily: 'Roboto', }, }); } else { @@ -443,47 +437,6 @@ const Graph: React.FC = ({ })); }; - const downloadStyle = { - ...buttonStyle, - top: '0px', - right: '5px', - }; - - const randomizeStyle = { - ...buttonStyle, - top: '45px', - right: '5px', - }; - - const organizeStyle = { - ...buttonStyle, - top: '90px', - right: '5px', - }; - - const toggleControlsStyle = { - ...buttonStyle, - top: '0px', - padding: '3px', - backgroundColor: 'white', - color: '#0095ff', - }; - - const labelStyle = { - ...buttonStyle, - top: '135px', - right: '5px', - }; - - const r = { - collapsed: { - right: '175px', - }, - uncollapsed: { - right: '2px', - }, - }; - return (
= ({ fontFamily: 'helvetica', }} > -
-

{title}

-
+ {title} + + {data.centered ? ( -
- +
+ + Degrees of Separation: + = ({ min={1} max={3} onChange={(e) => setDegree(parseInt(e.target.value))} + style={{ marginLeft: '5px', marginTop: '5px' }} />
) : null} @@ -525,56 +497,25 @@ const Graph: React.FC = ({ boxShadow: '0 0 10px rgba(0,0,0,0.5)', }} > - - - - - setShowLabels(!showLabels)} - > - - e.category)} - legendToggle={legendToggle} - colorFunc={getColor} elements={elements} edges={edges} + scales={scales} + scaleWidth={scale} + downloadScreenshot={downloadScreenshot} + randomize={randomize} + organize={organize} + toggleLabels={() => setShowLabels(!showLabels)} + colorFunc={getColor} + legendToggle={legendToggle} /> -
)} - -
= ({ ref={containerRef} id={k} style={{ - width: '100%', + width: '95%', height: '90vh', zIndex: 999, }} diff --git a/src/components/Graph/Legend.tsx b/src/components/Graph/Legend.tsx index 84c3813..ff7dd0e 100644 --- a/src/components/Graph/Legend.tsx +++ b/src/components/Graph/Legend.tsx @@ -1,5 +1,7 @@ -import React, { CSSProperties, useState } from 'react'; +import React from 'react'; +import Checkbox from '@mui/material/Checkbox'; import { Node, Edge } from './types'; +import { Typography } from '@mui/material'; interface LegendProps { toggles: { [key: string]: boolean }; @@ -22,41 +24,6 @@ const Legend: React.FC = ({ edges, legendToggle, }) => { - const [collapsed, setCollapsed] = useState(false); - - const buttonStyle: CSSProperties = { - zIndex: 1000, - margin: '2px', - backgroundColor: '#0095ff', - border: '0px', - borderRadius: '3px', - color: '#fff', - cursor: 'pointer', - fontFamily: - '-apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif', - fontSize: '12px', - outline: 'none', - padding: '7px .8em', - textAlign: 'center', - textDecoration: 'none', - userSelect: 'none', - WebkitUserSelect: 'none', - whiteSpace: 'nowrap', - transition: 'background-color 0.3s, color 0.3s', - }; - - const divStyle: CSSProperties = { - position: 'absolute', - bottom: '10px', - right: '3px', - zIndex: 1000, - backgroundColor: 'white', - padding: '10px', - borderRadius: '5px', - boxShadow: '0 0 10px rgba(0,0,0,0.5)', - }; - - const d = { width: '237px' }; const edgeTypes = Array.from( new Set( edges.map((e) => { @@ -71,83 +38,110 @@ const Legend: React.FC = ({ ); const uniqueCategories = Array.from(new Set(simpleCategories)); + return ( -
- +
+ {uniqueCategories.map((category) => { + let color = 'grey'; + let cat = ''; - {!collapsed && ( -
- {uniqueCategories.map((category) => { - let n = 'grey'; - let cat = ''; + elements.forEach((node) => { + if (colorFunc && legendToggle && legendToggle(node) === category) { + color = colorFunc(node); + cat = node.category; + } + }); - elements.forEach((node) => { - if ( - colorFunc && - legendToggle && - legendToggle(node) === category - ) { - n = colorFunc(node); - cat = node.category; - } - }); + return ( +
+ onToggle(category)} + color="primary" + size="small" + style={{ padding: 0 }} + /> + onToggle(category)} + > + {category} {legendToggle ? '(' : null} + {legendToggle ? cat : null} + {legendToggle ? ')' : null} + +
+ ); + })} - return ( -
- onToggle(category)} - /> - onToggle(category)} - > - {category} {legendToggle ? cat : null} - -
- ); - })} -
- )} - {!collapsed && edgeType && edgeTypes !== null ? ( + {edgeType && edgeTypes !== null ? (
{edgeTypes.map((category) => { if (category === undefined) { - return; + return null; } - let n = 'grey'; + let color = 'grey'; + edges.forEach((edge) => { if ( colorFunc && legendToggle && legendToggle(edge) === category ) { - n = colorFunc(edge); + color = colorFunc(edge); } }); return ( -
- + onToggle(category)} + color="primary" + size="small" + style={{ padding: 0 }} /> - onToggle(category)} > {category} - +
); })} diff --git a/src/components/Graph/ScaleLegend.tsx b/src/components/Graph/ScaleLegend.tsx index 23db8ad..6b45fe4 100644 --- a/src/components/Graph/ScaleLegend.tsx +++ b/src/components/Graph/ScaleLegend.tsx @@ -1,5 +1,5 @@ -import React, { CSSProperties, useState } from 'react'; -import GraphButton from './GraphButton'; +import React, { CSSProperties } from 'react'; +import Typography from '@mui/material/Typography'; interface ScaleProps { scales: number[]; @@ -7,7 +7,6 @@ interface ScaleProps { } const ScaleLegend: React.FC = ({ scales, width }) => { - const [collapsed, setCollapsed] = useState(false); if (scales.length === 0) return null; const sorted = [...scales].sort((a, b) => a - b); @@ -20,101 +19,137 @@ const ScaleLegend: React.FC = ({ scales, width }) => { const scaleFormula = scaleFunctionStr.match(/=>\s*(.*)/)?.[1]?.trim() || scaleFunctionStr; - const buttonStyle: CSSProperties = { - zIndex: 1000, - margin: '2px', - backgroundColor: '#0095ff', - border: '0px', - borderRadius: '3px', - color: '#fff', - cursor: 'pointer', - fontFamily: - '-apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif', - fontSize: '12px', - outline: 'none', - padding: '7px .8em', - textAlign: 'center', - textDecoration: 'none', - userSelect: 'none', - WebkitUserSelect: 'none', - whiteSpace: 'nowrap', - transition: 'background-color 0.3s, color 0.3s', - }; - const divStyle: CSSProperties = { position: 'absolute', - top: '200px', - right: '10px', + top: '28vh', zIndex: 1000, backgroundColor: 'white', padding: '10px', borderRadius: '5px', - border: '1px solid #ccc', - boxShadow: '0 0 10px rgba(0,0,0,0.5)', fontSize: '13px', + width: '225px', + textAlign: 'center', }; - const d = { - width: '230px', + const scaleItemStyle: CSSProperties = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: '5px', }; + return ( -
- {!collapsed && ( - <> -

Edge Weight Scale: {scaleFormula}

-
-
-
- {min.toFixed(2)} -
-
-
- {mid1.toFixed(2)} -
-
-
- {mid2.toFixed(2)} -
-
-
- {max.toFixed(2)} -
+
+ <> + + Edge Weight Scale: + + + {scaleFormula} + + +
+
+
+ + {min.toFixed(2)} + +
+
+
+ + {mid1.toFixed(2)} + +
+
+
+ + {mid2.toFixed(2)} + +
+
+
+ + {max.toFixed(2)} +
- - )} - setCollapsed(!collapsed)} - styles={buttonStyle} - > +
+
); }; diff --git a/src/components/Graph/constants.ts b/src/components/Graph/constants.ts deleted file mode 100644 index 87067bb..0000000 --- a/src/components/Graph/constants.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { CSSProperties } from "react"; - - export const buttonStyle: CSSProperties = { - position: 'absolute', - zIndex: 1000, - margin: '2px', - border: '0px', - backgroundColor: '#0095ff', - borderRadius: '3px', - boxShadow: 'rgba(255, 255, 255, .4) 0 1px 0 0 inset', - boxSizing: 'border-box', - color: '#fff', - cursor: 'pointer', - fontFamily: - '-apple-system,system-ui,"Segoe UI","Liberation Sans",sans-serif', - fontSize: '12px', - outline: 'none', - padding: '7px .8em', - textAlign: 'center', - textDecoration: 'none', - userSelect: 'none', - WebkitUserSelect: 'none', - whiteSpace: 'nowrap', - transition: 'background-color 0.3s, color 0.3s', - }; - - export const downloadStyle = { - ...buttonStyle, - top: '0px', - right: '5px', - }; - - export const randomizeStyle = { - ...buttonStyle, - top: '45px', - right: '5px', - }; - - export const organizeStyle = { - ...buttonStyle, - top: '90px', - right: '5px', - }; - - export const toggleControlsStyle = { - ...buttonStyle, - top: '0px', - padding: '3px', - backgroundColor: 'white', - color: '#0095ff', - }; - - export const labelStyle = { - ...buttonStyle, - top: '135px', - right: '5px', - }; diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index 2d4def2..2f39dc9 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -188,3 +188,12 @@ DifferentColor.args = { getColor: setColor2, legendToggle: convertToSimple, }; + +export const NoLegendToggle = Template.bind({}); +NoLegendToggle.args = { + data: data2.data, + title: 'No Legend Toggle', + id: 'noLegendToggle', + scale: (n: number) => 10 * n, + getColor: setColor3, +}; From b184559a0c3b3d8ac5f4a0b91771b265a912bfe5 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 11 Jul 2024 09:07:45 -0400 Subject: [PATCH 21/31] Update ControlPanel.tsx --- src/components/Graph/ControlPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Graph/ControlPanel.tsx b/src/components/Graph/ControlPanel.tsx index b8121eb..1eb285a 100644 --- a/src/components/Graph/ControlPanel.tsx +++ b/src/components/Graph/ControlPanel.tsx @@ -50,7 +50,6 @@ const ControlPanel: React.FC = ({ backgroundColor: 'white', transition: 'width 0.3s', zIndex: 1000, - overflowY: 'auto', border: '1px solid grey', }; From 8ab0c6781fd90b131741a2b3a7540b5894e9a70d Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 11 Jul 2024 09:44:21 -0400 Subject: [PATCH 22/31] Update ControlPanel.tsx --- src/components/Graph/ControlPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Graph/ControlPanel.tsx b/src/components/Graph/ControlPanel.tsx index 1eb285a..30e1a9b 100644 --- a/src/components/Graph/ControlPanel.tsx +++ b/src/components/Graph/ControlPanel.tsx @@ -45,7 +45,7 @@ const ControlPanel: React.FC = ({ position: 'absolute', top: '0', right: '0', - height: '92vh', + height: '91vh', width: collapsed ? '40px' : '250px', backgroundColor: 'white', transition: 'width 0.3s', From d17f8f416b27b1ea244474dc07521cd54f654322 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 11 Jul 2024 15:08:59 -0400 Subject: [PATCH 23/31] control panel fixes --- package.json | 2 +- src/components/Graph/ControlPanel.tsx | 124 +++++++++++++++----------- src/components/Graph/Graph.tsx | 2 +- src/components/Graph/Legend.tsx | 3 +- src/components/Graph/ScaleLegend.tsx | 14 +-- 5 files changed, 85 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index 37e8172..07eaaec 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@weng-lab/psychscreen-ui-components", "description": "Typescript and Material UI based components used for psychSCREEN", "author": "SCREEN Team @ UMass Chan Medical School", - "version": "0.8.9", + "version": "0.9.0", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/components/Graph/ControlPanel.tsx b/src/components/Graph/ControlPanel.tsx index 30e1a9b..f15ad7c 100644 --- a/src/components/Graph/ControlPanel.tsx +++ b/src/components/Graph/ControlPanel.tsx @@ -5,6 +5,8 @@ import Legend from './Legend'; import ScaleLegend from './ScaleLegend'; import KeyboardDoubleArrowRightIcon from '@mui/icons-material/KeyboardDoubleArrowRight'; import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrowLeft'; +import Stack from '@mui/material/Stack'; +import { Typography } from '@mui/material'; interface ControlPanelProps { toggles: { [key: string]: boolean }; @@ -45,7 +47,6 @@ const ControlPanel: React.FC = ({ position: 'absolute', top: '0', right: '0', - height: '91vh', width: collapsed ? '40px' : '250px', backgroundColor: 'white', transition: 'width 0.3s', @@ -54,13 +55,15 @@ const ControlPanel: React.FC = ({ }; const buttonStyle: CSSProperties = { - left: collapsed ? '-12px' : '0px', - width: collapsed ? '0px' : '100%', + // left: collapsed ? '-12px' : '-10px', + width: collapsed ? '0px' : '10px', + height: '40px', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', + display: 'inline-block', + alignItems: 'left', + justifyContent: 'left', cursor: 'pointer', + padding: '0px', }; return (
@@ -71,56 +74,77 @@ const ControlPanel: React.FC = ({ style={buttonStyle} > {collapsed ? ( - + ) : ( - + )} + {!collapsed && ( <> - - - - - - + Controls + + + + + + + + + + )}
diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 0dce63f..022a3da 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -483,7 +483,7 @@ const Graph: React.FC = ({ type="number" value={degree} min={1} - max={3} + max={5} onChange={(e) => setDegree(parseInt(e.target.value))} style={{ marginLeft: '5px', marginTop: '5px' }} /> diff --git a/src/components/Graph/Legend.tsx b/src/components/Graph/Legend.tsx index ff7dd0e..1835a6d 100644 --- a/src/components/Graph/Legend.tsx +++ b/src/components/Graph/Legend.tsx @@ -42,13 +42,12 @@ const Legend: React.FC = ({ return (
{uniqueCategories.map((category) => { diff --git a/src/components/Graph/ScaleLegend.tsx b/src/components/Graph/ScaleLegend.tsx index 6b45fe4..9c11ffe 100644 --- a/src/components/Graph/ScaleLegend.tsx +++ b/src/components/Graph/ScaleLegend.tsx @@ -17,24 +17,26 @@ const ScaleLegend: React.FC = ({ scales, width }) => { const scaleFunctionStr = width.toString(); const scaleFormula = - scaleFunctionStr.match(/=>\s*(.*)/)?.[1]?.trim() || scaleFunctionStr; + scaleFunctionStr + .match(/=>\s*(.*)/)?.[1] + ?.trim() + .replace('Math.', '') || scaleFunctionStr; const divStyle: CSSProperties = { - position: 'absolute', - top: '28vh', + top: '25vh', zIndex: 1000, backgroundColor: 'white', padding: '10px', borderRadius: '5px', fontSize: '13px', width: '225px', - textAlign: 'center', + textAlign: 'left', }; const scaleItemStyle: CSSProperties = { display: 'flex', alignItems: 'center', - justifyContent: 'center', + justifyContent: 'left', marginBottom: '5px', }; @@ -68,7 +70,7 @@ const ScaleLegend: React.FC = ({ scales, width }) => { style={{ display: 'flex', flexDirection: 'column', - alignItems: 'center', + alignItems: 'left', marginTop: '10px', }} > From 2f5bfa215c621d5bd110e18a3ea60ed738a0b900 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Fri, 12 Jul 2024 13:37:51 -0400 Subject: [PATCH 24/31] scrollable --- src/components/Graph/ControlPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Graph/ControlPanel.tsx b/src/components/Graph/ControlPanel.tsx index f15ad7c..d9c617b 100644 --- a/src/components/Graph/ControlPanel.tsx +++ b/src/components/Graph/ControlPanel.tsx @@ -52,12 +52,12 @@ const ControlPanel: React.FC = ({ transition: 'width 0.3s', zIndex: 1000, border: '1px solid grey', + overflowY: 'auto', + maxHeight: '100vh', }; const buttonStyle: CSSProperties = { - // left: collapsed ? '-12px' : '-10px', width: collapsed ? '0px' : '10px', - height: '40px', display: 'inline-block', alignItems: 'left', From 7fdd3e9b9a8fec908be55ee6b213617112f92b87 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Fri, 12 Jul 2024 13:44:01 -0400 Subject: [PATCH 25/31] Delete settings.json --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index b096a18..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "php.validate.run": "onType" -} \ No newline at end of file From 12d3a4f4290cc3af1356d44ac05aafe4767f98ff Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 18 Jul 2024 09:17:11 -0400 Subject: [PATCH 26/31] fixed changes --- src/components/Graph/ControlPanel.tsx | 119 ++++++++++++------ src/components/Graph/Graph.tsx | 174 ++++++++++---------------- src/components/Graph/Legend.tsx | 155 ++++++++++++++++------- src/components/Graph/ScaleLegend.tsx | 2 +- src/components/Graph/types.ts | 4 + stories/Graph.stories.tsx | 41 +++++- 6 files changed, 300 insertions(+), 195 deletions(-) diff --git a/src/components/Graph/ControlPanel.tsx b/src/components/Graph/ControlPanel.tsx index d9c617b..641a89f 100644 --- a/src/components/Graph/ControlPanel.tsx +++ b/src/components/Graph/ControlPanel.tsx @@ -7,6 +7,13 @@ import KeyboardDoubleArrowRightIcon from '@mui/icons-material/KeyboardDoubleArro import KeyboardDoubleArrowLeftIcon from '@mui/icons-material/KeyboardDoubleArrowLeft'; import Stack from '@mui/material/Stack'; import { Typography } from '@mui/material'; +import DownloadIcon from '@mui/icons-material/Download'; +import ShuffleIcon from '@mui/icons-material/Shuffle'; +import LabelIcon from '@mui/icons-material/Label'; +import LabelOffIcon from '@mui/icons-material/LabelOff'; +import InsightsIcon from '@mui/icons-material/Insights'; +import Tooltip from '@mui/material/Tooltip'; +import Paper from '@mui/material/Paper'; interface ControlPanelProps { toggles: { [key: string]: boolean }; @@ -22,7 +29,11 @@ interface ControlPanelProps { randomize: () => void; organize: () => void; toggleLabels: () => void; + labelsOn: boolean; legendToggle?: (node: Node | Edge) => string; + legendNodeLabel?: string; + legendEdgeLabel?: string; + uniqueCat?: string[]; } const ControlPanel: React.FC = ({ @@ -39,7 +50,11 @@ const ControlPanel: React.FC = ({ randomize, organize, toggleLabels, + labelsOn, legendToggle, + legendNodeLabel, + legendEdgeLabel, + uniqueCat, }) => { const [collapsed, setCollapsed] = useState(false); @@ -52,6 +67,7 @@ const ControlPanel: React.FC = ({ transition: 'width 0.3s', zIndex: 1000, border: '1px solid grey', + marginRight: '5px', overflowY: 'auto', maxHeight: '100vh', }; @@ -65,8 +81,9 @@ const ControlPanel: React.FC = ({ cursor: 'pointer', padding: '0px', }; + return ( -
+ - - - - - = ({ elements={elements} edges={edges} legendToggle={legendToggle} + legendNodeLabel={legendNodeLabel} + legendEdgeLabel={legendEdgeLabel} + uniqueCat={uniqueCat} /> + + + + + + + + + )} -
+ ); }; diff --git a/src/components/Graph/Graph.tsx b/src/components/Graph/Graph.tsx index 022a3da..79f89c7 100644 --- a/src/components/Graph/Graph.tsx +++ b/src/components/Graph/Graph.tsx @@ -29,6 +29,9 @@ const Graph: React.FC = ({ getLabel, getColor, legendToggle, + legendNodeLabel, + legendEdgeLabel, + order, }) => { const cyRef = useRef(null); @@ -40,7 +43,6 @@ const Graph: React.FC = ({ const [edges, setEdges] = useState([]); const [showLabels, setShowLabels] = useState(true); const [toggles, setToggles] = useState<{ [key: string]: boolean }>({}); - const [degree, setDegree] = useState(3); // unique categories for legend toggles const uniqueCategories = new Set(); @@ -67,10 +69,30 @@ const Graph: React.FC = ({ }); } - const initialToggles: { [key: string]: boolean } = {}; - uniqueCategories.forEach((category) => { - initialToggles[category] = true; + let a = new Set(); + data.node.forEach((node) => a.add(node.category)); + data.edge.forEach((edge) => { + if (edge.category && legendToggle) { + a.add(legendToggle(edge)); + } else if (edge.category) { + a.add(edge.category); + } }); + let u = Array.from(new Set(a)); + if (order) { + u = u.sort((a, b) => order.indexOf(a) - order.indexOf(b)); + } + + const initialToggles: { [key: string]: boolean } = {}; + if (order) { + u.forEach((category) => { + initialToggles[category] = true; + }); + } else { + uniqueCategories.forEach((category) => { + initialToggles[category] = true; + }); + } // DOWNLOAD SCREENSHOT const ref = useRef(null); @@ -119,85 +141,35 @@ const Graph: React.FC = ({ // each graph needs a unique id let k = 'cy-' + id; - 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.id; - let nodesToInclude = new Set([centeredNode]); - let edgesToInclude: Edge[] = []; - let visited = new Set([centeredNode]); - - // queue to manage BFS traversal - let queue: { node: string; depth: number }[] = [ - { node: centeredNode, depth: 0 }, - ]; - - while (queue.length > 0) { - const { node, depth } = queue.shift()!; - - // if the curr depth = to the degree, skip further expansion - if (depth >= degree) continue; - - // find edges involving the current node - data.edge.forEach((edge) => { - const neighbors = [ - { to: edge.to, from: edge.from }, - { to: edge.from, from: edge.to }, - ]; - - neighbors.forEach(({ to, from }) => { - if (from === node && !visited.has(to)) { - visited.add(to); - nodesToInclude.add(to); - edgesToInclude.push(edge); - queue.push({ node: to, depth: depth + 1 }); - } - }); - }); - } - - // filter nodes to include only those found within the specified degrees of separation - const filteredNodes = data.node.filter((node) => - nodesToInclude.has(node.id) - ); - return { nodes: filteredNodes, edges: edgesToInclude }; - }; - - useEffect(() => { - const filteredData = filterNodesAndEdges(degree); - setElements(filteredData.nodes); - setEdges(data.edge); - setScales(filteredData.edges.map((e) => e.effectSize)); - setEdgeTypes( - data.edge.map((e) => { - if (legendToggle) return legendToggle(e); - if (e.category !== undefined) return e.category; - return ''; - }) - ); - setToggles(initialToggles); - }, [data, degree]); - } else { - useEffect(() => { - setElements(data.node); - setEdges(data.edge); - setScales(data.edge.map((e: Edge) => e.effectSize)); - setEdgeTypes( - data.edge.map((e) => { - if (legendToggle) return legendToggle(e); - if (e.category !== undefined) return e.category; - return ''; - }) - ); - setToggles(initialToggles); - }, [data]); + useEffect(() => { + setElements(data.node); + setEdges(data.edge); + setScales(data.edge.map((e: Edge) => e.effectSize)); + setEdgeTypes( + data.edge.map((e) => { + if (legendToggle) return legendToggle(e); + if (e.category !== undefined) return e.category; + return 'Edge'; + }) + ); + setToggles(initialToggles); + }, [data]); + + let elem = elements.map((e) => e.category); + + let unique = Array.from(new Set(elem)); + + if (order) { + unique = unique.sort((a, b) => order.indexOf(a) - order.indexOf(b)); } + const simple: string[] = elements.map((e) => { if (legendToggle) return legendToggle(e); else { return e.category; } }); + const createID = (index: number): string => elements[index].id; useEffect(() => { if ( @@ -264,6 +236,13 @@ const Graph: React.FC = ({ } as any).run(); }); + const starSVG = ` + + + + +`; + const starSVGURL = `data:image/svg+xml;utf8,${encodeURIComponent(starSVG)}`; // ADD NODES for (var i = 0; i < elements.length; i++) { if (toggles[simple[i]] !== false) { @@ -283,6 +262,9 @@ const Graph: React.FC = ({ borderWidth: '2px', borderColor: 'black', fontFamily: 'Roboto', + backgroundFit: 'contain', + backgroundClip: 'none', + backgroundImage: `url(${starSVGURL})`, }, }); } else { @@ -302,12 +284,14 @@ const Graph: React.FC = ({ : elements[i].id : '', fontSize: '12px', + fontFamily: 'Roboto', }, }); } } } + const c = edgeTypes.every((e) => e !== 'Edge'); // ADD EDGES let edgeCount = 0; for (var j = 0; j < elements.length; j++) { @@ -327,8 +311,7 @@ const Graph: React.FC = ({ }, style: { 'line-color': getColor ? getColor(edges[j]) : 'grey', - 'target-arrow-shape': - edgeTypes[j] !== 'Edge' ? 'triangle' : null, + 'target-arrow-shape': c ? 'triangle' : null, 'target-arrow-color': getColor ? getColor(edges[j]) : 'grey', width: scale(scales[j]), }, @@ -459,37 +442,6 @@ const Graph: React.FC = ({ {title} - {data.centered ? ( -
- - Degrees of Separation: - - setDegree(parseInt(e.target.value))} - style={{ marginLeft: '5px', marginTop: '5px' }} - /> -
- ) : null} - {showControls && (
= ({ randomize={randomize} organize={organize} toggleLabels={() => setShowLabels(!showLabels)} + labelsOn={showLabels} colorFunc={getColor} legendToggle={legendToggle} + legendNodeLabel={legendNodeLabel} + legendEdgeLabel={legendEdgeLabel} + uniqueCat={order ? unique : undefined} />
)} diff --git a/src/components/Graph/Legend.tsx b/src/components/Graph/Legend.tsx index 1835a6d..5f1f3c0 100644 --- a/src/components/Graph/Legend.tsx +++ b/src/components/Graph/Legend.tsx @@ -12,6 +12,10 @@ interface LegendProps { elements: Node[]; edges: Edge[]; legendToggle?: (node: Node | Edge) => string; + legendNodeLabel?: string; + legendEdgeLabel?: string; + order?: string[]; + uniqueCat?: string[]; } const Legend: React.FC = ({ @@ -23,6 +27,9 @@ const Legend: React.FC = ({ elements, edges, legendToggle, + legendNodeLabel, + legendEdgeLabel, + uniqueCat, }) => { const edgeTypes = Array.from( new Set( @@ -50,53 +57,116 @@ const Legend: React.FC = ({ padding: '5px', }} > - {uniqueCategories.map((category) => { - let color = 'grey'; - let cat = ''; + + {legendNodeLabel ? legendNodeLabel : 'Node Type'} + + {uniqueCat + ? uniqueCat.map((category) => { + let color = 'grey'; + let c = ''; - elements.forEach((node) => { - if (colorFunc && legendToggle && legendToggle(node) === category) { - color = colorFunc(node); - cat = node.category; - } - }); + elements.forEach((node) => { + uniqueCategories.forEach((cat) => { + if ( + colorFunc && + legendToggle && + legendToggle(node) === cat && + node.category === category + ) { + color = colorFunc(node); + c = cat; + return; + } + }); + }); - return ( -
- onToggle(category)} - color="primary" - size="small" - style={{ padding: 0 }} - /> - onToggle(category)} - > - {category} {legendToggle ? '(' : null} - {legendToggle ? cat : null} - {legendToggle ? ')' : null} - -
- ); - })} + return ( +
+ onToggle(category)} + color="primary" + size="small" + style={{ padding: 0 }} + /> + onToggle(category)} + > + {c} {legendToggle ? '(' : null} + {legendToggle ? category : null} + {legendToggle ? ')' : null} + +
+ ); + }) + : uniqueCategories.map((category) => { + let color = 'grey'; + let cat = ''; + + elements.forEach((node) => { + if ( + colorFunc && + legendToggle && + legendToggle(node) === category + ) { + color = colorFunc(node); + cat = node.category; + } + }); + + return ( +
+ onToggle(category)} + color="primary" + size="small" + style={{ padding: 0 }} + /> + onToggle(category)} + > + {category} {legendToggle ? '(' : null} + {legendToggle ? cat : null} + {legendToggle ? ')' : null} + +
+ ); + })} {edgeType && edgeTypes !== null ? (
+ + {legendEdgeLabel ? legendEdgeLabel : 'Edge Type'} + {edgeTypes.map((category) => { if (category === undefined) { return null; @@ -119,7 +189,6 @@ const Legend: React.FC = ({ style={{ display: 'flex', alignItems: 'center', - marginBottom: '4px', }} > = ({ scales, width }) => { .replace('Math.', '') || scaleFunctionStr; const divStyle: CSSProperties = { - top: '25vh', + top: '20vh', zIndex: 1000, backgroundColor: 'white', padding: '10px', diff --git a/src/components/Graph/types.ts b/src/components/Graph/types.ts index a6e97c0..584f157 100644 --- a/src/components/Graph/types.ts +++ b/src/components/Graph/types.ts @@ -26,9 +26,13 @@ export interface Edge { getLabel?: (node: Node) => string, getColor?: (node: Node | Edge) => string, legendToggle?: (node: Node | Edge) => string, + legendNodeLabel?: string, + legendEdgeLabel?: string, + order?: string[], } + export type ToolTipData = { id?: string; type: string; diff --git a/stories/Graph.stories.tsx b/stories/Graph.stories.tsx index 2f39dc9..ed410ef 100644 --- a/stories/Graph.stories.tsx +++ b/stories/Graph.stories.tsx @@ -108,7 +108,7 @@ function convertToSimple(node: Node | Edge): string { return node.category; } } - return ''; + return 'Edge'; } function convertToSimple2(node: Node | Edge): string { @@ -121,7 +121,7 @@ function convertToSimple2(node: Node | Edge): string { return 'purple nodes'; default: if (node.category) return node.category; - return ''; + return 'Edge'; } } const meta: Meta = { @@ -148,6 +148,18 @@ PilotDataWithCentered.args = { id: 'PilotWithCentered', getColor: setColor, legendToggle: convertToSimple, + legendNodeLabel: 'cCRE Type', + order: [ + 'PLS', + 'pELS', + 'dELS', + 'CA-H3K4me3', + 'CA-CTCF', + 'CA-TF', + 'CA', + 'TF', + 'Low DNase', + ], }; export const FiftyPercent = Template.bind({}); @@ -159,6 +171,7 @@ FiftyPercent.args = { height: '50%', getColor: setColor, legendToggle: convertToSimple, + legendNodeLabel: 'cCRE Type', }; export const PilotDataWithoutCentered = Template.bind({}); @@ -168,6 +181,7 @@ PilotDataWithoutCentered.args = { id: 'PilotNoCentered', getColor: setColor, legendToggle: convertToSimple, + legendNodeLabel: 'cCRE Type', }; export const DifferentLabel = Template.bind({}); @@ -178,6 +192,8 @@ DifferentLabel.args = { getLabel: (node: Node) => node.category, getColor: setColor, legendToggle: convertToSimple, + legendNodeLabel: 'Different Node Label', + legendEdgeLabel: 'Different Edge Label', }; export const DifferentColor = Template.bind({}); @@ -197,3 +213,24 @@ NoLegendToggle.args = { scale: (n: number) => 10 * n, getColor: setColor3, }; + +export const DifferentOrder = Template.bind({}); +DifferentOrder.args = { + data: data.data, + title: 'Different Order', + id: 'diffOrder', + getColor: setColor, + legendToggle: convertToSimple, + legendNodeLabel: 'cCRE Type', + order: [ + 'Low DNase', + 'PLS', + 'dELS', + 'TF', + 'pELS', + 'CA-CTCF', + 'CA', + 'CA-H3K4me3', + 'CA-TF', + ], +}; From 002b48ca31510f41df73df62429b09e0ee91d047 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 18 Jul 2024 09:35:12 -0400 Subject: [PATCH 27/31] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 07eaaec..1000762 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@weng-lab/psychscreen-ui-components", "description": "Typescript and Material UI based components used for psychSCREEN", "author": "SCREEN Team @ UMass Chan Medical School", - "version": "0.9.0", + "version": "0.9.1", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", From 614f72ce4278a229147a3d3fb4e13a524a357794 Mon Sep 17 00:00:00 2001 From: evac-tcxr Date: Thu, 18 Jul 2024 11:23:07 -0400 Subject: [PATCH 28/31] Update ControlPanel.tsx --- src/components/Graph/ControlPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Graph/ControlPanel.tsx b/src/components/Graph/ControlPanel.tsx index 641a89f..89bec63 100644 --- a/src/components/Graph/ControlPanel.tsx +++ b/src/components/Graph/ControlPanel.tsx @@ -66,7 +66,7 @@ const ControlPanel: React.FC = ({ backgroundColor: 'white', transition: 'width 0.3s', zIndex: 1000, - border: '1px solid grey', + marginRight: '5px', overflowY: 'auto', maxHeight: '100vh', @@ -83,7 +83,7 @@ const ControlPanel: React.FC = ({ }; return ( - +