Skip to content

Commit

Permalink
Merge pull request #2 from AdiletBaimyrza/adilet/create-ctx-nav-add-ids
Browse files Browse the repository at this point in the history
Adilet/create ctx nav add ids
  • Loading branch information
andrii-venher authored Nov 12, 2023
2 parents 72ad6c3 + 1b8333c commit a85377c
Show file tree
Hide file tree
Showing 20 changed files with 319 additions and 152 deletions.
7 changes: 5 additions & 2 deletions dijkstra-prim-visualization/src/App.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<GraphParamsProvider>
<Navbar />
<Canvas />
</>
</GraphParamsProvider>
);
}

Expand Down
35 changes: 35 additions & 0 deletions dijkstra-prim-visualization/src/GraphParamsContext.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<GraphParamsContext.Provider value={graphParams}>
{children}
</GraphParamsContext.Provider>
);
};

// Makes sure the 'children' prop is a React node and that it is a required parameter
GraphParamsProvider.propTypes = {
children: PropTypes.node.isRequired,
};
142 changes: 71 additions & 71 deletions dijkstra-prim-visualization/src/components/Canvas/Canvas.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<svg
ref={canvasRef}
className={classes.canvas}
onClick={canvasClickHandler}
>
{edges.map((edge, index) => (
<Edge key={index} x1={edge.x1} y1={edge.y1} x2={edge.x2} y2={edge.y2} />
))}
{nodePoints.map((nodePoint, index) => (
<Node
key={index}
cx={nodePoint.x}
cy={nodePoint.y}
onNodeClick={nodeClickHandler}
/>
))}
<Edges edges={edges} />
<Nodes nodes={nodes} onNodeClick={nodeClickHandler} />
</svg>
);
};
Expand Down
54 changes: 27 additions & 27 deletions dijkstra-prim-visualization/src/components/Canvas/CanvasUtils.js
Original file line number Diff line number Diff line change
@@ -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));

Expand All @@ -17,38 +17,38 @@ 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) {
console.log("New node is too close to an existing node.");
return false;
}

const isOutOfBounds = !isNodeInBounds(newNodePoint, 14, canvasRef);
const isOutOfBounds = !isNodeInBounds(newNode, 14, canvasRef);

if (isOutOfBounds) {
console.log("New node is out of bounds.");
Expand All @@ -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) {
Expand Down
23 changes: 0 additions & 23 deletions dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.jsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 (
<line className={classes.line} id={id} x1={x1} y1={y1} x2={x2} y2={y2} />
);
};

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,
};
Loading

0 comments on commit a85377c

Please sign in to comment.