Matplotlib how to move axis along data in a real-time animation - python

I'm trying to plot data that is generated in runtime. In order to do so I'm using matplotlib.animation.FuncAnimation.
While the data is displayed correctly, the axis values are not updating accordingly to the values that are being displayed:
The x axis displays values from 0 to 10 eventhough I update them in every iteration in the update_line function (see code below).
DataSource contains the data vector and appends values at runtime, and also returns the indexes of the values being returned:
import numpy as np
class DataSource:
data = []
display = 10
# Append one random number and return last 10 values
def getData(self):
self.data.append(np.random.rand(1)[0])
if(len(self.data) <= self.display):
return self.data
else:
return self.data[-self.display:]
# Return the index of the last 10 values
def getIndexVector(self):
if(len(self.data) <= self.display):
return list(range(len(self.data)))
else:
return list(range(len(self.data)))[-self.display:]
I've obtained the plot_animation function from the matplotlib docs.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from datasource import DataSource
def update_line(num, source, line):
data = source.getData()
indexs = source.getIndexVector()
if indexs[0] != 0:
plt.xlim(indexs[0], indexs[-1])
dim=np.arange(indexs[0],indexs[-1],1)
plt.xticks(dim)
line.set_data(indexs,data)
return line,
def plot_animation():
fig1 = plt.figure()
source = DataSource()
l, = plt.plot([], [], 'r-')
plt.xlim(0, 10)
plt.ylim(0, 1)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig1, update_line, fargs=(source, l),
interval=150, blit=True)
# To save the animation, use the command: line_ani.save('lines.mp4')
plt.show()
if __name__ == "__main__":
plot_animation()
How can I update the x axis values in every iteration of the animation?
(I appreciate suggestions to improve the code if you see any mistakes, eventhough they might not be related to the question).

Here is a simple case of how you can achieve this.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
%matplotlib notebook
#data generator
data = np.random.random((100,))
#setup figure
fig = plt.figure(figsize=(5,4))
ax = fig.add_subplot(1,1,1)
#rolling window size
repeat_length = 25
ax.set_xlim([0,repeat_length])
ax.set_ylim([-2,2])
#set figure to be modified
im, = ax.plot([], [])
def func(n):
im.set_xdata(np.arange(n))
im.set_ydata(data[0:n])
if n>repeat_length:
lim = ax.set_xlim(n-repeat_length, n)
else:
lim = ax.set_xlim(0,repeat_length)
return im
ani = animation.FuncAnimation(fig, func, frames=data.shape[0], interval=30, blit=False)
plt.show()
#ani.save('animation.gif',writer='pillow', fps=30)

Solution
My problem was in the following line:
line_ani = animation.FuncAnimation(fig1, update_line, fargs=(source, l),
interval=150, blit=True)
What I had to do is change the blit parameter to False and the x axis started to move as desired.

Related

Scatterplot animate trajectory with fading trail

