简体   繁体   English

绘制 networkx.Graph:如何更改节点位置而不是重置每个节点?

[英]Plotting networkx.Graph: how to change node position instead of resetting every node?

I'm working on a project where I need to create a preview of nx.Graph() which allows to change position of nodes dragging them with a mouse.我正在做一个项目,我需要创建一个nx.Graph()的预览,它允许改变节点的位置,用鼠标拖动它们。 My current code is able to redraw whole figure immediately after each motion of mouse if it's clicked on specific node.如果单击特定节点,我当前的代码能够在鼠标每次移动后立即重绘整个图形。 However, this increases latency significantly.但是,这会显着增加延迟。 How can I update only artists needed, it is, clicked node, its label text and adjacent edges instead of refreshing every artist of plt.subplots() ?如何仅更新需要的艺术家,即单击节点、其标签文本和相邻边缘,而不是刷新plt.subplots()每个艺术家? Can I at least get a reference to all the artists that need to be relocated?我至少可以得到所有需要搬迁的艺术家的参考吗?

I started from a standard way of displaying a graph in networkx :我从在networkx中显示图形的标准方式开始:

import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
import scipy.spatial

def refresh(G):
    plt.axis((-4, 4, -1, 3))
    nx.draw_networkx_labels(G, pos = nx.get_node_attributes(G, 'pos'),
                                bbox = dict(fc="lightgreen", ec="black", boxstyle="square", lw=3))
    nx.draw_networkx_edges(G, pos = nx.get_node_attributes(G, 'pos'), width=1.0, alpha=0.5)
    plt.show()

nodes = np.array(['A', 'B', 'C', 'D', 'E', 'F', 'G'])
edges = np.array([['A', 'B'], ['A', 'C'], ['B', 'D'], ['B', 'E'], ['C', 'F'], ['C', 'G']])
pos = np.array([[0, 0], [-2, 1], [2, 1], [-3, 2], [-1, 2], [1, 2], [3, 2]])

G = nx.Graph()
# IG = InteractiveGraph(G) #>>>>> add this line in the next step
G.add_nodes_from(nodes)
G.add_edges_from(edges)
nx.set_node_attributes(G, dict(zip(G.nodes(), pos.astype(float))), 'pos')

fig, ax = plt.subplots()
# fig.canvas.mpl_connect('button_press_event', lambda event: IG.on_press(event))
# fig.canvas.mpl_connect('motion_notify_event', lambda event: IG.on_motion(event))
# fig.canvas.mpl_connect('button_release_event', lambda event: IG.on_release(event))
refresh(G) # >>>>> replace it with IG.refresh() in the next step

在此处输入图片说明

In the next step I changed 5 line of previous script (4 is uncommented and 1 replaced) plus used InteractiveGraph instance to make it interactive:在下一步中,我更改了前一个脚本的 5 行(4 行未注释,1 行被替换)加上使用InteractiveGraph实例使其具有交互性:

class InteractiveGraph:
    def __init__(self, G, node_pressed=None, xydata=None):
        self.G = G
        self.node_pressed = node_pressed
        self.xydata = xydata

    def refresh(self, show=True):
        plt.clf()
        nx.draw_networkx_labels(self.G, pos = nx.get_node_attributes(self.G, 'pos'),
                                bbox = dict(fc="lightgreen", ec="black", boxstyle="square", lw=3))
        nx.draw_networkx_edges(self.G, pos = nx.get_node_attributes(self.G, 'pos'), width=1.0, alpha=0.5)
        plt.axis('off')
        plt.axis((-4, 4, -1, 3))
        fig.patch.set_facecolor('white')
        if show:
            plt.show()

    def on_press(self, event):
        if event.inaxes is not None and len(self.G.nodes()) > 0:
            nodelist, coords = zip(*nx.get_node_attributes(self.G, 'pos').items())
            kdtree = scipy.spatial.KDTree(coords)
            self.xydata = np.array([event.xdata, event.ydata])
            close_idx = kdtree.query_ball_point(self.xydata, np.sqrt(0.1))
            i = close_idx[0]
            self.node_pressed = nodelist[i]

    def on_motion(self, event):
        if event.inaxes is not None and self.node_pressed:
            new_xydata = np.array([event.xdata, event.ydata])
            self.xydata += new_xydata - self.xydata
            #print(d_xy, self.G.nodes[self.node_pressed])
            self.G.nodes[self.node_pressed]['pos'] = self.xydata
            self.refresh(show=False)
            event.canvas.draw()

    def on_release(self, event):
        self.node_pressed = None

在此处输入图片说明

Related sources:相关来源:

To expand on my comment above, in netgraph , your example can be reproduced with为了扩展我上面的评论,在netgraph ,您的示例可以使用

import numpy as np
import matplotlib.pyplot as plt; plt.ion()
import networkx as nx
import netgraph

nodes = np.array(['A', 'B', 'C', 'D', 'E', 'F', 'G'])
edges = np.array([['A', 'B'], ['A', 'C'], ['B', 'D'], ['B', 'E'], ['C', 'F'], ['C', 'G']])
pos = np.array([[0, 0], [-2, 1], [2, 1], [-3, 2], [-1, 2], [1, 2], [3, 2]])

G = nx.Graph()
G.add_nodes_from(nodes)
G.add_edges_from(edges)

I = netgraph.InteractiveGraph(G,
                              node_positions=dict(zip(nodes, pos)),
                              node_labels=dict(zip(nodes,nodes)),
                              node_label_bbox=dict(fc="lightgreen", ec="black", boxstyle="square", lw=3),
                              node_size=12,
)

# move stuff with mouse

在此处输入图片说明


Regarding the code you wrote, the kd-tree is unnecessary if you have handles of all the artists.关于您编写​​的代码,如果您拥有所有艺术家的句柄,则不需要 kd-tree。 In general, matplotlib artists have a contains method, such that when you log button press events, you can simply check artist.contains(event) to find out if the button press occurred over the artist.一般来说,matplotlib 艺术家有一个contains方法,这样当您记录按钮按下事件时,您可以简单地检查artist.contains(event)以找出按钮按下是否发生在艺术家artist.contains(event) Of course, if you use networkx to do the plotting, you can't get the handles in a nice, query-able form ( ax.get_children() is neither) so that is not possible.当然,如果您使用 networkx 进行绘图,则无法以漂亮的、可查询的形式获得句柄( ax.get_children()两者都不是),因此这是不可能的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM