高效的Matplotlib重绘

发布于 2021-01-29 15:26:16

我正在使用Matplotlib允许用户使用鼠标单击来选择有趣的数据点,使用与此答案非常相似的方法

有效地,散点图显示在热图图像上,单击鼠标可以添加或删除散点。

我的数据是使用绘制在背景中的pcolormesh(),所以当我使用axis.figure.canvas.draw()分散点和背景热图更新画布时,都会重新绘制。给定热图的大小,这对于可用的界面来说太慢了。

有没有一种方法可以只重绘散点 而不 重绘背景?

示例代码:

points = []  # Holds a list of (x,y) scatter points

def onclick(event):
    # Click event handler to add points
    points.append( (event.x, event.y) )
    ax.figure.canvas.draw()

fig = plt.figure()
ax = plt.figure()
# Plot the background
ax.pcolormesh(heatmap_data)

# Add the listener to handle clicks
cid = fig.canvas.mpl_connect("button_press_event", onclick)
plt.show()
关注者
0
被浏览
205
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    当然!你想要的是发短信。如果您不是在编写gui,则可以使用来简化其中的一些操作matplotlib.animation,但是如果您希望事物是交互式的,则需要直接处理它。

    在matplotlib而言,想要的组合fig.canvas.copy_from_bbox,然后交替拨打fig.canvas.restore_region(background)ax.draw_artist(what_you_want_to_draw)并且fig.canvas.blit

    background = fig.canvas.copy_from_bbox(ax.bbox)
    
    for x, y in user_interactions:
        fig.canvas.restore_region(background)
        points.append([x, y])
        scatter.set_offsets(points)
        ax.draw_artist(scatter)
        fig.canvas.blit(ax.bbox)
    

    简单的划线示例:添加点

    就您而言,如果仅添加点,则实际上可以跳过保存和恢复背景。但是,如果走那条路线,由于抗锯齿点会相互重复重绘,因此您会对图进行一些微妙的更改。

    无论如何,这是您想要的事物类型的最简单的示例。如上所述,这仅涉及添加点,并且跳过了保存和恢复背景的操作:

    import matplotlib.pyplot as plt
    import numpy as np
    
    def main():
        fig, ax = plt.subplots()
        ax.pcolormesh(np.random.random((100, 100)), cmap='gray')
    
        ClickToDrawPoints(ax).show()
    
    class ClickToDrawPoints(object):
        def __init__(self, ax):
            self.ax = ax
            self.fig = ax.figure
            self.xy = []
            self.points = ax.scatter([], [], s=200, color='red', picker=20)
            self.fig.canvas.mpl_connect('button_press_event', self.on_click)
    
        def on_click(self, event):
            if event.inaxes is None:
                return
            self.xy.append([event.xdata, event.ydata])
            self.points.set_offsets(self.xy)
            self.ax.draw_artist(self.points)
            self.fig.canvas.blit(self.ax.bbox)
    
        def show(self):
            plt.show()
    
    main()
    

    有时候简单太简单

    但是,假设我们要右键单击删除一个点。

    在这种情况下,我们需要能够还原背景而不重绘背景。

    好的,一切都很好。我们将使用与答案顶部提到的伪代码段相似的内容。

    但是,有一个警告:如果调整了图的大小,我们需要更新背景。同样,如果以交互方式缩放/平移轴,则需要更新背景。基本上,您需要在绘制绘图时随时更新背景。

    很快您就需要变得相当复杂。


    更复杂:添加/拖动/删除点

    这是您最终放置到位的“脚手架”类型的一般示例。

    由于绘图绘制了两次,因此效率有些低下。(例如,平移会很 )。可以解决这个问题,但是我将这些例子再留一遍。

    这实现了添加点,拖动点和删除点。要在交互式缩放/平移后添加/拖动点,请再次单击工具栏上的缩放/平移工具以将其禁用。

    这是一个相当复杂的示例,但希望它能使人们理解通常以交互方式绘制/拖动/编辑/删除matplotlib艺术家而无需重绘整个图的框架类型。

    import numpy as np
    import matplotlib.pyplot as plt
    
    class DrawDragPoints(object):
        """
        Demonstrates a basic example of the "scaffolding" you need to efficiently
        blit drawable/draggable/deleteable artists on top of a background.
        """
        def __init__(self):
            self.fig, self.ax = self.setup_axes()
            self.xy = []
            self.tolerance = 10
            self._num_clicks = 0
    
            # The artist we'll be modifying...
            self.points = self.ax.scatter([], [], s=200, color='red',
                                          picker=self.tolerance, animated=True)
    
            connect = self.fig.canvas.mpl_connect
            connect('button_press_event', self.on_click)
            self.draw_cid = connect('draw_event', self.grab_background)
    
        def setup_axes(self):
            """Setup the figure/axes and plot any background artists."""
            fig, ax = plt.subplots()
    
            # imshow would be _much_ faster in this case, but let's deliberately
            # use something slow...
            ax.pcolormesh(np.random.random((1000, 1000)), cmap='gray')
    
            ax.set_title('Left click to add/drag a point\nRight-click to delete')
            return fig, ax
    
        def on_click(self, event):
            """Decide whether to add, delete, or drag a point."""
            # If we're using a tool on the toolbar, don't add/draw a point...
            if self.fig.canvas.toolbar._active is not None:
                return
    
            contains, info = self.points.contains(event)
            if contains:
                i = info['ind'][0]
                if event.button == 1:
                    self.start_drag(i)
                elif event.button == 3:
                    self.delete_point(i)
            else:
                self.add_point(event)
    
        def update(self):
            """Update the artist for any changes to self.xy."""
            self.points.set_offsets(self.xy)
            self.blit()
    
        def add_point(self, event):
            self.xy.append([event.xdata, event.ydata])
            self.update()
    
        def delete_point(self, i):
            self.xy.pop(i)
            self.update()
    
        def start_drag(self, i):
            """Bind mouse motion to updating a particular point."""
            self.drag_i = i
            connect = self.fig.canvas.mpl_connect
            cid1 = connect('motion_notify_event', self.drag_update)
            cid2 = connect('button_release_event', self.end_drag)
            self.drag_cids = [cid1, cid2]
    
        def drag_update(self, event):
            """Update a point that's being moved interactively."""
            self.xy[self.drag_i] = [event.xdata, event.ydata]
            self.update()
    
        def end_drag(self, event):
            """End the binding of mouse motion to a particular point."""
            for cid in self.drag_cids:
                self.fig.canvas.mpl_disconnect(cid)
    
        def safe_draw(self):
            """Temporarily disconnect the draw_event callback to avoid recursion"""
            canvas = self.fig.canvas
            canvas.mpl_disconnect(self.draw_cid)
            canvas.draw()
            self.draw_cid = canvas.mpl_connect('draw_event', self.grab_background)
    
        def grab_background(self, event=None):
            """
            When the figure is resized, hide the points, draw everything,
            and update the background.
            """
            self.points.set_visible(False)
            self.safe_draw()
    
            # With most backends (e.g. TkAgg), we could grab (and refresh, in
            # self.blit) self.ax.bbox instead of self.fig.bbox, but Qt4Agg, and
            # some others, requires us to update the _full_ canvas, instead.
            self.background = self.fig.canvas.copy_from_bbox(self.fig.bbox)
    
            self.points.set_visible(True)
            self.blit()
    
        def blit(self):
            """
            Efficiently update the figure, without needing to redraw the
            "background" artists.
            """
            self.fig.canvas.restore_region(self.background)
            self.ax.draw_artist(self.points)
            self.fig.canvas.blit(self.fig.bbox)
    
        def show(self):
            plt.show()
    
    DrawDragPoints().show()
    


知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看