Python有交互式绘图库吗?

63
我正在寻找一个适用于Python的交互式绘图库。
通过 "graph",我指的是一组由一组顶点连接而成的节点(不是沿着x-y轴绘制值的图形,也不是像素网格)。
通过“交互式”,我指的是我可以拖动节点,并且需要能够单击节点/顶点,使库将节点/顶点传递给我的回调函数,该函数可能会添加/删除节点/顶点或显示信息(由于数据集过大/复杂,我不能在启动时加载完整的图形,而是根据用户输入仅加载必要的数据片段)。
通过Python,我指的是编程语言Python,绘图库应该具有CPython绑定。我有Python 2.7和Python 3.1,但如果需要,可以降级到2.6。这种语言要求是因为我正在处理的数据集只有Python绑定。
绘图库必须支持有向图并能够自动布局节点。我需要在节点上放置标签。
首选,布局算法应将相邻节点放置在彼此附近。它应该能够在我的四年老笔记本上处理100到1000个节点和大约300-4000个顶点(我通常从大约100个节点开始,但根据用户输入数量可能会增加)。最好是一个没有太多依赖关系的库(除了Gnome)。开源是首选。
我已经使用Tkinter Canvas编写了一个简单的程序原型,但我需要更严谨的图形库来扩展程序。我已经看过graphviz和matplotlib,但显然它们只用于处理静态图形,而要进行交互式操作需要大量工作(如果我错了,请纠正我,我只是简单地看了一下它们)。我还尝试将图形生成为SVG文件,并使用Inkscape查看它,但它太慢并且占用太多内存,因为顶点的数量太多,导致它变成了混乱的一团。
4个回答

32

看起来Nodebox可能是你想要的:

http://nodebox.net/code/index.php/Graph Mac OSX

http://www.cityinabottle.org/nodebox/ Windows (使用OpenGL)

Nodebox截图

graph对象还具有鼠标交互功能,捆绑 在 graph.events 对象上。它具有以下属性:

  • graph.events.hovered:None或鼠标悬停在其上的节点。
  • graph.events.pressed:None或鼠标按下的节点。
  • graph.events.dragged:None或正在拖动的节点。
  • graph.events.clicked:None或最后一次点击的节点。
  • graph.events.popup:当True时,将在 悬停节点上显示弹出窗口。

也发现了Gephi,看起来它也具有你想要的功能。

http://gephi.org/ WindowsLinuxMac OSX

Gephi是一个交互式可视化 和探索平台,适用于各种类型的网络和复杂系统, 动态和分层图。

Gephi截图


9
如果你还没有使用过,你一定要看看igraph库。这是一个强大的库,可以处理大型图形和不同的布局风格。根据features列表,它也可以用于有向图以及2D和3D的交互和非交互式可视化。此外还有tutorial更新: 另一个著名的库是NetworkX,有Python包here。请注意,Acorn推荐的Mac/Windows软件Nodebox使用NetworkX算法。

根据我所见,igraph的Python绑定似乎没有提供tkplot。 - Lie Ryan

3

我有同样的问题。 最终,我认为nodebox opengl似乎可以解决这个问题。 不要尝试使用以下链接中的图形库。

http://nodebox.net/code/index.php/Graph

使用nodebox opengl,它无法工作,该图形库仅与mac OSX nodebox兼容。但无论如何都没关系,因为你不需要它。
例如,看下面的问题: 在nodebox opnegl中为图形的边缘添加标签 它显示了对我有效的示例代码,可以修改代码,以便单击节点不仅允许您移动节点,还允许您修改图形。
只需删除

即可。
label = "Placeholder"

来自代码,它可行。

编辑:

我在这里放了一些更详细的示例代码: Nodebox Open GL Graph,大小函数无法识别。(Ubuntu)


1
我思考并尝试了这个问题中给出的所有解决方案,最终得出了以下解决方案。
我认为最好的可扩展解决方案是使用Matplotlib的交互模式和networkx。以下代码段说明了如何在鼠标单击时显示数据点的注释。由于我们使用了Networkx,这个解决方案比预期的要可扩展得多。
import networkx as nx
import matplotlib.pyplot as plt
import nx_altair as nxa
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()

请问您能否提供一个df = pd.DataFrame('LOAD YOUR DATA')的示例? - Marian Lux
“LOAD YOUR DATA” 必须是您的数据文件路径。 - Malinda
是的,我知道。但是这个数据文件必须采用哪种格式?你能否提供一个简短的例子,展示一个有两条路径(例如A->B->C和A->D->C)的有向图?这样上面的例子就可以运行并且更容易理解了。我很感激你提供这个缺失的细节的答案。 - Marian Lux

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