Created
September 10, 2024 11:58
-
-
Save ehzawad/487c058c6f51d1f28f918a0e1f5bec42 to your computer and use it in GitHub Desktop.
interactive_dfs.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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>React D3.js Interactive Directed Graph Representation</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script> | |
<style> | |
body { font-family: Arial, sans-serif; } | |
.container { display: flex; flex-wrap: wrap; } | |
.graph, .adjacency-list, .adjacency-matrix { margin: 10px; } | |
table { border-collapse: collapse; } | |
td { border: 1px solid #ddd; padding: 8px; text-align: center; } | |
input[type="number"] { width: 40px; } | |
</style> | |
</head> | |
<body> | |
<div id="root"></div> | |
<script type="text/babel"> | |
const { useState, useEffect, useRef } = React; | |
const InteractiveGraph = () => { | |
const [graph, setGraph] = useState({ | |
nodes: Array.from({ length: 6 }, (_, i) => ({ id: i + 1 })), | |
links: [] | |
}); | |
const [adjList, setAdjList] = useState(Array.from({ length: 6 }, () => [])); | |
const [adjMatrix, setAdjMatrix] = useState(Array.from({ length: 6 }, () => Array(6).fill(0))); | |
const svgRef = useRef(null); | |
const width = 400; | |
const height = 300; | |
useEffect(() => { | |
const svg = d3.select(svgRef.current); | |
svg.selectAll("*").remove(); | |
const simulation = d3.forceSimulation(graph.nodes) | |
.force("link", d3.forceLink(graph.links).id(d => d.id)) | |
.force("charge", d3.forceManyBody()) | |
.force("center", d3.forceCenter(width / 2, height / 2)) | |
.force("collide", d3.forceCollide().radius(30)) | |
.force("x", d3.forceX(width / 2).strength(0.1)) | |
.force("y", d3.forceY(height / 2).strength(0.1)); | |
svg.append("defs").append("marker") | |
.attr("id", "arrowhead") | |
.attr("viewBox", "-0 -5 10 10") | |
.attr("refX", 20) | |
.attr("refY", 0) | |
.attr("orient", "auto") | |
.attr("markerWidth", 8) | |
.attr("markerHeight", 8) | |
.attr("xoverflow", "visible") | |
.append("svg:path") | |
.attr("d", "M 0,-5 L 10 ,0 L 0,5") | |
.attr("fill", "#999") | |
.style("stroke", "none"); | |
const nodeCount = graph.nodes.length; | |
const nodeRadius = Math.max(5, Math.min(20, 100 / nodeCount)); | |
const linkDistance = Math.max(30, Math.min(100, 400 / nodeCount)); | |
const link = svg.append("g") | |
.selectAll("path") | |
.data(graph.links) | |
.enter().append("path") | |
.attr("class", "link") | |
.attr("stroke", d => d.source === d.target ? "#ff0000" : "#999") | |
.attr("stroke-opacity", 0.6) | |
.attr("stroke-width", 2) | |
.attr("fill", "none") | |
.attr("marker-end", d => d.source !== d.target ? "url(#arrowhead)" : null); | |
const node = svg.append("g") | |
.selectAll("circle") | |
.data(graph.nodes) | |
.enter().append("circle") | |
.attr("class", "node") | |
.attr("r", nodeRadius) | |
.attr("fill", "#69b3a2"); | |
const label = svg.append("g") | |
.selectAll("text") | |
.data(graph.nodes) | |
.enter().append("text") | |
.attr("class", "label") | |
.attr("text-anchor", "middle") | |
.attr("dy", ".35em") | |
.text(d => d.id); | |
simulation.on("tick", () => { | |
link.attr("d", d => { | |
if (d.source === d.target) { | |
const x = d.source.x; | |
const y = d.source.y; | |
const rx = 15; | |
const ry = 10; | |
return `M${x-3},${y} A${rx},${ry} 0 1,1 ${x+3},${y}`; | |
} else { | |
return `M${d.source.x},${d.source.y} L${d.target.x},${d.target.y}`; | |
} | |
}); | |
node | |
.attr("cx", d => d.x = Math.max(nodeRadius, Math.min(width - nodeRadius, d.x))) | |
.attr("cy", d => d.y = Math.max(nodeRadius, Math.min(height - nodeRadius, d.y))); | |
label | |
.attr("x", d => d.x) | |
.attr("y", d => d.y); | |
}); | |
simulation.force("link").links(graph.links).distance(linkDistance); | |
simulation.force("charge").strength(-100 * nodeCount); | |
simulation.alpha(1).restart(); | |
}, [graph]); | |
const updateAdjListEntry = (source, value) => { | |
const newTargets = value.split(',').map(v => parseInt(v.trim())).filter(v => !isNaN(v) && v >= 1 && v <= 6); | |
const newAdjList = [...adjList]; | |
newAdjList[source] = newTargets; | |
setAdjList(newAdjList); | |
updateAdjMatrixFromList(newAdjList); | |
updateGraphFromAdjList(newAdjList); | |
}; | |
const updateAdjMatrixCell = (row, col, value) => { | |
const newAdjMatrix = adjMatrix.map(r => [...r]); | |
newAdjMatrix[row][col] = parseInt(value); | |
setAdjMatrix(newAdjMatrix); | |
updateAdjListFromMatrix(newAdjMatrix); | |
updateGraphFromAdjMatrix(newAdjMatrix); | |
}; | |
const updateAdjMatrixFromList = (list) => { | |
const newMatrix = Array.from({ length: 6 }, () => Array(6).fill(0)); | |
list.forEach((targets, source) => { | |
targets.forEach(target => { | |
newMatrix[source][target - 1] = 1; | |
}); | |
}); | |
setAdjMatrix(newMatrix); | |
}; | |
const updateAdjListFromMatrix = (matrix) => { | |
const newList = matrix.map((row, i) => | |
row.reduce((acc, val, j) => val ? [...acc, j + 1] : acc, []) | |
); | |
setAdjList(newList); | |
}; | |
const updateGraphFromAdjList = (list) => { | |
const newLinks = []; | |
list.forEach((targets, source) => { | |
targets.forEach(target => { | |
newLinks.push({ source: source + 1, target: target }); | |
}); | |
}); | |
setGraph(prevGraph => ({ ...prevGraph, links: newLinks })); | |
}; | |
const updateGraphFromAdjMatrix = (matrix) => { | |
const newLinks = []; | |
matrix.forEach((row, source) => { | |
row.forEach((value, target) => { | |
if (value === 1) { | |
newLinks.push({ source: source + 1, target: target + 1 }); | |
} | |
}); | |
}); | |
setGraph(prevGraph => ({ ...prevGraph, links: newLinks })); | |
}; | |
useEffect(() => { | |
// Initialize with example data | |
const initialAdjList = [[2, 4], [5], [5, 6], [2], [4], [6]]; | |
setAdjList(initialAdjList); | |
updateAdjMatrixFromList(initialAdjList); | |
updateGraphFromAdjList(initialAdjList); | |
}, []); | |
return ( | |
<div className="container"> | |
<h1>React D3.js Interactive Directed Graph</h1> | |
<div className="graph"> | |
<h2>Graph Visualization</h2> | |
<svg ref={svgRef} width={width} height={height}></svg> | |
</div> | |
<div className="adjacency-list"> | |
<h2>Adjacency List</h2> | |
{adjList.map((targets, source) => ( | |
<div key={source}> | |
<label>{source + 1}:</label> | |
<input | |
type="text" | |
value={targets.join(', ')} | |
onChange={(e) => updateAdjListEntry(source, e.target.value)} | |
/> | |
</div> | |
))} | |
</div> | |
<div className="adjacency-matrix"> | |
<h2>Adjacency Matrix</h2> | |
<table> | |
<tbody> | |
{adjMatrix.map((row, i) => ( | |
<tr key={i}> | |
{row.map((cell, j) => ( | |
<td key={j}> | |
<input | |
type="number" | |
min="0" | |
max="1" | |
value={cell} | |
onChange={(e) => updateAdjMatrixCell(i, j, e.target.value)} | |
/> | |
</td> | |
))} | |
</tr> | |
))} | |
</tbody> | |
</table> | |
</div> | |
</div> | |
); | |
}; | |
ReactDOM.render(<InteractiveGraph />, document.getElementById('root')); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment