-1

I am making a simulator that I want to use blit on for higher performance. For 2d scatterplots I can very safely use canvas.blit() to update my plots. But the same is not true for a 3d scatter plot. This post: Matplotlib 3D scatter animations Says that the problem is specifically linked to scatter plots in 3d. I dug into the source code but did not find much(mostly due to lack of experience). Can anyone help me find out a way to allow for blitting of scatter plots in 3d?

Below you will find code that tries to blit a scatter plot:

import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl

from mpl_toolkits.mplot3d import Axes3D


#create figure
fig = plt.gcf()
ax = fig.add_subplot(111, projection='3d')
lim=(-20,20)
ax.set(xlim=lim,ylim=lim, zlim = lim)
canvas = fig.canvas
t = canvas.new_timer()

fig2, ax2 = plt.subplots()

butt = mpl.widgets.Button(ax2, "play")

#Save reference figure
canvas.draw() #To make sure there is a figure
background = canvas.copy_from_bbox(ax.get_window_extent(ax.figure.canvas.renderer))

artist = ax.scatter([1],[1],[1], marker = "o")

def activate(event):
    global artist
    global canvas
    global fig
    global ax
    global background
    artist.set_animated(True)
    for x in range(300):
        artist._offsets3d =[[x/10], [1], [1]]
        #artist.set_data(a[0], a[1])
        #artist.set_3d_properties([1])
        ax.draw_artist(artist)
        canvas.blit(ax.bbox)
        canvas.restore_region(background)
        canvas.flush_events()
    artist.set_animated(False)
    canvas.draw()
    canvas.flush_events()

butt.on_clicked(activate)
plt.show()

Now you will find code that follows the same structure with a different artist:

import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl

from mpl_toolkits.mplot3d import Axes3D


#create figure
fig = plt.gcf()
ax = fig.add_subplot(111, projection='3d')
lim=(-20,20)
ax.set(xlim=lim,ylim=lim, zlim = lim)
canvas = fig.canvas
t = canvas.new_timer()

fig2, ax2 = plt.subplots()

butt = mpl.widgets.Button(ax2, "play")

#Save reference figure
canvas.draw() #To make sure there is a figure
background = canvas.copy_from_bbox(ax.get_window_extent(ax.figure.canvas.renderer))

artist = ax.scatter([1],[1],[1], marker = "o")

def activate(event):
    global artist
    global canvas
    global fig
    global ax
    global background
    artist.set_animated(True)
    for x in range(300):
        artist._offsets3d =[[x/10], [1], [1]]
        #artist.set_data(a[0], a[1])
        #artist.set_3d_properties([1])
        ax.draw_artist(artist)
        canvas.blit(ax.bbox)
        canvas.restore_region(background)
        canvas.flush_events()
    artist.set_animated(False)
    canvas.draw()
    canvas.flush_events()

butt.on_clicked(activate)
plt.show()
p479h
  • 169
  • 1
  • 8

2 Answers2

1

Adding

artist.do_3d_projection(fig._cachedRenderer)

after

artist._offsets3d = [[x/10], [1], [1]]

solved the problem.

William Miller
  • 9,839
  • 3
  • 25
  • 46
p479h
  • 169
  • 1
  • 8
0

Since the issue lies with matplotlib.pyplot.scatter (which returns a PathCollection) but not with matplotlib.pyplot.plot (which returns a Line2D) you should be able to get away with initializing artist like

artist = ax.plot([1], [1], [1], linestyle="none", marker="o")

which should then allow blitting - at least when using FuncAnimation it does, ostensibly canvas.blit() should work too.

@ImportanceOfBeingErnest notes this in the accepted answer to the linked question.

William Miller
  • 9,839
  • 3
  • 25
  • 46
  • I timed each frame with FuncAnimation and for a scatter plot, each draw took longer with `blit=True` than with `blit=False`, which means it would be better to stick with `canvas.draw()`. Furthermore, using `plt.plot()` can be limiting for certain animations where it might be necessary to implement colormaps and other functionality specific to scatterplots. – p479h Jun 15 '20 at 08:27