如何通过networkx制作可点击的图表?

6
我正在尝试在Python中生成可点击的图像。一开始我直接调用了Graphviz,后来发现了NetworkX http://networkx.lanl.gov
我希望我的程序能够获取有关用户单击图形的(x,y)坐标处显示哪个节点的信息。我猜我可以使用鼠标单击时的(x,y)坐标与打开并显示图形的pyplot窗口进行交互,但我需要一种图像映射来确定已在该坐标处可视化的节点!
你能告诉我如何完成吗?
3个回答

3
我在http://groups.google.com/group/networkx-discuss上得到了热心人的帮助,解决了这个问题(http://groups.google.com/group/networkx-discuss/browse_thread/thread/aac227e1fb2a4719)。以下是部分代码,它可以在Tkinter中工作,并允许创建一个包含networkx图形的matplotlib窗口(顺便说一下,是非阻塞的),并且如果单击给定节点,则执行visitNode()过程。
import networkx as nx 
import matplotlib.pyplot as plt 
import pylab

class AnnoteFinder:  # thanks to http://www.scipy.org/Cookbook/Matplotlib/Interactive_Plotting
    """
    callback for matplotlib to visit a node (display an annotation) when points are clicked on.  The
    point which is closest to the click and within xtol and ytol is identified.
    """
    def __init__(self, xdata, ydata, annotes, axis=None, xtol=None, ytol=None):
        self.data = zip(xdata, ydata, annotes)
        if xtol is None: xtol = ((max(xdata) - min(xdata))/float(len(xdata)))/2
        if ytol is None: ytol = ((max(ydata) - min(ydata))/float(len(ydata)))/2
        self.xtol = xtol
        self.ytol = ytol
        if axis is None: axis = pylab.gca()
        self.axis= axis
        self.drawnAnnotations = {}
        self.links = []

    def __call__(self, event):
        if event.inaxes:
            clickX = event.xdata
            clickY = event.ydata
            if self.axis is None or self.axis==event.inaxes:
                annotes = []
                for x,y,a in self.data:
                    if  clickX-self.xtol < x < clickX+self.xtol and  clickY-self.ytol < y < clickY+self.ytol :
                        dx,dy=x-clickX,y-clickY
                        annotes.append((dx*dx+dy*dy,x,y, a) )
                if annotes:
                    annotes.sort() # to select the nearest node
                    distance, x, y, annote = annotes[0]
                    self.visitNode(annote)

    def visitNode(self, annote): # Visit the selected node
        # do something with the annote value
        print "visitNode", annote

fig = plt.figure() ax = fig.add_subplot(111) ax.set_title('select nodes to navigate there')

G=nx.MultiDiGraph()  # directed graph G = nx.wheel_graph(5)

pos=nx.spring_layout(G) # the layout gives us the nodes position x,y,annotes=[],[],[] for key in pos:
    d=pos[key]
    annotes.append(key)
    x.append(d[0])
    y.append(d[1]) nx.draw(G,pos,font_size=8)

af =  AnnoteFinder(x,y, annotes) fig.canvas.mpl_connect('button_press_event', af)

plt.show()

4
抱歉,贴上不完整/不可用的代码,含有未定义名称和错误缩进,这样做有什么意义呢?除了你自己,它能帮助其他人吗? - minerals
@minerals 我已经提交了一份编辑来纠正你提到的问题。 - Marc Van Daele

1
我发现@minerals发布的解决方案中有几个不完整的代码片段。在这里,我将发布一个适用于python3的工作代码片段。
import networkx as nx
import matplotlib.pyplot as plt
import pandas as pd
from pylab import *

class AnnoteFinder:  # thanks to http://www.scipy.org/Cookbook/Matplotlib/Interactive_Plotting
    """
    callback for matplotlib to visit a node (display an annotation) when points are clicked on.  The
    point which is closest to the click and within xtol and ytol is identified.
    """
    def __init__(self, xdata, ydata, annotes, axis=None, xtol=None, ytol=None):
        self.data = list(zip(xdata, ydata, annotes))
        if xtol is None: xtol = ((max(xdata) - min(xdata))/float(len(xdata)))/2
        if ytol is None: ytol = ((max(ydata) - min(ydata))/float(len(ydata)))/2
        self.xtol = xtol
        self.ytol = ytol
        if axis is None: axis = gca()
        self.axis= axis
        self.drawnAnnotations = {}
        self.links = []

    def __call__(self, event):
        if event.inaxes:
            clickX = event.xdata
            clickY = event.ydata
            print(dir(event),event.key)
            if self.axis is None or self.axis==event.inaxes:
                annotes = []
                smallest_x_dist = float('inf')
                smallest_y_dist = float('inf')

                for x,y,a in self.data:
                    if abs(clickX-x)<=smallest_x_dist and abs(clickY-y)<=smallest_y_dist :
                        dx, dy = x - clickX, y - clickY
                        annotes.append((dx*dx+dy*dy,x,y, a) )
                        smallest_x_dist=abs(clickX-x)
                        smallest_y_dist=abs(clickY-y)
                        print(annotes,'annotate')
                    # if  clickX-self.xtol < x < clickX+self.xtol and  clickY-self.ytol < y < clickY+self.ytol :
                    #     dx,dy=x-clickX,y-clickY
                    #     annotes.append((dx*dx+dy*dy,x,y, a) )
                print(annotes,clickX,clickY,self.xtol,self.ytol )
                if annotes:
                    annotes.sort() # to select the nearest node
                    distance, x, y, annote = annotes[0]
                    self.drawAnnote(event.inaxes, x, y, annote)

    def drawAnnote(self, axis, x, y, annote):
        if (x, y) in self.drawnAnnotations:
            markers = self.drawnAnnotations[(x, y)]
            for m in markers:
                m.set_visible(not m.get_visible())
            self.axis.figure.canvas.draw()
        else:
            t = axis.text(x, y, "%s" % (annote), )
            m = axis.scatter([x], [y], marker='d', c='r', zorder=100)
            self.drawnAnnotations[(x, y)] = (t, m)
            self.axis.figure.canvas.draw()


df = pd.DataFrame("LOAD YOUR DATA")

# Build your graph
G = nx.from_pandas_edgelist(df, 'from', 'to')
pos = nx.spring_layout(G,k=0.1, iterations=20)  # the layout gives us the nodes position x,y,annotes=[],[],[] for key in pos:
x, y, annotes = [], [], []
for key in pos:
    d = pos[key]
    annotes.append(key)
    x.append(d[0])
    y.append(d[1])

fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
ax.set_title('select nodes to navigate there')

nx.draw(G, pos, font_size=6,node_color='#A0CBE2', edge_color='#BB0000', width=0.1,
                  node_size=2,with_labels=True)


af = AnnoteFinder(x, y, annotes)
connect('button_press_event', af)

show()

0

[alessandro的回答更新]

这是针对Python 3更新的alessandro的回答。

请注意,不建议使用pylab。

import networkx as nx
import matplotlib.pyplot as plt
import pylab


class AnnoteFinder:  # thanks to http://www.scipy.org/Cookbook/Matplotlib/Interactive_Plotting
    """
    callback for matplotlib to visit a node (display an annotation) when points are clicked on.  The
    point which is closest to the click and within xtol and ytol is identified.
    """

    def __init__(self, xdata, ydata, annotes2, axis=None, xtol=None, ytol=None):
        self.data = zip(xdata, ydata, annotes2)
        if xtol is None:
            xtol = ((max(xdata) - min(xdata)) / float(len(xdata))) / 2
        if ytol is None:
            ytol = ((max(ydata) - min(ydata)) / float(len(ydata))) / 2
        self.xtol = xtol
        self.ytol = ytol
        if axis is None:
            axis = pylab.gca()
        self.axis = axis
        self.drawnAnnotations = {}
        self.links = []

    def __call__(self, event):
        if event.inaxes:
            clickX = event.xdata
            clickY = event.ydata
            if self.axis is None or self.axis == event.inaxes:
                annotes2 = []
                for x2, y2, a in self.data:
                    if clickX - self.xtol < x2 < clickX + self.xtol and clickY - self.ytol < y2 < clickY + self.ytol:
                        dx, dy = x2 - clickX, y2 - clickY
                        annotes2.append((dx * dx + dy * dy, x2, y2, a))
                if annotes2:
                    annotes2.sort()  # to select the nearest node
                    distance, x2, y2, annote = annotes2[0]
                    self.visitnode(annote)

    def visitNode(self, annote):  # Visit the selected node
        # do something with the annote value
        print("visitNode", annote)


fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('select nodes to navigate there')

G = nx.MultiDiGraph()  # directed graph G = nx.wheel_graph(5)

pos = nx.spring_layout(G)  # the layout gives us the nodes position
x, y, annotes = [], [], []
for key in pos:
    d = pos[key]
    annotes.append(key)
    x.append(d[0])
    y.append(d[1])
    nx.draw(G, pos, font_size=8)

af = AnnoteFinder(x, y, annotes)
fig.canvas.mpl_connect('button_press_event', af)

plt.show()


为什么不推荐使用pylab? - Lou

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