LineCollection animation when updating the frames in a loop - python

The following code supposed to do the same thing here where lines colours are updated during an animation. However, the colour is not being updated. Which part of this code is wrong?
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.animation as animation
lines = []
for i in range(10):
for j in range(10):
lines.append([(0, i), (1, j)])
fig, ax = plt.subplots()
colors = np.random.random(len(lines))
col = LineCollection(lines, array=colors, cmap=plt.cm.gray)
ax.add_collection(col)
ax.autoscale()
ims = []
for i in range(100):
colors = np.random.random(len(lines))
col.set_array(colors)
ims.append([ax.add_collection(col)])
ani = animation.ArtistAnimation(fig, ims, interval=200, blit=True,repeat_delay=10000)
plt.show()
The output I get from the above code is below

You need to draw the changed artist (your LineCollection) just after the line
col.set_array(colors)
You need to do this because the LineCollection was first added to the axes with the line
ax.add_collection(col)
and then it is changed in the loop. You must then update the figure. The simplest way to do this is by calling plt.draw() just before the line
im=ax.add_collection(col)
This however replots everything, and slows done a lot the animation. The solution is to draw first all the rest with a call to plt.draw() before the loop, and then updating only the changed artist with a call to ax.draw_artist(col). The resulting code looks like this
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.animation as animation
lines=[]
for i in range(10):
for j in range(10):
lines.append([(0, i), (1, j)])
fig, ax = plt.subplots()
colors = np.random.random(len(lines))
col = LineCollection(lines, array=colors, cmap=plt.cm.gray)
ax.add_collection(col)
ax.autoscale()
ims=[]
plt.draw()
for i in range(100):
col = LineCollection(lines,cmap=plt.cm.gray)
colors = np.random.random(len(lines))
col.set_array(colors)
ax.draw_artist(col)
im=ax.add_collection(col)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=25, blit=True,repeat_delay=10000)
plt.show()
It is less tricky to do the same thing using FuncAnimation(), see an example here, but you said you don't want to use it for some reasons.

I found a solution but its so dumb. I have to create the same package of lines again:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.animation as animation
lines=[]
for i in range(10):
for j in range(10):
lines.append([(0, i), (1, j)])
fig, ax = plt.subplots()
colors = np.random.random(len(lines))
col = LineCollection(lines, array=colors, cmap=plt.cm.gray)
ax.add_collection(col)
ax.autoscale()
ims=[]
for i in range(100):
col = LineCollection(lines,cmap=plt.cm.gray)
colors = np.random.random(len(lines))
col.set_array(colors)
im=ax.add_collection(col)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=25, blit=True,repeat_delay=10000)
plt.show()
I would be happy if somebody gives a me a solution without renewing the lines but only updating the colours.

According to the documentation of "LineCollection" we can set the individual color of each segment by using the method "set_color" and providing a sequence of rgba tupels:
c : color or list of colors
Single color (all patches have same color), or a
sequence of rgba tuples; if it is a sequence the patches will
cycle through the sequence.
The following code should work properly. It is using FuncAnimation though.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.animation as animation
lines = []
for i in range(10):
for j in range(10):
lines.append([(0, i), (1, j)])
fig, ax = plt.subplots()
colors = np.random.random(len(lines))
col = LineCollection(lines, array=colors, cmap=plt.cm.gray)
ax.add_collection(col)
ax.autoscale()
def update(num, collection):
colors = [(c, c, c) for c in np.random.random(len(lines))]
collection.set_color(colors)
return collection,
ani = animation.FuncAnimation(fig, update, 100, fargs=[col], interval=25, blit=True)
plt.show()

Related

Animate labels using FuncAnimation in Matplotlib

I am not able to make (animated) labels using FuncAnimation from matplotlib. Please find below a minimal code that I made. ax.annotate has no effect at all - the animation itself works though. What can I change to get animated labels/titles, which are different for each frame?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
fig.clear()
steps = 10
data = np.random.rand(20,20,10)
imagelist = [data[:,:,i] for i in range(steps) ]
im = plt.imshow(imagelist[0], cmap='Greys', origin='lower', animated=True)
plt.colorbar(shrink=1, aspect=30, label='Counts')
# does not work
ax.annotate("Frame: %d " % steps,(0.09,0.92),xycoords ='figure fraction')
def updatefig(j):
im.set_array(imagelist[j])
return [im]
ani = animation.FuncAnimation(fig, updatefig, frames=range(steps), interval=200, blit=True)
plt.show()
Two problems overall:
The annotation text never gets updated in updatefig()
The canvas gets cleared+blitted, which wipes out annotations
Five steps to resolve:
Remove fig.clear() to preserve annotations
Save the initial annotation's handle
Update the annotation's text in updatefig()
Include the annotation in the return of updatefig()
Set blit=False to preserve annotations
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
#1 do NOT call fig.clear()
steps = 10
data = np.random.rand(20, 20, steps)
im = plt.imshow(data[:, :, 0], cmap='Greys', origin='lower', animated=True)
plt.colorbar(shrink=1, aspect=30, label='Counts')
#2 annotate frame 0 and save handle
annot = ax.annotate('Frame: 0', (0.09, 0.92), xycoords='figure fraction')
def updatefig(j):
im.set_array(data[:, :, j])
#3 update annotation text
annot.set_text(f'Frame: {j}')
#4 include annotation when returning
return im, annot
#5 set blit=False
anim = animation.FuncAnimation(fig, updatefig, frames=steps, blit=False)