I have a df containing x,y coordinates of a mouse's snout that I want to use for an animated scatterplot. Currently, I have the code for a static scatterplot.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import os
from pathlib import Path
from IPython.display import HTML
#import video pose estimation data
video='topNoF.mp4'
DLCscorer='C:/Users/Bri-Guy/Desktop/DLC/h5/topFDLC_resnet50_topAnalysisJan20shuffle1_100000'
dataname = DLCscorer+'.h5' #can change to .csv instead; make sure to change pd.read_hdf() to pd.read_csv()
df=pd.read_hdf(os.path.join(dataname))
#get X & Y coordinates of snout bdp
scorer=df.columns.get_level_values(0)[0]
x=df[scorer]['snout']['x'].values
y=df[scorer]['snout']['y'].values
#produce a static scatterplot trace of snout movement
length=len(x)
n = len(x)
color = []
for i in range (1,n+1):
color.append(i/n)
scatter=plt.scatter(x,y,c=color, cmap='inferno')
ax = scatter.axes
which yields
I want to use a variation of the code from this Stack Exchange answer: Matplotlib Plot Points Over Time Where Old Points Fade to animate the scatterplot in a manner that looks like this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
from matplotlib.colors import LinearSegmentedColormap
plt.rcParams['animation.ffmpeg_path'] = r'C:/Users/Bri-Guy/anaconda3/envs/pWGA/Library/bin/ffmpeg.exe'
x=df[scorer]['snout']['x'].values
y=df[scorer]['snout']['y'].values
fig, ax = plt.subplots()
plt.xlim(0, max(x)+100)
plt.ylim(0, max(y)+100)
graph, = plt.plot([], [], 'o')
def animate(i):
graph.set_data(x[:i+1], y[:i+1])
return graph
ani = FuncAnimation(fig, animate, frames=len(y), interval=20)
html = ani.to_html5_video()
HTML(html)
Ideally, I'd like for the points in this animation to fade over time (smooth fade like the example linked). Also, I'm not sure how to set a colormap such as inferno to the animated scatterplot. Fade & the ability to set a colormap are my most important priorities.
The main issue I'm running into with the code from the linked example is the following area:
def get_new_vals():
n = np.random.randint(1,5)
x = np.random.rand(n)
y = np.random.rand(n)
return list(x), list(y)
def update(t):
global x_vals, y_vals, intensity
# Get intermediate points
new_xvals, new_yvals = get_new_vals()
x_vals.extend(new_xvals)
y_vals.extend(new_yvals)
# Put new values in your plot
scatter.set_offsets(np.c_[x_vals,y_vals])
#calculate new color values
intensity = np.concatenate((np.array(intensity)*0.96, np.ones(len(new_xvals))))
scatter.set_array(intensity)
# Set title
ax.set_title('Time: %0.3f' %t)
I need to get one new value from x=df[scorer]['snout']['x'].values & y=df[scorer]['snout']['y'].values. These values have to be called in order from x[0] to x[len(x)-1] so that the plot will update chronologically. However, I get errors when trying to add a parameter in get_new_vals(i) because I think the t variable for animation isn't an integer. I'm not sure if there is also an issue with the element types inside my arrays x & y since they are floating points.
I appreciate your help in advance! Please let me know if I can clarify anything for you. Below I have posted some of the data inside my x,y variables:
print(x[:200])
[276.89370728 280.57974243 285.25439453 285.55096436 284.71258545
283.52386475 284.31976318 285.08609009 285.56118774 285.38183594
289.21246338 295.28497314 303.41043091 315.51828003 324.87826538
333.36367798 338.73730469 341.11685181 349.20300293 357.63671875
366.72702026 395.68356323 385.37298584 387.86871338 387.58526611
382.20205688 378.13674927 373.97241211 368.39953613 591.94116211
347.27310181 616.52069092 608.12902832 605.11340332 602.10974121
598.72052002 598.48504639 599.19256592 601.30432129 603.32104492
604.9621582 605.21533203 621.36779785 627.51617432 626.20269775
621.00164795 618.92498779 617.44885254 615.73883057 598.8916626
594.17883301 593.38647461 589.3248291 592.67895508 593.67053223
589.05767822 589.08850098 303.0085144 568.39239502 555.08520508
550.79425049 547.77197266 547.21954346 313.01544189 333.96121216
348.59899902 353.26141357 358.76705933 360.81588745 363.94262695
527.38165283 522.80316162 518.20489502 521.84442139 525.30664062
526.43286133 532.38995361 536.35961914 536.51574707 540.41906738
545.77844238 545.22381592 545.34112549 540.22357178 537.93457031
534.03442383 532.6651001 522.52618408 505.38290405 489.37664795
469.75460815 448.28039551 424.54315186 403.87719727 383.25265503
359.93307495 335.06869507 318.53125 296.43450928 288.86499023
442.87780762 443.70950317 440.31652832 439.50854492 439.11328125
434.4161377 216.55622864 216.7456665 215.32554626 213.63644409
213.8143158 213.96568298 213.87882996 214.10801697 214.05957031
212.54373169 216.25740051 214.80444336 216.47532654 218.31072998
215.78303528 213.40249634 292.92352295 290.80630493 287.03222656
283.45129395 390.10848999 274.83648682 269.53741455 215.76341248
218.75086975 220.38156128 219.87997437 219.83804321 218.52023315
216.93737793 218.18110657 218.31959534 224.79884338 224.69064331
221.88998413 218.67016602 216.9510498 216.63031006 215.88612366
217.3243103 217.01783752 214.08659363 213.87808228 211.14770508
206.47595215 214.88208008 214.39358521 212.50665283 212.39123535
213.95169067 217.72639465 313.20504761 288.23443604 283.30273438
283.1756897 281.46990967 276.20397949 273.39535522 274.38088989
267.42678833 269.19915771 271.11810303 331.80795288 330.61746216
329.03930664 227.14578247 226.81338501 227.80999756 229.65690613
229.97644043 207.91325378 219.31289673 225.3374939 230.50515747
313.22525024 310.83474731 306.02667236 299.73217773 288.91854858
278.45489502 266.55349731 264.91229248 256.15029907 249.70783997
245.47244263 246.11851501 245.35572815 246.03157043 246.50708008
246.63691711 245.77215576 245.09873962 241.44792175 243.87677002]
print(y[:200])
[1321.18652344 1316.84301758 1316.04064941 1315.66455078 1315.38586426
1315.74560547 1317.04711914 1318.11218262 1320.09631348 1321.45703125
1328.39794922 1339.04956055 1353.28076172 1364.76757812 1371.4901123
1376.57568359 1381.58361816 1383.17236328 1390.66870117 1401.93652344
1412.72961426 1393.1583252 1437.81616211 1440.15380859 1440.01843262
1438.75891113 1437.50012207 1436.55932617 1429.39416504 1620.10900879
1400.08654785 1506.79125977 1497.49243164 1491.12316895 1488.84606934
1487.79296875 1489.73840332 1488.69665527 1490.09631348 1490.75927734
1490.99291992 1487.58154297 1610.09851074 1612.06115723 1613.1282959
1615.44006348 1619.22009277 1620.50915527 1618.51501465 1495.30175781
1496.44470215 1497.99816895 1492.10546875 1474.48339844 1481.59130859
1475.67248535 1477.48083496 1375.00964355 1480.91625977 1486.29675293
1493.73193359 1501.6204834 1507.31176758 1366.4050293 1358.88977051
1346.67797852 1339.04455566 1333.7755127 1328.54821777 1322.38439941
1502.17102051 1497.28198242 1493.86022949 1479.5690918 1469.38928223
1458.9095459 1448.56921387 1448.53613281 1443.38757324 1444.77124023
1443.64233398 1440.03857422 1431.31848145 1427.53820801 1427.53393555
1428.34887695 1429.39526367 1431.30249023 1440.09594727 1457.33044434
1474.85864258 1492.55554199 1498.40905762 1497.65649414 1503.4185791
1507.8626709 1507.43164062 1506.86315918 1507.16052246 1506.81115723
1313.21789551 1317.2467041 1322.02380371 1320.96960449 1314.60107422
1314.20336914 1640.76843262 1656.12866211 1667.98669434 1675.00683594
1675.64379883 1678.5567627 1678.24157715 1673.58984375 1667.44628906
1651.3112793 1635.78564453 1626.48413086 1616.28967285 1594.74316406
1580.41320801 1579.52124023 1506.03894043 1504.37646484 1501.6105957
1500.49902344 1316.76806641 1491.15759277 1471.89294434 1575.55322266
1585.22619629 1594.93151855 1598.60961914 1597.25134277 1598.54443359
1599.59936523 1597.15466309 1594.58544922 1585.51281738 1582.63903809
1584.89025879 1585.41784668 1589.9654541 1592.00085449 1596.66369629
1598.4128418 1598.8269043 1600.46069336 1596.1394043 1597.69421387
1598.36975098 1582.69897461 1580.45617676 1581.58618164 1580.9498291
1578.98144531 1575.28918457 1494.18151855 1490.47180176 1490.87109375
1485.41467285 1480.73291016 1481.30554199 1485.26867676 1489.50756836
1497.07263184 1501.99645996 1505.94543457 1267.27172852 1270.28515625
1281.31738281 1568.67248535 1576.60119629 1578.63500977 1577.04748535
1577.89709473 1681.06103516 1681.95227051 1684.63745117 1686.96484375
1325.62084961 1331.21569824 1331.70007324 1331.41809082 1336.49401855
1345.08703613 1351.00073242 1356.37145996 1360.50805664 1367.5177002
1371.69470215 1376.6151123 1377.27844238 1376.92382812 1376.37487793
1377.03894043 1377.87390137 1378.53796387 1373.02990723 1365.00915527]
In case anyone's curious. I managed to solve the issue of adapting the linked code to my dataset by converting x,y numpy arrays to lists using the list() function, then chronologically iterate over each list in get_new_vals() without introducing a parameter by using .pop(). The last piece of troubleshooting was with extend() and len() in update() function. Since extend() doesn't work on numpy.float64, I switched to append(). The variable new_xvals has no real length due to how the data is returned from get_new_vals(), so I switched np.ones(len()) to np.ones(1) because I'm moving through one data point at a time anyway. Final code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation
import os
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.animation import PillowWriter
from IPython.display import HTML
#set path to ffmpeg for animation processing
plt.rcParams['animation.ffmpeg_path'] = r'C:/Users/Bri-Guy/anaconda3/envs/pWGA/Library/bin/ffmpeg.exe'
#import video pose estimation data
video='topNoF.mp4'
DLCscorer='C:/Users/Bri-Guy/Desktop/DLC/h5/topFDLC_resnet50_topAnalysisJan20shuffle1_100000'
dataname = DLCscorer+'.h5' #can change to .csv instead; make sure to change pd.read_hdf() to pd.read_csv()
df=pd.read_hdf(os.path.join(dataname))
#get X & Y coordinates of snout bdp
scorer=df.columns.get_level_values(0)[0]
x=df[scorer]['snout']['x'].values
y=df[scorer]['snout']['y'].values
x=list(x)
y=list(y)
#set plt & axes elements (empty canvas to be iterated over)
fig, ax = plt.subplots()
ax.set_xlabel('X Axis', size = 12)
ax.set_ylabel('Y Axis', size = 12)
ax.axis([0,max(x),0,max(y)])
x_vals = []
y_vals = []
intensity = []
iterations = 1000 #set number of frames for video
t_vals = np.linspace(0,1, iterations)
#define colormap and scatterplot
colors = [[0,0,1,0],[0,0,1,0.5],[0,0.2,0.4,1], [1,0.2,0.4,1]]
cmap = LinearSegmentedColormap.from_list("", colors)
scatter = ax.scatter(x_vals,y_vals, c=[], cmap=cmap, vmin=0,vmax=1)
def get_new_vals():
xp = x.pop(0)
yp = y.pop(0)
return xp, yp
def update(t):
global x_vals, y_vals, intensity
# Get intermediate points
new_xvals, new_yvals = get_new_vals()
x_vals.append(new_xvals)
y_vals.append(new_yvals)
# Put new values in your plot
scatter.set_offsets(np.c_[x_vals,y_vals])
#calculate new color values
intensity = np.concatenate((np.array(intensity)*0.98, np.ones(1)))
scatter.set_array(intensity)
# Set title
ax.set_title('title')
ani = matplotlib.animation.FuncAnimation(fig, update, frames=t_vals,interval=50)
html = ani.to_html5_video() #necessary to view anim in Jup.NoteBook
HTML(html)

