Matplotlib + NetworkX - 在一个图形中绘制两个网络图。

4
如果你知道如何使用Matplotlib,那么你可能能够在不了解NetworkX的情况下回答。
我有两个网络要使用NetworkX绘制,并且希望在单个图形中并排绘制它们,显示每个轴。基本上,这是在Matplotlib中创建2个子图的问题(这是NetworkX用于绘制图形的库)。
每个网络节点的位置分布在[0,area_size]范围内,但通常不存在x = 0.0或y = area_size的坐标点。也就是说,点出现在[0,area_size]或更小的区域内。不会更大。
每个子图的比例应为0.8 x 1.0,由0.16 x 1.0的比例区域分隔开。
实际上,它应该像这样(假设area_size = 100)。

graphs with proportions

然后我必须在这两个图之间绘制线条,因此我需要一些方法来返回每个图中的位置,以便连接它们。
节点位置是这样生成、存储和分配的。
# generate and store node positions
positions = {}
for node_id in G.nodes():
    pos_x = # generate pos_x in [0.0, area_size]
    pos_y = # generate pos_y in [0.0, area_size]

    positions[node_id]['x'] = pos_x
    positions[node_id]['y'] = pos_y

    G.node[node_id]['x'] = pos_x
    G.node[node_id]['y'] = pos_y

这些位置信息会被存储在一个字典中:pos = {node_id: (x, y), ...}。 NetworkX 会使用该结构来以正确的位置绘制节点:nx.draw_network(G, pos=positions)
目前,我执行以下操作:
  • 计算第一个网络在[0,area_size]区间内的位置,然后将其拉伸到[0, area_size*0.8]
  • 用相同的方式计算第二个网络的位置
  • 将第二个网络的位置向右移动,将x坐标相加area_size*0.8 + area_size*0.16
  • 设置图像的大小(以英寸为单位)plt.figure(figsize=(h,w), dpi=100)
  • 设置x轴 plt.xlim(0.0, area_size*0.8*2 + area_size*0.16)
  • 设置y轴 plt.ylim(0.0, area_size)
  • 绘制第一个网络(传递其位置)
  • 绘制第二个网络(传递其位置)
  • 在两个网络之间画线
我得到的图形比例正确,但轴没有显示正确的信息。我应该隐藏轴并绘制虚拟轴吗?
我可能需要一些更好的程序来绘制适当的子图。
我还注意到,当我导出为pdf时,设置的图形大小并不总是被尊重。
NetworkX用于绘制图形的函数是这些,特别是我正在使用draw_networkx
如果它们带来太多麻烦,我并不介意绘制单独的轴。

将要绘制的轴对象作为 ax 关键字参数传递给 draw_networkx - tacaswell
我对matplotlib还很陌生。我可以直接在主图上进行轴的修改,但是我不明白如何使用ax来绘制我所描述的2个网络。此外,这两个网络之间还有连接线。 - Agostino
1个回答

5
我发现调整Matplotlib以产生精确比例需要一些微调。通常需要进行一些简单的计算来确定所需的比率(例如,您希望每个子图的高度为其宽度的1.25倍,根据您的测量结果)。至于PDF未遵守图形大小,可能是因为您正在指定DPI。没有必要指定DPI,因为PDF保存在矢量格式中。或者您正在使用plt.tight_layout函数,这可能很有用,但会改变图形大小。在子图之间绘制线条有点困难。首先,您必须使用transformations从轴坐标系转换为图形坐标系。然后,您可以直接将线条绘制到图形上。此帖子可能有所帮助。下面的代码应该有所帮助。您需要以各种方式进行微调,但希望它能帮助您生成所需的图形。
import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.lines as lines

# Generate some random graphs and positions for demonstrative purposes.
G1 = nx.gnp_random_graph(10, 0.2)
G2 = nx.gnp_random_graph(10, 0.2)
pos1 = nx.spring_layout(G1)
pos2 = nx.spring_layout(G2)

# Set up the figure with two subplots and a figure size of 6.5 by 4 inches.
fig, ax = plt.subplots(1, 2, figsize=(6.5, 4))

