Saving Animation in matplotlib of Progress Line - python

Objective: To save a plotted graph(x,y) with a moving a vertical line over the graph w.r.t to timer.
I've built a simple graph with a moving vertical progress line basically identical to this example, but I now need to save it. I do not need the functionality to relocate the progress bar on-click, only to have the progress bar move along the data. I'm not sure where to call the save function because this code does not rely on FuncAnimation.
import sys
import matplotlib.pyplot as plt
import time
import numpy
fig = plt.figure()
ax = fig.add_subplot(111)
max_height = 100
n_pts = 100
y1 = [0, max_height]
x1 = [0, 0]
y = numpy.random.randn(n_pts) * max_height
x = numpy.arange(0, n_pts)
# draw the data
line1, = ax.plot(x, y, color='black')
# fix the limits of the plot
ax.set_ylim(0, max_height)
ax.set_xlim(0, n_pts)
# draw the plot so that we can capture the background and then use blitting
plt.show(block=False)
# get the canvas object
canvas = ax.figure.canvas
background = canvas.copy_from_bbox(ax.bbox)
# add the progress line.
# XXX consider using axvline
line, = ax.plot(x1, y1, color='r', animated=True)
starttime=time.time()
mytimer=0
mytimer_ref=0
def update(canvas, line, ax):
# revert the canvas to the state before any progress line was drawn
canvas.restore_region(background)
# compute the distance that the progress line has made (based on running time)
t = time.time() - starttime
mytimer = t + mytimer_ref
x1 = [mytimer,mytimer]
# update the progress line with its new position
line.set_xdata(x1)
# draw the line, and blit the axes
ax.draw_artist(line)
canvas.blit(ax.bbox)
timer=fig.canvas.new_timer(interval=100)
args=[canvas,line,ax]
timer.add_callback(update,*args) # every 100ms it calls update function
timer.start()
plt.show()

Related

How to draw a second line on this specific way to plot?

I normally plot graphs in a completely different way, which doesn't work here. Here, the y data is defined by set_ydata. So, I don't see a way to have a second graph in the same plot. Does someone have an idea how to achieve it here?
I tried
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
import numpy as np
from matplotlib import pyplot as plt
import serial
stroke=30
conrod=75
bore=40
def update_plot():
y = 59 * np.random.rand(50)
lines[0].set_ydata(array_pressure_1)
canvas.draw()
window.after(25, update_plot) # run again after 25ms (1000ms/25ms = 40 FPS (frames per second))
def main():
global window
global canvas
global lines
#below has to be fiddled with for axis scale
x = np.linspace(0, stroke, 50)
y = 100 * np.random.rand(50)
window = tk.Tk()
window.title('Plotting in Tkinter')
window.geometry("500x500")
#The canvas the figure is drawn on:
fig = Figure(figsize = (4, 4), dpi = 100)
canvas = FigureCanvasTkAgg(fig, master = window)
canvas.draw()
canvas.get_tk_widget().pack()
plot1 = fig.add_subplot(111)
lines = plot1.plot(x, y)
window.after(25, update_plot) # run first time after 25ms
update_plot() # run first time at once
window.mainloop()
if __name__ == '__main__':
main()
I've slightly adapted your code to include new lines... and I've introduced the concept of blitting which will greatly speed up your animations!
(I also re-named plot1 to ax which is the commonly used name for matplotlib axes)
The general concept is the following:
a call to ax.plot() will return a set with 1 new artist
(to directly unpack it use something like l, = ax.plot())
now simply create as much lines as you need,
put them in some container (list, dict etc.)
and then use l.set_xdata(), l.set_color() etc. to update the artist
to avoid re-drawing the whole image all the time, use blitting (e.g. cache the background and only re-draw animated artists on top)
here's a working example with multiple lines:
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
import numpy as np
from matplotlib import pyplot as plt
# import serial
stroke=30
conrod=75
bore=40
lines = []
def update_plot():
global lines
global ax
global bg
y = 59 * np.random.rand(50)
# update the lines
lines[0].set_ydata(y)
lines[1].set_ydata(y + np.random.normal(5,10))
lines[2].set_ydata(y - np.random.normal(5,10))
# restore the background
canvas.restore_region(bg)
# re-draw only the artists that have changed
for l in lines:
ax.draw_artist(l)
# efficiently re-draw the image using "blitting"
canvas.blit(ax.bbox)
window.after(25, update_plot) # run again after 25ms (1000ms/25ms = 40 FPS (frames per second))
def main():
global window
global canvas
global lines
global ax
global bg
#below has to be fiddled with for axis scale
x = np.linspace(0, stroke, 50)
y = 100 * np.random.rand(50)
window = tk.Tk()
window.title('Plotting in Tkinter')
window.geometry("500x500")
#The canvas the figure is drawn on:
fig = Figure(figsize = (4, 4), dpi = 100)
canvas = FigureCanvasTkAgg(fig, master = window)
canvas.get_tk_widget().pack()
# add a new axes
ax = fig.add_subplot(111)
ax.set_ylim(-20,100)
# plot a static line on the background
static_l1 = ax.plot(x, y/2, c="r", lw=4)
# draw everything that goes on the background
canvas.draw()
# cache the background
bg = fig.canvas.copy_from_bbox(ax.bbox)
# add some lines (will be updated later)
l1, = ax.plot(x, y)
lines.append(l1)
l2, = ax.plot(x, y)
lines.append(l2)
l3, = ax.plot(x, y)
lines.append(l3)
# set dynamically updated lines to "animated"
# (e.g. exclude them from ordinary draw-events)
for l in lines:
l.set_animated(True)
window.after(25, update_plot) # run first time after 25ms
update_plot() # run first time at once
window.mainloop()
if __name__ == '__main__':
main()