Matplotlib: How to combine scatter and line plot to one legend entry

I draw my data points with ax.scatter() and connect the data points with a fit using ax.plot().
How do I create a common entry in the legend that combines the marker for the data point with the line of the fit? I want to get a legend entry as I would get it for ax.plot(x, y, '-o', label = 'abc').
I have created the following minimal example:
import matplotlib.pyplot as plt
import numpy as np
x_scatter = np.linspace(0,10,10)
x_line = np.linspace(0,10,100)
fig, ax = plt.subplots()
for i in range(5):
ax.scatter(x_scatter, np.sin(x_scatter) + i, label = i)
ax.plot(x_line, np.sin(x_line)+i)
plt.legend(loc='best')
plt.show()
This 'hack' should work:
import matplotlib.pyplot as plt
import numpy as np
x_scatter = np.linspace(0,10,10)
x_line = np.linspace(0,10,100)
fig, ax = plt.subplots()
prop = ax._get_lines.prop_cycler
for i in range(5):
color = next(prop)['color']
ax.scatter(x_scatter, np.sin(x_scatter) + i, color=color)
ax.plot(x_line, np.sin(x_line)+i, color=color)
ax.plot([], [], '-o', color=color, label = i)
plt.legend(loc='best')
plt.show()

Showing subplots at each pass of a loop

I would essentially like to do the following:
import matplotlib.pyplot as plt
import numpy as np
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
for i in range(10):
ax1.scatter(i, np.sqrt(i))
ax1.show() # something equivalent to this
ax2.scatter(i, i**2)
That is, each time a point is plotted on ax1, it is shown - ax2 being shown once.
You cannot show an axes alone. An axes is always part of a figure. For animations you would want to use an interactive backend. Then the code in a jupyter notebook could look like
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
frames = 10
x = np.arange(frames)
line1, = ax1.plot([],[], ls="", marker="o")
line2, = ax2.plot(x, x**2, ls="", marker="o")
ax2.set_visible(False)
def animate(i):
line1.set_data(x[:i], np.sqrt(x[:i]))
ax1.set_title(f"{i}")
ax1.relim()
ax1.autoscale_view()
if i==frames-1:
ax2.set_visible(True)
fig2.canvas.draw_idle()
ani = FuncAnimation(fig1, animate, frames=frames, repeat=False)
plt.show()
If you want to change plots dynamically I'd suggest you don't redraw the whole plot every time, this will result in very laggy behavior. Instead you could use Blit to do this. I used it in a previous project. Maybe it can help you too if you just take the parts from this you need:
Python project dynamically updating plot

matplotlib animation doesn't animate with imshow

I'm trying to animate a few simple subplots with imshow but there's apparently an issue.
This is a small demo of my problem:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import animation
fig,axs=plt.subplots(2,5,figsize=(10,4))
imgs=[]
for row in axs:
for col in row:
col.set_xlim(4.5,-.5)
col.set_ylim(4.5,-.5)
col.set_xticks([])
col.set_yticks([])
#line A
imgs.append(col.imshow([[]],animated=1))
#freezes the animation([[]] is just a placeholder)
#line B
#imgs.append(col.imshow(np.arange(25).reshape((5,5)),animated=1))
#animation does work if the above line is used instead
def func(frm):
for i in range(10):
imgs[i].set_array(np.arange(25).reshape(5,5)*np.log10(frm+1))
return imgs
anim=animation.FuncAnimation(fig,func,10,interval=100)
plt.show()
If I use line A, the animation freezes as if func is not executed (while actually it is), raising no errors. If line B is used instead, the animation works. Am I missing something about imshow animating?
Animation from multiple imshow can be created by ArtistAnimation object using multiple axes generated by subplots command.
from numpy import random
from matplotlib import animation
import matplotlib.pyplot as plt
img_lst_1 = [random.random((368,1232)) for i in range(10)] # Test data
img_lst_2 = [random.random((368,1232)) for i in range(10)] # Test data
fig, (ax1, ax2) = plt.subplots(2,1)
frames = [] # store generated images
for i in range(len(img_lst_1)):
img1 = ax1.imshow(img_lst_1[i], animated=True)
img2 = ax2.imshow(img_lst_2[i], cmap='gray', animated=True)
frames.append([img1, img2])
ani = animation.ArtistAnimation(fig, frames, interval=500, blit=True,
repeat_delay=1000)
ani.save('movie_example.mp4')
Code output:

