Matplotlib plot canvas

A anim.plot.canva embeds a Matplotlib figure in an anim.window. It follows the same animation cycle as a anim.plane.canva, while exposing Matplotlib’s object-oriented interface. Users do not need to import Qt or matplotlib.pyplot.

Creating a plot canvas

A new plot canvas contains one empty axes by default:

import anim

W = anim.window('Plot')
C = anim.plot.canva(W)
W.add(C)

C.axes.plot([0, 1, 2], [0, 1, 4])
W.show()

The main Matplotlib objects are available directly on the canvas:

C.figure

The complete Matplotlib figure. Use it to create figure-level titles or additional axes.

C.axes

The default axes, or an array of axes when several subplots are requested.

C.canvas

The Matplotlib rendering canvas. Calling it directly is rarely necessary, because anim.plot.canva.update() redraws it automatically.

Configuring axes

Use the exposed axes just as in Matplotlib’s object-oriented API:

C = anim.plot.canva(W)
C.axes.set_title('Position over time')
C.axes.set_xlabel('Time (s)')
C.axes.set_ylabel('Position (m)')
C.axes.set_xlim(0, 10)
C.axes.set_ylim(-1, 1)
C.axes.grid(True, alpha=0.3)

line, = C.axes.plot([], [], label='x(t)')
C.axes.legend()

Keeping the returned artist, such as line above, makes later updates simple and efficient. Modify the artist rather than clearing and rebuilding the complete axes on every frame.

For standard axes, matplotlib.axes.Axes.legend() automatically uses the axes background and foreground colors. A plain C.axes.legend() is therefore consistent with both dark and light plot styles. Explicit legend options remain possible and take priority over these defaults:

C.axes.legend(facecolor='navy', labelcolor='white')

Plot style

The plot inherits its parent window style by default. The supported values are 'dark', 'light' and 'white':

W = anim.window('Dark plot', style='dark')
inherited = anim.plot.canva(W)
light_plot = anim.plot.canva(W, style='light')

The style is applied only while the embedded figure is created. It does not change Matplotlib’s global settings and therefore does not affect other plot canvas instances.

Relative canvas size

width and height are positive integer weights used by the window grid. They are proportions, not pixels and not data-axis units. Their default value is 1, which gives a plot the same layout weight as other canvas types.

W.add(anim.plane.canva(W), row=0, col=0)
W.add(anim.plot.canva(W, width=2), row=0, col=1)

Here the right column receives twice as much horizontal space as the left column. Likewise, height=2 gives a row twice the vertical weight of a row whose weight is 1.

Creating several axes

Use nrows and ncols for a regular grid of subplots. Any other keyword argument is forwarded directly to Matplotlib’s subplot creation method:

C = anim.plot.canva(
  W,
  nrows=2,
  ncols=1,
  sharex=True
)

C.axes[0].set_ylabel('x')
C.axes[1].set_ylabel('y')
C.axes[1].set_xlabel('Time (s)')

Advanced options such as sharey, squeeze, subplot_kw and gridspec_kw can be passed in the same way. By Matplotlib convention, C.axes is a single axes for a default 1 by 1 layout, but usually becomes a NumPy array when several subplots are created. The squeeze option can change that behavior.

Updating a plot dynamically

Define a child class of anim.plot.canva, keep references to the artists created in the constructor, then change their data in update. Always finish with anim.plot.canva.update() so the completed frame is drawn and reported to the window:

import numpy as np
import anim

class LivePlot(anim.plot.canva):

  def __init__(self, window):
    super().__init__(window)
    self.line, = self.axes.plot([], [])
    self.axes.set_xlim(0, 10)
    self.axes.set_ylim(-1, 1)

  def update(self, t):
    times = np.arange(t.step + 1)*self.window.dt
    self.line.set_data(times, np.sin(times))
    super().update(t)

W = anim.window('Live plot')
W.add(LivePlot)
W.show()

Warning

Do not blindly append one data point on every call to update unless the animation is guaranteed to move strictly forward and every step is visited exactly once. A backward step, a direct jump to another step, or a repeated update of the same step can otherwise create duplicate, out-of-order or stale plot data.

Prefer deriving the displayed arrays from t.step or t.time on every update, as in the example above. If rebuilding all data is too expensive, use an explicitly reversible and idempotent history structure: updating the same step twice must produce the same result, and moving backward must undo or discard samples beyond the current step.

The final call to anim.plot.canva.update() performs a synchronous Matplotlib draw. This keeps the plot synchronized with other canvas panels and ensures that movie captures contain the fully updated frame.