user1054158
user1054158

Reputation:

matplotlib - Drawing directly on the canvas

Due to performance issues for dynamical updates, I need to draw directly a lot of rectangle on the canvas as very low level, that is to say without using matplotlib.patches and as we have to do with classical GUI.

More precisely, I would like to only draw one rectangle and not only all the figure.

Is it possible ?

Here is my testing code using the link given by Joe Kington.

#!/usr/bin/env python3

from random import randint, choice
import time

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.colors as colors
import matplotlib

back_color = "black"
colors     = ['red', 'green', 'cyan', 'yellow']

width  = 16
height = 16

ax     = plt.subplot(111)
canvas = ax.figure.canvas

ax.set_xlim([0, width])
ax.set_ylim([0, height])

def update():
    global ax, canvas, colors, width, height

    x = randint(0, width - 1)
    y = randint(0, height - 1)

    rect = mpatches.Rectangle(
        (x, y), 1, 1,
        facecolor = choice(colors),
        edgecolor = back_color
    )

    start = time.time()
    ax.draw_artist(rect)
    canvas.blit(ax.bbox)
    print("draw >>>", time.time() - start)

timer = canvas.new_timer(interval = 1)
timer.add_callback(update)
timer.start()

plt.show()

Under Mac O$, I obtain the following message.

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/site-packages/matplotlib/backend_bases.py", line 1203, in _on_timer
    ret = func(*args, **kwargs)
  File "/Users/xxx/test.py", line 86, in update
    ax.draw_artist(rect)
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/site-packages/matplotlib/axes.py", line 2100, in draw_artist
    a.draw(self._cachedRenderer)
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/site-packages/matplotlib/artist.py", line 56, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/site-packages/matplotlib/patches.py", line 393, in draw
    gc = renderer.new_gc()
  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/site-packages/matplotlib/backends/backend_macosx.py", line 97, in new_gc
    self.gc.save()
RuntimeError: CGContextRef is NULL

For Maverick users

I have a good news for the Mac users who want to play with the following codes, and also with animations. I've reinstalled Maverick OS on my Mac without any softwares, this is called a clean installation, and then I've installed Anaconda, and XQuark (see this).

To make animations work, you just have to use the following two lines before any other matplotlib imports.

import matplotlib
matplotlib.use('TkAgg')

I think this will work for any Mac OS supported by Anaconda. The use of XQuark is only needed for Maverick.

Upvotes: 4

Views: 20519

Answers (1)

Joe Kington
Joe Kington

Reputation: 284750

You have two problems with your current code.

  1. You're trying to blit without having drawn the canvas first. The error you're getting is backend-specific, but basically, it's saying that the canvas's renderer hasn't been initialized yet.
  2. You're not adding the rectangle artist to the axes. Therefore, it's not being drawn with you call ax.draw_artist(rect).

Here's a working example:

from random import randint, choice
import time
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

back_color = "black"
colors     = ['red', 'green', 'cyan', 'yellow']
width, height = 16, 16

fig, ax = plt.subplots()
ax.set(xlim=[0, width], ylim=[0, height]) # Or use "ax.axis([x0,x1,y0,y1])"

# Be sure to draw the canvas once before we start blitting. Otherwise
# a) the renderer doesn't exist yet, and b) there's noting to blit onto
fig.canvas.draw()

def update():
    x = randint(0, width - 1)
    y = randint(0, height - 1)

    rect = mpatches.Rectangle(
        (x, y), 1, 1,
        facecolor = choice(colors),
        edgecolor = back_color
    )
    ax.add_artist(rect)

    start = time.time()
    ax.draw_artist(rect)
    fig.canvas.blit(ax.bbox)
    print("draw >>>", time.time() - start)

timer = fig.canvas.new_timer(interval=1)
timer.add_callback(update)
timer.start()

plt.show()

At this point, though, it would make a lot more sense not to add a new artist each time. Instead, add the initial rectangle and update it each time. For example:

from random import randint, choice
import time
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

back_color = "black"
colors     = ['red', 'green', 'cyan', 'yellow']

width, height = 16, 16

fig, ax = plt.subplots()
ax.set(xlim=[0, width], ylim=[0, height]) # Or use "ax.axis([x0,x1,y0,y1])"

rect = mpatches.Rectangle(
    (0, 0), 1, 1,
    facecolor = choice(colors),
    edgecolor = back_color
)
ax.add_artist(rect)

# Be sure to draw the canvas once before we start blitting. Otherwise
# a) the renderer doesn't exist yet, and b) there's noting to blit onto
fig.canvas.draw()

def update():
    x = randint(0, width - 1)
    y = randint(0, height - 1)
    rect.set(xy=[x,y], facecolor=choice(colors))

    start = time.time()
    ax.draw_artist(rect)
    fig.canvas.blit(ax.bbox)
    print("draw >>>", time.time() - start)

timer = fig.canvas.new_timer(interval=1)
timer.add_callback(update)
timer.start()

plt.show()

Upvotes: 1

Related Questions