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?
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:
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)}