{
- const containerRef = useRef(null);
-
- useEffect(() => {
- if (!graphData) return;
-
- // Set up SVG dimensions
- const width = window.innerWidth;
- const height = window.innerHeight - 50;
-
- // Clear previous graph
- d3.select(containerRef.current).select('svg').remove();
-
- // Create SVG element
- const svg = d3
- .select(containerRef.current)
- .append('svg')
- .attr('width', width)
- .attr('height', height)
- .call(
- d3.zoom().on('zoom', (event) => {
- svg.attr('transform', event.transform);
- }),
- )
- .append('g');
-
- // Set up the simulation
- const simulation = d3
- .forceSimulation(graphData.nodes)
- .force(
- 'link',
- d3
- .forceLink(graphData.edges)
- .id((d) => d.id)
- .distance(100),
- )
- .force('charge', d3.forceManyBody().strength(-300))
- .force('center', d3.forceCenter(width / 2, height / 2));
-
- // Apply different layout algorithms
- if (layout === 'hierarchical') {
- simulation.force('y', d3.forceY().strength(0.1));
- simulation.force('x', d3.forceX().strength(0.1));
- }
-
- // Create links
- const link = svg
- .append('g')
- .attr('class', 'links')
- .selectAll('line')
- .data(graphData.edges)
- .enter()
- .append('line')
- .attr('stroke-width', 2)
- .attr('stroke', '#fff');
-
- // Create link labels
- const linkLabels = svg
- .append('g')
- .attr('class', 'link-labels')
- .selectAll('text')
- .data(graphData.edges)
- .enter()
- .append('text')
- .attr('class', 'link-label')
- .attr('dx', 15)
- .attr('dy', '.35em')
- .text((d) => d.label) // Correctly reading the label property for edges
- .attr('fill', '#fff');
-
- // Create nodes
- const node = svg
- .append('g')
- .attr('class', 'nodes')
- .selectAll('circle')
- .data(graphData.nodes)
- .enter()
- .append('circle')
- .attr('r', 25)
- .attr('fill', '#69b3a2')
- .attr('stroke', '#508e7f')
- .call(
- d3
- .drag()
- .on('start', dragstarted)
- .on('drag', dragged)
- .on('end', dragended),
- );
-
- // Node labels
- const nodeLabels = svg
- .append('g')
- .attr('class', 'node-labels')
- .selectAll('text')
- .data(graphData.nodes)
- .enter()
- .append('text')
- .attr('class', 'node-label')
- .attr('dx', 15)
- .attr('dy', '.35em')
- .text((d) => d.label) // Correctly reading the label property for nodes
- .attr('fill', '#fff');
-
- // Update simulation
- simulation.nodes(graphData.nodes).on('tick', ticked);
-
- simulation.force('link').links(graphData.edges);
-
- function ticked() {
- link
- .attr('x1', (d) => d.source.x)
- .attr('y1', (d) => d.source.y)
- .attr('x2', (d) => d.target.x)
- .attr('y2', (d) => d.target.y);
-
- node.attr('cx', (d) => d.x).attr('cy', (d) => d.y);
-
- nodeLabels.attr('x', (d) => d.x).attr('y', (d) => d.y);
-
- linkLabels
- .attr('x', (d) => (d.source.x + d.target.x) / 2)
- .attr('y', (d) => (d.source.y + d.target.y) / 2);
- }
-
- function dragstarted(event, d) {
- if (!event.active) simulation.alphaTarget(0.3).restart();
- d.fx = d.x;
- d.fy = d.y;
- }
-
- function dragged(event, d) {
- d.fx = event.x;
- d.fy = event.y;
- }
-
- function dragended(event, d) {
- if (!event.active) simulation.alphaTarget(0);
- d.fx = null;
- d.fy = null;
- }
-
- // Stabilize nodes after a certain time
- setTimeout(() => {
- simulation.alphaTarget(0).restart();
- }, 5000); // 5 seconds stabilization time
- }, [graphData, layout]);
-
- return (
-
- );
-};
-
-export default D3Graph;
diff --git a/Project/frontend/src/components/Graph/FloatingControlCard_d3.jsx b/Project/frontend/src/components/Graph/FloatingControlCard_d3.jsx
deleted file mode 100644
index 4992e88..0000000
--- a/Project/frontend/src/components/Graph/FloatingControlCard_d3.jsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import {
- Card,
- CardContent,
- FormControl,
- InputLabel,
- Select,
- MenuItem,
- Box,
-} from '@mui/material';
-
-const FloatingControlCard = ({ layout, setLayout }) => {
- return (
-
-
-
- Layout
-
-
-
-
- );
-};
-
-export default FloatingControlCard;
diff --git a/Project/frontend/src/components/Graph/FloatingControlCard_sigma.jsx b/Project/frontend/src/components/Graph/FloatingControlCard_sigma.jsx
deleted file mode 100644
index 9aba734..0000000
--- a/Project/frontend/src/components/Graph/FloatingControlCard_sigma.jsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import React from 'react';
-import {
- Card,
- CardContent,
- FormControl,
- InputLabel,
- Select,
- MenuItem,
- Slider,
- Typography,
- Box,
-} from '@mui/material';
-
-const FloatingControlCard = ({
- layout,
- setLayout,
- physicsOptions,
- handlePhysicsChange,
-}) => {
- const handleSliderChange = (name) => (event, value) => {
- handlePhysicsChange(name, value);
- };
-
- const renderSliders = () => {
- return (
-
- Iterations
-
- Barnes Hut Theta
-
- Gravity
-
- Scaling Ratio
-
- Edge Weight Influence
-
- Edge Length
-
-
- );
- };
-
- return (
-
-
-
- Layout
-
-
- {renderSliders()}
-
-
- );
-};
-
-export default FloatingControlCard;
diff --git a/Project/frontend/src/components/Graph/index_3js.tsx b/Project/frontend/src/components/Graph/index_3js.tsx
deleted file mode 100644
index af11d2a..0000000
--- a/Project/frontend/src/components/Graph/index_3js.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import { useEffect, useState, useRef } from 'react';
-import {
- select,
- forceSimulation,
- forceLink,
- forceManyBody,
- forceCenter,
- forceCollide,
- zoom,
- drag,
-} from 'd3';
-import { useParams } from 'react-router-dom';
-import './index.css';
-import { VISUALIZE_API_PATH } from '../../constant';
-
-const GraphVisualization = () => {
- const svgRef = useRef();
- const { fileId = '' } = useParams();
- const [graphData, setGraphData] = useState(null);
- const [isLoading, setIsLoading] = useState(true);
-
- useEffect(() => {
- const API = `${import.meta.env.VITE_BACKEND_HOST}${VISUALIZE_API_PATH.replace(':fileId', fileId)}`;
- fetch(API)
- .then((res) => res.json())
- .then((data) => {
- setGraphData(data);
- setIsLoading(false);
- })
- .catch((error) => {
- console.error('Error fetching graph data:', error);
- setIsLoading(false);
- });
- }, [fileId]);
-
- useEffect(() => {
- if (!graphData) return;
-
- const svg = select(svgRef.current);
- const width = svgRef.current.clientWidth;
- const height = svgRef.current.clientHeight;
-
- const g = svg.append('g');
-
- const simulation = forceSimulation(graphData.nodes)
- .force(
- 'link',
- forceLink(graphData.edges)
- .id((d) => d.id)
- .distance(100),
- )
- .force('charge', forceManyBody().strength(-200))
- .force('center', forceCenter(width / 2, height / 2))
- .force('collide', forceCollide().radius(30).strength(1))
- .alphaDecay(0.01)
- .alphaMin(0.001);
-
- const link = g
- .selectAll('line')
- .data(graphData.edges)
- .enter()
- .append('line')
- .attr('stroke', '#999')
- .attr('stroke-width', 1.5)
- .attr('id', (d, i) => `link${i}`);
-
- const node = g
- .selectAll('circle')
- .data(graphData.nodes)
- .enter()
- .append('circle')
- .attr('r', 15)
- .attr('fill', '#69b3a2')
- .call(
- drag()
- .on('start', (event, d) => {
- if (!event.active) simulation.alphaTarget(0.3).restart();
- d.fx = d.x;
- d.fy = d.y;
- })
- .on('drag', (event, d) => {
- d.fx = event.x;
- d.fy = event.y;
- })
- .on('end', (event, d) => {
- if (!event.active) simulation.alphaTarget(0);
- d.fx = null;
- d.fy = null;
- }),
- )
- .on('mouseover', function (event, d) {
- select(this).attr('fill', 'orange');
- svg
- .select('#tooltip')
- .style('display', 'block')
- .html(`ID: ${d.id}
Label: ${d.label || 'N/A'}`)
- .style('left', `${event.pageX + 10}px`)
- .style('top', `${event.pageY + 10}px`);
- })
- .on('mouseout', function () {
- select(this).attr('fill', '#69b3a2');
- svg.select('#tooltip').style('display', 'none');
- });
-
- const nodeLabels = g
- .selectAll('text.node-label')
- .data(graphData.nodes)
- .enter()
- .append('text')
- .attr('class', 'node-label')
- .attr('dy', -20)
- .attr('text-anchor', 'middle')
- .style('font-size', '12px')
- .style('pointer-events', 'none')
- .text((d) => d.id);
-
- // Create path elements for each link
- const linkPaths = g
- .selectAll('path')
- .data(graphData.edges)
- .enter()
- .append('path')
- .attr('class', 'link-path')
- .attr('id', (d, i) => `link-path${i}`)
- .attr('fill', 'none')
- .attr('stroke', 'none');
-
- const edgeLabels = g
- .selectAll('text.edge-label')
- .data(graphData.edges)
- .enter()
- .append('text')
- .attr('class', 'edge-label')
- .attr('dy', -5)
- .style('font-size', '10px')
- .style('pointer-events', 'none')
- .append('textPath')
- .attr('xlink:href', (d, i) => `#link-path${i}`)
- .attr('startOffset', '50%')
- .style('text-anchor', 'middle')
- .text((d) => d.id);
-
- const zoomBehavior = zoom().on('zoom', (event) => {
- g.attr('transform', event.transform);
- });
-
- svg.call(zoomBehavior);
-
- simulation.on('tick', () => {
- link
- .attr('x1', (d) => d.source.x)
- .attr('y1', (d) => d.source.y)
- .attr('x2', (d) => d.target.x)
- .attr('y2', (d) => d.target.y);
-
- node.attr('cx', (d) => d.x).attr('cy', (d) => d.y);
-
- nodeLabels.attr('x', (d) => d.x).attr('y', (d) => d.y);
-
- linkPaths.attr(
- 'd',
- (d) => `M${d.source.x},${d.source.y} L${d.target.x},${d.target.y}`,
- );
-
- edgeLabels
- .attr('x', (d) => (d.source.x + d.target.x) / 2)
- .attr('y', (d) => (d.source.y + d.target.y) / 2);
- });
-
- svg
- .append('foreignObject')
- .attr('id', 'tooltip')
- .style('position', 'absolute')
- .style('background', '#fff')
- .style('border', '1px solid #ccc')
- .style('padding', '10px')
- .style('display', 'none')
- .append('xhtml:div')
- .style('font-size', '10px')
- .html('Tooltip');
-
- return () => simulation.stop();
- }, [graphData]);
-
- if (isLoading) {
- return Loading graph...
;
- }
- if (!graphData) {
- return Sorry, an error has occurred!
;
- }
- return (
-
- Graph Visualization
-
-
- );
-};
-
-export default GraphVisualization;
diff --git a/Project/frontend/src/components/Graph/index_sigma.tsx b/Project/frontend/src/components/Graph/index_sigma.tsx
deleted file mode 100644
index 23f2e85..0000000
--- a/Project/frontend/src/components/Graph/index_sigma.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import { useEffect, useState } from 'react';
-import { MultiDirectedGraph } from 'graphology';
-import { SigmaContainer, useSigma } from '@react-sigma/core';
-import { useParams } from 'react-router-dom';
-import '@react-sigma/core/lib/react-sigma.min.css';
-import EdgeCurveProgram, {
- DEFAULT_EDGE_CURVATURE,
- indexParallelEdgesIndex,
-} from '@sigma/edge-curve';
-import { EdgeArrowProgram } from 'sigma/rendering';
-import forceAtlas2 from 'graphology-layout-forceatlas2';
-import './index.css';
-import { VISUALIZE_API_PATH } from '../../constant';
-
-const ForceAtlas2Layout = ({ maxIterations }) => {
- const sigma = useSigma();
- const graph = sigma.getGraph();
- const [iterations, setIterations] = useState(0);
-
- useEffect(() => {
- const settings = {
- iterations: maxIterations,
- barnesHutOptimize: true,
- barnesHutTheta: 0.5,
- slowDown: 1,
- gravity: 1,
- scalingRatio: 10,
- edgeWeightInfluence: 1,
- strongGravityMode: true,
- adjustSizes: true,
- };
-
- const applyLayout = () => {
- forceAtlas2.assign(graph, settings);
- setIterations((prev) => prev + 1);
- };
-
- if (iterations < maxIterations) {
- const interval = setInterval(applyLayout, 100);
- return () => clearInterval(interval);
- }
- }, [graph, iterations, maxIterations]);
-
- return null;
-};
-
-export default function GraphVisualization() {
- const [graphData, setGraphData] = useState(null);
- const { fileId = '' } = useParams();
- const [isLoading, setIsLoading] = useState(true);
-
- useEffect(() => {
- const API = `${import.meta.env.VITE_BACKEND_HOST}${VISUALIZE_API_PATH.replace(':fileId', fileId)}`;
- fetch(API)
- .then((res) => res.json())
- .then((graphData) => {
- const graph = new MultiDirectedGraph();
- graphData?.nodes?.forEach((node) => {
- const { id, ...rest } = node;
- graph.addNode(id, {
- ...rest,
- size: 15, // just for testing, i am making all the same size
- x: Math.random() * 1000,
- y: Math.random() * 1000,
- });
- });
- graphData?.edges?.forEach((edge) => {
- const { id, source, target, ...rest } = edge;
- graph.addEdgeWithKey(id, source, target, {
- ...rest,
- size: 2, // edge
- });
- });
- indexParallelEdgesIndex(graph, {
- edgeIndexAttribute: 'parallelIndex',
- edgeMaxIndexAttribute: 'parallelMaxIndex',
- });
- graph.forEachEdge((edge, { parallelIndex, parallelMaxIndex }) => {
- if (typeof parallelIndex === 'number') {
- graph.mergeEdgeAttributes(edge, {
- type: 'curved',
- curvature:
- DEFAULT_EDGE_CURVATURE +
- (3 * DEFAULT_EDGE_CURVATURE * parallelIndex) /
- (parallelMaxIndex || 1),
- });
- } else {
- graph.setEdgeAttribute(edge, 'type', 'straight');
- }
- });
- setGraphData(graph);
- })
- .catch((error) => {
- console.log('Error fetching graphData:', error);
- })
- .finally(() => {
- setIsLoading(false);
- });
- }, [fileId]);
-
- if (isLoading) {
- return Loading graph...
;
- }
- if (!graphData) {
- return Sorry, an error has occurred!
;
- }
- return (
-
- Graph Visualization
-
-
-
-
- );
-}
diff --git a/Project/frontend/src/components/Graph/index_visjs.tsx b/Project/frontend/src/components/Graph/index_visjs.tsx
index 621fe9a..cdf2547 100644
--- a/Project/frontend/src/components/Graph/index_visjs.tsx
+++ b/Project/frontend/src/components/Graph/index_visjs.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react';
import { Network, Options } from 'vis-network/standalone/esm/vis-network';
import { useParams } from 'react-router-dom';
import './index.css';
-import { KEYWORDS_API_PATH, VISUALIZE_API_PATH } from '../../constant';
+import { KEYWORDS_API_PATH, VISUALIZE_API_PATH, GRAPH_SEARCH_API_PATH } from '../../constant';
import SearchIcon from '@mui/icons-material/Search';
import {
Box,
@@ -202,6 +202,8 @@ const GraphVisualization: React.FC = () => {
const { fileId = '' } = useParams<{ fileId: string }>();
const [graphData, setGraphData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
+ const [searchIsLoading, setSearchIsLoading] = useState(false);
+ const [answerText, setAnswerText] = useState("");
const [layout, setLayout] = useState('barnesHut');
const [searchQuery, setSearchQuery] = useState('');
const [keywords, setKeywords] = useState([]);
@@ -227,70 +229,6 @@ const GraphVisualization: React.FC = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
- useEffect(() => {
- const fetchGraphData = async () => {
- try {
- const response = await fetch(
- `${import.meta.env.VITE_BACKEND_HOST}${VISUALIZE_API_PATH.replace(':fileId', fileId)}`,
- );
- const data = await response.json();
- setGraphData(data);
-
- // Get the list of unique topics
- const uniqueTopics = Array.from(
- new Set(data.nodes.map((node) => node.topic)),
- );
-
- // Create color scheme for the topics
- const colorSchemes = [
- d3.schemeCategory10,
- d3.schemePaired,
- d3.schemeSet1,
- ];
- const uniqueColors = Array.from(new Set(colorSchemes.flat()));
-
- const otherIndex = uniqueTopics.indexOf('other');
- if (otherIndex !== -1) {
- uniqueTopics.splice(otherIndex, 1);
- }
-
- const topicColorMap: ITopicColourMap = uniqueTopics.reduce(
- (acc: ITopicColourMap, topic, index) => {
- acc[topic] = uniqueColors[index % uniqueColors.length];
- return acc;
- },
- {},
- );
-
- if (otherIndex !== -1) {
- topicColorMap['other'] =
- uniqueColors[uniqueTopics.length % uniqueColors.length];
- }
-
- setTopicColorMap(topicColorMap);
- } catch (error) {
- console.error('Error fetching graph data:', error);
- } finally {
- setIsLoading(false);
- }
- };
-
- const fetchKeywords = async () => {
- try {
- const response = await fetch(
- `${import.meta.env.VITE_BACKEND_HOST}${KEYWORDS_API_PATH.replace(':fileId', fileId)}`,
- );
- const data = await response.json();
- setKeywords(data);
- } catch (error) {
- console.error('Error fetching keywords:', error);
- }
- };
-
- fetchGraphData();
- fetchKeywords();
- }, [fileId]);
-
useEffect(() => {
switch (layout) {
case 'barnesHut':
@@ -439,9 +377,27 @@ const GraphVisualization: React.FC = () => {
}
};
- const performSearch = () => {
+ const performSearch = async () => {
// Perform the search based on searchQuery
- console.log('Searching for:', searchQuery);
+ const API = `${import.meta.env.VITE_BACKEND_HOST}${GRAPH_SEARCH_API_PATH.replace(':fileId', fileId)}`;
+
+ setSearchIsLoading(true);
+ try {
+ const response = await fetch(API, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ query: searchQuery }),
+ });
+ const result = await response.json();
+ setAnswerText(result.answer);
+ setSearchIsLoading(false);
+ } catch (error) {
+ console.error("Error fetching the search results:", error);
+ setAnswerText("An error occurred while fetching the search results.");
+ setSearchIsLoading(false);
+ }
};
if (isLoading) {
@@ -548,6 +504,10 @@ const GraphVisualization: React.FC = () => {
}}
sx={{ marginBottom: '10px' }}
/>
+ {searchIsLoading ? <>
+
+ Searching...
+ > : <>>}
{
readOnly: true,
}}
sx={{ marginBottom: '10px' }}
+ value={searchIsLoading ? '' : answerText}
/>