Matplotlib:交互式绘图

日期2016-01-15(最后修改),2006-01-22(创建)

交互式点识别

我发现能够通过简单地点击来识别绘图中的点非常有用。此食谱提供了一个相当简单的 函数对象,它可以连接到任何绘图。我已经在散点图和标准图中使用过它。

因为您通常会拥有跨越多个图形或至少多个轴的多个数据集视图,所以我还提供了一个实用程序来将这些绘图链接在一起,以便单击一个绘图中的点将突出显示并识别所有其他链接绘图中的该数据点。

在 [1]
import math

import matplotlib.pyplot as plt

class AnnoteFinder(object):
    """callback for matplotlib to display an annotation when points are
    clicked on.  The point which is closest to the click and within
    xtol and ytol is identified.
    
    Register this function like this:
    
    scatter(xdata, ydata)
    af = AnnoteFinder(xdata, ydata, annotes)
    connect('button_press_event', af)
    """

    def __init__(self, xdata, ydata, annotes, ax=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 ax is None:
            self.ax = plt.gca()
        else:
            self.ax = ax
        self.drawnAnnotations = {}
        self.links = []

    def distance(self, x1, x2, y1, y2):
        """
        return the distance between two points
        """
        return(math.sqrt((x1 - x2)**2 + (y1 - y2)**2))

    def __call__(self, event):

        if event.inaxes:

            clickX = event.xdata
            clickY = event.ydata
            if (self.ax is None) or (self.ax is event.inaxes):
                annotes = []
                # print(event.xdata, event.ydata)
                for x, y, a in self.data:
                    # print(x, y, a)
                    if ((clickX-self.xtol < x < clickX+self.xtol) and
                            (clickY-self.ytol < y < clickY+self.ytol)):
                        annotes.append(
                            (self.distance(x, clickX, y, clickY), x, y, a))
                if annotes:
                    annotes.sort()
                    distance, x, y, annote = annotes[0]
                    self.drawAnnote(event.inaxes, x, y, annote)
                    for l in self.links:
                        l.drawSpecificAnnote(annote)

    def drawAnnote(self, ax, x, y, annote):
        """
        Draw the annotation on the plot
        """
        if (x, y) in self.drawnAnnotations:
            markers = self.drawnAnnotations[(x, y)]
            for m in markers:
                m.set_visible(not m.get_visible())
            self.ax.figure.canvas.draw_idle()
        else:
            t = ax.text(x, y, " - %s" % (annote),)
            m = ax.scatter([x], [y], marker='d', c='r', zorder=100)
            self.drawnAnnotations[(x, y)] = (t, m)
            self.ax.figure.canvas.draw_idle()

    def drawSpecificAnnote(self, annote):
        annotesToDraw = [(x, y, a) for x, y, a in self.data if a == annote]
        for x, y, a in annotesToDraw:
            self.drawAnnote(self.ax, x, y, a)

要使用此函数对象,您可以简单地执行以下操作

在 [ ]
x = range(10)
y = range(10)
annotes = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

fig, ax = plt.subplots()
ax.scatter(x,y)
af =  AnnoteFinder(x,y, annotes, ax=ax)
fig.canvas.mpl_connect('button_press_event', af)
plt.show()

这相当有用,但有时你会有一个数据集的多个视图,并且点击一个图中的点并在另一个图中找到它会很有用。下面的代码演示了这种链接,并且应该在多个轴或图形之间工作。

在 [ ]
def linkAnnotationFinders(afs):
  for i in range(len(afs)):
    allButSelfAfs = afs[:i]+afs[i+1:]
    afs[i].links.extend(allButSelfAfs)

subplot(121)
scatter(x,y)
af1 = AnnoteFinder(x,y, annotes)
connect('button_press_event', af1)

subplot(122)
scatter(x,y)
af2 = AnnoteFinder(x,y, annotes)
connect('button_press_event', af2)

linkAnnotationFinders([af1, af2])

我发现这相当有用。通过子类化和重新定义 drawAnnote,这个简单的框架可以用来驱动更复杂的用户界面。

目前,当数据点的数量变大时,这个实现有点慢。我特别感兴趣的是人们可能提出的让它更快更好的建议。


在缩放时处理点击事件

通常,你不想在缩放或平移时响应点击事件(使用工具栏模式按钮选择)。你可以通过检查工具栏实例的属性来避免响应这些事件。下面的第一个示例展示了如何使用 pylab 接口来实现这一点。

在 [ ]
from pylab import *

def click(event):
   """If the left mouse button is pressed: draw a little square. """
   tb = get_current_fig_manager().toolbar
   if event.button==1 and event.inaxes and tb.mode == '':
       x,y = event.xdata,event.ydata
       plot([x],[y],'rs')
       draw()

plot((arange(100)/99.0)**3)
gca().set_autoscale_on(False)
connect('button_press_event',click)
show()

如果你的应用程序位于 wxPython 窗口中,那么你很有可能在设置过程中创建了一个工具栏的句柄,如 embedding_in_wx2.py 示例脚本的 {{{add_toolbar}}} 方法所示,然后你可以在你的点击处理方法中访问该对象的 {{{mode}}} 属性(在本例中为 self.toolbar.mode)。

章节作者:AndrewStraw、GaelVaroquaux、Unknown[99]、AngusMcMorland、newacct、danielboone