gongzhitaao
gongzhitaao

Reputation: 6682

LineCollection animation

For the following code (MWE), if the line

ax.set_axis_off()

is commented out, then the animation shows nothing. Otherwise, the animation works as expected. My question is: why?

from itertools import tee

import numpy as np

import matplotlib
matplotlib.use('Agg')           # noqa
from matplotlib.animation import FuncAnimation, FFMpegWriter
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
import matplotlib.pyplot as plt


def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)


def make_segs(x, y):
    xs = np.array(list(pairwise(x)))  # (n,2)
    ys = np.array(list(pairwise(y)))  # (n,2)
    segs = np.stack((xs, ys), axis=-1)  # (n,2,2)
    return segs


class _AnimationHelper():
    def __init__(self, xs, ys, z, colors=['r', 'g']):
        self.segs = np.array([make_segs(x, y) for x, y in zip(xs, ys)])
        self.z = z
        self.colors = colors
        self.fig = plt.figure()
        self.lns = []
        self.ani = FuncAnimation(
            self.fig,
            self.update,
            interval=200,
            init_func=self.init,
            frames=self.segs.shape[1],
            blit=True)

    def init(self):
        m, n = self.segs.shape[:2]
        self.fig.set_size_inches(m*5, 5)
        self.axes = self.fig.subplots(
            nrows=1, ncols=m, sharey='row')
        self.fig.tight_layout()
        cmap = ListedColormap(self.colors)
        norm = BoundaryNorm([0, 0.5, 1], cmap.N)
        zero = np.zeros(n, dtype=np.int32)
        for i in range(m):
            inds = (zero + i) == self.z
            ln = LineCollection([], cmap=cmap, norm=norm, lw=2, animated=True)
            ln.set_array(inds)
            self.lns.append(ln)

            ax = self.axes[i]
            ax.add_collection(ln)
            ax.set_xlim(0, n)
            ax.set_axis_off()  # <---- HERE

        self.axes[0].set_ylim(0, 1.1)
        for i in range(1, m):
            self.axes[i].tick_params(left=False)
        return self.lns

    def update(self, ind):
        print(ind)
        m = self.segs.shape[0]
        for i in range(m):
            self.lns[i].set_segments(self.segs[i, :(ind + 1)])
        return self.lns


N = 20
M = 3

x = np.arange(N)
xs = np.repeat(x.reshape([1, -1]), M, axis=0)
ys = np.exp(-xs) + 0.05*np.random.random((M, N))
z = np.array([2]*4 + [1]*5 + [0]*5 + [2]*5)

helper = _AnimationHelper(xs, ys, z)
writer = FFMpegWriter(fps=5, codec='mpeg4')
helper.ani.save('out/test_saveani.mp4', writer=writer, dpi=80)

Upvotes: 0

Views: 611

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339250

For saving a figure, the use of blit is not necessary. If that is left out, the figure is saved correctly. However, the figure size change is not taken into account, because the animation is created before that change. Creating the figure and all the axes before creating the animation works perfectly fine though, even with blitting being used.

from itertools import tee

import numpy as np

import matplotlib
matplotlib.use('Agg')           # noqa
from matplotlib.animation import FuncAnimation, FFMpegWriter
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
import matplotlib.pyplot as plt


def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)


def make_segs(x, y):
    xs = np.array(list(pairwise(x)))  # (n,2)
    ys = np.array(list(pairwise(y)))  # (n,2)
    segs = np.stack((xs, ys), axis=-1)  # (n,2,2)
    return segs


class _AnimationHelper():
    def __init__(self, xs, ys, z, colors=['r', 'g']):
        self.segs = np.array([make_segs(x, y) for x, y in zip(xs, ys)])
        self.z = z
        self.colors = colors
        self.fig = plt.figure()
        self.lns = []

        m, n = self.segs.shape[:2]
        self.fig.set_size_inches(m*5, 5, forward=True)
        self.axes = self.fig.subplots(
            nrows=1, ncols=m, sharey='row')
        self.fig.tight_layout()

        self.ani = FuncAnimation(
            self.fig,
            self.update,
            interval=200,
            init_func=self.init,
            frames=self.segs.shape[1],
            blit=True)

    def init(self):
        m, n = self.segs.shape[:2]
        cmap = ListedColormap(self.colors)
        norm = BoundaryNorm([0, 0.5, 1], cmap.N)
        zero = np.zeros(n, dtype=np.int32)
        for i in range(m):
            inds = (zero + i) == self.z
            ln = LineCollection([], cmap=cmap, norm=norm, lw=2, animated=True)
            ln.set_array(inds)
            self.lns.append(ln)

            ax = self.axes[i]
            ax.add_collection(ln)
            ax.set_xlim(0, n)
            #ax.set_axis_off()  # <---- HERE

        self.axes[0].set_ylim(0, 1.1)
        for i in range(1, m):
            self.axes[i].tick_params(left=False)
        return self.lns

    def update(self, ind):
        print(ind)
        m = self.segs.shape[0]
        for i in range(m):
            self.lns[i].set_segments(self.segs[i, :(ind + 1)])
        return self.lns


N = 20
M = 3

x = np.arange(N)
xs = np.repeat(x.reshape([1, -1]), M, axis=0)
ys = np.exp(-xs) + 0.05*np.random.random((M, N))
z = np.array([2]*4 + [1]*5 + [0]*5 + [2]*5)

helper = _AnimationHelper(xs, ys, z)
writer = FFMpegWriter(fps=5, codec='mpeg4')
helper.ani.save('test_saveani.mp4', writer=writer, dpi=80)

The above now even allows to use an interactive backend, matplotlib.use('TkAgg') and showing the figure via plt.show().

Upvotes: 1

Related Questions