python matplotlib shared xlabel description / title on multiple subplots for animation

I'm using the following code to produce an animation with matplotlib that is intended to visualize my experiments.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation, PillowWriter
plt.rcParams['animation.html'] = 'jshtml'
def make_grid(X, description=None, labels=None, title_fmt="label: {}", cmap='gray', ncols=3, colors=None):
L = len(X)
nrows = -(-L // ncols)
frame_plot = []
for i in range(L):
plt.subplot(nrows, ncols, i + 1)
im = plt.imshow(X[i].squeeze(), cmap=cmap, interpolation='none')
if labels is not None:
color = 'k' if colors is None else colors[i]
plt.title(title_fmt.format(labels[i]), color=color)
plt.xticks([])
plt.yticks([])
frame_plot.append(im)
return frame_plot
def animate_step(X):
return X ** 2
n_splots = 6
X = np.random.random((n_splots,32,32,3))
Y = X
X_t = []
for i in range(10):
Y = animate_step(Y)
X_t.append((Y, i))
frames = []
for X, step in X_t:
frame = make_grid(X,
description="step={}".format(step),
labels=range(n_splots),
title_fmt="target: {}")
frames.append(frame)
anim = ArtistAnimation(plt.gcf(), frames,
interval=300, repeat_delay=8000, blit=True)
plt.close()
anim.save("test.gif", writer=PillowWriter())
anim
The result can be seen here:
https://i.stack.imgur.com/OaOsf.gif
It works fine so far, but I'm having trouble getting a shared xlabel to add a description for all of the 6 subplots in the animation. It is supposed to show what step the image is on, i.e. "step=5".
Since it is an animation, I cannot use xlabel or set_title (since it would be constant over the whole animation) and have to draw the text myself.
I've tried something along the lines of..
def make_grid(X, description=None, labels=None, title_fmt="label: {}", cmap='gray', ncols=3, colors=None):
L = len(X)
nrows = -(-L // ncols)
frame_plot = []
desc = plt.text(0.5, .04, description,
size=plt.rcparams["axes.titlesize"],
ha="center",
transform=plt.gca().transAxes
)
frame_plot.append(desc)
...
This, of course, won't work, because the axes are not yet created. I tried using the axis of another subplot(nrows, 1, nrows), but then the existing images are drawn over..
Does anyone have a solution to this?
Edit:
unclean, hacky solution for now:
Wait for the axes of the middle image of the last row to be created and use that for plotting the text.
In the for loop:
...
if i == int((nrows - 0.5) * ncols):
title = ax.text(0.25, -.3, description,
size=plt.rcParams["axes.titlesize"],
# ha="center",
transform=ax.transAxes
)
frame_plot.append(title)
...
To me, your case is easier to solve with FuncAnimation instead of ArtistAnimation, even if you already have access to the full list of data you want to show animated (see this thread for a discussion about the difference between the two functions).
Inspired from this FuncAnimation example, I wrote the code below that does what you needed (using the same code with ArtistAnimation and correct list of arguments does not work).
The main idea is to initialize all elements to be animated at the beginning, and to update them over the animation frames. This can be done for the text object (step_txt = fig.text(...)) in charge of displaying the current step, and for the images out from ax.imshow. You can then update whatever object you would like to see animated with this recipe.
Note that the technique works if you want the text to be an x_label or any text you choose to show. See the commented line in the code.
#!/Users/seydoux/anaconda3/envs/jupyter/bin/python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter
# parameters
n_frames = 10
n_splots = 6
n_cols = 3
n_rows = n_splots // n_cols
def update_data(x):
return x ** 2
# create all snapshots
snapshots = [np.random.rand(n_splots, 32, 32, 3)]
for _ in range(n_frames):
snapshots.append(update_data(snapshots[-1]))
# initialize figure and static elements
fig, axes = plt.subplots(2, 3)
axes = axes.ravel() # so we can access all axes with a single index
for i, ax in enumerate(axes):
ax.set_xticks([])
ax.set_yticks([])
ax.set_title("target: {}".format(i))
# initialize elements to be animated
step_txt = fig.text(0.5, 0.95, "step: 0", ha="center", weight="bold")
# step_txt = axes[4].set_xlabel("step: 0") # also works with x_label
imgs = list()
for a, s in zip(axes, snapshots[0]):
imgs.append(a.imshow(s, interpolation="none", cmap="gray"))
# animation function
def animate(i):
# update images
for img, s in zip(imgs, snapshots[i]):
img.set_data(s)
# update text
step_txt.set_text("step: {}".format(i))
# etc
anim = FuncAnimation(fig, animate, frames=n_frames, interval=300)
anim.save("test.gif", writer=PillowWriter())
Here is the output I got from the above code:

Plot doesn't refresh to plot new points when using matplotlib

I'm trying to create a plot that updates when given a set of points ([x,y]) but the figure gets stuck on the first plot points and won't plot the rest of the data. I looped a function call but it gets stuck on the first call. I need to be able to give the function multiple sets of single x and y values, and have them plot in a graph.
This is the code I have so far.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
from numpy import *
from time import sleep
import random as rd
class graphUpdater():
def __init__(self):
# Initialize arrays to be plotted
self.xs = []
self.ys = []
style.use('fivethirtyeight') # Figure Style
self.fig = plt.figure() # Initialize figure
self.ax1 = self.fig.add_subplot(111) # Create a subplot
# Ensure the figure auto-scales to fit all points. Might be overkill
self.ax1.set_autoscalex_on(True)
self.ax1.set_autoscaley_on(True)
self.ax1.set_autoscale_on(True)
self.ax1.autoscale(enable = True, axis = 'both', tight = False)
self.ax1.autoscale_view(False, True, True)
# Function that plots the arrays xs and ys. Also plots a linear regression of the data
def plotPoint(self):
self.ax1.clear() # Clears previous values to save memory
xp = linspace(min(self.xs), max(self.xs)) # x-range for regression
if(len(self.xs) > 1): # Conditional for regression, can't linearise 1 point
p1 = polyfit(self.xs, self.ys, 1) # Get the coefficients of the polynomial (slope of line)
self.ax1.plot(xp, polyval(p1, xp)) # Plot the line
self.ax1.plot(self.xs, self.ys, "+") # Plot the raw data points
self.ax1.set_xlabel('(L/A)*I') # Axis and title labels
self.ax1.set_ylabel('V')
self.ax1.set_title('DC Potential Drop')
def appendPlot(self, x, y):
self.xs.append(float(x)) # Append xs with x value
self.ys.append(float(y)) # Append ys with y value
self.plotPoint() # Call the plotPoint function to plot new array values
plt.show(block=False) # Plot and release so graphs can be over written
# Call the function
plsWork = graphUpdater() # I'm very hopeful
i = 0
while(i < 50):
plsWork.appendPlot(i, rd.randint(0, 20))
i += 1
sleep(0.1)
quit_case = input("Hit 'Enter' to Quit") # Conditional so the plot won't disappear
It doesn't work fully. If you put a breakpoint on the quit_case line and run it on debugger on pycharm it plots the graph "properly".
Don't use plt.show(block=False) and don't use time.sleep. Instead, matplotlib provides an animation module, which can be used to avoid such problems as here.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
from numpy import *
from time import sleep
import random as rd
#%matplotlib notebook use in case of running this in a Jupyter notebook
class graphUpdater():
def __init__(self):
# Initialize arrays to be plotted
self.xs = []
self.ys = []
style.use('fivethirtyeight') # Figure Style
self.fig = plt.figure() # Initialize figure
self.ax1 = self.fig.add_subplot(111) # Create a subplot
# Ensure the figure auto-scales to fit all points. Might be overkill
self.ax1.set_autoscalex_on(True)
self.ax1.set_autoscaley_on(True)
self.ax1.set_autoscale_on(True)
self.ax1.autoscale(enable = True, axis = 'both', tight = False)
self.ax1.autoscale_view(False, True, True)
# Function that plots the arrays xs and ys. Also plots a linear regression of the data
def plotPoint(self):
self.ax1.clear() # Clears previous values to save memory
xp = linspace(min(self.xs), max(self.xs)) # x-range for regression
if(len(self.xs) > 1): # Conditional for regression, can't linearise 1 point
p1 = polyfit(self.xs, self.ys, 1) # Get the coefficients of the polynomial (slope of line)
self.ax1.plot(xp, polyval(p1, xp)) # Plot the line
self.ax1.plot(self.xs, self.ys, "+") # Plot the raw data points
self.ax1.set_xlabel('(L/A)*I') # Axis and title labels
self.ax1.set_ylabel('V')
self.ax1.set_title('DC Potential Drop')
def appendPlot(self, x, y):
self.xs.append(float(x)) # Append xs with x value
self.ys.append(float(y)) # Append ys with y value
self.plotPoint() # Call the plotPoint function to plot new array values
# Call the function
plsWork = graphUpdater() # I'm very hopeful
f = lambda i: plsWork.appendPlot(i, rd.randint(0, 20))
ani = animation.FuncAnimation(plsWork.fig, f, frames=50, interval=100, repeat=False)
plt.show()

Plot dynamically changing graph using matplotlib in Jupyter Notebook

I have a M x N 2D array: ith row represents that value of N points at time i.
I want to visualize the points [1 row of the array] in the form of a graph where the values get updated after a small interval. Thus the graph shows 1 row at a time, then update the values to next row, so on and so forth.
I want to do this in a jupyter notebook. Looking for reference codes.
I tried following things but no success:
http://community.plot.ly/t/updating-graph-with-new-data-every-100-ms-or-so/812
https://pythonprogramming.net/live-graphs-matplotlib-tutorial/
Create dynamic updated graph with Python
Update Lines in matplotlib
Here's an alternative, possibly simpler solution:
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
m = 100
n = 100
matrix = np.random.normal(0,1,m*n).reshape(m,n)
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion()
fig.show()
fig.canvas.draw()
for i in range(0,100):
ax.clear()
ax.plot(matrix[i,:])
fig.canvas.draw()
I had been particularly looking for a good answer for the scenario where one thread is pumping data and we want Jupyter notebook to keep updating graph without blocking anything. After looking through about dozen or so related answers, here are some of the findings:
Caution
Do not use below magic if you want a live graph. The graph update does not work if the notebook uses below:
%load_ext autoreload
%autoreload 2
You need below magic in your notebook before you import matplotlib:
%matplotlib notebook
Method 1: Using FuncAnimation
This has a disadvantage that graph update occurs even if your data hasn't been updated yet. Below example shows another thread updating data while Jupyter notebook updating graph through FuncAnimation.
%matplotlib notebook
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from random import randrange
from threading import Thread
import time
class LiveGraph:
def __init__(self):
self.x_data, self.y_data = [], []
self.figure = plt.figure()
self.line, = plt.plot(self.x_data, self.y_data)
self.animation = FuncAnimation(self.figure, self.update, interval=1000)
self.th = Thread(target=self.thread_f, daemon=True)
self.th.start()
def update(self, frame):
self.line.set_data(self.x_data, self.y_data)
self.figure.gca().relim()
self.figure.gca().autoscale_view()
return self.line,
def show(self):
plt.show()
def thread_f(self):
x = 0
while True:
self.x_data.append(x)
x += 1
self.y_data.append(randrange(0, 100))
time.sleep(1)
g = LiveGraph()
g.show()
Method 2: Direct Update
The second method is to update the graph as data arrives from another thread. This is risky because matplotlib is not thread safe but it does seem to work as long as there is only one thread doing updates.
%matplotlib notebook
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from random import randrange
from threading import Thread
import time
class LiveGraph:
def __init__(self):
self.x_data, self.y_data = [], []
self.figure = plt.figure()
self.line, = plt.plot(self.x_data, self.y_data)
self.th = Thread(target=self.thread_f, daemon=True)
self.th.start()
def update_graph(self):
self.line.set_data(self.x_data, self.y_data)
self.figure.gca().relim()
self.figure.gca().autoscale_view()
def show(self):
plt.show()
def thread_f(self):
x = 0
while True:
self.x_data.append(x)
x += 1
self.y_data.append(randrange(0, 100))
self.update_graph()
time.sleep(1)
from live_graph import LiveGraph
g = LiveGraph()
g.show()
I explored this and produced the following which is largely self-documenting:
import matplotlib.pyplot as plt
%matplotlib notebook
print('This text appears above the figures')
fig1 = plt.figure(num='DORMANT')
print('This text appears betweeen the figures')
fig2 = plt.figure()
print('This text appears below the figures')
fig1.canvas.set_window_title('Canvas active title')
fig1.suptitle('Figure title', fontsize=20)
# Create plots inside the figures
ax1 = fig1.add_subplot(111)
ax1.set_xlabel('x label')
ax2 = fig2.add_subplot(111)
# Loop to update figures
end = 40
for i in range(end):
ax2.cla() # Clear only 2nd figure's axes, figure 1 is ADDITIVE
ax1.set_title('Axes title') # Reset as removed by cla()
ax1.plot(range(i,end), (i,)*(end-i))
ax2.plot(range(i,end), range(i,end), 'rx')
fig1.canvas.draw()
fig2.canvas.draw()
Another simple solution, based on IPython.display functions display and clear_output. I found it here. Here is the code (based on #graham-s's answer):
from IPython.display import display, clear_output
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
m = 100
n = 100
matrix = np.random.normal(0, 1, size=(m, n))
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(m):
ax.clear()
ax.plot(matrix[i, :])
display(fig)
clear_output(wait=True)
plt.pause(0.2)
It uses %matplotlib inline instead of notebook, and does not produce small image as mentioned by #MasterScrat. Works both in Jupyter Notebook and in Jupyter Lab. Sometimes image blinks that's not very nice, but usable for quick investigations.
If you need to keep axes ranges between different frames, add ax.set_xlim/ax.set_ylim after ax.clear().
With a moderate modification of #Shital Shah's solution, I've created a more general framework which can simply apply to various scenario:
import matplotlib
from matplotlib import pyplot as plt
class LiveLine:
def __init__(self, graph, fmt=''):
# LiveGraph object
self.graph = graph
# instant line
self.line, = self.graph.ax.plot([], [], fmt)
# holder of new lines
self.lines = []
def update(self, x_data, y_data):
# update the instant line
self.line.set_data(x_data, y_data)
self.graph.update_graph()
def addtive_plot(self, x_data, y_data, fmt=''):
# add new line in the same figure
line, = self.graph.ax.plot(x_data, y_data, fmt)
# store line in lines holder
self.lines.append(line)
# update figure
self.graph.update_graph()
# return line index
return self.lines.index(line)
def update_indexed_line(self, index, x_data, y_data):
# use index to update that line
self.lines[index].set_data(x_data, y_data)
self.graph.update_graph()
class LiveGraph:
def __init__(self, backend='nbAgg', figure_arg={}, window_title=None,
suptitle_arg={'t':None}, ax_label={'x':'', 'y':''}, ax_title=None):
# save current backend for later restore
self.origin_backend = matplotlib.get_backend()
# check if current backend meets target backend
if self.origin_backend != backend:
print("original backend:", self.origin_backend)
# matplotlib.use('nbAgg',warn=False, force=True)
plt.switch_backend(backend)
print("switch to backend:", matplotlib.get_backend())
# set figure
self.figure = plt.figure(**figure_arg)
self.figure.canvas.set_window_title(window_title)
self.figure.suptitle(**suptitle_arg)
# set axis
self.ax = self.figure.add_subplot(111)
self.ax.set_xlabel(ax_label['x'])
self.ax.set_ylabel(ax_label['y'])
self.ax.set_title(ax_title)
# holder of lines
self.lines = []
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
# check if current beckend meets original backend, if not, restore it
if matplotlib.get_backend() != self.origin_backend:
# matplotlib.use(self.origin_backend,warn=False, force=True)
plt.switch_backend(self.origin_backend)
print("restore to backend:", matplotlib.get_backend())
def add_line(self, fmt=''):
line = LiveLine(graph=self, fmt=fmt)
self.lines.append(line)
return line
def update_graph(self):
self.figure.gca().relim()
self.figure.gca().autoscale_view()
self.figure.canvas.draw()
With above 2 class, you can simply reproduce #Graham S's example:
import numpy as np
m = 100
n = 100
matrix = np.random.normal(0,1,m*n).reshape(m,n)
with LiveGraph(backend='nbAgg') as h:
line1 = h.add_line()
for i in range(0,100):
line1.update(range(len(matrix[i,:])), matrix[i,:])
Note that, the default backend is nbAgg, you can pass other backend like qt5Agg. When it is finished, it'll restore to your original backend.
and #Tom Hale's example:
with LiveGraph(figure_arg={'num':'DORMANT2'}, window_title='Canvas active title',
suptitle_arg={'t':'Figure title','fontsize':20},
ax_label={'x':'x label', 'y':''}, ax_title='Axes title') as g:
with LiveGraph() as h:
line1 = g.add_line()
line2 = h.add_line('rx')
end = 40
for i in range(end):
line1.addtive_plot(range(i,end), (i,)*(end-i))
line2.update(range(i,end), range(i,end))
Also, you can update particular line in the additive plot of #Tom Hale's example:
import numpy as np
with LiveGraph(figure_arg={'num':'DORMANT3'}, window_title='Canvas active title',
suptitle_arg={'t':'Figure title','fontsize':20},
ax_label={'x':'x label', 'y':''}, ax_title='Axes title') as g:
line1 = g.add_line()
end = 40
for i in range(end):
line_index = line1.addtive_plot(range(i,end), (i,)*(end-i))
for i in range(100):
j = int(20*(1+np.cos(i)))
# update line of index line_index
line1.update_indexed_line(line_index, range(j,end), (line_index,)*(end-j))
Note that, the second for loop is just for updating a particular line with index line_index. you can change that index to other line's index.
In my case, I use it in machine learning training loop to progressively update learning curve.
import numpy as np
import time
# create a LiveGraph object
g = LiveGraph()
# add 2 lines
line1 = g.add_line()
line2 = g.add_line()
# create 2 list to receive training result
list1 = []
list2 = []
# training loop
for i in range(100):
# just training
time.sleep(0.1)
# get training result
list1.append(np.random.normal())
list2.append(np.random.normal())
# update learning curve
line1.update(np.arange(len(list1)), list1)
line2.update(np.arange(len(list2)), list2)
# don't forget to close
g.close()
In addition to #0aslam0 I used code from here. I've just changed animate function to get next row every next time. It draws animated evolution (M steps) of all N points.
from IPython.display import HTML
import numpy as np
from matplotlib import animation
N = 5
M = 100
points_evo_array = np.random.rand(M,N)
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, M), ylim=(0, np.max(points_evo_array)))
lines = []
lines = [ax.plot([], [])[0] for _ in range(N)]
def init():
for line in lines:
line.set_data([], [])
return lines
def animate(i):
for j,line in enumerate(lines):
line.set_data(range(i), [points_evo_array[:i,j]])
return lines
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate,np.arange(1, M), init_func=init, interval=10, blit=True)
HTML(anim.to_html5_video())
Hope it will be useful
Here is a library that deals with real-time plotting/logging data (joystick), although I am not sure it is working with jupyter. You can install it using the usual pip install joystick.
Hard to make a working solution without more details on your data. Here is an option:
import joystick as jk
import numpy as np
class test(jk.Joystick):
# initialize the infinite loop decorator
_infinite_loop = jk.deco_infinite_loop()
def _init(self, *args, **kwargs):
"""
Function called at initialization, see the docs
"""
# INIT DATA HERE
self.shape = (10, 4) # M, N
self.data = np.random.random(self.shape)
self.xaxis = range(self.shape[1])
############
# create a graph frame
self.mygraph = self.add_frame(
jk.Graph(name="TheName", size=(500, 500), pos=(50, 50),
fmt="go-", xnpts=self.shape[1], freq_up=5, bgcol="w",
xylim=(0, self.shape[1]-1, None, None)))
#_infinite_loop(wait_time=0.5)
def _generate_fake_data(self): # function looped every 0.5 second
"""
Loop starting with the simulation start, getting data and
pushing it to the graph every 0.5 seconds
"""
# NEW (RANDOM) DATA
new_data = np.random.random(self.shape[1])
# concatenate data
self.data = np.vstack((self.data, new_data))
# push new data to the graph
self.mygraph.set_xydata(self.xaxis, self.data[-1])
t = test()
t.start()
t.stop()
t.exit()
This code will create a graph that is auto-updating 5 times a second (freq_up=5), while new data is (randomly) generated every 0.5 seconds (wait_time=0.5) and pushed to the graph for display.
If you don't want the Y-axis to wiggle around, type t.mygraph.xylim = (0, t.shape[1]-1, 0, 1).
I don't know much about matplotlib or jupyter. However, Graphs interest me. I just did some googling and came across this post. Seems like you have to render the graph as an HTML video to see a dynamic graph.
I tried that post. This is the notebook, if you wish to try. Note that the kernel (python 2) takes sometime to build the video. You can read more about it here.
Now you want to display a graph row to row. I tried this. In that notebook, I have a dump_data with 10 rows. I randomly take one and plot them and display as video.
It was interesting to learn about jupyter. Hope this helps.

