Note: I figured out a solution to this question while writing it. My answer is below.
Is there an easy way to get sub-pixel antialiased placement of circles using matplotlib? I was able to create the following .gif but the fact that the motion of the circles steps by integer pixels is really bugging me.

I can of course render a large image (plt.savefig("image.png",dpi=1000)) and scale down, but the added complexity is a pain (this is an example for students, so importing extra tools will annoy the students and installing extra tools will annoy campus IT!)
import matplotlib.pyplot as plt
from math import sin,cos
x=[]
y=[]
#Create 41**2 particles in the unit square
for i in range(41):
for j in range(41):
x.append(i*0.05-1.0)
y.append(j*0.05-1.0)
#5 second video at 30 fps = 150 frames
for n in range(150):
plt.close('all')
fig, axes = plt.subplots(figsize=(5,5))
#some cool motion
for i in range(len(x)):
x[i]+=0.001*cos(x[i]+3*y[i])
y[i]+=0.001*sin(6*x[i]-4*y[i])
#create the scatter plot
axes.scatter(x,y,s=3,antialiased=True)
axes.set_xlim(-1.4,1.4)
axes.set_ylim(-1.4,1.4)
axes.set_aspect('equal')
plt.savefig("out/fig%03d.png"%n,dpi=80)
The scatter function must be optimized to just do quick integer placement. If you manually add Circle objects, the sub-pixel antialiasing is handled for you. It's quite a bit slower. Example:
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from math import sin,cos
x=[]
y=[]
#Create 41**2 particles in the unit square
for i in range(41):
for j in range(41):
x.append(i*0.05-1.0)
y.append(j*0.05-1.0)
#5 second video at 30 fps = 150 frames
for n in range(150):
plt.close('all')
fig, axes = plt.subplots(figsize=(5,5))
#adding the Circles to the plot using a PatchCollection is much faster.
patches=[]
#some cool motion
for i in range(len(x)):
x[i]+=0.001*cos(x[i]+3*y[i])
y[i]+=0.001*sin(6*x[i]-4*y[i])
patches.append(plt.Circle((x[i],y[i]),0.015))
axes.add_collection(PatchCollection(patches, alpha=0.95))
axes.set_xlim(-1.4,1.4)
axes.set_ylim(-1.4,1.4)
axes.set_aspect('equal')
plt.savefig("out/fig%03d.png"%n,dpi=80)
The result:
You may be interested in https://github.com/anntzer/mplcairo, a new cairo-based backend for Matplotlib which I originally wrote exactly because I was unhappy with this issue. It does not display the aforementioned problem, while maintaining reasonable performance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With