Skip to content

Commit

Permalink
Merge pull request finos#698 from oliviajanejohns/explore-cytoscape
Browse files Browse the repository at this point in the history
Added toggle to hide/show node descriptions
  • Loading branch information
oliviajanejohns authored Dec 24, 2024
2 parents 3cc9513 + 77057af commit da26d35
Show file tree
Hide file tree
Showing 17 changed files with 28,565 additions and 11,233 deletions.
1,345 changes: 1,306 additions & 39 deletions calm-visualizer/package-lock.json

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions calm-visualizer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"build": "tsc -b && vite build",
"lint": "eslint .",
"format": "prettier --write .",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"cytoscape": "^3.30.3",
Expand All @@ -26,7 +27,11 @@
},
"devDependencies": {
"@eslint/js": "^9.14.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
"@types/cytoscape": "^3.21.8",
"@types/cytoscape-fcose": "^2.2.4",
"@types/file-saver": "^2.0.7",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
Expand All @@ -38,11 +43,13 @@
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.12",
"globals": "^15.12.0",
"jsdom": "^25.0.1",
"postcss": "^8.4.49",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.14",
"typescript": "^5.5.3",
"typescript-eslint": "^8.14.0",
"vite": "^5.4.8"
"vite": "^5.4.8",
"vitest": "^2.1.8"
}
}
13 changes: 10 additions & 3 deletions calm-visualizer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import React from 'react';
function App() {
const [title, setTitle] = useState<string | undefined>(undefined);
const [instance, setCALMInstance] = useState<CALMInstantiation | undefined>(undefined);
const [isDescActive, setDescriptionsActive] = React.useState(false);
const [isConDescActive, setConDescActive] = React.useState(false);
const [isNodeDescActive, setNodeDescActive] = React.useState(false);

async function handleFile(instanceFile: File) {
const title = instanceFile.name;
Expand All @@ -23,10 +24,16 @@ function App() {
<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}
toggleDescriptions={() => setDescriptionsActive((isDescActive) => !isDescActive)}
title={title}
/>
<Drawer isDescActive={isDescActive} calmInstance={instance} title={title} />
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const fcoseLayoutOptions = {
export type Node = {
classes?: string;
data: {
description: string;
type: string;
label: string;
id: string;
[idx: string]: string;
Expand All @@ -68,21 +70,28 @@ export type Edge = {
};

interface Props {
title: string;
title?: string;
isNodeDescActive: boolean;
isConDescActive: boolean;
nodes: Node[];
edges: Edge[];
}

const CytoscapeRenderer = ({ title, nodes = [], edges = [] }: Props) => {
const CytoscapeRenderer = ({
title,
nodes = [],
edges = [],
isConDescActive,
isNodeDescActive,
}: Props) => {
const cyRef = useRef<HTMLDivElement>(null);
const [cy, setCy] = useState<Core | null>(null);
const [selectedNode, setSelectedNode] = useState<Node['data'] | null>(null);
const [selectedEdge, setSelectedEdge] = useState<Edge['data'] | null>(null);

useEffect(() => {
if (cy) {
//@ts-expect-error types are missing from the library
cy.nodeHtmlLabel([
(cy as any).nodeHtmlLabel([
{
query: '.node',
halign: 'center',
Expand All @@ -92,7 +101,8 @@ const CytoscapeRenderer = ({ title, nodes = [], edges = [] }: Props) => {
tpl: (data: Node['data']) => {
return `<div class="node element">
<p class="title">${data.label}</p>
<p class="type">[database]</p>
<p class="type">${data.type}</p>
<p class="description">${isNodeDescActive ? data.description : ''}</p>
</div>`;
},
},
Expand Down Expand Up @@ -131,20 +141,12 @@ const CytoscapeRenderer = ({ title, nodes = [], edges = [] }: Props) => {
container: container, // container to render
elements: [...nodes, ...edges], // graph data
style: [
{
selector: 'node',
style: {
width: '200px',
height: '100px',
shape: 'rectangle',
},
},
{
selector: 'edge',
style: {
width: 2,
'curve-style': 'bezier',
label: 'data(label)', // labels from data property
label: isConDescActive ? 'data(label)' : '', // labels from data property
'target-arrow-shape': 'triangle',
'text-wrap': 'ellipsis',
'text-background-color': 'white',
Expand All @@ -166,7 +168,11 @@ const CytoscapeRenderer = ({ title, nodes = [], edges = [] }: Props) => {

return (
<div className="relative flex m-auto border">
<div className="text-l font-bold absolute">{title}</div>
{title && (
<div className="graph-title absolute m-5 bg-primary-content shadow-md">
<span className="text-m">Architecture: {title}</span>
</div>
)}
<div
ref={cyRef}
className="flex-1 bg-white visualizer"
Expand Down
12 changes: 11 additions & 1 deletion calm-visualizer/src/components/cytoscape-renderer/cytoscape.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@
border: 1px solid;
text-align: center;
width: 200px;
height: 100px;
min-height: 100px;
font-family: Arial;
padding: 10px 30px 10px 30px;
background-color: white;
}

:focus {
border: 1px solid;
border-color: blue;
}

.graph-title {
z-index: 1;
padding: var(--navbar-padding, 0.5rem);
}

.element {
display: flex;
flex-direction: column;
Expand Down
27 changes: 18 additions & 9 deletions calm-visualizer/src/components/drawer/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
interface DrawerProps {
calmInstance?: CALMInstantiation;
title?: string;
isDescActive: boolean;
isNodeDescActive: boolean;
isConDescActive: boolean;
}

function isComposedOf(relationship: CALMRelationship): relationship is CALMComposedOfRelationship {
Expand Down Expand Up @@ -78,7 +79,7 @@ const getDeployedInRelationships = (calmInstance: CALMInstantiation) => {
return deployedInRelationships;
};

function Drawer({ calmInstance, title, isDescActive }: DrawerProps) {
function Drawer({ calmInstance, title, isConDescActive, isNodeDescActive }: DrawerProps) {
const [selectedNode, setSelectedNode] = useState(null);

function closeSidebar() {
Expand Down Expand Up @@ -151,15 +152,14 @@ function Drawer({ calmInstance, title, isDescActive }: DrawerProps) {
return {
data: {
id: relationship['unique-id'],
label: (isDescActive && relationship.description) || '',
label: relationship.description || '',
source,
target,
},
};
}
})
.filter((edge) => edge !== undefined);

return edges;
}

Expand All @@ -171,16 +171,25 @@ function Drawer({ calmInstance, title, isDescActive }: DrawerProps) {
<div className={`drawer drawer-end ${selectedNode ? 'drawer-open' : ''}`}>
<input
type="checkbox"
aria-label="drawer-toggle"
className="drawer-toggle"
checked={!!selectedNode}
onChange={closeSidebar}
/>
<div className="drawer-content">
<div id="app m-5">
{calmInstance && (
<CytoscapeRenderer title={title || ''} nodes={nodes} edges={edges} />
)}
</div>
{calmInstance ? (
<CytoscapeRenderer
isConDescActive={isConDescActive}
isNodeDescActive={isNodeDescActive}
title={title}
nodes={nodes}
edges={edges}
/>
) : (
<div className="flex justify-center items-center h-full">
No file selected
</div>
)}
</div>
{selectedNode && (
<Sidebar selectedData={selectedNode} closeSidebar={closeSidebar} />
Expand Down
36 changes: 0 additions & 36 deletions calm-visualizer/src/components/fileuploader/FileUploader.tsx

This file was deleted.

59 changes: 41 additions & 18 deletions calm-visualizer/src/components/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import React from 'react';
import { CALMInstantiation } from '../../../../shared/src';

interface NavbarProps {
handleUpload: (instanceFile: File) => void;
toggleDescriptions: () => void;
calmInstance?: CALMInstantiation;
isGraphRendered: boolean;
toggleConnectionDesc: () => void;
toggleNodeDesc: () => void;
}

function Navbar({ handleUpload, toggleDescriptions, calmInstance }: NavbarProps) {
function Navbar({
handleUpload,
isGraphRendered,
toggleConnectionDesc,
toggleNodeDesc,
}: NavbarProps) {
const upload = (file: File) => {
handleUpload(file);
};
Expand All @@ -16,19 +21,22 @@ function Navbar({ handleUpload, toggleDescriptions, calmInstance }: NavbarProps)
<div className="navbar bg-base-300">
<div className="flex-1">
<a className="btn btn-ghost text-xl">CALM</a>
<div className="divider divider-horizontal"></div>
<span className="text-lg">Visualizer</span>
</div>
<div className="flex-none">
<ul className="menu menu-horizontal px-1">
<ul className="menu menu-horizontal px-1" aria-label="navbar-menu-items">
<li>
<details>
<summary>Upload</summary>
<ul className="p-2 z-1">
<ul className="p-2 z-1" aria-label="upload-dropdown-items">
<li>
<label>
Architecture
<input
id="file"
type="file"
aria-label="upload-architecture"
className="hidden"
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
e.target.files && upload(e.target.files[0])
Expand All @@ -40,18 +48,33 @@ function Navbar({ handleUpload, toggleDescriptions, calmInstance }: NavbarProps)
</details>
</li>
</ul>
<div className="toggles">
{calmInstance && (
<label className="label cursor-pointer">
<span className="label-text">Connection Descriptions</span>
<input
type="checkbox"
className="toggle"
onClick={toggleDescriptions}
/>
</label>
)}
</div>

{isGraphRendered && (
<>
<div className="divider divider-horizontal"></div>
<div className="toggles menu-horizontal">
<label className="label cursor-pointer">
<span className="label label-text">Connection Descriptions</span>
<input
type="checkbox"
className="toggle"
name="connection-description"
aria-label="connection-description"
onClick={toggleConnectionDesc}
/>
</label>
<label className="label cursor-pointer">
<span className="label label-text">Node Descriptions</span>
<input
type="checkbox"
className="toggle"
aria-label="node-description"
onClick={toggleNodeDesc}
/>
</label>
</div>
</>
)}
</div>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion calm-visualizer/src/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ function Sidebar({ selectedData, closeSidebar }: SidebarProps) {
// Determine if we have selected a node or edge or something else
const isCALMNode = isCALMNodeData(selectedData);
const isCALMEdge = isCALMEdgeData(selectedData);
console.log('This is the selectedData => ', selectedData);

return (
<div className="fixed right-0 h-full w-80 bg-gray-100 shadow-lg">
<label htmlFor="node-details" className="drawer-overlay" onClick={closeSidebar}></label>
<div className="menu bg-base-200 text-base-content min-h-full w-80 p-4">
<div className="flex justify-end">
<button
aria-label="close-sidebar"
onClick={(e) => {
e.stopPropagation();
closeSidebar();
Expand Down
Loading

0 comments on commit da26d35

Please sign in to comment.