如何用Holoviews/Bokeh绘制与Networkx节点边位置一致的图?
我懂你的痛点——用Networkx生成的图明明有固定的节点布局(比如你用spring_layout加random_state得到的可复现位置),但转到Holoviews/Bokeh后就完全变样了,而且还担心边权重没正确传递。其实核心问题是布局算法不统一,以及没有把Networkx生成的坐标复用给Holoviews,下面给你一步步解决:
1. 先固定并保存Networkx的布局坐标
首先,你需要把Networkx绘图时用的布局坐标单独生成并保存下来,而不是只在nx.draw里临时用。比如你原来的代码里用了nx.spring_layout(G, random_state=100),我们把它存成变量:
%pylab inline import pandas as pd import networkx as nx import holoviews as hv import numpy as np hv.extension('bokeh') np.random.seed(111) # 构建图 G=nx.Graph() ndxs = [1,2,3,4] G.add_nodes_from(ndxs) G.add_weighted_edges_from([(1,2,0), (1,3,1), (1,4,-1), (2,4,1), (2,3,-1), (3,4,10)]) # 生成并保存固定布局坐标 pos = nx.spring_layout(G, random_state=100) # 可选:打印坐标确认 print("节点布局坐标:", pos)
这一步的作用是把你想要的节点位置固定下来,避免每次布局都随机变化。
2. 把预存的布局传给Holoviews
原来你用了nx.layout.circular_layout,这和spring_layout是完全不同的布局算法,所以节点位置自然不一样。现在我们把刚才保存的pos作为布局函数传给Holoviews:
# 设置绘图参数 %opts Graph [width=400 height=400] padding = dict(x=(-1.1, 1.1), y=(-1.1, 1.1)) # 关键:用lambda函数返回预存的pos,而不是用新的布局算法 hv_graph = hv.Graph.from_networkx(G, lambda G: pos).redim.range(**padding) hv_graph
这样绘制出来的图,节点位置就和你用nx.draw(G, pos)得到的完全一致了。
3. 正确传递并可视化边权重
你担心边权重没正确传递,其实Holoviews会自动把Networkx图的边属性(包括weight)带进来,只是默认没可视化出来。我们可以把权重映射到边的宽度或颜色上,让图的信息更准确:
# 把边权重映射到线宽,权重越大线越粗 %opts Graph (edge_line_width='weight') # 或者同时把权重映射到颜色(需要处理负权重的话可以调整颜色映射) %opts Graph (edge_line_width='weight' edge_color='weight') # 重新绘制 hv.Graph.from_networkx(G, lambda G: pos).redim.range(**padding)
为什么原来的代码呈现差异大?
你原来的Holoviews代码用了nx.layout.circular_layout,这是一种把节点均匀排列在圆周上的布局,和你Networkx绘图时用的spring_layout(基于力导向的布局)逻辑完全不同,所以节点位置自然天差地别。只要复用同一个布局坐标集合,就能解决这个问题。
内容的提问来源于stack exchange,提问作者Soerendip

