-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from AdiletBaimyrza/adilet/canvas
Adilet/canvas
- Loading branch information
Showing
17 changed files
with
290 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
dijkstra-prim-visualization/src/components/Canvas/Canvas.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { useState, useRef } from "react"; | ||
import classes from "./Canvas.module.css"; | ||
import Node from "./Node/Node"; | ||
import Edge from "./Edge/Edge"; | ||
import { newEdgeValid, newNodePositionValid } from "./CanvasUtils"; | ||
|
||
/** | ||
* Canvas component for visualizing 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([]); | ||
|
||
// Object with default data to reset firstNode, when needed | ||
const resetNodeData = { | ||
isClicked: false, | ||
x: null, | ||
y: null, | ||
}; | ||
|
||
// State variable to keep track of the first node clicked when creating an edge | ||
const [firstNode, setFirstNode] = useState(resetNodeData); | ||
|
||
// Reference to the canvas SVG element | ||
const canvasRef = useRef(null); | ||
|
||
// Handler function for when the canvas is clicked | ||
const canvasClickHandler = (event) => { | ||
// Reset the firstNode state variable | ||
setFirstNode(resetNodeData); | ||
|
||
// 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, | ||
}; | ||
|
||
// Check if the new node position is valid (not overlapping with existing nodes) | ||
if (!newNodePositionValid(newNodePoint, nodePoints, canvasRef)) { | ||
return; | ||
} | ||
|
||
// Add the new node point to the list of all nodes | ||
setNodePoints((prevNodePoints) => [...prevNodePoints, newNodePoint]); | ||
}; | ||
|
||
// Handler function for when a node is clicked | ||
const nodeClickHandler = (event, points) => { | ||
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); | ||
} | ||
}; | ||
|
||
// 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, | ||
}; | ||
|
||
// Check if the new edge is valid (not overlapping with existing edges) | ||
if (!newEdgeValid(newEdge, edges)) { | ||
return; | ||
} | ||
|
||
// 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} | ||
/> | ||
))} | ||
</svg> | ||
); | ||
}; | ||
|
||
export default Canvas; |
4 changes: 4 additions & 0 deletions
4
dijkstra-prim-visualization/src/components/Canvas/Canvas.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.canvas { | ||
background-color: black; | ||
cursor: crosshair; | ||
}/*# sourceMappingURL=Canvas.module.css.map */ |
1 change: 1 addition & 0 deletions
1
dijkstra-prim-visualization/src/components/Canvas/Canvas.module.css.map
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
4 changes: 4 additions & 0 deletions
4
dijkstra-prim-visualization/src/components/Canvas/Canvas.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.canvas { | ||
background-color: black; | ||
cursor: crosshair; | ||
} |
86 changes: 86 additions & 0 deletions
86
dijkstra-prim-visualization/src/components/Canvas/CanvasUtils.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
/** | ||
* 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. | ||
* @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 distance = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); | ||
|
||
return distance <= 35; | ||
}; | ||
|
||
/** | ||
* Checks if a new node is within the bounds of the canvas. | ||
* @param {Object} newNodePoint - 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) => { | ||
return ( | ||
newNodePoint.x - nodeRadius >= 0 && | ||
newNodePoint.y - nodeRadius >= 0 && | ||
newNodePoint.x + nodeRadius <= canvasRef.current.clientWidth && | ||
newNodePoint.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} 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), | ||
); | ||
|
||
if (isTooClose) { | ||
console.log("New node is too close to an existing node."); | ||
return false; | ||
} | ||
|
||
const isOutOfBounds = !isNodeInBounds(newNodePoint, 14, canvasRef); | ||
|
||
if (isOutOfBounds) { | ||
console.log("New node is out of bounds."); | ||
return false; | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
/** | ||
* Checks if a new edge is valid. | ||
* @param {Object} newEdge - The coordinates of the new edge. | ||
* @param {Array} edges - An array of existing edge coordinates. | ||
* @returns {boolean} - Returns true if the new edge is valid, false otherwise. | ||
*/ | ||
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), | ||
); | ||
|
||
if (edgeExists) { | ||
console.log("An edge with the same coordinates already exists."); | ||
return false; | ||
} | ||
|
||
return true; | ||
}; |
23 changes: 23 additions & 0 deletions
23
dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
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 <line className={classes.line} 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 = { | ||
x1: PropTypes.number.isRequired, | ||
y1: PropTypes.number.isRequired, | ||
x2: PropTypes.number.isRequired, | ||
y2: PropTypes.number.isRequired, | ||
}; |
4 changes: 4 additions & 0 deletions
4
dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.line { | ||
stroke: #ff0000; | ||
stroke-width: 2; | ||
}/*# sourceMappingURL=Edge.module.css.map */ |
1 change: 1 addition & 0 deletions
1
dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.module.css.map
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
4 changes: 4 additions & 0 deletions
4
dijkstra-prim-visualization/src/components/Canvas/Edge/Edge.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.line { | ||
stroke: #ff0000; | ||
stroke-width: 2; | ||
} |
29 changes: 29 additions & 0 deletions
29
dijkstra-prim-visualization/src/components/Canvas/Node/Node.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
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 ( | ||
<circle | ||
className={classes.circle} | ||
cx={cx} | ||
cy={cy} | ||
r="14" // TODO: Make the radius a prop to allow for customization | ||
onClick={(event) => 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, | ||
}; |
7 changes: 7 additions & 0 deletions
7
dijkstra-prim-visualization/src/components/Canvas/Node/Node.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.circle { | ||
stroke: #fff; | ||
stroke-width: 1.5; | ||
cursor: pointer; | ||
fill: #fff; | ||
transition: all 0.2s linear; | ||
}/*# sourceMappingURL=Node.module.css.map */ |
1 change: 1 addition & 0 deletions
1
dijkstra-prim-visualization/src/components/Canvas/Node/Node.module.css.map
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
7 changes: 7 additions & 0 deletions
7
dijkstra-prim-visualization/src/components/Canvas/Node/Node.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.circle { | ||
stroke: #fff; | ||
stroke-width: 1.5; | ||
cursor: pointer; | ||
fill: #fff; | ||
transition: all 0.2s linear; | ||
} |
Oops, something went wrong.