I have a question about using fig, axes function in a loop in matplotlib.
I am trying to create a few plots with multiple subplots (the number of subplots isn not fixed) in a loop as follows:
def start_plot(self):
if self.running:
run_fig, run_ax = plt.subplots(*self.matrix)
if self.histogram:
hist_fig, hist_ax = plt.subplots(*self.matrix)
def create_signal_plots(self, iwindow, window_name):
if self.running:
run_ax[iwindow+1].plot(running_perf, label=window_name) # throws error run_ax not recognized
if self.histogram:
hist_ax[iwindow+1].hist(timeseries, label=window_name)
plot = plot_class(run =1, hist =1, matrix = get_matrix(*args)) # e.g. matrix = (3,2)
for istrat, strat in enumerate(strats):
plot.start_plot()
for iwindow, window in enumerate(windows):
plot.create_plots(iwindow, window)
Is there a way to make this work without having to return axes in function and pass it around? If I use plt.figure instead of fix,axes, then i can simply update any figure using plt.figure(fig_no).
You can store run_fig and run_ax in your object as instance attributes and then access them from any other method of that object. This would be done using self.
Use self.run_fig, etc. in start_plot and create_signal_plots as in:
def start_plot(self):
if self.running:
self.run_fig, self.run_ax = plt.subplots(*self.matrix)
if self.histogram:
self.hist_fig, self.hist_ax = plt.subplots(*self.matrix)
def create_signal_plots(self, iwindow, window_name):
if self.running:
self.run_ax[iwindow+1].plot(running_perf, label=window_name) # throws error run_ax not recognized
if self.histogram:
self.hist_ax[iwindow+1].hist(timeseries, label=window_name)
Related
I'm working on a program that displays drawings, with the option of animating the drawing to show which order the lines should be drawn in. When I used plt.show() to display the drawing as a still image, everything works as expected: the code pauses, and then resumes again as soon as the popup window is closed. However, when I use the same function to display an animated drawing, the code remains frozen even after I close the popup window. The only way to get it unstuck is to fully restart the python shell - it doesn't respond to KeyboardInterrupt.
UPDATE:
When I set repeat to False in the FuncAnimation call, it behaves slightly differently. If I close the popup window while the animation is running, the glitch happens, locking up my program. However, if I close the popup after the animation has finished, the program continues as intended. It seems like the glitch here has something to do with closing the window before the animation is done.
UPDATE 2:
For some reason, replacing all of the plt.plot() calls in the animate_pattern function with ax.plot() fixes the issue. I have no idea why this works, because as far as I know the two functions do the same thing. However, the problem is solved.
Below is the code for the module that handles the animation. Some notes:
Normally, I create the animations by calling plot_animated() from a different module. However, the bug happens whether or not I create the animation that way or do it through the code in this module's if name == main statement.
convert_to_points() is a function from the main module that turns the data it's given into a list of x-values and a list of y-values to be plotted.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation,PillowWriter
from functools import partial
from os.path import isfile
import json
end_marker = [None]
smooth = 40
def animate_pattern(f,anim_data):
x_anim,y_anim,scale = anim_data
global end_marker
# starting point
if x_anim[f] is None:
end_marker = plt.plot(x_anim[1],y_anim[1],marker='o',ms=1.8*scale,mew=0.4*scale,mec="black",c="#ff6bff")
# segment going into a point
if f%smooth == 1:
plt.plot(x_anim[f-1:f+1],y_anim[f-1:f+1],c="#ff6bff",lw=scale)
plt.plot(x_anim[f],y_anim[f],marker='o',ms=1.8*scale,mew=0.4*scale,mec="black",c="#ff6bff")
# segment coming out of a point
elif f%smooth in (2,4):
plt.plot(x_anim[f-1:f+1],y_anim[f-1:f+1],c="#ff6bff",lw=scale)
plt.plot(x_anim[f-f%smooth+1],y_anim[f-f%smooth+1],marker='o',ms=1.8*scale,mew=0.4*scale,mec="black",c="#ff6bff")
# all other segments
else:
plt.plot(x_anim[f-1:f+1],y_anim[f-1:f+1],c="#ff6bff",lw=scale)
# marker for current endpoint of animated line
if x_anim[f]:
end_marker[0].remove()
end_marker = plt.plot(x_anim[f],y_anim[f],marker='h',ms=2.4*scale,mew=0.5*scale,mec="#547dd6",c="#6bc9e8")
def init_pattern(plot_data,settings):
x_vals,y_vals,scale = plot_data[:3]
# clear the canvas
plt.cla()
plt.gca().axis("off")
# draw the full pattern in the background
for i in range(len(x_vals)-1):
plt.plot(x_vals[i:i+2],y_vals[i:i+2],color=settings["monochrome_color"],lw=scale)
plt.plot(x_vals[i],y_vals[i],'ko',ms=2*scale)
plt.plot(x_vals[-1],y_vals[-1],'ko',ms=2*scale)
def anim_interpolate(plot_data):
x_vals,y_vals,scale = plot_data[:3]
x_anim,y_anim = [None],[None]
# create five interpolated points after each point
for i in range(len(x_vals)-1):
x_dist = x_vals[i+1] - x_vals[i]
y_dist = y_vals[i+1] - y_vals[i]
x_anim += [x_vals[i]+x_dist*(1/smooth)*j for j in range(smooth)]
y_anim += [y_vals[i]+y_dist*(1/smooth)*j for j in range(smooth)]
# add the last point
x_anim.append(x_vals[-1])
y_anim.append(y_vals[-1])
return x_anim,y_anim,scale
def plot_animated(plot_data,settings,):
# convert basic pointlist into special version for animating
anim_data = anim_interpolate(plot_data)
# create animation object by repeatedly invoking animate_pattern()
ani = FuncAnimation(plt.gcf(),
func=animate_pattern,
fargs=[anim_data],
frames=len(anim_data[0]),
init_func=partial(init_pattern,plot_data,settings),
interval=1000/smooth,
repeat=True)
return ani
if __name__ == "__main__":
with open("settings.json",mode="r") as file:
settings = json.load(file)
from hex_draw import convert_to_points
print("Displaying test animation...")
plot_data = convert_to_points("qeewdweddw","northeast",settings)
ax = plt.figure(figsize=(4,4)).add_axes([0,0,1,1])
ax.set_aspect("equal")
ani = plot_animated(plot_data,settings)
plt.show()
I wish to plot some data from an array with multiple columns, and would like each column to be a different line on the same scrolling graph. As there are many columns, I think it would make sense to plot them within a loop. I'd also like to plot a second scrolling graph with a single line.
I can get the single line graph to scroll correctly, but the graph containing the multiple lines over-plots from the updated array without clearing the previous lines.
How do I get the lines to clear within the for loop. I thought that setData, might do the clearing. Do I have to have a pg.QtGui.QApplication.processEvents() or something similar within the loop? I tried to add that call but had it no effect.
My code:
#Based on example from PyQtGraph documentation
import numpy as np
import pyqtgraph as pg
win = pg.GraphicsLayoutWidget(show=True)
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
timer = pg.QtCore.QTimer()
plot_1 = win.addPlot()
plot_2 = win.addPlot()
data1 = np.random.normal(size=(300))
curve1 = plot_1.plot(data1)
data_2d = np.random.normal(size=(3,300))
def update_plot():
global data1, data_2d
data1[:-1] = data1[1:]
data1[-1] = np.random.normal()
curve1.setData(data1)
for idx, n in enumerate(data_2d):
n[:-1] = n[1:]
n[-1] = np.random.normal()
curve2 = plot_2.plot(n,pen=(idx))
curve2.setData(n)
#pg.QtGui.QApplication.processEvents() #Does nothing
timer = pg.QtCore.QTimer()
timer.timeout.connect(update_plot)
timer.start(50)
if __name__ == '__main__':
pg.exec()
You could clear the plot of all curves each time with .clear(), but that wouldn't be very performant. A better solution would be to keep all the curve objects around and call setData on them each time, like you're doing with the single-curve plot. E.g.
curves_2d = [plot_2.plot(pen=idx) for idx, n in enumerate(data_2d)]
# ... in update_plot
curves_2d[idx].setData(n)
I am trying to animate a plot using visvis.
This is the example code they have:
import visvis as vv
# read image
ims = [vv.imread('astronaut.png')]
# make list of images: decrease red channel in subsequent images
for i in range(9):
im = ims[i].copy()
im[:,:,0] = im[:,:,0]*0.9
ims.append(im)
# create figure, axes, and data container object
a = vv.gca()
m = vv.MotionDataContainer(a)
# create textures, loading them into opengl memory, and insert into container.
for im in ims:
t = vv.imshow(im)
t.parent = m
and I added:
app = vv.use()
app.Run()
This worked. But I needed to animate a plot, not an image, so I tried doing this:
import visvis as vv
from visvis.functions import getframe
# create figure, axes, and data container object
a = vv.gca()
m = vv.MotionDataContainer(a, interval=100)
for i in range(3):
vv.plot([0, 2+i*10], [0, 2+i*10])
f = getframe(a)
t = vv.imshow(f)
t.parent = m
a.SetLimits(rangeX=[-2, 25], rangeY=[-2, 25])
app = vv.use()
app.Run()
The axes are being initialized very big, that is why I am using set limits, and the output is not animated. I am getting only the last frame so a line from (0,0) to (22, 22).
Does anyone know a way of doing this with visvis?
It turns out adding the frame as a child of MotionDataContainer was not the way to go. The function vv.plot returns an instance of the class Line, and one should add the line as a child. If anyone is having the same problem, I could write a more detailed answer.
EDIT Adding a more detailed answer as requested:
To animate a plot made of lines, one must simply add the lines as children of MotionDataContainer. Taking my example in the question above, one would write:
import visvis as vv
# create figure, axes, and data container object
a = vv.gca()
m = vv.MotionDataContainer(a, interval=100)
for i in range(3):
line = vv.plot([0, 2+i*10], [0, 2+i*10])
line.parent = m
app = vv.use()
app.Run()
In my special case, I even needed to animate multiple lines being drawn at the same time.
To do this, I ended up defining a new class that, like MotionDataContainer, also inherits from MotionMixin, and change the class attribute delta which specifies how many objects should be made visible at the same time. For that, one has to also rewrite the function _SetMotionIndex.
(See visvis official source code: https://github.com/almarklein/visvis/blob/master/wobjects/motion.py)
Disclaimer: Concerning the animation of multiple objects, I have no idea if this is the intended use or if this is the easiest solution, but this is what worked for me.
I'm trying to use ginput to register clicks on a map, and wanted to add action buttons using matplotlib widgets. In the following code, I can pass back the value of action to the main code by declaring it a global. If I click on the map, action=0, if I click on the button, action=1, as desired.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class Index:
def test(self, event):
global action
action=1
# fake data
x=np.arange(30)
y=x**2
fig,ax=plt.subplots()
ax.plot(x,y)
callback = Index()
buttonname=['test']
colors=['white']
idx=[0.2]
bax,buttons={},{}
# set up list of buttons.
for i,col,button in zip(idx,colors,buttonname):
bax[button] = plt.axes([0.92, i, 0.07, 0.07])
buttons[button] = Button(bax[button],button,color=col,hovercolor='green')
buttons[button].on_clicked(getattr(callback,button))
# register click on plot
while True:
pts=plt.ginput(1)
plt.pause(0.5)
print("action is ",action)
action=0 # reset
But my confusion is, if I take the exact same code and place it in a def block, the value of action is no longer passed back, action is always zero.
def subtest():
class Index:
def test(self, event):
global action
action=1
# fake data
action=0
x=np.arange(30)
y=x**2
fig,ax=plt.subplots()
ax.plot(x,y)
callback = Index()
buttonname=['test']
colors=['white']
idx=[0.2]
bax,buttons={},{}
# set up list of buttons.
for i,col,button in zip(idx,colors,buttonname):
bax[button] = plt.axes([0.92, i, 0.07, 0.07])
buttons[button] = Button(bax[button],button,color=col,hovercolor='green')
buttons[button].on_clicked(getattr(callback,button))
# register click on plot
while True:
pts=plt.ginput(1)
plt.pause(0.5)
print("action is ",action)
action=0 # reset
res=subtest()
I'm very confused as to why this happens. I tried moving the class definition out into the main code but that didn't help. I'm happy for any kind of solution (e.g. passing action through an argument, which I have not understood how to do with widgets), as I think that the use of global is often frowned apon. But also a global -based solution is fine.
action inside subtest is local to subtest, while action inside Index.test is global. Either declare action global in subtest, or use nonlocal in Index.test.
(I suspect there may be better solutions without globals, but since I'm not familiar with the GUI toolkit I'll leave that to someone else.)
you want to check out this article on closures and this other article on closures. I think you need to declare action outside of both your class and functions and then reference it with global in the class.
I don't think you need to use a class - you can get away with passing a variable, boolean, dictionary around functions and achieve the same thing.
You can use the "on_click" event to do what you want see this widget tutorial widget tutorial and this on matplotlib event handling
Here is an example piece of code using "global" to effect state changes. Better to pass around a variable or use the event to trigger whatever you want the state to effect.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
state = False
def change_state(btn_action):
#do something
btn_action= not(btn_action)
return btn_action
def grid(val):
global state
#do something
state =change_state(state)
print(state)
ax.grid()
fig.canvas.draw()
#reset value
state =change_state(state)
print(f'resetting state to {state}')
# fake data
x=np.arange(30)
y=x**2
#create fig, ax
fig =plt.figure()
ax= fig.subplots()
p, = ax.plot(x,y)
# set up list of buttons.
buttonname=['test']
colors=['white']
idx=[0.2]
bax,buttons={},{}
for i,col,button in zip(idx,colors,buttonname):
bax[button] = plt.axes([0.92, i, 0.07, 0.07])
buttons[button] = Button(bax[button],button,color=col,hovercolor='green')
buttons[button].on_clicked(grid)
#show plot
fig.canvas.draw()
plt.show()
I'm in doubt about what is the difference among the codes below. I'm using matplotlib's animation class to render numpy's arrays. In the atualizaMundo() function, if I use mundo[:] = new_mundo[:] it works just fine, but if I use mundo=new_mundo the arrays get equal but the animation doesn't work. What is the difference here?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
ON = 255
OFF = 0
def criaMundo(N):
return(np.random.choice([ON,OFF],N*N,p=[0.5,0.5]).reshape(N,N))
def atualizaMundo(frameNum,N,mundo,img):
new_mundo = np.random.choice([ON,OFF],N*N,p=[0.5,0.5]).reshape(N,N)
img.set_data(mundo)
mundo[:]=new_mundo[:]
#mundo=new_mundo
return(img,)
def main():
try:
N = 4
mundo = criaMundo(N)
print(mundo)
fig1,ax = plt.subplots()
img = ax.imshow(mundo)
animacao = animation.FuncAnimation(fig1, atualizaMundo, fargs=(N,mundo,img,), blit=True)
plt.show()
except Exception as ex:
pass
if __name__ == '__main__':
try:
main()
except Exception as fk:
pass
The line mundo[:]=new_mundo[:] modifies the existing array mundo. You're therefore always operating on the same object and changes made to it are reflected in the animation. Next time the function is called by the animation the same object is passed as argument so the changes made in the previous call are preserved. Note that mundo[:]=new_mundo[:] is equivalent to mundo[:]=new_mundo.
Opposed to that mundo=new_mundo assigns the new array to a local variable called mundo, which replaces the passed argument mundo. However, it is only of local scope and once the function finishes, the changed mundo is simply not present anymore. In the next call to the function, the old and unchanged mundo is passed again to the function, leading to a static animation.
It should be noted that you don't actually need to pass mundo at all in this case, as you could simply set the newly calculated array new_mundo directly to the image: img.set_data(new_mundo).