# Set the x and y limits for each axis, leaving a 0.2 margin te ensure
# that nodes near the edges of the graph are not clipped.
ax[0].set_xlim([-0.2, 1.2])
ax[0].set_ylim([-0.2, 1.2])
ax[1].set_xlim([-0.2, 1.2])
ax[1].set_ylim([-0.2, 1.2])

# Stretch the subplots up, so that each unit in the y direction appears 1.25
# times taller than the width of each unit in the x direction.
ax[0].set_aspect(1.25)
ax[1].set_aspect(1.25)

# Remove the tick labels from the axes.
ax[0].xaxis.set_visible(False)
ax[0].yaxis.set_visible(False)
ax[1].xaxis.set_visible(False)
ax[1].yaxis.set_visible(False)

# Set the space between the subplots to be 0.2 times their width.
# Also reduce the margins of the figure.
plt.subplots_adjust(wspace=0.2, left=0.05, right=0.95, bottom=0.05, top=0.95)

# Draw the networks in each subplot
nx.draw_networkx(G1, pos1, ax=ax[0], node_color='r')
nx.draw_networkx(G2, pos2, ax=ax[1], node_color='b')

# Now suppose we want to draw a line between nodes 5 in each subplot. First, we need to
# be able to convert from the axes coordinates to the figure coordinates, like so.
# (Read the matplotlib transformations documentation for more detail).
def ax_to_fig(coordinates, axis):
    transFig = fig.transFigure.inverted()
    return transFig.transform((axis.transData.transform((coordinates))))

# Now we can get the figure coordinates for the nodes we want to connect.
line_start = ax_to_fig(pos1[5], ax[0])
line_end = ax_to_fig(pos2[5], ax[1])

# Create the line and draw it on the figure.
line = lines.Line2D((line_start[0], line_end[0]), (line_start[1], line_end[1]), transform=fig.transFigure)
fig.lines = [line]

# Save the figure.
plt.savefig('test_networks.pdf', format='pdf')

编辑

以上代码似乎不能在相应节点的中心之间准确绘制坐标轴之间的线条。删除ax.set_aspect函数可以解决此问题,但现在比例不正确。为了适应这一点,您可以手动更改节点的y位置(这比NetworkX应该做的要困难得多)。接下来,您将不得不更改ax.set_ymin值以获得正确的比例,如下所示:

# Manually rescale the positions of the nodes in the y-direction only.
for node in pos1:
    pos1[node][1] *= 1.35
for node in pos2:
    pos2[node][1] *= 1.35

# Set the x and y limits for each axis, leaving a 0.2 margin te ensure
# that nodes near the edges of the graph are not clipped.
ax[0].set_xlim([-0.2, 1.2])
ax[0].set_ylim([-0.2, 1.55])
ax[1].set_xlim([-0.2, 1.2])
ax[1].set_ylim([-0.2, 1.55])

谢谢。这些图看起来很好,但是它们之间的线条没有命中节点的正中心。当我注释掉ax[...].set_aspect(...)这些行时,我看到中心点被完美地连接在一起,而将宽高比改为0.8则会使情况变得更糟。也许有一个错误? - Agostino
我已经编辑了帖子以解决问题。解决方案有点繁琐。可能有更简单的方法,但至少它能够工作。 - McMath
谢谢。你可能是指 1.35,或者更一般地说,是 y_scale。我还会创建一个 margin 变量来保存 0.2。然而,这种解决方案无法正确标记轴。也许有一种方法可以绕过这个问题并绘制虚假的轴?我猜测绘制两个带有正确轴的空子图,而不清除图形,可能会起作用。 - Agostino
我觉得我不太清楚你尝试做什么。也许你可以编辑你的问题来包含你的代码。否则,我想知道一些事情可能会有帮助:1)你需要显示刻度标签吗?2)你是手动生成节点位置还是让NetworkX为你生成?3)节点的位置是否重要,或者它们是任意的? - McMath
更新的问题。感谢您的关注。拥有轴刻度将是很好的。 - Agostino

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接