diff --git a/dijkstra-prim-visualization/src/App.jsx b/dijkstra-prim-visualization/src/App.jsx index a691e5d..fab882f 100644 --- a/dijkstra-prim-visualization/src/App.jsx +++ b/dijkstra-prim-visualization/src/App.jsx @@ -1,11 +1,14 @@ import "./App.css"; import Canvas from "./components/Canvas/Canvas"; +import Navbar from "./components/Navbar/Navbar"; +import { GraphParamsProvider } from "./GraphParamsContext"; function App() { return ( - <> + + - + ); } diff --git a/dijkstra-prim-visualization/src/GraphParamsContext.jsx b/dijkstra-prim-visualization/src/GraphParamsContext.jsx new file mode 100644 index 0000000..b5cca0c --- /dev/null +++ b/dijkstra-prim-visualization/src/GraphParamsContext.jsx @@ -0,0 +1,35 @@ +import PropTypes from "prop-types"; +import { createContext, useState } from "react"; + +export const GraphParamsContext = createContext(); + +// Custom hook useGraphParams to manage the states +const useGraphParams = () => { + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + + return { nodes, setNodes, edges, setEdges }; +}; + +/** + * Provides the context for the graph parameters. + * @param {Object} props - The component props. + * @param {ReactNode} props.children - The child components to be rendered. + * @returns {JSX.Element} - The context provider component with the graph parameters as value. + */ +export const GraphParamsProvider = ({ children }) => { + // Get the graph parameters from the useGraphParams hook + const graphParams = useGraphParams(); + + // Return the context provider component with the graph parameters as value + return ( + + {children} + + ); +}; + +// Makes sure the 'children' prop is a React node and that it is a required parameter +GraphParamsProvider.propTypes = { + children: PropTypes.node.isRequired, +}; diff --git a/dijkstra-prim-visualization/src/components/Canvas/Canvas.jsx b/dijkstra-prim-visualization/src/components/Canvas/Canvas.jsx index b289267..53b19ea 100644 --- a/dijkstra-prim-visualization/src/components/Canvas/Canvas.jsx +++ b/dijkstra-prim-visualization/src/components/Canvas/Canvas.jsx @@ -1,115 +1,115 @@ -import { useState, useRef } from "react"; +import { useState, useRef, useContext } from "react"; import classes from "./Canvas.module.css"; -import Node from "./Node/Node"; -import Edge from "./Edge/Edge"; import { newEdgeValid, newNodePositionValid } from "./CanvasUtils"; +import { GraphParamsContext } from "../../GraphParamsContext"; +import Nodes from "./Nodes/Nodes"; +import Edges from "./Edges/Edges"; + +const MAX_EDGE_WEIGHT = 100; /** * Canvas component for visualizing nodes and edges. + * Handles user interactions for adding nodes and edges * @returns {JSX.Element} The Canvas component. */ const Canvas = () => { - // State variables to keep track of all nodes and edges - const [nodePoints, setNodePoints] = useState([]); - const [edges, setEdges] = useState([]); + // Destructure the states from context + const { nodes, setNodes, edges, setEdges } = useContext(GraphParamsContext); - // Object with default data to reset firstNode, when needed - const resetNodeData = { + // Object with default data to reset firstClickedNode, when needed + const resetFirstClickedNode = { isClicked: false, - x: null, - y: null, + node: null, }; - // State variable to keep track of the first node clicked when creating an edge - const [firstNode, setFirstNode] = useState(resetNodeData); + // Initialize state to track the first clicked node + const [firstClickedNode, setFirstClickedNode] = useState( + resetFirstClickedNode, + ); // Reference to the canvas SVG element const canvasRef = useRef(null); - // Handler function for when the canvas is clicked + /** + * Handler function for when the canvas is clicked. + * Adds a new node at the clicked position if the position is valid. + * @param {MouseEvent} event - The click event. + */ const canvasClickHandler = (event) => { - // Reset the firstNode state variable - setFirstNode(resetNodeData); + setFirstClickedNode(resetFirstClickedNode); // Calculate the coordinates of the clicked point relative to the canvas - const nodePointAbsoluteX = event.clientX; - const nodePointAbsoluteY = event.clientY; - const nodePointCanvasRelativeX = - nodePointAbsoluteX - canvasRef.current.getBoundingClientRect().left; - const nodePointCanvasRelativeY = - nodePointAbsoluteY - canvasRef.current.getBoundingClientRect().top; - - // Create a new node point object - const newNodePoint = { - x: nodePointCanvasRelativeX, - y: nodePointCanvasRelativeY, + const nodeAbsoluteX = event.clientX; + const nodeAbsoluteY = event.clientY; + const nodeCanvasRelativeX = + nodeAbsoluteX - canvasRef.current.getBoundingClientRect().left; + const nodeCanvasRelativeY = + nodeAbsoluteY - canvasRef.current.getBoundingClientRect().top; + + const newNode = { + id: nodes.length, + x: nodeCanvasRelativeX, + y: nodeCanvasRelativeY, }; // Check if the new node position is valid (not overlapping with existing nodes) - if (!newNodePositionValid(newNodePoint, nodePoints, canvasRef)) { + if (!newNodePositionValid(newNode, nodes, canvasRef)) { return; } - // Add the new node point to the list of all nodes - setNodePoints((prevNodePoints) => [...prevNodePoints, newNodePoint]); + setNodes((prevNodes) => [...prevNodes, newNode]); }; - // Handler function for when a node is clicked - const nodeClickHandler = (event, points) => { + /** + * Handler function for when a node is clicked. + * Adds a new edge between the first and second nodes if a second node is clicked. + * Resets the first node if the same node is clicked again. + * @param {MouseEvent} event - The click event. + * @param {Object} node - The clicked node object. + */ + const nodeClickHandler = (event, node) => { event.stopPropagation(); - // If no first node has been clicked yet, set the clicked node as the first node - if (firstNode.isClicked === false) { - setFirstNode({ isClicked: true, x: points.x, y: points.y }); - } - // If the same node is clicked again, reset the first node - else if (firstNode.x === points.x && firstNode.y === points.y) { - console.log("first node clicked again, reset firstNode"); - setFirstNode(resetNodeData); - } - // If a different node is clicked, create a new edge between the first and second nodes - else { - addEdge(firstNode, { x: points.x, y: points.y }); - setFirstNode(resetNodeData); - } - }; + const addEdge = (firstNode, secondNode) => { + const newEdge = { + id: `${firstNode.id}-${secondNode.id}`, + weight: Math.floor(Math.random() * MAX_EDGE_WEIGHT) + 1, + firstNode: firstNode, + secondNode: secondNode, + }; + + if (!newEdgeValid(newEdge, edges)) { + return; + } - // Function to add a new edge to the list of all edges - const addEdge = (firstNode, secondNode) => { - const newEdge = { - x1: firstNode.x, - y1: firstNode.y, - x2: secondNode.x, - y2: secondNode.y, + setEdges((prevEdges) => [...prevEdges, newEdge]); }; - // Check if the new edge is valid (not overlapping with existing edges) - if (!newEdgeValid(newEdge, edges)) { - return; + if (!firstClickedNode.isClicked) { + // If no node has been clicked yet, set the current node as the first clicked node + setFirstClickedNode({ isClicked: true, node: node }); + } else if ( + firstClickedNode.node.x === node.x && + firstClickedNode.node.y === node.y + ) { + // If the same node is clicked again, reset the first clicked node + console.log("same node clicked again, reset the first clicked node"); + setFirstClickedNode(resetFirstClickedNode); + } else { + // If a different node is clicked, add an edge and reset the first clicked node + addEdge(firstClickedNode.node, node); + setFirstClickedNode(resetFirstClickedNode); } - - // Add the new edge to the list of all edges - setEdges((prevEdges) => [...prevEdges, newEdge]); }; - // Render the canvas SVG element with all nodes and edges return ( - {edges.map((edge, index) => ( - - ))} - {nodePoints.map((nodePoint, index) => ( - - ))} + + ); }; diff --git a/dijkstra-prim-visualization/src/components/Canvas/CanvasUtils.js b/dijkstra-prim-visualization/src/components/Canvas/CanvasUtils.js index 1c961fc..1cf4cce 100644 --- a/dijkstra-prim-visualization/src/components/Canvas/CanvasUtils.js +++ b/dijkstra-prim-visualization/src/components/Canvas/CanvasUtils.js @@ -1,14 +1,14 @@ /** * Calculates the Euclidean distance between two points. - * @param {Object} existingNodePoint - The coordinates of an existing node. - * @param {Object} newNodePoint - The coordinates of a new node. + * @param {Object} existingNode - The coordinates of an existing node. + * @param {Object} newNode - The coordinates of a new node. * @returns {boolean} - Returns true if the distance is less than or equal to 35px, false otherwise. */ -const isTooCloseToExistingNode = (existingNodePoint, newNodePoint) => { - const x1 = existingNodePoint.x; - const y1 = existingNodePoint.y; - const x2 = newNodePoint.x; - const y2 = newNodePoint.y; +const isTooCloseToExistingNode = (existingNode, newNode) => { + const x1 = existingNode.x; + const y1 = existingNode.y; + const x2 = newNode.x; + const y2 = newNode.y; const distance = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); @@ -17,30 +17,30 @@ const isTooCloseToExistingNode = (existingNodePoint, newNodePoint) => { /** * Checks if a new node is within the bounds of the canvas. - * @param {Object} newNodePoint - The coordinates of the new node. + * @param {Object} newNode - The coordinates of the new node. * @param {number} nodeRadius - The radius of the node. * @param {Object} canvasRef - A reference to the canvas element. * @returns {boolean} - Returns true if the new node is within the bounds of the canvas, false otherwise. */ -const isNodeInBounds = (newNodePoint, nodeRadius, canvasRef) => { +const isNodeInBounds = (newNode, nodeRadius, canvasRef) => { return ( - newNodePoint.x - nodeRadius >= 0 && - newNodePoint.y - nodeRadius >= 0 && - newNodePoint.x + nodeRadius <= canvasRef.current.clientWidth && - newNodePoint.y + nodeRadius <= canvasRef.current.clientHeight + newNode.x - nodeRadius >= 0 && + newNode.y - nodeRadius >= 0 && + newNode.x + nodeRadius <= canvasRef.current.clientWidth && + newNode.y + nodeRadius <= canvasRef.current.clientHeight ); }; /** * Checks if a new node position is valid. - * @param {Object} newNodePoint - The coordinates of the new node. - * @param {Array} nodePoints - An array of existing node coordinates. + * @param {Object} newNode - The coordinates of the new node. + * @param {Array} nodes - An array of existing node coordinates. * @param {Object} canvasRef - A reference to the canvas element. * @returns {boolean} - Returns true if the new node position is valid, false otherwise. */ -export const newNodePositionValid = (newNodePoint, nodePoints, canvasRef) => { - const isTooClose = nodePoints.some((existingNodePoint) => - isTooCloseToExistingNode(existingNodePoint, newNodePoint), +export const newNodePositionValid = (newNode, nodes, canvasRef) => { + const isTooClose = nodes.some((existingNode) => + isTooCloseToExistingNode(existingNode, newNode), ); if (isTooClose) { @@ -48,7 +48,7 @@ export const newNodePositionValid = (newNodePoint, nodePoints, canvasRef) => { return false; } - const isOutOfBounds = !isNodeInBounds(newNodePoint, 14, canvasRef); + const isOutOfBounds = !isNodeInBounds(newNode, 14, canvasRef); if (isOutOfBounds) { console.log("New node is out of bounds."); @@ -67,14 +67,14 @@ export const newNodePositionValid = (newNodePoint, nodePoints, canvasRef) => { export const newEdgeValid = (newEdge, edges) => { const edgeExists = edges.some( (edge) => - (edge.x1 === newEdge.x1 && - edge.y1 === newEdge.y1 && - edge.x2 === newEdge.x2 && - edge.y2 === newEdge.y2) || - (edge.x1 === newEdge.x2 && - edge.y1 === newEdge.y2 && - edge.x2 === newEdge.x1 && - edge.y2 === newEdge.y1), + (edge.firstNode.x === newEdge.firstNode.x && + edge.firstNode.y === newEdge.firstNode.y && + edge.secondNode.x === newEdge.secondNode.x && + edge.secondNode.y === newEdge.secondNode.y) || + (edge.firstNode.x === newEdge.secondNode.x && + edge.firstNode.y === newEdge.secondNode.y && + edge.secondNode.x === newEdge.firstNode.x && + edge.secondNode.y === newEdge.firstNode.y), ); if (edgeExists) { diff --git a/dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.jsx b/dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.jsx deleted file mode 100644 index 01f74e0..0000000 --- a/dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import PropTypes from "prop-types"; -import classes from "./Edge.module.css"; - -/** - * Renders a line connecting two points on the canvas. - * @param {number} x1 - The x-coordinate of the starting point. - * @param {number} y1 - The y-coordinate of the starting point. - * @param {number} x2 - The x-coordinate of the ending point. - * @param {number} y2 - The y-coordinate of the ending point. - */ -const Edge = ({ x1, y1, x2, y2 }) => { - return ; -}; - -export default Edge; - -// Validates that the required props are passed and their type is number -Edge.propTypes = { - x1: PropTypes.number.isRequired, - y1: PropTypes.number.isRequired, - x2: PropTypes.number.isRequired, - y2: PropTypes.number.isRequired, -}; diff --git a/dijkstra-prim-visualization/src/components/Canvas/Edges/Edge/Edge.jsx b/dijkstra-prim-visualization/src/components/Canvas/Edges/Edge/Edge.jsx new file mode 100644 index 0000000..6b66a1e --- /dev/null +++ b/dijkstra-prim-visualization/src/components/Canvas/Edges/Edge/Edge.jsx @@ -0,0 +1,29 @@ +import PropTypes from "prop-types"; +import classes from "./Edge.module.css"; + +/** + * Renders an edge between two points on the canvas. + * @param {Object} props - The component props. + * @param {string} props.id - The unique identifier for the edge. + * @param {number} props.x1 - The x-coordinate of the starting point. + * @param {number} props.y1 - The y-coordinate of the starting point. + * @param {number} props.x2 - The x-coordinate of the ending point. + * @param {number} props.y2 - The y-coordinate of the ending point. + * @returns {JSX.Element} - The rendered component. + */ +const Edge = ({ id, x1, y1, x2, y2 }) => { + return ( + + ); +}; + +export default Edge; + +// Validates that the required props are passed and their type is number +Edge.propTypes = { + id: PropTypes.string.isRequired, + x1: PropTypes.number.isRequired, + y1: PropTypes.number.isRequired, + x2: PropTypes.number.isRequired, + y2: PropTypes.number.isRequired, +}; diff --git a/dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.module.css b/dijkstra-prim-visualization/src/components/Canvas/Edges/Edge/Edge.module.css similarity index 100% rename from dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.module.css rename to dijkstra-prim-visualization/src/components/Canvas/Edges/Edge/Edge.module.css diff --git a/dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.module.css.map b/dijkstra-prim-visualization/src/components/Canvas/Edges/Edge/Edge.module.css.map similarity index 100% rename from dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.module.css.map rename to dijkstra-prim-visualization/src/components/Canvas/Edges/Edge/Edge.module.css.map diff --git a/dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.module.scss b/dijkstra-prim-visualization/src/components/Canvas/Edges/Edge/Edge.module.scss similarity index 100% rename from dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.module.scss rename to dijkstra-prim-visualization/src/components/Canvas/Edges/Edge/Edge.module.scss diff --git a/dijkstra-prim-visualization/src/components/Canvas/Edges/Edges.jsx b/dijkstra-prim-visualization/src/components/Canvas/Edges/Edges.jsx new file mode 100644 index 0000000..f52eaaa --- /dev/null +++ b/dijkstra-prim-visualization/src/components/Canvas/Edges/Edges.jsx @@ -0,0 +1,54 @@ +import PropTypes from "prop-types"; +import React from "react"; +import Edge from "./Edge/Edge"; + +/** + * Renders edges on the canvas. + * @param {Object[]} edges - An array of edge objects. + * @param {string} edges[].id - The unique identifier of the edge. + * @param {Object} edges[].firstNode - The first node of the edge. + * @param {number} edges[].firstNode.x - The x-coordinate of the first node. + * @param {number} edges[].firstNode.y - The y-coordinate of the first node. + * @param {Object} edges[].secondNode - The second node of the edge. + * @param {number} edges[].secondNode.x - The x-coordinate of the second node. + * @param {number} edges[].secondNode.y - The y-coordinate of the second node. + * @param {number} edges[].weight - The weight of the edge. + * @returns {JSX.Element} - The rendered edges and their weights. + */ +const Edges = ({ edges }) => { + return ( + <> + {edges.map((edge) => ( + + + + {edge.weight} + + + ))} + + ); +}; + +export default Edges; + +Edges.propTypes = { + edges: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + weight: PropTypes.number.isRequired, + firstNode: PropTypes.object.isRequired, + secondNode: PropTypes.object.isRequired, + }), + ).isRequired, +}; diff --git a/dijkstra-prim-visualization/src/components/Canvas/Node/Node.jsx b/dijkstra-prim-visualization/src/components/Canvas/Node/Node.jsx deleted file mode 100644 index 8062ff6..0000000 --- a/dijkstra-prim-visualization/src/components/Canvas/Node/Node.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import PropTypes from "prop-types"; -import classes from "./Node.module.css"; - -/** - * A component that renders a circle representing a node on the canvas. - * @param {number} cx - The x-coordinate of the center of the circle. - * @param {number} cy - The y-coordinate of the center of the circle. - * @param {function} onNodeClick - A callback function to handle click events on the node. - */ -const Node = ({ cx, cy, onNodeClick }) => { - return ( - onNodeClick(event, { x: cx, y: cy })} - /> - ); -}; - -export default Node; - -// Ensures that the required props are passed and have the correct type -Node.propTypes = { - onNodeClick: PropTypes.func.isRequired, - cx: PropTypes.number.isRequired, - cy: PropTypes.number.isRequired, -}; diff --git a/dijkstra-prim-visualization/src/components/Canvas/Nodes/Node/Node.jsx b/dijkstra-prim-visualization/src/components/Canvas/Nodes/Node/Node.jsx new file mode 100644 index 0000000..3347b3f --- /dev/null +++ b/dijkstra-prim-visualization/src/components/Canvas/Nodes/Node/Node.jsx @@ -0,0 +1,34 @@ +import PropTypes from "prop-types"; +import classes from "./Node.module.css"; + +/** + * Renders a node on the canvas. + * @param {Object} props - The props object. + * @param {string} props.id - The id of the node. + * @param {number} props.cx - The x-coordinate of the node. + * @param {number} props.cy - The y-coordinate of the node. + * @param {function} props.onNodeClick - The function to call when the node is clicked. + * @returns {JSX.Element} - The node element. + */ +const Node = ({ id, cx, cy, onNodeClick }) => { + return ( + onNodeClick(event, { id: id, x: cx, y: cy })} + /> + ); +}; + +export default Node; + +// Ensures that the required props are passed and have the correct type +Node.propTypes = { + onNodeClick: PropTypes.func.isRequired, + id: PropTypes.string.isRequired, + cx: PropTypes.number.isRequired, + cy: PropTypes.number.isRequired, +}; diff --git a/dijkstra-prim-visualization/src/components/Canvas/Node/Node.module.css b/dijkstra-prim-visualization/src/components/Canvas/Nodes/Node/Node.module.css similarity index 100% rename from dijkstra-prim-visualization/src/components/Canvas/Node/Node.module.css rename to dijkstra-prim-visualization/src/components/Canvas/Nodes/Node/Node.module.css diff --git a/dijkstra-prim-visualization/src/components/Canvas/Node/Node.module.css.map b/dijkstra-prim-visualization/src/components/Canvas/Nodes/Node/Node.module.css.map similarity index 100% rename from dijkstra-prim-visualization/src/components/Canvas/Node/Node.module.css.map rename to dijkstra-prim-visualization/src/components/Canvas/Nodes/Node/Node.module.css.map diff --git a/dijkstra-prim-visualization/src/components/Canvas/Node/Node.module.scss b/dijkstra-prim-visualization/src/components/Canvas/Nodes/Node/Node.module.scss similarity index 100% rename from dijkstra-prim-visualization/src/components/Canvas/Node/Node.module.scss rename to dijkstra-prim-visualization/src/components/Canvas/Nodes/Node/Node.module.scss diff --git a/dijkstra-prim-visualization/src/components/Canvas/Nodes/Nodes.jsx b/dijkstra-prim-visualization/src/components/Canvas/Nodes/Nodes.jsx new file mode 100644 index 0000000..52edaba --- /dev/null +++ b/dijkstra-prim-visualization/src/components/Canvas/Nodes/Nodes.jsx @@ -0,0 +1,32 @@ +import PropTypes from "prop-types"; +import Node from "./Node/Node"; + +/** + * Renders a list of nodes on the canvas. + * @param {Object[]} nodes - An array of node objects. + * @param {string} nodes[].id - The unique identifier of the node. + * @param {number} nodes[].x - The x-coordinate of the node. + * @param {number} nodes[].y - The y-coordinate of the node. + * @param {function} onNodeClick - A callback function to handle node click events. + * @returns {JSX.Element} - A list of Node components. + */ +const Nodes = ({ nodes, onNodeClick }) => ( + <> + {nodes.map((node) => ( + + ))} + +); + +export default Nodes; + +Nodes.propTypes = { + nodes: PropTypes.array.isRequired, + onNodeClick: PropTypes.func.isRequired, +}; diff --git a/dijkstra-prim-visualization/src/components/Navbar/Navbar.jsx b/dijkstra-prim-visualization/src/components/Navbar/Navbar.jsx new file mode 100644 index 0000000..6517ecf --- /dev/null +++ b/dijkstra-prim-visualization/src/components/Navbar/Navbar.jsx @@ -0,0 +1,30 @@ +import { useContext } from "react"; +import { GraphParamsContext } from "../../GraphParamsContext"; + +/** + * Navbar component displays buttons to print nodes and edges. + * @returns {JSX.Element} Navbar component + */ +const Navbar = () => { + const { nodes, edges } = useContext(GraphParamsContext); + return ( + <> + + + + ); +}; + +export default Navbar; diff --git a/dijkstra-prim-visualization/src/components/Navbar/Navbar.module.css b/dijkstra-prim-visualization/src/components/Navbar/Navbar.module.css new file mode 100644 index 0000000..e18b6bc --- /dev/null +++ b/dijkstra-prim-visualization/src/components/Navbar/Navbar.module.css @@ -0,0 +1 @@ +/*# sourceMappingURL=Navbar.module.css.map */ \ No newline at end of file diff --git a/dijkstra-prim-visualization/src/components/Navbar/Navbar.module.css.map b/dijkstra-prim-visualization/src/components/Navbar/Navbar.module.css.map new file mode 100644 index 0000000..37bd8f9 --- /dev/null +++ b/dijkstra-prim-visualization/src/components/Navbar/Navbar.module.css.map @@ -0,0 +1 @@ +{"version":3,"sources":[],"names":[],"mappings":"","file":"Navbar.module.css"} \ No newline at end of file diff --git a/dijkstra-prim-visualization/src/components/Navbar/Navbar.module.scss b/dijkstra-prim-visualization/src/components/Navbar/Navbar.module.scss new file mode 100644 index 0000000..e69de29