Network Graph

For this exercise we will be creating a random graph using NetworkX.

Aric A. Hagberg, Daniel A. Schult and Pieter J. Swart, “Exploring network structure, dynamics, and function using NetworkX”, in Proceedings of the 7th Python in Science Conference (SciPy2008), Gäel Varoquaux, Travis Vaught, and Jarrod Millman (Eds), (Pasadena, CA USA), pp. 11–15, Aug 2008

https://networkx.org/

https://networkx.org/documentation/latest/auto_examples/index.html

We will be using the Barabási–Albert algorithm to create the network

A. L. Barabási and R. Albert “Emergence of scaling in random networks”, Science 286, pp 509-512, 1999.

https://networkx.org/documentation/stable/reference/generated/networkx.generators.random_graphs.barabasi_albert_graph.html?highlight=barabasi#networkx.generators.random_graphs.barabasi_albert_graph

D3.js Force-Directed Graph Notebook: https://observablehq.com/@d3/force-directed-graph

%%capture
!pip3 install networkx
import networkx as nx
import json

graphsize = 100
nodes = {}
links = {}
Graph = nx.barabasi_albert_graph(graphsize, 1)
j = "" # JSON object
j = j + "{"    
j = j + """\t"nodes": ["""

for n in nx.nodes(Graph):
    nodes[n] = {}
    nodes[n]['name'] = n
for n in nodes:
    j = j + str(json.dumps(nodes[n])) + ",\n"
j = j[:-2]
j = j + "\t],\n"
j = j + """\t"links":[\n"""
for link in nx.edges(Graph):
    links[str(link)] = {}
    links[str(link)]['source'] = link[0]
    links[str(link)]['target'] = link[1]
for l in links:
    j = j + str(json.dumps(links[l])) + ",\n"
j = j[:-2]
j = j + "\t]\n"
j = j + "}"
#print (j)

f = open("network.json", "w")
f.write(j)
f.close()

Start D3.js

from IPython.display import HTML, Javascript, display

def configure_d3():
    display(Javascript("""
    require.config({
      paths: {
        d3: "https://d3js.org/d3.v6.min"
      }
    })"""))


configure_d3()

Getting the Data into D3.js

%%html
<script type="text/javascript">   
require(['d3'], function (d3) {
    
    d3.json('network.json')
        .then(function(data) {
            nodes = data.nodes.map(d=> d)
            console.log(nodes)
            links = data.links.map(d=> ({source:nodes[d.source], target:nodes[d.target]}))
            console.log(links)
        })
        .catch(function(error){
        
        })
    
})
</script>

Creating the Graph

%%html
<div id="gohere1"></div>

<script type="text/javascript">   
require(['d3'], function (d3) {
    
    d3.json('network.json')
        .then(function(data) {
            nodes = data.nodes.map(d=> d)
            links = data.links.map(d=> ({source:nodes[d.source], target:nodes[d.target]}))
            const width = 600
            const height = 600

            const svg = d3.select("div#gohere1").append("svg")
                .attr("width", width)
                .attr("height", height)

            const link = svg
                .selectAll("line.link")
                .data(links)
                .join("line")
                .attr("class", "link")
                .style("stroke", "#999")
                .style("stroke-width", ".6px");

            // append the nodes with specified data and style properties
            const node = svg
                .selectAll("circle.node")
                .data(nodes)
                .join("circle")
                .attr("class", "node")
                .attr("r", 5)
                .style("stroke", "#fff")
                .style("stroke-width", "1.5px")
                .style("fill", (d,i)=> (i%2) ? "steelblue" : "purple")
            
            // attach titles to nodes, so when the mouse hovers over the nodes it projects the name
            node.append("title")
                .text(function(d) { return d.name; });
        })
        .catch(function(error){
        
        })
    
})
</script>

Simulation Time

%%html
<div id="gohere2"></div>