Add timer/counter of frames to animated plot in python (using imshow)

I want to plot spatial data that depends on time. I already have an animated plot, similar to animated plot, and I would like to add a timer/counter of the frames. The timer should not show the current time but rather the time of the data (so to make it easier it could be e.g. a counter of frames).
--- update ---
I added a text to the code but I don't know how to update the text for each frame.
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
def f(x, y):
return np.sin(x) + np.cos(y)
x = np.linspace(0, 2 * np.pi, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
# ims is a list of lists, each row is a list of artists to draw in the
# current frame; here we are just animating one artist, the image, in
# each frame
ims = []
for i in range(60):
x += np.pi / 15.
y += np.pi / 20.
# edit: add text
ttl = plt.text(0.5, 1.01, str(i),
horizontalalignment='center',
verticalalignment='bottom',
transform=ax.transAxes)
im = plt.imshow(f(x, y), animated=True)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
# ani.save('dynamic_images.mp4')
plt.show()
I thought it might be the easiest approach to add a title to each frame but I don't know how to do that. And maybe there is an easier way?

Animating a time-dependent LineCollection using matplotlib

As stated above, I am trying to animate a set of data that varies over time (position). I would like my graph to only show the position data but animate the position history over time. I have started with this example here, and got it working. Now, instead of the whole line animating, I would like for the line to be drawn from left to right. I also need the line to be colored relative to a secondary set of data, which I have been able to accomplish with a LineCollection.
My code:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line = LineCollection([], cmap=plt.cm.jet)
line.set_array(np.linspace(0, 2, 1000))
ax.add_collection(line)
x = np.linspace(0, 2, 10000)
y = np.sin(2 * np.pi * (x))
# initialization function: plot the background of each frame
def init():
line.set_segments([])
return line,
# animation function. This is called sequentially
def animate(i, xss, yss, line):
xs = xss[:i]
ys = yss[:i]
points = np.array([xs, ys]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
line.set_segments(segments)
return line,
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, fargs=[x, y, line], init_func=init, frames=200, interval=20)
plt.show()
I create a basic sine wave data set and again would like to animate the line being drawn from left to right. Right now, the LineCollection is being colored by the y-value of the line at the current x-position. Eventually, this will be a position data set pulled from a .csv file.
Finally, the issue. The code above runs without errors, however the line is not being drawn. I can see in my debugger that the xs and ys arrays are being added to during each step so that syntax seems to be working, just the updated LineCollection is not being displayed.
I am working on macOS Mojave 10.14.6.
Your code is correct, the line you're plotting is just very small. This is because the function you animate is given by
x = np.linspace(0, 2, 10000) # Note that `num=10000`
y = np.sin(2 * np.pi * (x))
which has 10000 points, but you only animate the first 200 points.
anim = animation.FuncAnimation(..., frames=200, interval=20)
Easy fix
num_frames = 200
x = np.linspace(0, 2, num_frames)
...
anim = animation.FuncAnimation(..., frames=num_frames, interval=20)

How to dynamically display time data stream on matplotlib

I'm using matplotlib to display data saved on a csv file periodically,
now the data is plotted well but the time axis is hardly moving, in fact
the script is trying to show all the data stored on that file, I want to see only latest data and be able to scrol horizontaly to see older data
this is a part of the script :
style.use('grayscale')
fig = plt.figure()
ax0= fig.add_subplot(511)
def animate(i):
graph_data = open('filelocation','r').read()
lines = graph_data.split('\n')
xs = []
ys = []
for line in lines :
if len(line)>1:
time0 , quantity0 = line.split (',')
xs.append(dt.datetime.strptime(time0,'%H:%M:%S.%f'))
ys.append(quantity0)
ax0.clear()
ax0.plot(xs,ys)
xs = matplotlib.dates.date2num(xs)
hfmt = matplotlib.dates.DateFormatter('%H:%M:%S')
ax0.ticklabel_format(style='sci', axis='y', scilimits=(0, 0))
ax0.set_ylabel('risk')
ax0.xaxis.set_major_formatter(hfmt)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.ticklabel_format(style='sci',axis ='y' , scilimits = (0,0))
plt.show()
plt.clear()
From that video, it looks like you want something like the following - I can still see a scrolling window in the video you posted so I'm still a little confused as to what you want. This uses fig.canvas.draw but there are other options using the matplotlib animation module (you didn't specify that it had to be using that module).
import numpy as np, time
from matplotlib import pyplot as plt
import matplotlib
matplotlib.interactive(True)
rest_time = 0.001
data_stream = np.random.randn(200)
# Define the width of the viewing window (xaxis limits),
# and the number of points to be displayed until a scrolling window starts
window_width = 40
n = 60
fig, ax = plt.subplots()
plotted_data, = ax.plot([], [], 'bo-', lw = 1.5)
x = []
y = []
ax.set_xlim([0, window_width])
ax.set_ylim([-4, 4])
for t, d in enumerate(data_stream):
x.append(t)
y.append(d)
plotted_data.set_xdata(x)
plotted_data.set_ydata(y)
if t > window_width:
ax.set_xlim([0, t])
if len(x) > n:
ax.set_xlim([t-n, t])
time.sleep(rest_time)
fig.canvas.draw()

