Warning
This code does not work with the macosx backend (but does work with other\n GUI backends on Mac).
\n\n## Minimal example\n\nWe can use the `.FigureCanvasAgg` methods\n`~.FigureCanvasAgg.copy_from_bbox` and\n`~.FigureCanvasAgg.restore_region` in conjunction with setting\n``animated=True`` on our artist to implement a minimal example that\nuses blitting to accelerate rendering\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\nimport numpy as np\n\nx = np.linspace(0, 2 * np.pi, 100)\n\nfig, ax = plt.subplots()\n\n# animated=True tells matplotlib to only draw the artist when we\n# explicitly request it\n(ln,) = ax.plot(x, np.sin(x), animated=True)\n\n# make sure the window is raised, but the script keeps going\nplt.show(block=False)\n\n# stop to admire our empty window axes and ensure it is rendered at\n# least once.\n#\n# We need to fully draw the figure at its final size on the screen\n# before we continue on so that :\n# a) we have the correctly sized and drawn background to grab\n# b) we have a cached renderer so that ``ax.draw_artist`` works\n# so we spin the event loop to let the backend process any pending operations\nplt.pause(0.1)\n\n# get copy of entire figure (everything inside fig.bbox) sans animated artist\nbg = fig.canvas.copy_from_bbox(fig.bbox)\n# draw the animated artist, this uses a cached renderer\nax.draw_artist(ln)\n# show the result to the screen, this pushes the updated RGBA buffer from the\n# renderer to the GUI framework so you can see it\nfig.canvas.blit(fig.bbox)\n\nfor j in range(100):\n # reset the background back in the canvas state, screen unchanged\n fig.canvas.restore_region(bg)\n # update the artist, neither the canvas state nor the screen have changed\n ln.set_ydata(np.sin(x + (j / 100) * np.pi))\n # re-render the artist, updating the canvas state, but not the screen\n ax.draw_artist(ln)\n # copy the image to the GUI state, but screen might not be changed yet\n fig.canvas.blit(fig.bbox)\n # flush any pending GUI events, re-painting the screen if needed\n fig.canvas.flush_events()\n # you can put a pause in if you want to slow things down\n # plt.pause(.1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This example works and shows a simple animation, however because we\nare only grabbing the background once, if the size of the figure in\npixels changes (due to either the size or dpi of the figure\nchanging) , the background will be invalid and result in incorrect\n(but sometimes cool looking!) images. There is also a global\nvariable and a fair amount of boilerplate which suggests we should\nwrap this in a class.\n\n## Class-based example\n\nWe can use a class to encapsulate the boilerplate logic and state of\nrestoring the background, drawing the artists, and then blitting the\nresult to the screen. Additionally, we can use the ``'draw_event'``\ncallback to capture a new background whenever a full re-draw\nhappens to handle resizes correctly.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class BlitManager:\n def __init__(self, canvas, animated_artists=()):\n \"\"\"\n Parameters\n ----------\n canvas : FigureCanvasAgg\n The canvas to work with, this only works for subclasses of the Agg\n canvas which have the `~FigureCanvasAgg.copy_from_bbox` and\n `~FigureCanvasAgg.restore_region` methods.\n\n animated_artists : Iterable[Artist]\n List of the artists to manage\n \"\"\"\n self.canvas = canvas\n self._bg = None\n self._artists = []\n\n for a in animated_artists:\n self.add_artist(a)\n # grab the background on every draw\n self.cid = canvas.mpl_connect(\"draw_event\", self.on_draw)\n\n def on_draw(self, event):\n \"\"\"Callback to register with 'draw_event'.\"\"\"\n cv = self.canvas\n if event is not None:\n if event.canvas != cv:\n raise RuntimeError\n self._bg = cv.copy_from_bbox(cv.figure.bbox)\n self._draw_animated()\n\n def add_artist(self, art):\n \"\"\"\n Add an artist to be managed.\n\n Parameters\n ----------\n art : Artist\n\n The artist to be added. Will be set to 'animated' (just\n to be safe). *art* must be in the figure associated with\n the canvas this class is managing.\n\n \"\"\"\n if art.figure != self.canvas.figure:\n raise RuntimeError\n art.set_animated(True)\n self._artists.append(art)\n\n def _draw_animated(self):\n \"\"\"Draw all of the animated artists.\"\"\"\n fig = self.canvas.figure\n for a in self._artists:\n fig.draw_artist(a)\n\n def update(self):\n \"\"\"Update the screen with animated artists.\"\"\"\n cv = self.canvas\n fig = cv.figure\n # paranoia in case we missed the draw event,\n if self._bg is None:\n self.on_draw(None)\n else:\n # restore the background\n cv.restore_region(self._bg)\n # draw all of the animated artists\n self._draw_animated()\n # update the GUI state\n cv.blit(fig.bbox)\n # let the GUI event loop process anything it has to do\n cv.flush_events()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here is how we would use our class. This is a slightly more complicated\nexample than the first case as we add a text frame counter as well.\n\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# make a new figure\nfig, ax = plt.subplots()\n# add a line\n(ln,) = ax.plot(x, np.sin(x), animated=True)\n# add a frame number\nfr_number = ax.annotate(\n \"0\",\n (0, 1),\n xycoords=\"axes fraction\",\n xytext=(10, -10),\n textcoords=\"offset points\",\n ha=\"left\",\n va=\"top\",\n animated=True,\n)\nbm = BlitManager(fig.canvas, [ln, fr_number])\n# make sure our window is on the screen and drawn\nplt.show(block=False)\nplt.pause(.1)\n\nfor j in range(100):\n # update the artists\n ln.set_ydata(np.sin(x + (j / 100) * np.pi))\n fr_number.set_text(f\"frame: {j}\")\n # tell the blitting manager to do its thing\n bm.update()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This class does not depend on `.pyplot` and is suitable to embed\ninto larger GUI application.\n\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.2"
}
},
"nbformat": 4,
"nbformat_minor": 0
}