'canvas.mpl_connect' doestn't work inside a class - python

I am trying to make a response (return the value of event.xdata) concerning the mouse press event on fig. But it doesn't work. Could anyone tell me if there is anything wrong with my code? Thank you guys.
class ImDisp(mywindow):
def __init__(self):
super(ImDisp,self).__init__()
def BsFig(self,rd,daxis,taxis):
pos = self.horizontalSlider.value()
amplim = [self.AmpMinEdit.text(),self.AmpMaxEdit.text()]
dispopt = [int(self.WindowSizeEdit.text()),int(self.SlidePrecEdit.text())]
plt.ioff()
plt.close()
fig,ax = plt.subplots()
subwindowsize=dispopt[0]
slideprecision=dispopt[1]
d_windowstep = (daxis[-1] - subwindowsize)/(slideprecision-0)
x_windowmin = int(round((0+pos*d_windowstep)/(daxis[1]-daxis[0])))
x_windowmax = x_windowmin+int(round(subwindowsize/(daxis[1]-daxis[0])))
plt.imshow(rd[:,x_windowmin:x_windowmax], cmap='gray',vmin=amplim[0], vmax=amplim[1], extent=[daxis[x_windowmin], daxis[x_windowmax], taxis[-1], taxis[0]], aspect='auto')
ax.set_xlabel('D')
ax.set_ylabel('T')
fig.canvas.mpl_connect('button_press_event',self.BsInf)
bs = FigureCanvas(fig)
self.mainDisp.setWidget(bs)
return bs
def BsInf(self,event):
bs_d=event.xdata
bs_t=event.ydata
print(bs_d)

Related

Updating a matplotlib figure during simulation

I try to implement a matplotlib figure that updates during the simulation of my environment.
The following Classes works fine in my test but doesn't update the figure when I use it in my environment. During the simulation of the environment, the graph is shown, but no lines are plotted.
My guess is that .draw() is not working how I think it does.
Can anyone figure out the issue here?
class Visualisation:
def __init__(self, graphs):
self.graphs_dict = {}
for graph in graphs:
fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot(graph.x, graph.y, 'r-')
self.graphs_dict[graph.title] = {"fig": fig, "ax": ax, "line": line, "graph": graph}
self.graphs_dict[graph.title]["fig"].canvas.draw()
plt.ion()
plt.show()
def update(self, graph):
graph = self.graphs_dict[graph.title]["graph"]
self.graphs_dict[graph.title]["line"].set_xdata(graph.x)
self.graphs_dict[graph.title]["line"].set_ydata(graph.y)
self.graphs_dict[graph.title]["fig"].canvas.flush_events()
x_lim, y_lim = self.get_lim(graph)
self.graphs_dict[graph.title]["ax"].set_xlim(x_lim)
self.graphs_dict[graph.title]["ax"].set_ylim(y_lim)
self.graphs_dict[graph.title]["fig"].canvas.draw()
#staticmethod
def get_lim(graph):
if graph.x_lim is None:
x = np.array(graph.x)
y = np.array(graph.y)
x_lim = [x.min(), x.max()]
y_lim = [y.min(), y.max()]
else:
x_lim = graph.x_lim
y_lim = graph.y_lim
return x_lim, y_lim
class Graph:
def __init__(self, title, x, y, x_label="", y_label=""):
"""
Sets up a graph for Matplotlib
Parameters
----------
title : String
Title of the plot
x : float
y : float
x_label : String
x Label
y_label : String
y Label
"""
self.title = title
self.x = x
self.y = y
self.x_label = x_label
self.y_label = y_label
self.x_lim, self.y_lim = None, None
def set_lim(self, x_lim, y_lim):
self.x_lim = x_lim
self.y_lim = y_lim
class Environment:
def __init__(self, [..], verbose=0):
"""verbose : int
0 - No Visualisation
1 - Visualisation
2 - Visualisation and Logging"""
self.vis = None
self.verbose = verbose
[......]
def simulate(self):
for _ in range(self.n_steps):
[...]
self.visualize()
def visualize(self):
if self.verbose == 1 or self.verbose == 2:
if self.vis is None:
graphs = [Graph(title="VariableY", x=[], y=[])]
graphs[0].set_lim(x_lim=[0, 100], y_lim=[0, 300])
self.vis = Visualisation(graphs=graphs)
else:
self.vis.graphs_dict["VariableY"]["graph"].x.append(self.internal_step)
self.vis.graphs_dict["VariableY"]["graph"].y.append(150)
self.vis.update(self.vis.graphs_dict["VariableY"]["graph"])
When I run the code I more or less just write: env.simulate().
The code runs fine here:
class TestSingularVisualisation(unittest.TestCase):
def setUp(self):
self.graph = Graph(title="Test", x=[0], y=[0])
self.vis = Visualisation(graphs=[self.graph])
class TestSingleUpdate(TestSingularVisualisation):
def test_repeated_update(self):
for i in range(5):
self.graph.x.append(i)
self.graph.y.append(np.sin(i))
self.vis.update(self.graph)
time.sleep(1)
Turns out your code works the way it is set up. Here is the sole problem with the code you provided:
self.vis.graphs_dict["VariableY"]["graph"].x.append(self.internal_step)
self.vis.graphs_dict["VariableY"]["graph"].y.append(150)
You are plotting a line and correctly updating the canvas, however, you keep appending the exact same (x, y) coordinate. So the simulation does update the line, but the line simplifies to a point. Your test case does not do this. You can run a dummy example with your code by simply adding a line like this:
self.internal_step += 5
before adding the new x point, and you will produce a horizontal line.
Let me know if this solves your problem.
Probably not the most elegant, but I use plt.pause(0.1) when I want to update plots during execution. It pauses for 0.1s and forces all plots to be actually displayed. (It work in %debug in ipython as a bonus)

