- networkx
import networkx as nx import d3 import js from pyodide import create_proxy, to_js G = nx.karate_club_graph() node_list = [{"id": node[0], "name": node[1]} for node in G.nodes(data="club")] edge_list = [{"source": edge[0], "target": edge[1]} for edge in nx.to_edgelist(G)] margin = {"top": 30, "right": 30, "bottom": 70, "left": 40} width = 400 - margin["left"] - margin["right"] max_height = 400 - margin["top"] - margin["bottom"] viz = d3.select("#viz") viz.select(".loading").remove() svg = (viz .append("svg") .attr("width", width + margin["left"] + margin["right"]) .attr("height", max_height + margin["top"] + margin["bottom"]) .append("g") .attr("transform", f"translate({margin['left']},{margin['top']})") ) nodes_js = to_js(node_list) edges_js = to_js(edge_list, dict_converter=js.Object.fromEntries) link = (svg .selectAll("line") .data(edges_js) .enter() .append("line") .style("stroke", "#aaa") ) node = (svg .selectAll("circle") .data(nodes_js) .enter() .append("circle") .attr("r", 20) .style("fill", "#69b3a2") ) def ticked(): (link .attr("x1", create_proxy(lambda d, *_:d.source.x)) .attr("y1", create_proxy(lambda d, *_:d.source.y)) .attr("x2", create_proxy(lambda d, *_:d.target.x)) .attr("y2", create_proxy(lambda d, *_:d.target.y)) ) (node .attr("cx", create_proxy(lambda d, *_:d.x+6)) .attr("cy", create_proxy(lambda d, *_:d.y-6)) ) ticked_fn = create_proxy(ticked) id_fn = create_proxy(lambda d, *_:d["id"]) simulation = (d3.forceSimulation(nodes_js) .force("link", d3.forceLink().id(id_fn).links(edges_js)) .force("charge", d3.forceManyBody().strength(-400)) .force("center", d3.forceCenter(width / 2, max_height / 2)) .on("end", ticked_fn) )