Skip to content

Commit

Permalink
VT-23. Add components for GraphView, TreeView, InfoBox and Workspace …
Browse files Browse the repository at this point in the history
…with some functionalities for each
  • Loading branch information
romina1601 committed Aug 16, 2024
1 parent c235815 commit b7884b4
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 119 deletions.
59 changes: 36 additions & 23 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,52 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import '../style/layout.css';
import { OntologyWidgetComponent } from './OntologyWidget';
import TreeViewComponent from './components/TreeView';
import SeachBarComponent from './components/SearchBar';
import { GraphViewComponent } from './components/GaphView';
import InfoBoxComponent from './components/InfoBox';
import WorkspaceComponent from './components/Workspace';
import { ISelectedNodeType } from './components/interfaces/InfoBoxInterfaces';
import { IWorkspaceState } from './components/interfaces/WorkspaceInterfaces';

interface IAppProps {
fetchData: () => Promise<any>;
}

const App: React.FC<IAppProps> = ({ fetchData }) => {
const [data, setData] = useState(null);
const App: React.FC<IAppProps> = () => {
const [selectedNode, setSelectedNode] = useState<ISelectedNodeType | null>(null);

useEffect(() => {
const fetchAndSetData = async () => {
try {
const result = await fetchData();
setData(result);
console.log(data);
} catch (error) {
console.error('Failed to fetch data:', error);
}
};
const [workspace, setWorkspace] = useState<IWorkspaceState>({
model: null,
connectivity: null,
coupling: null,
noise: null,
integrationMethod: null
});

fetchAndSetData();
}, [fetchData]);
const addToWorkspace = (node: ISelectedNodeType) => {
setWorkspace(prevWorkspace => {
switch (node.type) {
case 'Neural Mass Model':
return { ...prevWorkspace, model: node };
case 'TheVirtualBrain':
if (node.label.includes('Noise')) {
return { ...prevWorkspace, noise: node };
} else {
return { ...prevWorkspace, connectivity: node };
}
case 'Coupling':
return { ...prevWorkspace, coupling: node };
case 'Integrator':
return { ...prevWorkspace, integrationMethod: node };
default:
return prevWorkspace; // No changes if the type doesn't match
}
});
};

return (
<div className="layout">
<SeachBarComponent />
<TreeViewComponent />
<InfoBoxComponent />
<WorkspaceComponent />
<OntologyWidgetComponent fetchData={fetchData} />
<InfoBoxComponent selectedNode={selectedNode} addToWorkspace={addToWorkspace} />
<WorkspaceComponent workspace={workspace} />
<GraphViewComponent setSelectedNode={setSelectedNode} />
</div>
);
};
Expand Down
78 changes: 0 additions & 78 deletions src/OntologyWidget.tsx

This file was deleted.

173 changes: 173 additions & 0 deletions src/components/GaphView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React, { useEffect, useState } from 'react';
import ForceGraph2D from 'react-force-graph-2d';
import { fetchNodeByLabel, fetchNodeConnections } from '../handler';
import { ISelectedNodeType } from './interfaces/InfoBoxInterfaces';
import { ILinkType, INodeType } from './interfaces/GraphViewInterfaces';
import { ITreeNode } from './interfaces/TreeViewInterfaces';
import TreeViewComponent from './TreeView';

interface IGraphViewProps {
setSelectedNode: (node: ISelectedNodeType) => void;
}

export const GraphViewComponent: React.FC<IGraphViewProps> = ({
setSelectedNode
}) => {
const [data, setData] = useState<{ nodes: INodeType[]; links: ILinkType[]; }>({ nodes: [], links: [] });
const [searchLabel, setSearchLabel] = useState<string>('');
const [treeData, setTreeData] = useState<ITreeNode | null>(null);

useEffect(() => {
const fetchAndSetData = async (label?: string) => {
try {
// Fetch data
const result = await fetchNodeByLabel(label || '');
setData(result);
} catch (error) {
console.error('Failed to fetch data:', error);
setData({ nodes: [], links: [] });
}
};

fetchAndSetData(searchLabel);
}, [searchLabel]);

const buildTree = (currentNode: INodeType): ITreeNode => {
const nodeMap = new Map<number, ITreeNode>();

// Initialize parents and children for all nodes in graph
data.nodes.forEach(node => {
nodeMap.set(node.id, {
id: node.id,
label: node.label,
type: node.type,
children: [],
parents: []
});
});
console.log(nodeMap);

const currentTreeNode = nodeMap.get(currentNode.id)!;

// Get parents and children
data.links.forEach(link => {
console.log('Link: ', link);
const sourceNode = nodeMap.get(link.source);
const targetNode = nodeMap.get(link.target);
console.log('Source Node: ', sourceNode);
console.log('Target Node: ', targetNode);

if (sourceNode && targetNode) {
if (link.target === currentNode.id) {
currentTreeNode.parents.push(sourceNode);
} else if (link.source === currentNode.id) {
currentTreeNode.children.push(targetNode);
}

// Handle cases where a child node is also connected to the parents
if (currentTreeNode.parents.includes(targetNode) && currentTreeNode.children.includes(sourceNode)) {
sourceNode.parents.push(targetNode);
targetNode.children.push(sourceNode);
}
}
});

return currentTreeNode;
};

const handleNodeClick = async (node: INodeType) => {
setSelectedNode({
label: node.label,
type: node.type,
definition: node.definition,
iri: node.iri,
childLinks: node.childLinks,
collapsed: false
});
console.log('Node clicked');

// Build the tree view for the clicked node
const tree = buildTree(node);
setTreeData(tree);

const connections = await fetchNodeConnections(node.label);

const nodesById = Object.fromEntries(
data.nodes.map(node => [node.id, node])
);

// link parent/children
data.nodes.forEach(n => {
n.collapsed = n.id !== node.id;
n.childLinks = [];
});

connections!.links.forEach(link => {
const sourceNode = nodesById[link.source];
if (sourceNode) {
sourceNode.childLinks!.push(link);
} else {
console.error(
`Node with id ${link.source} does not exist in nodesById`
);
}
});
data.nodes = Object.values(nodesById);
};

// Handle search
const handleSearch = () => {
if (searchLabel.trim()) {
setSearchLabel(searchLabel.trim());
}
};

// Handle Enter key press to trigger the search
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
handleSearch();
}
};