problem embedding matplotlib graph in tkinter

I'm trying to embed an animation graph into my GUI, however, whenever I try what i've seen according to tutorials regarding the use of canvas, I get a blank tkinter screen.
No errors, nothing.
However, when I use plt.show, it works fine but its not confined to the GUI. Which is the problem.
Its probably a quick fix, but this is what I have.
Any help would be appreciated!
class popupWindowOscil(tk.Frame):
def __init__(self,master,ser):
OscilTop= self.OscilTop= Toplevel(master)
tk.Frame.__init__(self)
self.ser = ser
self.fig = plt.figure()
self.ax = self.fig.add_subplot(1, 1, 1)
self.xs = []
self.ys = []
self.xval =0
self.OscilLoop()
def OscilLoop(self):
ani = animation.FuncAnimation(self.fig, self.Oscilliscope, fargs=(self.xs, self.ys))
#self.canvas = FigureCanvasTkAgg(self.fig, self)
#self.canvas.draw()
#self.canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
plt.show()
return
# The function that creates the values for the plot.
def Oscilliscope(self,i,xs,ys):
tryagain =1
while tryagain == 1:
try:
reading = self.ser.readline().decode()
tryagain = 0
except UnicodeDecodeError:
pass
Incominglist = str(reading).split(",")
try:
numbers = [float(x) for x in Incominglist]
except ValueError:
print ('Failure during string decode, Restart and Try again')
return
# Add x and y to lists
self.ys.extend(numbers)
for val in range(len(Incominglist)):
if self.xval == 0 and val ==0:
self.xs.append(self.xval) # or any arbitrary update to your figure's data
else:
self.xval += 0.005
self.xs.append(self.xval)
# Draw x and y lists
self.ax.clear()
self.ax.plot(self.xs, self.ys)
# Format plot
self.ax.yaxis.set_ticks(np.arange(0,5,0.25))
plt.subplots_adjust(bottom=0.30)

Using a matplotlib button to alternate/switch between plots I created

