diff --git a/calm-visualizer/src/App.tsx b/calm-visualizer/src/App.tsx index 3f58c1c5..6796d23b 100644 --- a/calm-visualizer/src/App.tsx +++ b/calm-visualizer/src/App.tsx @@ -4,6 +4,7 @@ import Drawer from './components/drawer/Drawer'; import Navbar from './components/navbar/Navbar'; import { CALMArchitecture } from '../../shared/src/types'; import React from 'react'; +import { ZoomProvider } from './components/zoom-context.provider'; function App() { const [title, setTitle] = useState(undefined); @@ -21,20 +22,22 @@ function App() { } return ( -
- setNodeDescActive((isNodeDescActive) => !isNodeDescActive)} - toggleConnectionDesc={() => setConDescActive((isConDescActive) => !isConDescActive)} - /> - -
+ +
+ setNodeDescActive((isNodeDescActive) => !isNodeDescActive)} + toggleConnectionDesc={() => setConDescActive((isConDescActive) => !isConDescActive)} + /> + +
+
); } diff --git a/calm-visualizer/src/components/cytoscape-renderer/CytoscapeRenderer.tsx b/calm-visualizer/src/components/cytoscape-renderer/CytoscapeRenderer.tsx index 25096504..8021859b 100644 --- a/calm-visualizer/src/components/cytoscape-renderer/CytoscapeRenderer.tsx +++ b/calm-visualizer/src/components/cytoscape-renderer/CytoscapeRenderer.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import './cytoscape.css'; -import { useEffect, useRef, useState } from 'react'; +import { useContext, useEffect, useRef, useState } from 'react'; import cytoscape, { Core, EdgeSingular, NodeSingular } from 'cytoscape'; import nodeHtmlLabel from 'cytoscape-node-html-label'; import nodeEdgeHtmlLabel from 'cytoscape-node-edge-html-label'; @@ -8,6 +8,7 @@ import coseBilkent from 'cytoscape-cose-bilkent'; import expandCollapse from 'cytoscape-expand-collapse'; import fcose from 'cytoscape-fcose'; import Sidebar from '../sidebar/Sidebar'; +import { ZoomContext } from '../zoom-context.provider'; //Make some information available on tooltip hover @@ -50,7 +51,6 @@ export type Edge = { }; }; - interface Props { title?: string; isNodeDescActive: boolean; @@ -68,11 +68,16 @@ const CytoscapeRenderer = ({ }: Props) => { const cyRef = useRef(null); const [cy, setCy] = useState(null); + const { zoomLevel, updateZoom } = useContext(ZoomContext); const [selectedNode, setSelectedNode] = useState(null); const [selectedEdge, setSelectedEdge] = useState(null); useEffect(() => { if (cy) { + //Ensure cytoscape zoom and context state are synchronised + if (cy.zoom() !== zoomLevel) { + updateZoom(cy.zoom()); + } /* eslint-disable @typescript-eslint/no-explicit-any */ (cy as Core & { nodeHtmlLabel: any }).nodeHtmlLabel([ { @@ -103,11 +108,13 @@ const CytoscapeRenderer = ({ setSelectedEdge(edge?.data()); // Update state with the clicked node's data }); + cy.on('zoom', () => updateZoom(cy.zoom())); + return () => { cy.destroy(); }; } - }, [cy, isNodeDescActive]); + }, [cy, zoomLevel, updateZoom, isNodeDescActive]); useEffect(() => { // Initialize Cytoscape instance @@ -154,6 +161,13 @@ const CytoscapeRenderer = ({ ); }, [nodes, edges, isConDescActive]); // Re-render on cy, nodes or edges change + useEffect(() => { + //Ensure cytoscape zoom and context state are synchronised + if (cy?.zoom() !== zoomLevel) { + cy?.zoom(zoomLevel); + } + }, [zoomLevel]); + return (
{title && ( diff --git a/calm-visualizer/src/components/navbar/Navbar.css b/calm-visualizer/src/components/navbar/Navbar.css new file mode 100644 index 00000000..00848c1f --- /dev/null +++ b/calm-visualizer/src/components/navbar/Navbar.css @@ -0,0 +1,8 @@ +.zoom-button { + border: solid; + border-width: 1px; +} + +.zoom-button:hover { + background-color: aliceblue; +} \ No newline at end of file diff --git a/calm-visualizer/src/components/navbar/Navbar.tsx b/calm-visualizer/src/components/navbar/Navbar.tsx index 1a83b003..f3d0565b 100644 --- a/calm-visualizer/src/components/navbar/Navbar.tsx +++ b/calm-visualizer/src/components/navbar/Navbar.tsx @@ -1,4 +1,6 @@ -import React from 'react'; +import './Navbar.css' +import React, { useContext } from 'react'; +import { ZoomContext } from '../zoom-context.provider'; interface NavbarProps { handleUpload: (instanceFile: File) => void; @@ -16,6 +18,23 @@ function Navbar({ const upload = (file: File) => { handleUpload(file); }; + const { zoomLevel, updateZoom } = useContext(ZoomContext); + + function zoomIn() { + //Obtain percentage as integer + const currentPercentageZoom = Math.round(zoomLevel*100); + //Add 10% to the zoom or round to upper 10% interval + const newPercentageZoom = Math.floor(currentPercentageZoom/10)*10 + 10; + updateZoom(newPercentageZoom/100); + } + + function zoomOut() { + //Obtain percentage as integer + const currentPercentageZoom = Math.round(zoomLevel*100); + //Subtract 10% from the zoom or round to lower 10% interval - but not less than zero + const newPercentageZoom = Math.max(Math.ceil(currentPercentageZoom/10)*10 - 10, 0); + updateZoom(newPercentageZoom/100); + } return (
@@ -51,8 +70,13 @@ function Navbar({ {isGraphRendered && ( <> -
+
+
+ Zoom: {(zoomLevel*100).toFixed(0)}% + + +