Skip to content

Instantly share code, notes, and snippets.

@ehzawad
Created September 10, 2024 11:58
Show Gist options
  • Save ehzawad/487c058c6f51d1f28f918a0e1f5bec42 to your computer and use it in GitHub Desktop.
Save ehzawad/487c058c6f51d1f28f918a0e1f5bec42 to your computer and use it in GitHub Desktop.
interactive_dfs.js
<!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