Python, QT and matplotlib scatter plots with blitting
<p>I am trying to animate a scatter plot (it needs to be a scatter plot as I want to vary the circle sizes). I have gotten the matplotlib documentation tutorial <a href="http://matplotlib.org/examples/animation/rain.html" rel="nofollow">matplotlib documentation tutorial </a> to work in my PyQT application, but would like to introduce blitting into the equation as my application will likely run on slower machines where the animation may not be as smooth.</p>

<p>I have had a look at many examples of animations with blitting, but none ever use a scatter plot (they use plot or lines) and so I am really struggling to figure out how to initialise the animation (the bits that don't get re-rendered every time) and the ones that do. I have tried quite a few things, and seem to be getting nowhere (and I am sure they would cause more confusion than help!). I assume that I have missed something fairly fundamental. Has anyone done this before? Could anyone help me out splitting the figure into the parts that need to be initiated and the ones that get updates?</p>

<p>The code below works, but does not blit. Appending </p>

<pre><code>blit=True
</code></pre>

<p>to the end of the animation call yields the following error:</p>

<pre><code>RuntimeError: The animation function must return a sequence of Artist objects.
</code></pre>

<p>Any help would be great.</p>

<p>Regards </p>

<p>FP</p>

<pre><code>import numpy as np
from PyQt4 import QtGui, uic
import sys
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.setupAnim()

        self.show()

    def setupAnim(self):
        self.fig = plt.figure(figsize=(7, 7))
        self.ax = self.fig.add_axes([0, 0, 1, 1], frameon=False)
        self.ax.set_xlim(0, 1), self.ax.set_xticks([])
        self.ax.set_ylim(0, 1), self.ax.set_yticks([])

        # Create rain data
        self.n_drops = 50
        self.rain_drops = np.zeros(self.n_drops, dtype=[('position', float, 2),
                                              ('size',     float, 1),
                                              ('growth',   float, 1),
                                              ('color',    float, 4)])

        # Initialize the raindrops in random positions and with
        # random growth rates.
        self.rain_drops['position'] = np.random.uniform(0, 1, (self.n_drops, 2))
        self.rain_drops['growth'] = np.random.uniform(50, 200, self.n_drops)

        # Construct the scatter which we will update during animation
        # as the raindrops develop.
        self.scat = self.ax.scatter(self.rain_drops['position'][:, 0], self.rain_drops['position'][:, 1],
                          s=self.rain_drops['size'], lw=0.5, edgecolors=self.rain_drops['color'],
                          facecolors='none')

        self.animation = FuncAnimation(self.fig, self.update, interval=10)
        plt.show()

    def update(self, frame_number):
        # Get an index which we can use to re-spawn the oldest raindrop.
        self.current_index = frame_number % self.n_drops

        # Make all colors more transparent as time progresses.
        self.rain_drops['color'][:, 3] -= 1.0/len(self.rain_drops)
        self.rain_drops['color'][:, 3] = np.clip(self.rain_drops['color'][:, 3], 0, 1)

        # Make all circles bigger.
        self.rain_drops['size'] += self.rain_drops['growth']

        # Pick a new position for oldest rain drop, resetting its size,
        # color and growth factor.
        self.rain_drops['position'][self.current_index] = np.random.uniform(0, 1, 2)
        self.rain_drops['size'][self.current_index] = 5
        self.rain_drops['color'][self.current_index] = (0, 0, 0, 1)
        self.rain_drops['growth'][self.current_index] = np.random.uniform(50, 200)

        # Update the scatter collection, with the new colors, sizes and positions.
        self.scat.set_edgecolors(self.rain_drops['color'])
        self.scat.set_sizes(self.rain_drops['size'])
        self.scat.set_offsets(self.rain_drops['position'])

if __name__== '__main__':
    app = QtGui.QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec_())
</code></pre>

<p>You need to add <code>return self.scat,</code> at the end of the <code>update</code> method if you want to use <code>FuncAnimation</code> with <code>blit=True</code>. See also this nice <a href="http://stackoverflow.com/a/9416663/4481445">StackOverflow  post</a> that presents an example of a scatter plot animation with matplotlib using blit.</p>

<p>As a side-note, if you wish to embed a mpl figure in a Qt application, it is better to avoid using the pyplot interface and to use instead the Object Oriented API of mpl as suggested in the <a href="http://matplotlib.org/examples/user_interfaces/embedding_in_qt4.html" rel="nofollow">matplotlib documentation</a>.</p>

<p>This could be achieved, for example, as below, where <code>mplWidget</code> can be embedded as any other Qt widget in your main application. Note that I renamed the <code>update</code> method to <code>update_plot</code> to avoid conflict with the already existing method of the <code>FigureCanvasQTAgg</code> class.</p>

<pre><code>import numpy as np
from PyQt4 import QtGui
import sys
import matplotlib as mpl
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt

class mplWidget(FigureCanvasQTAgg):
    def __init__(self):
        super(mplWidget, self).__init__(mpl.figure.Figure(figsize=(7, 7)))

        self.setupAnim()
        self.show()

    def setupAnim(self):
        ax = self.figure.add_axes([0, 0, 1, 1], frameon=False)
        ax.axis([0, 1, 0, 1])
        ax.axis('off')

        # Create rain data
        self.n_drops = 50
        self.rain_drops = np.zeros(self.n_drops, dtype=[('position', float, 2),
                                                        ('size',     float, 1),
                                                        ('growth',   float, 1),
                                                        ('color',    float, 4)
                                                        ])

        # Initialize the raindrops in random positions and with
        # random growth rates.
        self.rain_drops['position'] = np.random.uniform(0, 1, (self.n_drops, 2))
        self.rain_drops['growth'] = np.random.uniform(50, 200, self.n_drops)

        # Construct the scatter which we will update during animation
        # as the raindrops develop.
        self.scat = ax.scatter(self.rain_drops['position'][:, 0],
                               self.rain_drops['position'][:, 1],
                               s=self.rain_drops['size'],
                               lw=0.5, facecolors='none',
                               edgecolors=self.rain_drops['color'])

        self.animation = FuncAnimation(self.figure, self.update_plot,
                                       interval=10, blit=True)

    def update_plot(self, frame_number):
        # Get an index which we can use to re-spawn the oldest raindrop.
        indx = frame_number % self.n_drops

        # Make all colors more transparent as time progresses.
        self.rain_drops['color'][:, 3] -= 1./len(self.rain_drops)
        self.rain_drops['color'][:, 3] = np.clip(self.rain_drops['color'][:, 3], 0, 1)

        # Make all circles bigger.
        self.rain_drops['size'] += self.rain_drops['growth']

        # Pick a new position for oldest rain drop, resetting its size,
        # color and growth factor.
        self.rain_drops['position'][indx] = np.random.uniform(0, 1, 2)
        self.rain_drops['size'][indx] = 5
        self.rain_drops['color'][indx] = (0, 0, 0, 1)
        self.rain_drops['growth'][indx] = np.random.uniform(50, 200)

        # Update the scatter collection, with the new colors,
        # sizes and positions.
        self.scat.set_edgecolors(self.rain_drops['color'])
        self.scat.set_sizes(self.rain_drops['size'])
        self.scat.set_offsets(self.rain_drops['position'])

        return self.scat,


if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    window = mplWidget()
    sys.exit(app.exec_())
</code></pre>

python qt matplotlib scatter-plot blit