<script type="text/javascript">   
require(['d3'], function (d3) {
    
    d3.json('network.json')
        .then(function(data) {
            nodes = data.nodes.map(d=> d)
            links = data.links.map(d=> ({source:nodes[d.source], target:nodes[d.target]}))
            const width = 600
            const height = 600

            const svg = d3.select("div#gohere2").append("svg")
                .attr("width", width)
                .attr("height", height)

            const link = svg
                .selectAll("line.link")
                .data(links)
                .join("line")
                .attr("class", "link")
                .style("stroke", "#999")
                .style("stroke-width", ".6px");

            // append the nodes with specified data and style properties
            const node = svg
                .selectAll("circle.node")
                .data(nodes)
                .join("circle")
                .attr("class", "node")
                .attr("r", 5)
                .style("stroke", "#fff")
                .style("stroke-width", "1.5px")
                .style("fill", (d,i)=> (i%2) ? "steelblue" : "purple")
            
            // attach titles to nodes, so when the mouse hovers over the nodes it projects the name
            node.append("title")
                .text(function(d) { return d.name; });

            // this actually adheres the data to the force base layout and starts the layout
            const simulation = d3.forceSimulation(nodes)
                .force("link", 
                       d3.forceLink(links).id(d => d.name)
                       .distance(10)
                       .strength(1)
                      )
                .force("charge", d3.forceManyBody())
                .force("center", d3.forceCenter(width / 2, height / 2))
                .on("tick", ticked);            
            
            // this is the main mechanism of the force based diagram
            // which moves the nodes and links from their starting and finishing positions
            // once it hits equilibrium, it will stop moving the positions
            function ticked() {
                svg.selectAll("line.link")
                    .attr("x1", d => d.source.x )
                    .attr("y1", d => d.source.y )
                    .attr("x2", d => d.target.x )
                    .attr("y2", d => d.target.y )
                
                svg.selectAll("circle.node")
                    .attr("cx", d => d.x)
                    .attr("cy", d => d.y)
            };
        })
        .catch(function(error){
        
        })
    
})
</script>

Making it move

%%html
<div id="gohere3"></div>

<script type="text/javascript">   
require(['d3'], function (d3) {
    
    d3.json('network.json')
        .then(function(data) {
            nodes = data.nodes.map(d=> d)
            links = data.links.map(d=> ({source:nodes[d.source], target:nodes[d.target]}))
            const width = 600
            const height = 600

            const svg = d3.select("div#gohere3").append("svg")
                .attr("width", width)
                .attr("height", height)

            const link = svg
                .selectAll("line.link")
                .data(links)
                .join("line")
                .attr("class", "link")
                .style("stroke", "#999")
                .style("stroke-width", ".6px");

            // append the nodes with specified data and style properties
            const node = svg
                .selectAll("circle.node")
                .data(nodes)
                .join("circle")
                .attr("class", "node")
                .attr("r", 5)
                .style("stroke", "#fff")
                .style("stroke-width", "1.5px")
                .style("fill", (d,i)=> (i%2) ? "steelblue" : "purple")
                .call(d3.drag()
                      .on("start", dragstarted)
                      .on("drag", dragged)
                      .on("end", dragended))
            
            // attach titles to nodes, so when the mouse hovers over the nodes it projects the name
            node.append("title")
                .text(function(d) { return d.name; });

            // this actually adheres the data to the force base layout and starts the layout
            const simulation = d3.forceSimulation(nodes)
                .force("link", 
                       d3.forceLink(links).id(d => d.name)
                       .distance(d => 0)
                       .strength(1)
                      )
                .force("charge", d3.forceManyBody())
                .force("center", d3.forceCenter(width / 2, height / 2))
                .on("tick", ticked);            
            
            // this is the main mechanism of the force based diagram
            // which moves the nodes and links from their starting and finishing positions
            // once it hits equilibrium, it will stop moving the positions
            function ticked() {
                svg.selectAll("line.link").attr("x1", function(d) { return d.source.x; })
                    .attr("y1", function(d) { return d.source.y; })
                    .attr("x2", function(d) { return d.target.x; })
                    .attr("y2", function(d) { return d.target.y; });
                svg.selectAll("circle.node").attr("cx", function(d) { return d.x; })
                    .attr("cy", function(d) { return d.y; });
            };
            

            function dragstarted(event) {
                if (!event.active) simulation.alphaTarget(0.3).restart();
                event.subject.fx = event.subject.x;
                event.subject.fy = event.subject.y;
            }

            function dragged(event) {
                event.subject.fx = event.x;
                event.subject.fy = event.y;
            }

            function dragended(event) {
                if (!event.active) simulation.alphaTarget(0);
                event.subject.fx = event.x;
                event.subject.fy = event.y;
            }            
            
        })
        .catch(function(error){
        
        })
    
})
</script>