【问题标题】:Matplotlib + NetworkX - draw 2 networks in a single graphicMatplotlib + NetworkX - 在单个图形中绘制 2 个网络
【发布时间】:2015-03-18 18:13:20
【问题描述】:

如果你知道如何使用 Matplotlib,你可能不知道 NetworkX 也能回答。

我有 2 个要使用 NetworkX 绘制的网络,我想在一个图形中并排绘制,显示每个网络的轴。本质上,这是在 Matplotlib(NetworkX 用来绘制图形的库)中创建 2 个子图的问题。

网络中每个节点的位置分布在[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)。

然后我必须在 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。

我真的不介意绘制单独的轴,如果它们太麻烦的话。

【问题讨论】:

  • 将要绘制的坐标区对象作为ax kwarg 传递给draw_networkx。
  • 我对 matplotlib 还是很陌生。我可以直接在主图上修改轴,但我不明白如何使用ax 来绘制我描述的两个网络。另外,两个网之间还有线。

标签: python matplotlib networkx


【解决方案1】:

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

至于 PDF 不考虑图形大小,可能是因为您指定了 DPI。无需指定 DPI,因为 PDF 以矢量格式保存。或者您可能正在使用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])

【讨论】:

  • 谢谢。这些数字看起来不错,但是它们之间的线没有碰到节点的确切中心。评论ax[...].set_aspect(...) 行我看到中心完美连接,而将外观更改为0.8 会使情况变得更糟。也许有错误?
  • 我已编辑帖子以解决问题。解决方案有点繁琐。可能有一种更简单的处理方式,但至少它有效。
  • 谢谢。您的意思可能是1.35,或更一般地说,y_scale。我还会创建一个margin 变量来保存0.2。但是,此解决方案不允许正确标记轴。也许有办法绕过这个并绘制假轴?我猜想在不清除图形的情况下用正确的轴绘制 2 个空子图可以工作。
  • 我想我有点不清楚你想要做什么。也许您可以编辑您的问题以包含您的代码。否则,我认为了解一些事情会有所帮助:1)您需要显示刻度标签吗? 2)您是手动生成节点位置还是让 NetworkX 为您完成?和 3) 节点的位置是否重要,或者它们是任意的?
  • 更新问题。感谢您的关注。轴刻度会很好。
猜你喜欢
  • 2012-03-13
  • 2013-10-13
  • 1970-01-01
  • 1970-01-01
  • 2013-08-03
  • 2017-01-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多