So I've created several charts using the matplotlib library in python 3.5, but I want to be able to have the flexibility to utilize a button to alternate between the views I created within a single window. I've been trying to experiment with an example here, but have not succeeded in doing so. I was curious in how to have the flexibility to click through different views that I created.
My code is sort of organized like this:
def plot1(data1, 'name1'):
...
ax.plot(x,y)
plt.draw()
def plot2(data2, 'name2'):
...
ax2.plot(x,y)
plt.draw()
def plot3(data3, 'name3'):
...
ax3.plot(x,y)
plt.draw()
plot1(data1,'name1')
plot2(data2,'name2')
plot3(data3,'name3')
plt.show()
Currently it will show up in three different windows. Now when I try to make this all into one view accessible via buttons, I'm unable to do so because quite frankly I'm unfamiliar with how to pass on the variables in my methods to create my desired subplots with the callback function. Is there a way to sort of structure my code to have them all run under one matplotlib window?
The following would be a class that uses the functions that you create. Those would not actually plot anything, but provide the required data. They should be put in a list called funcs, and when you click next or prev the corresponding graph would pop up. This should get you started.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
x = range(-50,50)
y = range(-50,50)
l, = plt.plot(x, y, lw=2)
ax.title.set_text('y = x')
class Index(object):
ind = 0
global funcs # used so yu can access local list, funcs, here
def next(self, event):
self.ind += 1
i = self.ind %(len(funcs))
x,y,name = funcs[i]() # unpack tuple data
l.set_xdata(x) #set x value data
l.set_ydata(y) #set y value data
ax.title.set_text(name) # set title of graph
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind %(len(funcs))
x,y, name = funcs[i]() #unpack tuple data
l.set_xdata(x) #set x value data
l.set_ydata(y) #set y value data
ax.title.set_text(name) #set title of graph
plt.draw()
def plot1():
x = range(-20,20)
y = x
name = "y = x"
return (x,y, name)
def plot2():
x = range(-20,20)
y = np.power(x, 2)
name = "y = x^2"
return (x,y,name)
def plot3():
x = range(-20,20) # sample data
y = np.power(x, 3)
name = "y = x^3"
return (x,y, name)
funcs = [plot1, plot2, plot3] # functions in a list so you can interate over
callback = Index()
axprev = plt.axes([0.7, 0.05, 0.1, 0.075])
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.show()

How to pause/resume matplotlib ArtistsAnimation

