如何在Bokeh中为NetworkX图节点添加可随缩放刷新的非交互式永久标签?
Got it, let's work through this together! You’ve already got your node positions from nx.spring_layout(G)—the trick here is tying those labels directly to your nodes in Bokeh so they stick with them through zooms, pans, or refreshes. Here's a step-by-step solution to add non-interactive, permanent labels that move with your nodes:
The core tool you need here is Bokeh's LabelSet—it lets you create batch labels that are bound to your node data source, so they automatically adjust position whenever the plot is zoomed or refreshed.
1. Prep your graph data and positions
First, convert your NetworkX graph into Bokeh-compatible data structures while preserving the spring layout positions:
import networkx as nx from bokeh.plotting import figure, show, output_file from bokeh.models import ColumnDataSource, LabelSet, Circle, MultiLine # Replace this with your actual graph G = nx.karate_club_graph() # Get spring layout positions pos = nx.spring_layout(G) # Extract node positions and labels into lists node_x = [pos[node][0] for node in G.nodes()] node_y = [pos[node][1] for node in G.nodes()] # Use node IDs as labels, or replace with your custom node attributes (e.g., G.nodes[node]['name']) node_labels = [str(node) for node in G.nodes()] # Create a Bokeh data source for nodes (this ties positions and labels together) node_source = ColumnDataSource(data=dict( x=node_x, y=node_y, labels=node_labels )) # Prep edge data for Bokeh start_x, start_y, end_x, end_y = [], [], [], [] for u, v in G.edges(): start_x.append(pos[u][0]) start_y.append(pos[u][1]) end_x.append(pos[v][0]) end_y.append(pos[v][1]) edge_source = ColumnDataSource(data=dict( start_x=start_x, start_y=start_y, end_x=end_x, end_y=end_y ))
2. Build the base plot
Initialize your Bokeh figure with the tools you need (like zoom and pan to test label behavior):
p = figure( title="Spring Layout with Permanent Node Labels", tools="pan,wheel_zoom,reset,save", match_aspect=True # Keeps aspect ratio to prevent label-node misalignment on zoom ) # Add edges first so they sit behind nodes p.multi_line( xs='start_x', ys='start_y', x2s='end_x', y2s='end_y', source=edge_source, color='gray', line_width=1 ) # Add nodes p.circle( x='x', y='y', source=node_source, size=12, color='cornflowerblue', alpha=0.8 )
3. Add the permanent labels
Use LabelSet to bind labels directly to your node data source. Adjust offsets to avoid overlapping with nodes:
# Create label set tied to the node data source labels = LabelSet( x='x', y='y', text='labels', source=node_source, x_offset=6, y_offset=6, # Shift labels slightly away from nodes text_font_size='10pt', text_color='darkslategray', interaction_policy='none' # Ensures labels are non-interactive ) # Add labels to the plot p.add_layout(labels)
4. Test the plot
Save and show your plot to verify labels stay with nodes during zoom/pan:
output_file("network_labels.html") show(p)
Key Notes
- Since
LabelSetis linked to the same data source as your nodes, any changes to the plot (like zooming) will automatically update label positions to match nodes. - To use custom labels (instead of node IDs), just modify the
node_labelslist to pull from your node attributes (e.g.,node_labels = [G.nodes[node]['description'] for node in G.nodes()]). - The
interaction_policy='none'ensures labels don’t respond to mouse clicks or hovers, keeping them strictly non-interactive as you requested.
内容的提问来源于stack exchange,提问作者Ly Harriet Bui

