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

时间:2015-03-18 18:13:21

标签: python matplotlib networkx

如果您知道如何使用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

然后我必须在2个图之间画线,所以我需要一些方法来回到每个图中的位置,以便连接它们。

像这样生成,存储和分配节点位置

# 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]
  • 以相同的方式计算第二个网络的位置
  • 将第二个网络的位置向右移动,将area_size * 0.8 + area_size * 0.16加到x坐标
  • 设置图形尺寸(以英寸为单位)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用于绘制图表的函数为these,特别是我正在使用draw_networkx

如果他们给出太多麻烦,我真的不介意画出单独的轴。

1 个答案:

答案 0 :(得分:4)

我发现要让Matplotlib产生精确的比例需要一些调整。通常它需要做一些简单的计算来计算出你想要的比例(例如你希望每个子图的高度是你的宽度的1.25)。

对于不考虑图形大小的PDF,可能是因为您指定了DPI。由于PDF以矢量格式保存,因此无需指定DPI。或者您可能正在使用plt.tight_layout函数,这可能很有用,但会改变图形大小。

在子图之间绘制线条有点像噩梦。首先,您必须使用transformations从轴坐标系转换为图形坐标系。然后你可以直接在图上画线。 This post可能有帮助。

以下代码应该有所帮助。你需要以各种方式调整它,但希望它能帮助你产生你想要的数字。

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])