My problem is to resume an matplotlib ArtistsAnimation
My code is:
def PlotFields(self):
fig = plt.figure()
axess = []
for i in range(0,self.number_fields):
axess.append(fig.add_subplot((self.number_fields+1)//2,2,i+1))
#find min and max in time
mins = {}
maxs = {}
for key,field in self.fields.items():
mins[key] = field[:,:,:,self.z_slice].min()
maxs[key] = field[:,:,:,self.z_slice].max()
if mins[key] == maxs[key]:#this fixes bug in imshow when vmin = vmax
mins[key] = mins[key]-0.1
maxs[key] = maxs[key]+0.1
#set up list of images for animation
movie = []
images = []
nt = self.fields[list(self.fields)[0]].shape[0] #number of time slices
print('time slices = {}'.format(nt))
first = 1
for time in range(0,nt):
images.clear()
i = 0
for key,field in self.fields.items():
if self.cut == 'xy':
images.append(axess[i].pcolor(field[time,:,:,self.z_slice].T, vmin = mins[key], vmax = maxs[key]))
axess[i].set_xlabel('x')
axess[i].set_ylabel('y')
elif self.cut == 'xz':
images.append(axess[i].pcolor(field[time,:,:,self.y_slice].T, vmin = mins[key], vmax = maxs[key]))
axess[i].set_xlabel('x')
axess[i].set_ylabel('z')
else:
print('unknown cut --- exiting !!')
quit()
axess[i].set_title(key)
i = i + 1
if first == 1:
for i in range(0,self.number_fields):
fig.colorbar(images[i], ax = axess[i])
first = 0
# time_title.set_text('t={}'.format(t_array[time]))
time_title = axess[0].annotate('t={}'.format(self.t_array[time]),xy = (0.1,1.2))
collected_list = [*images] #apparently a bug in matplotlib forces this solution
collected_list.append(time_title)
movie.append(collected_list)
#for i in range(0,number_fields):
# fig.colorbar(images[i], ax = axess[i])
#run animation
self.ani = anim.ArtistAnimation(fig, movie, interval=500, blit = False, repeat_delay = 1000)
fig.canvas.mpl_connect('button_press_event', self.onClick)
if self.save_movie == True:
try:
ani.save('xy_film.mp4')
#ani.save('film.mp4',writer = FFwriter, fps=30, extra_args=['-vcodec', 'libx264'])
except Exception:
print("Save failed: Check ffmpeg path")
plt.show()
def onClick(self, event):
self.pause == True
if self.pause == False:
self.ani._stop()
self.pause = True
print('self pause = False')
else:
self.ani._start()
self.pause = False
#pause ^= True
print('self pause = True')
The animation stop onClick but throws the following error on the second click which is supposed to resume the animation (if possible):
File "PlotFieldsFieldAligned.py", line 149, in onClick
self.ani._start()
File "/home/yolen/scicomp/anaconda3/lib/python3.5/site- packages/matplotlib/animation.py", line 665, in _start
self.event_source.add_callback(self._step)
AttributeError: 'NoneType' object has no attribute 'add_callback'
Any help appreciated:-D
Although it seems that in this case a FuncAnimation might be better suited than an ArtistAnimation, both can be
stopped / started the same way. See this question stop / start / pause in python matplotlib animation.
The main point is that the 'private' ArtistAnimation._start() function is not doing what you think it does. Therefore it is sensible to use the ArtistAnimation.event_source.stop() and ArtistAnimation.event_source.start() functions.
Below is a minimal, runnable example showing how to start/stop an ArtistAnimation by clicking with the mouse button.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
class SomeClass():
def __init__(self):
self.pause = False
self.fig, ax = plt.subplots()
ax.set_aspect("equal")
self.movie = []
nt = 10
X,Y = np.meshgrid(np.arange(16), np.arange(16))
for t in range(nt):
data = np.sin((X+t*1)/3.)**2+ 1.5*np.cos((Y+t*1)/3.)**2
pc = ax.pcolor(data)
self.movie.append([pc])
self.ani = animation.ArtistAnimation(self.fig, self.movie, interval=100)
self.fig.canvas.mpl_connect('button_press_event', self.onClick)
plt.show()
def onClick(self, event):
if self.pause:
self.ani.event_source.stop()
else:
self.ani.event_source.start()
self.pause ^= True
a = SomeClass()

Python Script to animate a set of lines in a class

I am trying to put the matplotlib.animation set into a class function. Though I don't seem to be having much luck. I have tried both, FunctionAnimation() & ArtistAnimation(). For both I don't seem to be able to get them to work (though they are vastly different).
# ------------------------------ #
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# ------------------------------ #
class AniPlot():
def __init__(self):
self.fig = plt.figure()
self.ax = plt.axes(xlim=(-3.5, 3.5), ylim=(-5, 2))
self.line, = self.ax.plot([], [], lw=2)
def set_data(self,tvector):
self.data = tvector
def ani_init(self):
self.line.set_data([], [])
def ani_update(i):
x = self.data[i][0]
y = self.data[i][1]
self.line.set_data(x, y)
return self.line,
def animate(self):
anim = animation.FuncAnimation(self.fig, self.ani_update, init_func=self.ani_init,
frames=4, interval=20, blit=True)
plt.show()
# ------------------------------ #
data = [
[[0,0,1,0],[0,-1,-2,-3]],
[[0,0,0,0.1],[0,-1,-3,-4]],
[[0,0,0.5,0],[0,-1,-2.5,-3.5]],
[[0,0,1,2],[0,-1,-2,-2.5]]
]
myani = AniPlot()
myani.set_data(data)
myani.animate()
I want to try get my head around it, rather than use someone else's code. Though I did use others as a starting point. Can anyone help?
(warning: Newbie here.)
I think the best way for "anim" to stick is actually to set it as instance variable, using self.anim:
self.anim = ...
You also need to add "self" here:
def ani_update(self, i)
I use Spyder 2.1.10 and it seems to be working, although the animation is a bit fast.
You can either set blit as False, or as True but make sure you replace the line return self.line by return self.line,.
#!/usr/bin/env python3
# ------------------------------ #
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# ------------------------------ #
class AniPlot():
def __init__(self):
self.fig = plt.figure()
self.ax = plt.axes(xlim=(-3.5, 3.5), ylim=(-5, 2))
self.line, = self.ax.plot([], [], lw=2)
def set_data(self,data):
self.data = data
def ani_init(self):
self.line.set_data([], [])
return self.line
def ani_update(self, i):
x = self.data[i][0]
y = self.data[i][1]
self.line.set_data(x, y)
return self.line
def animate(self):
self.anim = animation.FuncAnimation(self.fig, self.ani_update, init_func=self.ani_init, frames=4, interval=20, blit=False)
plt.show()
# ------------------------------ #
data = [
[[0,0,1,0],[0,-1,-2,-3]],
[[0,0,0,0.1],[0,-1,-3,-4]],
[[0,0,0.5,0],[0,-1,-2.5,-3.5]],
[[0,0,1,2],[0,-1,-2,-2.5]]
]
myani = AniPlot()
myani.set_data(data)
myani.animate()

Categories

Resources