Clearing background in matplotlib using wxPython

I want to create an animation with matplotlib to monitor the convergence of a clustering algorithm. It should draw a scatterplot of my data when called the first time and draw error ellipses each time the plot is updated. I am trying to use canvas_copy_from_bbox() and restore_region() to save the scatterplot and then draw a new set of ellipses whenever I'm updating the plot.
However, the code just plots the new ellipses on top of the old ones, without clearing the previous plot first.
I suspect, somehow this approach doesn't work well with the Ellipse() and add_path() commands, but I don't know how to fix this.
Here is the code:
import wx
import math
from math import pi
from matplotlib.patches import Ellipse
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigureCanvas
TIMER_ID = wx.NewId()
class _MonitorPlot(wx.Frame):
def __init__(self, data, scale=1):
self.scale = scale
wx.Frame.__init__(self, None, wx.ID_ANY,
title="FlowVB Progress Monitor", size=(800, 600))
self.fig = Figure((8, 6), 100)
self.canvas = FigureCanvas(self, wx.ID_ANY, self.fig)
self.ax = self.fig.add_subplot(111)
x_lims = [data[:, 0].min(), data[:, 0].max()]
y_lims = [data[:, 1].min(), data[:, 1].max()]
self.ax.set_xlim(x_lims)
self.ax.set_ylim(y_lims)
self.ax.set_autoscale_on(False)
self.l_data = self.ax.plot(data[:, 0], data[:, 1], color='blue',
linestyle='', marker='o')
self.canvas.draw()
self.bg = self.canvas.copy_from_bbox(self.ax.bbox)
self.Bind(wx.EVT_IDLE, self._onIdle)
def update_plot(self, pos, cov):
self.canvas.restore_region(self.bg)
for k in range(pos.shape[0]):
l_center, = self.ax.plot(pos[k, 0], pos[k, 1],
color='red', marker='+')
U, s, Vh = np.linalg.svd(cov[k, :, :])
orient = math.atan2(U[1, 0], U[0, 0]) * 180 / pi
ellipsePlot = Ellipse(xy=pos[k, :], width=2.0 * math.sqrt(s[0]),
height=2.0 * math.sqrt(s[1]),
angle=orient, facecolor='none',
edgecolor='red')
self.ax.add_patch(ellipsePlot)
self.canvas.draw()
self.canvas.blit(self.ax.bbox)
What's happening is that you're adding new patches to the plot each time, and then drawing all of them when you call self.canvas.draw().
The quickest fix is to just call self.canvas.draw_artist(ellipsePlot) after adding each patch and remove the call to self.canvas.draw()
As a simple, stand-alone example:
# Animates 3 ellipses overlain on a scatterplot
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import numpy as np
num = 10
x = np.random.random(num)
y = np.random.random(num)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line = ax.plot(x, y, 'bo')
fig.canvas.draw()
bg = fig.canvas.copy_from_bbox(ax.bbox)
# Pseudo-main loop
for i in range(100):
fig.canvas.restore_region(bg)
# Make a new ellipse each time... (inefficient!)
for i in range(3):
width, height, angle = np.random.random(3)
angle *= 180
ellip = Ellipse(xy=(0.5, 0.5), width=width, height=height,
facecolor='red', angle=angle, alpha=0.5)
ax.add_patch(ellip)
ax.draw_artist(ellip)
fig.canvas.blit(ax.bbox)
However, this will probably cause memory consumptions problems over time, as the axes object will keep track of all artists added to it. If your axes doesn't hang around for a long time, this may be negligible, but you should at least be aware that it will cause a memory leak. One way around this is to remove the ellipsis artists from the axes by calling ax.remove(ellipsePlot) for each ellipse after drawing them. However, this is still slightly inefficient, as you're constantly creating and destroying ellipse artists, when you could just update them. (Creating and destroying them doesn't have much overhead at all, though, it's mostly a stylistic issue...)
If the number of ellipses is staying the same over time it's better and easier to just update the properties of each ellipse artist object instead of creating and adding new ones. This will avoid have remove "old" ellipses from the axes, as only the number that you need will ever exist.
As a simple, stand-alone example of this:
# Animates 3 ellipses overlain on a scatterplot
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import numpy as np
num = 10
x = np.random.random(num)
y = np.random.random(num)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line = ax.plot(x, y, 'bo')
fig.canvas.draw()
bg = fig.canvas.copy_from_bbox(ax.bbox)
# Make and add the ellipses the first time (won't ever be drawn)
ellipses = []
for i in range(3):
ellip = Ellipse(xy=(0.5, 0.5), width=1, height=1,
facecolor='red', alpha=0.5)
ax.add_patch(ellip)
ellipses.append(ellip)
# Pseudo-main loop
for i in range(100):
fig.canvas.restore_region(bg)
# Update the ellipse artists...
for ellip in ellipses:
ellip.width, ellip.height, ellip.angle = np.random.random(3)
ellip.angle *= 180
ax.draw_artist(ellip)
fig.canvas.blit(ax.bbox)

Categories

Resources