Animation of pcolormesh() with mpl_toolkit.basemap giving attribute error

I am trying to animate some density data on a basemap map. Following an approach as was done in [this SO question][1], I get the following error:
/usr/local/lib/python2.7/dist-packages/matplotlib/collections.pyc in update_scalarmappable(self)
627 if self._A is None:
628 return
--> 629 if self._A.ndim > 1:
630 raise ValueError('Collections can only map rank 1 arrays')
631 if not self.check_update("array"):
AttributeError: 'list' object has no attribute 'ndim'
If I instead set the data in init() with null values by self.quad.set_array(self.z.ravel()), I end up with two plotted maps with no data being animated.
Any light that anybody could shed on what I'm doing wrong would be greatly appreciated. Thanks!
example code:
def plot_pcolor(lons,lats):
class UpdateQuad(object):
def __init__(self,ax, map_object, lons, lats):
self.ax = ax
self.m = map_object
self.lons = lons
self.lats = lats
self.ydim, self.xdim = lons.shape
self.z = np.zeros((self.ydim-1,self.xdim-1))
x, y = self.m(lons, lats)
self.quad = ax.pcolormesh(x, y, self.z, cmap=plt.cm.Reds)
def init(self):
print 'update init'
self.quad.set_array([])
return self.quad
def __call__(self,i):
data = np.zeros((self.ydim-1,self.xdim-1))
for i in range(self.ydim-1):
for j in range(self.xdim-1):
data[i,j]=random.random()+4
self.quad.set_array(data.ravel())
return self.quad
fig = plt.figure()
ax = fig.add_axes([0.1,0.1,0.8,0.8])
m = Basemap(width=2000000,height=2000000,
resolution='l', projection='laea',\
lat_ts=10.,\
lat_0=64.,lon_0=10., ax=ax)
m.fillcontinents()
ud = UpdateQuad(ax, m, lons, lats)
anim = animation.FuncAnimation(fig, ud, init_func=ud.init,
frames=20, blit=False)
plt.show()
if __name__ == '__main__':
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.basemap import Basemap
import numpy as np
import random
lons = np.linspace(-5.,25., num = 25)[:50]
lats = np.linspace(56., 71., num = 25)[:50]
lons,lats = np.meshgrid(lons,lats)
plot_pcolor(lons,lats)
It looks like the set_data method should require an ndarray (not sure why the example I had followed was working correctly).
So in the init() function, you should use quad.set_array(np.array([])) rather than quad.set_array([]).
Other problems to be aware of:
As mentioned before, you also want set blit=False in your FuncAnimation() call.
I was also experiencing problems when I set the quad artist attribute animated to True. Leave that be (i.e. quad.set_animated(False), which is the default anyway).
If you do not specify the bounds via norm in your first pcolormesh() call, it will set them according to the data you pass (in my case null), which resulting in my getting blank animations. Setting them according to the data you will animate later in the initial call prevented this problem in my case.
pcolormesh() takes the bounding positions to the data field, which should be +1 in the y and x dimension of the data array. If the data array is equal (or greater than) the dimensions of the position data, pcolormesh() will omit any data outside of this boundary requirement. I thought that my data would just appear offset by one grid cell, but everything was all whacky before I passed the correct boundary positions. See another question of mine for calculating these HERE.
Older versions of matplotlib do not have very good error reporting. I recommend upgrading to the latest version if that is an option for you.
Some random trouble-shooting:
After updating matplotlib and basemap and attempting to implement this in my existing plotting routine, I received the following error:
ValueError: All values in the dash list must be positive
I first thought it had to do with my pcolormesh() objects, but it took me way too long to discover that it was due to my previous setting of the dash attribute in my m.drawmeridians() call to dashes=[1,0] for a solid meridian. In the new version of matplotlib the handling of dashes was changed to give this error. The new prefered method for setting a solid line for the dash attribute is dashes=(None,None), which I don't like.
Resulting animation:
Code example for above output:
def plot_pcolor(lons,lats):
class UpdateQuad(object):
def __init__(self,ax, map_object, lons, lats):
self.ax = ax
self.m = map_object
self.lons = lons
self.lats = lats
vmin = 0
vmax = 1
self.ydim, self.xdim = lons.shape
self.z = np.zeros((self.ydim-1,self.xdim-1))
levels = MaxNLocator(nbins=15).tick_values(vmin,vmax)
cmap = plt.cm.cool
norm = BoundaryNorm(levels, ncolors=cmap.N, clip=True)
x, y = self.m(lons, lats)
self.quad = self.ax.pcolormesh(x, y, self.z, alpha=0.9,
norm=norm, cmap=cmap,
vmin=vmin, vmax=vmax)
def init(self):
print 'update init'
self.quad.set_array(np.asarray([]))
return self.quad
def __call__(self,i):
for i in range(self.ydim-1):
for j in range(self.xdim-1):
self.z[i,j]=random.random()
self.quad.set_array(self.z.ravel())
return self.quad
fig, ax = plt.subplots()
m = Basemap(width=2000000,height=2000000,
resolution='l', projection='laea',\
lat_ts=10.,\
lat_0=64.,lon_0=10., ax=ax)
m.fillcontinents()
ud = UpdateQuad(ax, m, lons, lats)
anim = animation.FuncAnimation(fig, ud, init_func=ud.init,
frames=20, blit=False)
fig.tight_layout()
plt.show()
return ud.quad
if __name__ == '__main__':
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.basemap import Basemap
import numpy as np
import random
from matplotlib.colors import BoundaryNorm
from matplotlib.ticker import MaxNLocator
lons = np.linspace(-5.,25., num = 25)[:50]
lats = np.linspace(56., 71., num = 25)[:50]
lons,lats = np.meshgrid(lons,lats)
quad = plot_pcolor(lons,lats)

Categories