From d29cea12667deb3926f3c915170a10d8639ede16 Mon Sep 17 00:00:00 2001 From: afonso Date: Thu, 1 Aug 2024 13:41:47 +0100 Subject: [PATCH] CELE-32 fix: Fix graph container resize --- .../src/components/viewers/TwoD/TwoDMenu.tsx | 6 +- .../components/viewers/TwoD/TwoDViewer.tsx | 6 +- .../frontend/src/helpers/twoD/twoDHelpers.ts | 169 ++++++++++-------- 3 files changed, 98 insertions(+), 83 deletions(-) diff --git a/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDMenu.tsx b/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDMenu.tsx index b043ecf7..c434d24b 100644 --- a/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDMenu.tsx +++ b/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDMenu.tsx @@ -15,12 +15,12 @@ import { } from "@mui/material"; import {useState} from "react"; import {ColoringOptions} from "../../../helpers/twoD/coloringHelper.ts"; -import {applyLayout} from "../../../helpers/twoD/twoDHelpers.ts"; import {GRAPH_LAYOUTS, ZOOM_DELTA} from "../../../settings/twoDSettings.tsx"; import {vars} from "../../../theme/variables.ts"; import CustomSwitch from "../../ViewerContainer/CustomSwitch.tsx"; import QuantityInput from "./NumberInput.tsx"; import {useSelectedWorkspace} from "../../../hooks/useSelectedWorkspace.ts"; +import {applyLayout, refreshLayout} from "../../../helpers/twoD/twoDHelpers.ts"; const {gray500} = vars; @@ -77,9 +77,7 @@ const TwoDMenu = ({ if (!cy) { return; } - cy.reset(); // Reset the zoom and pan positions - applyLayout(cy, layout); - cy.fit(); + applyLayout(cy, layout) }; const handleOpenSettings = (event) => { diff --git a/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDViewer.tsx b/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDViewer.tsx index 823a6177..3b3878bc 100644 --- a/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDViewer.tsx +++ b/applications/visualizer/frontend/src/components/viewers/TwoD/TwoDViewer.tsx @@ -5,7 +5,7 @@ import dagre from "cytoscape-dagre"; import {useSelectedWorkspace} from "../../../hooks/useSelectedWorkspace"; import {type Connection, ConnectivityService} from "../../../rest"; import {GRAPH_STYLES} from "../../../theme/twoDStyles"; -import {applyLayout} from "../../../helpers/twoD/twoDHelpers"; +import {applyLayout, refreshLayout} from "../../../helpers/twoD/twoDHelpers"; import { CHEMICAL_THRESHOLD, ELECTRICAL_THRESHOLD, @@ -65,7 +65,7 @@ const TwoDViewer = () => { const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { if (entry.target === cyContainer.current) { - updateLayout(); + refreshLayout(cy); } } }); @@ -285,7 +285,7 @@ const TwoDViewer = () => { const updateLayout = () => { if (cyRef.current) { - applyLayout(cyRef, layout); + applyLayout(cyRef.current, layout); } }; diff --git a/applications/visualizer/frontend/src/helpers/twoD/twoDHelpers.ts b/applications/visualizer/frontend/src/helpers/twoD/twoDHelpers.ts index d7b0a8d7..9ea5a969 100644 --- a/applications/visualizer/frontend/src/helpers/twoD/twoDHelpers.ts +++ b/applications/visualizer/frontend/src/helpers/twoD/twoDHelpers.ts @@ -1,96 +1,93 @@ -import type { Core, ElementDefinition } from "cytoscape"; -import type { Connection } from "../../rest"; -import type { Workspace } from "../../models/workspace.ts"; +import type {Core, ElementDefinition, Position} from "cytoscape"; +import type {Connection} from "../../rest"; +import type {Workspace} from "../../models/workspace.ts"; import {annotationLegend} from "../../settings/twoDSettings.tsx"; import {cellConfig, neurotransmitterConfig} from "./coloringHelper.ts"; export const createEdge = (id: string, conn: Connection, workspace: Workspace, includeAnnotations: boolean): ElementDefinition => { - const synapses = conn.synapses || {}; - const annotations = conn.annotations || []; + const synapses = conn.synapses || {}; + const annotations = conn.annotations || []; - const label = createEdgeLabel(workspace, synapses); - const longLabel = createEdgeLongLabel(workspace, synapses); + const label = createEdgeLabel(workspace, synapses); + const longLabel = createEdgeLongLabel(workspace, synapses); - let annotationClasses: string[] = []; + let annotationClasses: string[] = []; - if (includeAnnotations) { - annotationClasses = annotations.map(annotation => annotationLegend[annotation]?.id).filter(Boolean); - if (annotationClasses.length === 0) { - annotationClasses.push(annotationLegend.notClassified.id); + if (includeAnnotations) { + annotationClasses = annotations.map(annotation => annotationLegend[annotation]?.id).filter(Boolean); + if (annotationClasses.length === 0) { + annotationClasses.push(annotationLegend.notClassified.id); + } + } else { + annotationClasses.push(conn.type); } - } else { - annotationClasses.push(conn.type); - } - - const classes = annotationClasses.join(" "); - - return { - group: "edges", - data: { - id: id, - source: conn.pre, - target: conn.post, - label: label, - longLabel: longLabel, - type: conn.type, - }, - classes: classes, - }; + + const classes = annotationClasses.join(" "); + + return { + group: "edges", + data: { + id: id, + source: conn.pre, + target: conn.post, + label: label, + longLabel: longLabel, + type: conn.type, + }, + classes: classes, + }; }; // Helper functions to create edge labels const createEdgeLabel = (workspace: Workspace, synapses: Record) => { - const datasets = Object.values(workspace.activeDatasets).map(dataset => dataset.id); - return datasets.map(datasetId => synapses[datasetId] || 0).join(','); + const datasets = Object.values(workspace.activeDatasets).map(dataset => dataset.id); + return datasets.map(datasetId => synapses[datasetId] || 0).join(','); }; const createEdgeLongLabel = (workspace: Workspace, synapses: Record) => { - const datasets = Object.values(workspace.activeDatasets) - return datasets.map(dataset => { - const datasetLabel = synapses[dataset.id] || 0; - return `${dataset.name}: ${datasetLabel}`; - }).join('\n'); + const datasets = Object.values(workspace.activeDatasets) + return datasets.map(dataset => { + const datasetLabel = synapses[dataset.id] || 0; + return `${dataset.name}: ${datasetLabel}`; + }).join('\n'); }; -export const createNode = ( - nodeId: string, - selected: boolean, - attributes: string[] -): ElementDefinition => { - const data = { id: nodeId, label: nodeId }; - - // Set each attribute in the data object to true - attributes.forEach(attr => { - data[attr] = true; - }); - - return { - group: "nodes", - data, - classes: selected ? "selected" : "", - }; +export const createNode = (nodeId: string, selected: boolean, attributes: string[], position?: Position): ElementDefinition => { + const node: ElementDefinition = { + group: "nodes", + data: {id: nodeId, label: nodeId, ...attributes.reduce((acc, attr) => ({...acc, [attr]: true}), {})}, + classes: selected ? "selected" : "" + }; + if (position) { + node.position = position; + } + return node; }; -export function applyLayout(cyRef: React.MutableRefObject, layout: string) { - if (cyRef.current) { - cyRef.current - .layout({ +export function applyLayout(cy: Core, layout: string) { + cy.layout({ name: layout, - }) - .run(); - } + }).run(); + + refreshLayout(cy) +} + +export function refreshLayout(cy: Core) { + cy.resize(); // Adjust the viewport size + cy.center(); // Center the graph in the container + cy.fit(); // Fit the graph to the container } // Helper functions export const isNeuronCell = (neuronId: string, workspace: Workspace): boolean => { - const neuron = workspace.availableNeurons[neuronId]; - return neuron ? neuron.name !== neuron.nclass : false; + const neuron = workspace.availableNeurons[neuronId]; + return neuron ? neuron.name !== neuron.nclass : false; }; export const isNeuronClass = (neuronId: string, workspace: Workspace): boolean => { - const neuron = workspace.availableNeurons[neuronId]; - return neuron ? neuron.name === neuron.nclass : false; + const neuron = workspace.availableNeurons[neuronId]; + return neuron ? neuron.name === neuron.nclass : false; }; export const getEdgeId = (conn: Connection, includeAnnotations: boolean): string => { @@ -101,21 +98,41 @@ export const getEdgeId = (conn: Connection, includeAnnotations: boolean): string export const extractNeuronAttributes = (neuron) => { - const cellAttributes = neuron.type.split('').map(char => cellConfig[char]?.type).filter(Boolean); - const neurotransmitterAttributes = neuron.neurotransmitter.split('').map(char => neurotransmitterConfig[char]?.type).filter(Boolean); + const cellAttributes = neuron.type.split('').map(char => cellConfig[char]?.type).filter(Boolean); + const neurotransmitterAttributes = neuron.neurotransmitter.split('').map(char => neurotransmitterConfig[char]?.type).filter(Boolean); - return [...cellAttributes, ...neurotransmitterAttributes]; + return [...cellAttributes, ...neurotransmitterAttributes]; }; export const getNclassSet = (neuronIds: Set, workspace: Workspace): Set => { - const nclassSet = new Set(); - neuronIds.forEach(neuronId => { - const neuron = workspace.availableNeurons[neuronId]; - if (neuron && neuron.nclass) { - nclassSet.add(neuron.nclass); - } - }); - return nclassSet; + const nclassSet = new Set(); + neuronIds.forEach(neuronId => { + const neuron = workspace.availableNeurons[neuronId]; + if (neuron && neuron.nclass) { + nclassSet.add(neuron.nclass); + } + }); + return nclassSet; }; +export const calculateMeanPosition = (nodeIds: string[], cy: Core): Position => { + let totalX = 0; + let totalY = 0; + let count = 0; + + nodeIds.forEach(nodeId => { + const node = cy.getElementById(nodeId); + if (node && node.position) { + const position = node.position(); + totalX += position.x; + totalY += position.y; + count++; + } + }); + + return { + x: totalX / count, + y: totalY / count + }; +}; \ No newline at end of file