13

I am using the following code to produce an interactive bokeh network graph. How do I add the name of the nodes to the nodes in the bokeh plot?

from bokeh.io import show, output_notebook
from bokeh.models import Plot, Range1d, MultiLine, Circle, HoverTool, TapTool, BoxSelectTool
from bokeh.models.graphs import from_networkx, NodesAndLinkedEdges, EdgesAndLinkedNodes
from bokeh.palettes import Spectral4
from bokeh.models import LabelSet

plot = Plot(plot_width=900, plot_height=500,
            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.node_renderer.glyph.properties_with_values()
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)

show(plot)

resulting bokeh networkx graph: enter image description here

Leonardo Alves Machado
  • 2,747
  • 10
  • 38
  • 53
Kate Lewis
  • 131
  • 1
  • 3

3 Answers3

9

I guess you mean interactive labels for the nodes, something I also wanted to do. To achieve this, you need to modify a couple of lines:

hover = HoverTool(tooltips=[("Name:", "@name")])
plot.add_tools(hover, TapTool(), BoxSelectTool(), WheelZoomTool())
...
graph_renderer.inspection_policy = NodesAndLinkedEdges()

Then modify the data source for the nodes:

graph_renderer.node_renderer.data_source.data['name'] = [name1, ... ,nameN]

(the data source already exists; it's a dictionary for which the only key is 'index', a numbered list of nodes. Thus you can add more keys that reference lists of the same length - such as a list of names - and these lists can be accessed via '@key')

Paddy Alton
  • 1,855
  • 1
  • 7
  • 11
  • I discovered that graph_renderer.node_renderer.data_source.data is a dict with and entry "index" whose value was a list of nodenames from when I created the network. Thus hover = HoverTool(tooltips=[('','@index')]) was all I needed. – jdmarino Sep 20 '18 at 13:37
  • 1
    @PaulBrown When I give `graph_renderer.inspection_policy = NodesAndLinkedEdges()`, I lose the ability to hover over an edge to highlight it. To have this functionality, I need to set `graph_renderer.inspection_policy = EdgesAndLinkedNodes()`; but if I do so, I lose the info that appears on hovering over the nodes - they appear as "`???`". How do I fix this? – Kristada673 May 20 '19 at 03:04
5

I found @SergioLucero's answer too incomplete to answer the question, the code sample is not working.

However, with code and answer created by @ifearthenight (from this question: Lining up labels with the nodes on a Bokeh figure generated from a NetworkX graph) I was able to produce a working example.

from bokeh.io import show, output_notebook
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, LabelSet
from bokeh.models.graphs import from_networkx
output_notebook()

G = nx.karate_club_graph()

plot = figure(title="Karate Club Graph", tools="", x_range=(-1.5, 1.5),
          y_range=(-1.5, 1.5), toolbar_location=None)
graph = from_networkx(G, nx.spring_layout)
plot.renderers.append(graph)

x, y = zip(*graph.layout_provider.graph_layout.values())
node_labels = nx.get_node_attributes(G, 'club')
source = ColumnDataSource({'x': x, 'y': y,
                           'club': [node_labels[i] for i in range(len(x))]})
labels = LabelSet(x='x', y='y', text='club', source=source,
                  background_fill_color='white')

plot.renderers.append(labels)
show(plot)
Tom Hemmes
  • 2,000
  • 2
  • 17
  • 23
  • Replace `range(len(x))` with `graph.layout_provider.graph_layout` (`.keys()`) if your graph keys is not ordered consecutive numeric starting from 0. – Alcolo47 Apr 16 '22 at 13:42
2

Hot topic!! I think I just got it. They may be more pythonic ways, but what I did was

  1. Create a DataSource with the relevant labels
  2. Retrieve positions from the graph using pos = nx.circular_layout(G)
  3. add these positions to my Datasource
  4. Create a LabelSet from the positions and the source

Leonardo, you are missing the line where G gets created. I took the karate_club_graph and this works for me:

from bokeh.models import ColumnDataSource
pos = nx.circular_layout(G)
x,y=zip(*pos.values())

source = ColumnDataSource({'x':x,'y':y,'kid':['Joe %d' %ix for ix in range(len(x))]})
labels = LabelSet(x='x', y='y', text='kid', source=source)

plot.renderers.append(labels)
Sergio Lucero
  • 862
  • 1
  • 12
  • 21
  • 1
    While this question is pretty old, I found it still helpful. However, in order to get the actual values one needs to query them from the layout provider. This answer calls the provider again, which can result in a different layout calculated with different positions. To get the values used, this snippet works: `x, y = zip(*graph.layout_provider.graph_layout.values())` – Florian Oct 25 '22 at 14:11