Matplotlib 3D: Remove axis ticks & draw upper edge border?

It seems like some of the methods that work for matplotlib 2D might not be working for matplotlib 3D. I'm not sure.
I'd like to remove the tick marks from all axes, and extend the edge color from the bottom and sides to the top as well. The farthest I have gotten is being able to draw the ticks as white, which looks bad as they are rendered on top of the edge lines.
Below is a big chunk of self-contained code that results in the following image. Any help is much appreciated!
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib import animation
from mpl_toolkits.mplot3d import Axes3D
mpl.rcParams['ytick.color'] = 'white'
#mpl.rcParams['ytick.left'] = False
sample = np.random.random_integers(low=1,high=5, size=(10,3))
# Create a figure and a 3D Axes
fig = plt.figure(figsize=(5,5))
ax = Axes3D(fig)
#ax.w_xaxis.set_tick_params(color='white')
#ax.axes.tick_params
ax.axes.tick_params(bottom=False, color='blue')
##['size', 'width', 'color', 'tickdir', 'pad', 'labelsize',
##'labelcolor', 'zorder', 'gridOn', 'tick1On', 'tick2On',
##'label1On', 'label2On', 'length', 'direction', 'left', 'bottom',
##'right', 'top', 'labelleft', 'labelbottom',
##'labelright', 'labeltop', 'labelrotation']
colors = np.mean(sample[:, :], axis=1)
ax.scatter(sample[:,0], sample[:,1], sample[:,2],
marker='o', s=20, c=colors, alpha=1)
ax.tick_params(color='red')
frame1 = plt.gca()
frame1.axes.xaxis.set_ticklabels([])
frame1.axes.yaxis.set_ticklabels([])
frame1.axes.zaxis.set_ticklabels([])
#frame1.axes.yaxis.set_tick_params(color='white')
To answer the first bit of the question, about tick removal,
it's probably easiest to just disable the tick lines:
for line in ax.xaxis.get_ticklines():
line.set_visible(False)
for line in ax.yaxis.get_ticklines():
line.set_visible(False)
for line in ax.zaxis.get_ticklines():
line.set_visible(False)
E.g.:
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib import animation
from mpl_toolkits.mplot3d import Axes3D
sample = np.random.random_integers(low=1,high=5, size=(10,3))
# Create a figure and a 3D Axes
fig = plt.figure(figsize=(5,5))
ax = Axes3D(fig)
colors = np.mean(sample[:, :], axis=1)
ax.scatter(sample[:,0], sample[:,1], sample[:,2],
marker='o', s=20, c=colors, alpha=1)
ax = plt.gca()
ax.xaxis.set_ticklabels([])
ax.yaxis.set_ticklabels([])
ax.zaxis.set_ticklabels([])
for line in ax.xaxis.get_ticklines():
line.set_visible(False)
for line in ax.yaxis.get_ticklines():
line.set_visible(False)
for line in ax.zaxis.get_ticklines():
line.set_visible(False)
For newer versions (e.g. matplotlib 3.5.1) a lot of formatting can be done via mpl_toolkits.mplot3d.axis3d._axinfo:
import numpy as np
from matplotlib import pyplot as plt
sample = np.random.randint(low=1,high=5, size=(10,3))
# Create a figure and a 3D Axes
fig = plt.figure(figsize=(5,5))
ax = fig.add_subplot(projection='3d')
colors = np.mean(sample[:, :], axis=1)
ax.scatter(sample[:,0], sample[:,1], sample[:,2],
marker='o', s=20, c=colors, alpha=1)
for axis in [ax.xaxis, ax.yaxis, ax.zaxis]:
axis.set_ticklabels([])
axis._axinfo['axisline']['linewidth'] = 1
axis._axinfo['axisline']['color'] = (0, 0, 0)
axis._axinfo['grid']['linewidth'] = 0.5
axis._axinfo['grid']['linestyle'] = "-"
axis._axinfo['grid']['color'] = (0, 0, 0)
axis._axinfo['tick']['inward_factor'] = 0.0
axis._axinfo['tick']['outward_factor'] = 0.0
axis.set_pane_color((0.95, 0.95, 0.95))
plt.show()

Categories