Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce zoom control with tentative UI #791

Merged
merged 3 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions calm-visualizer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | undefined>(undefined);
Expand All @@ -21,20 +22,22 @@ function App() {
}

return (
<div className="h-screen flex flex-col">
<Navbar
handleUpload={handleFile}
isGraphRendered={instance ? true : false}
toggleNodeDesc={() => setNodeDescActive((isNodeDescActive) => !isNodeDescActive)}
toggleConnectionDesc={() => setConDescActive((isConDescActive) => !isConDescActive)}
/>
<Drawer
isNodeDescActive={isNodeDescActive}
isConDescActive={isConDescActive}
calmInstance={instance}
title={title}
/>
</div>
<ZoomProvider>
<div className="h-screen flex flex-col">
<Navbar
handleUpload={handleFile}
isGraphRendered={instance ? true : false}
toggleNodeDesc={() => setNodeDescActive((isNodeDescActive) => !isNodeDescActive)}
toggleConnectionDesc={() => setConDescActive((isConDescActive) => !isConDescActive)}
/>
<Drawer
isNodeDescActive={isNodeDescActive}
isConDescActive={isConDescActive}
calmInstance={instance}
title={title}
/>
</div>
</ZoomProvider>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/* 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';
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

Expand Down Expand Up @@ -50,7 +51,6 @@ export type Edge = {
};
};


interface Props {
title?: string;
isNodeDescActive: boolean;
Expand All @@ -68,11 +68,16 @@ const CytoscapeRenderer = ({
}: Props) => {
const cyRef = useRef<HTMLDivElement>(null);
const [cy, setCy] = useState<Core | null>(null);
const { zoomLevel, updateZoom } = useContext(ZoomContext);
const [selectedNode, setSelectedNode] = useState<Node['data'] | null>(null);
const [selectedEdge, setSelectedEdge] = useState<Edge['data'] | null>(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([
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 (
<div className="relative flex m-auto border">
{title && (
Expand Down
8 changes: 8 additions & 0 deletions calm-visualizer/src/components/navbar/Navbar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.zoom-button {
border: solid;
border-width: 1px;
}

.zoom-button:hover {
background-color: aliceblue;
}
28 changes: 26 additions & 2 deletions calm-visualizer/src/components/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 (
<div className="navbar bg-secondary text-secondary-content">
Expand Down Expand Up @@ -51,8 +70,13 @@ function Navbar({

{isGraphRendered && (
<>
<div className="divider divider-horizontal"></div>
<div className="divider divider-horizontal"></div>
<div className="toggles menu-horizontal">
<div className="label">
<span className="label label-text">Zoom: {(zoomLevel*100).toFixed(0)}%</span>
<button className='ms-1 ps-2 pe-2 zoom-button' onClick={zoomIn}>+</button>
<button className='ms-1 ps-2 pe-2 zoom-button' onClick={zoomOut}>-</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the daisyui styles? https://daisyui.com/components/button/

</div>
<label className="label cursor-pointer">
<span className="label label-text text-secondary-content">
Connection Descriptions
Expand Down
28 changes: 28 additions & 0 deletions calm-visualizer/src/components/zoom-context.provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { createContext, useState } from 'react';

type ZoomContextProps = {
zoomLevel: number,
updateZoom: (newZoomLevel: number) => void
}

type ZoomProviderProps = { children: React.ReactNode; };

const ZoomContext = createContext<ZoomContextProps>({
zoomLevel: 1, updateZoom: () => {}
});

const ZoomProvider = ({children}: ZoomProviderProps) => {
const [zoomLevel, setZoomLevel] = useState(1);

const updateZoom = (newZoomLevel: number) => {
setZoomLevel(newZoomLevel);
};

return (
<ZoomContext.Provider value={{ zoomLevel, updateZoom }}>
{children}
</ZoomContext.Provider>
);
};

export { ZoomContext, ZoomProvider };
Loading