return (
<div className="ontology-graph">
<TreeViewComponent treeData={treeData} />
<div className="search-bar">
<input
type="text"
value={searchLabel}
onChange={e => setSearchLabel(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Enter node label"
/>
<button onClick={handleSearch}>Search</button>
</div>
<div>
{data ? (
<ForceGraph2D
graphData={data}
onNodeClick={handleNodeClick}
linkCurvature={0.25}
nodeCanvasObject={(node, ctx, globalScale) => {
const label = node.label;
const fontSize = 12 / globalScale;
ctx.font = `${fontSize}px Sans-Serif`;
ctx.fillStyle = 'black';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';

const xCoord = node.x as number;
const yCoord = node.y as number;
ctx.fillText(label, xCoord, yCoord + 5);
}}
nodeCanvasObjectMode={() => 'after'}
linkDirectionalArrowLength={3.5}
linkDirectionalArrowRelPos={1}
/>
) : (
<div>Loading...</div>
)}
</div>
</div>
);
};
59 changes: 57 additions & 2 deletions src/components/InfoBox.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,62 @@
import React from 'react';
import { ISelectedNodeType } from './interfaces/InfoBoxInterfaces';

const InfoBoxComponent: React.FC = () => {
return <div className="info-box">Info Box</div>;
interface IInfoBoxProps {
selectedNode: {
label: string;
type: string;
definition: string;
iri: string;
} | null;
addToWorkspace: (node: ISelectedNodeType) => void;
}

const InfoBoxComponent: React.FC<IInfoBoxProps> = ({
selectedNode,
addToWorkspace
}) => {
// Valid types for adding objects to workspace
const validTypes = [
'Neural Mass Model',
'TheVirtualBrain', // TODO: change to actual type for connectivity, noise
'Coupling',
'Integrator'
];

const isAddable = selectedNode && validTypes.includes(selectedNode.type);

return (
<div className="info-box">
{selectedNode ? (
<div>
<h3>Node Information</h3>
<p>
<strong>Name:</strong> {selectedNode.label}
</p>
<p>
<strong>Type:</strong> {selectedNode.type}
</p>
<p>
<strong>Definition:</strong> {selectedNode.definition}
</p>
<p>
<strong>IRI:</strong>{' '}
<a href={selectedNode.iri} target="_blank" rel="noopener noreferrer">
{selectedNode.iri}
</a>
</p>
<button
onClick={() => addToWorkspace(selectedNode)}
disabled={!isAddable}
>
Add to Workspace
</button>
</div>
) : (
<p>Select a node to see its details here</p>
)}
</div>
);
};

export default InfoBoxComponent;
Loading

0 comments on commit b7884b4

Please sign in to comment.