Search code examples
pythonchartsnetworkxbokeh

How to arrange Bokeh networkx graph nodes in a grid?


I like to create rectangle elements in a grid, like in the periodic table. periodic table https://docs.bokeh.org/en/2.4.1/docs/gallery/periodic.html

However some elements have to be connected to others, like edges between nodes in a graph. graph with connected nodes https://docs.bokeh.org/en/2.4.1/docs/user_guide/graph.html

I tried to use networkx in Bokeh for creating a graph. Unfortunately it is not possible to set static positions for the nodes.

import networkx as nx
from bokeh.io import save, output_file, show
from bokeh.models import Rect, MultiLine, Plot
from bokeh.models import HoverTool
from bokeh.models.graphs import NodesAndLinkedEdges
from bokeh.plotting import from_networkx

# Create Graph with seperate adding edges
G = nx.MultiGraph()
G.add_edge(1, 2)
G.add_edge(2, 3)
G.add_node(4)

# Create Graph with edge_list
G = nx.MultiGraph()
edge_list = [(1,2), (2,3), (2,4), (1,3)]
G.add_edges_from(edge_list)
G.add_nodes_from((5,6,7,8,9,10))

# create plot
plot = Plot(width = 1200,height = 900)
plot.add_tools(HoverTool(tooltips=[("Index", "@index")]))

# create graph with spring_layout
network_graph = from_networkx(G, nx.spring_layout, scale=3.4, center=(0,0))

# nodes
# test with x=10 and y=10, to set custom position
network_graph.node_renderer.glyph = Rect(x=10,y=10,width=0.3, height=0.2, fill_color='skyblue')
network_graph.node_renderer.hover_glyph = Rect(width=0.3, height=0.2, fill_color='navy')

# edges
network_graph.edge_renderer.glyph = MultiLine(line_width=3, line_color="lightgray", line_alpha=1.0)
network_graph.edge_renderer.hover_glyph = MultiLine(line_width=5, line_color="darkblue", line_alpha=1.0)

network_graph.inspection_policy = NodesAndLinkedEdges()
plot.renderers.append(network_graph)

output_file("graph_test.html")
save(plot)
show(plot)

How to set custom position for nodes in a networkx graph, so that they arrange like the elements in the periodic table example?


Solution

  • The function from_networkx from bokeh offers you the opportunity to pass a dict to the seconde parameter named layout_function with the position in x-y for each node.

    A dict could look like

    custom_positions = {0: (0,9), 1: (2,3)}
    

    To see how it works I adapted the from_networkx example.

    import networkx as nx
    import numpy as np
    
    from bokeh.palettes import Category20_20
    from bokeh.plotting import figure, from_networkx, show, output_notebook
    output_notebook()
    
    G = nx.desargues_graph() # always 20 nodes
    
    # added positioning
    fac = len(G)/(2*np.pi)
    custom_positions = {i: (np.sin(i/fac),np.cos(i/fac)) for i in G.nodes}
    
    p = figure(x_range=(-2, 2), y_range=(-2, 2),
               x_axis_location=None, y_axis_location=None,
               tools="hover", tooltips="index: @index")
    p.grid.grid_line_color = None
    
    graph = from_networkx(G, custom_positions, scale=1.8, center=(0,0))
    p.renderers.append(graph)
    
    # Add some new columns to the node renderer data source
    graph.node_renderer.data_source.data['index'] = list(range(len(G)))
    graph.node_renderer.data_source.data['colors'] = Category20_20
    
    graph.node_renderer.glyph.update(size=20, fill_color="colors")
    
    show(p)
    

    The result looks now like this:

    result with custom positions

    and the dict custom_positions has the following values:

    {0: (0.0, 1.0),
     1: (0.3090169943749474, 0.9510565162951535),
     2: (0.5877852522924731, 0.8090169943749475),
     3: (0.8090169943749475, 0.5877852522924731),
     4: (0.9510565162951535, 0.30901699437494745),
     5: (1.0, 6.123233995736766e-17),
     6: (0.9510565162951536, -0.30901699437494734),
     7: (0.8090169943749475, -0.587785252292473),
     8: (0.5877852522924732, -0.8090169943749473),
     9: (0.3090169943749475, -0.9510565162951535),
     10: (1.2246467991473532e-16, -1.0),
     11: (-0.3090169943749473, -0.9510565162951536),
     12: (-0.587785252292473, -0.8090169943749475),
     13: (-0.8090169943749473, -0.5877852522924732),
     14: (-0.9510565162951535, -0.30901699437494756),
     15: (-1.0, -1.8369701987210297e-16),
     16: (-0.9510565162951536, 0.30901699437494723),
     17: (-0.8090169943749476, 0.5877852522924729),
     18: (-0.5877852522924734, 0.8090169943749473),
     19: (-0.3090169943749477, 0.9510565162951535)}