Bokeh lets you create network graph visualizations and configure interactions between edges and nodes.
The GraphRenderer model maintains separate sub-GlyphRenderers for graph nodes and edges. This lets you customize nodes by modifying the node_renderer property of the GraphRenderer. You can replace the default Circle node glyph with any instance of the XYGlyph such as Rect or Ellipse glyph. You can similarly modify the style properties of edges through the edge_renderer property. To work with edge glyphs, use the multi_line glyph method.
GraphRenderer
GlyphRenderers
node_renderer
edge_renderer
multi_line
Observe the following requirements for the data sources belonging to these sub-renderers:
The ColumnDataSource of the node sub-renderer must have an "index" column with the unique indices of the nodes.
ColumnDataSource
"index"
The ColumnDataSource of the edge sub-renderer must have a "start" and "end" column. These columns contain the node indices for the start and end of the edges.
"start"
"end"
You can add extra meta-data to these sources to enable vectorized glyph styling or make data available for callbacks or hover tooltips.
The following code snippet
replaces a node glyph with an Ellipse,
assigns scalar values to the height and width attributes of the Ellipse,
height
width
assigns a palette to the fill_color attribute of the Ellipse,
fill_color
and adds the assigned values to the node data source.
import math from bokeh.plotting import figure from bokeh.models import GraphRenderer, Ellipse from bokeh.palettes import Spectral8 # list the nodes and initialize a plot N = 8 node_indices = list(range(N)) plot = figure(title="Graph layout demonstration", x_range=(-1.1,1.1), y_range=(-1.1,1.1), tools="", toolbar_location=None) graph = GraphRenderer() # replace the node glyph with an ellipse # set its height, width, and fill_color graph.node_renderer.glyph = Ellipse(height=0.1, width=0.2, fill_color="fill_color") # assign a palette to ``fill_color`` and add it to the data source graph.node_renderer.data_source.data = dict( index=node_indices, fill_color=Spectral8) # add the rest of the assigned values to the data source graph.edge_renderer.data_source.data = dict( start=[0]*N, end=node_indices)
Bokeh comes with a built-in LayoutProvider model that includes a dictionary of (x,y) coordinates for nodes. This lets you arrange plot elements in Cartesian space.
LayoutProvider
The following codes snippet uses this provider model to produce a plot based on the setup above.
# generate ellipses based on the ``node_indices`` list circ = [i*2*math.pi/8 for i in node_indices] # create lists of x- and y-coordinates x = [math.cos(i) for i in circ] y = [math.sin(i) for i in circ] # convert the ``x`` and ``y`` lists into a dictionary of 2D-coordinates # and assign each entry to a node on the ``node_indices`` list graph_layout = dict(zip(node_indices, zip(x, y))) # use the provider model to supply coourdinates to the graph graph.layout_provider = StaticLayoutProvider(graph_layout=graph_layout) # render the graph plot.renderers.append(graph) # specify the name of the output file output_file('graph.html') # display the plot show(plot)
Put together, the above code snippets produce the following result:
By default, the StaticLayoutProvider model draws straight-line paths between the supplied node positions. To set explicit edge paths, supply lists of paths to the bokeh.models.sources.ColumnDataSource data source of the edge_renderer. The StaticLayoutProvider model looks for these paths in the "xs" and "ys" columns of the data source. The paths should be in the same order as the "start" and "end" points. Be extra careful when setting explicit paths because there is no validation to check if they match with node positions.
StaticLayoutProvider
bokeh.models.sources.ColumnDataSource
"xs"
"ys"
The following extends the example above and draws quadratic bezier curves between the nodes:
import math from bokeh.io import output_file, show from bokeh.models import Ellipse, GraphRenderer, StaticLayoutProvider from bokeh.palettes import Spectral8 from bokeh.plotting import figure N = 8 node_indices = list(range(N)) plot = figure(title="Graph Layout Demonstration", x_range=(-1.1,1.1), y_range=(-1.1,1.1), tools="", toolbar_location=None) graph = GraphRenderer() graph.node_renderer.data_source.add(node_indices, 'index') graph.node_renderer.data_source.add(Spectral8, 'color') graph.node_renderer.glyph = Ellipse(height=0.1, width=0.2, fill_color="color") graph.edge_renderer.data_source.data = dict( start=[0]*N, end=node_indices) ### start of layout code circ = [i*2*math.pi/8 for i in node_indices] x = [math.cos(i) for i in circ] y = [math.sin(i) for i in circ] graph_layout = dict(zip(node_indices, zip(x, y))) graph.layout_provider = StaticLayoutProvider(graph_layout=graph_layout) ### Draw quadratic bezier paths def bezier(start, end, control, steps): return [(1-s)**2*start + 2*(1-s)*s*control + s**2*end for s in steps] xs, ys = [], [] sx, sy = graph_layout[0] steps = [i/100. for i in range(100)] for node_index in node_indices: ex, ey = graph_layout[node_index] xs.append(bezier(sx, ex, 0, steps)) ys.append(bezier(sy, ey, 0, steps)) graph.edge_renderer.data_source.data['xs'] = xs graph.edge_renderer.data_source.data['ys'] = ys plot.renderers.append(graph) output_file("graph.html") show(plot)
Bokeh integrates the NetworkX package so you can quickly plot network graphs. The bokeh.plotting.from_networkx convenience method accepts a networkx.Graph object and a NetworkX layout method and returns a configured instance of the GraphRenderer model.
bokeh.plotting.from_networkx
networkx.Graph
Here is how the networkx.spring_layout method lays out the “Zachary’s karate club graph” data set built into NetworkX:
networkx.spring_layout
import networkx as nx from bokeh.io import output_file, show from bokeh.plotting import figure, from_networkx G = nx.karate_club_graph() plot = figure(title="Networkx Integration Demonstration", x_range=(-1.1,1.1), y_range=(-1.1,1.1), tools="", toolbar_location=None) graph = from_networkx(G, nx.spring_layout, scale=2, center=(0,0)) plot.renderers.append(graph) output_file("networkx_graph.html") show(plot)
You can configure the selection or inspection behavior of graphs by setting the selection_policy and inspection_policy attributes of the GraphRenderer. These policy attributes accept a special GraphHitTestPolicy model instance.
selection_policy
inspection_policy
GraphHitTestPolicy
For example, setting selection_policy to NodesAndLinkedEdges() lets you select a node and all associated edges. Similarly, setting inspection_policy to EdgesAndLinkedNodes() lets you inspect the "start" and "end" nodes of an edge by hovering over it with the HoverTool.
NodesAndLinkedEdges()
EdgesAndLinkedNodes()
You can customize the selection_glyph, nonselection_glyph, and/or hover_glyph attributes of the edge and node sub-renderers to add dynamic visual elements to your graph interactions.
selection_glyph
nonselection_glyph
hover_glyph
Here is an example of a graph with added node and edge interactions:
import networkx as nx from bokeh.io import output_file, show from bokeh.models import (BoxSelectTool, Circle, EdgesAndLinkedNodes, HoverTool, MultiLine, NodesAndLinkedEdges, Plot, Range1d, TapTool,) from bokeh.palettes import Spectral4 from bokeh.plotting import from_networkx G=nx.karate_club_graph() plot = Plot(plot_width=400, plot_height=400, x_range=Range1d(-1.1,1.1), y_range=Range1d(-1.1,1.1)) plot.title.text = "Graph Interaction Demonstration" plot.add_tools(HoverTool(tooltips=None), TapTool(), BoxSelectTool()) graph_renderer = from_networkx(G, nx.circular_layout, scale=1, center=(0,0)) graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral4[0]) graph_renderer.node_renderer.selection_glyph = Circle(size=15, fill_color=Spectral4[2]) graph_renderer.node_renderer.hover_glyph = Circle(size=15, fill_color=Spectral4[1]) graph_renderer.edge_renderer.glyph = MultiLine(line_color="#CCCCCC", line_alpha=0.8, line_width=5) graph_renderer.edge_renderer.selection_glyph = MultiLine(line_color=Spectral4[2], line_width=5) graph_renderer.edge_renderer.hover_glyph = MultiLine(line_color=Spectral4[1], line_width=5) graph_renderer.selection_policy = NodesAndLinkedEdges() graph_renderer.inspection_policy = EdgesAndLinkedNodes() plot.renderers.append(graph_renderer) output_file("interactive_graphs.html") show(plot)
The from_networkx method converts node and edge attributes of the NetworkX package for use with node_renderer and edge_renderer of the GraphRenderer model.
from_networkx
For example, “Zachary’s karate club graph” data set has a node attribute named “club”. You can hover this information with node attributes converted with the from_networkx method. You can also use node and edge attributes for color information.
Here is an example of a graph that hovers node attributes and changes colors with edge attributes:
import networkx as nx from bokeh.io import output_file, show from bokeh.models import (BoxZoomTool, Circle, HoverTool, MultiLine, Plot, Range1d, ResetTool,) from bokeh.palettes import Spectral4 from bokeh.plotting import from_networkx # Prepare Data G = nx.karate_club_graph() SAME_CLUB_COLOR, DIFFERENT_CLUB_COLOR = "black", "red" edge_attrs = {} for start_node, end_node, _ in G.edges(data=True): edge_color = SAME_CLUB_COLOR if G.nodes[start_node]["club"] == G.nodes[end_node]["club"] else DIFFERENT_CLUB_COLOR edge_attrs[(start_node, end_node)] = edge_color nx.set_edge_attributes(G, edge_attrs, "edge_color") # Show with Bokeh plot = Plot(plot_width=400, plot_height=400, x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1)) plot.title.text = "Graph Interaction Demonstration" node_hover_tool = HoverTool(tooltips=[("index", "@index"), ("club", "@club")]) plot.add_tools(node_hover_tool, BoxZoomTool(), ResetTool()) graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0)) graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral4[0]) graph_renderer.edge_renderer.glyph = MultiLine(line_color="edge_color", line_alpha=0.8, line_width=1) plot.renderers.append(graph_renderer) output_file("interactive_graphs.html") show(plot)