Continue code after closing matplotlib figure - python

I am writing a routine where I can append a set of datafiles. The first part of the code imports a single datafile and displays it using matplotlib. I can then use a slider to define a specific x range (i.e. to exclude noisy or irrelevant parts). The second part of the code involves an edit routine where I edit the range of all the datafiles in the specific folder based on this newly set x range (i.e. removing all of the rows).
The first part of the code is not a problem for me and results in the following interface. My problem is with the second part. I want the Edit button to close the figure and continue the rest of the code (the datafile editing part). This is necessary because I need the slider values to define the new datarange.
My initial thought was to place part 2 of the code after plt.show(). The Edit button would simply close the figure and the rest of the code would continue. An example of my code without using my actual data:
### Part 1 ###
import os
import sys
import six
import tkinter as tk
from tkinter import Tk
from tkinter import filedialog
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.gridspec as gridspec
from matplotlib.widgets import Slider, Button
class MinMaxSlider(Slider):
def __init__(self, ax, label, valmin, valmax, **kwargs):
self.valinit2 = kwargs.pop("valinit2", valmax)
self.val2 = self.valinit2
Slider.__init__(self,ax, label, valmin, valmax, **kwargs)
self.poly.xy = np.array([[self.valinit,0],[self.valinit,1],
[self.valinit2,1],[self.valinit2,0]])
self.vline.set_visible(False)
def set_val(self, val):
if np.abs(val-self.val) < np.abs(val-self.val2):
self.val = val
else:
self.val2 = val
self.poly.xy = np.array([[self.val,0],[self.val,1],
[self.val2,1],[self.val2,0]])
self.valtext.set_text(self.valfmt % self.val +"\n"+self.valfmt % self.val2)
if self.drawon:
self.ax.figure.canvas.draw_idle()
if not self.eventson:
return
for cid, func in six.iteritems(self.observers):
func(self.val,self.val2)
def update(mini,maxi):
ax.set_xlim(mini,maxi)
def edit(event):
plt.close()
def find_nearest(array,value):
return (np.abs(array-value)).argmin()
## Part 1 ##
plt.ion()
x = np.array(range(100))
y_low = 10*np.array(range(100))
y_high = 10*np.array(range(100))
font = {'family' : 'Calibri',
'weight' : 'normal',
'size' : 14}
mpl.rc('font', **font)
axcolor = 'lightgoldenrodyellow'
fig,(ax, sliderax) = plt.subplots(figsize=(12,8),
nrows=2,gridspec_kw={"height_ratios":[1,0.05]})
fig.subplots_adjust(hspace=0.5)
ln1 = ax.plot(x,y_low, color = 'dodgerblue', label = 'Low')
ax.set_xlabel('x', fontsize=24)
ax.xaxis.labelpad = 15
ax.set_ylabel('y', fontsize=24, color = 'dodgerblue')
ax.yaxis.labelpad = 15
for tl in ax.get_yticklabels():
tl.set_color('dodgerblue')
ax1 = ax.twinx()
ln2 = ax1.plot(x,y_high, color = 'darkred', label = 'High')
ax1.set_ylabel('y', fontsize=24, color = 'darkred')
for tl in ax1.get_yticklabels():
tl.set_color('darkred')
lns = ln1+ln2
labs = [l.get_label() for l in lns]
ax1.legend(lns, labs, loc=0)
slider = MinMaxSlider(sliderax,'x(min/max)',x.min(),x.max(),
valinit=x.min(),valinit2=x.max())
slider.on_changed(update)
update(x.min(),x.max())
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Edit', color=axcolor, hovercolor='0.975')
button.on_clicked(edit)
plt.show()
## Part 2 ##
idx_low = find_nearest(x, slider.val)
idx_high = find_nearest(x, slider.val2)
print(idx_low)
print(idx_high)
However the idx_low and idx_high are already calculated and printed while the figure is open, and thus based on the initial values of the slider. I want them to be calculated after clicking the Edit button and closing the figure so that they are based on the slider values set by the user. How can I achieve this? Do I need to edit the Edit event function? Thank you for your help.

Related

MultiCursor in matplotlib over multiple subplot does not work

i am developing a GUI application for which there will be multiple dynamic subplots for which i would like to have a cursor integration for which is used "Matplotlib's MultiCursor widget" the code seems to be all good without any errors but the cursor alone is not being displayed on the screen
The below is a small snippet function which iam currently using
import numpy as np
import PySimpleGUI as sg
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
from matplotlib.widgets import Cursor
from matplotlib.widgets import MultiCursor
from matplotlib.gridspec import GridSpec
from matplotlib.figure import Figure
class Toolbar(NavigationToolbar2Tk):
def __init__(self, *args, **kwargs):
super(Toolbar, self).__init__(*args, **kwargs)
def repack(widget, option):
pack_info = widget.pack_info()
pack_info.update(option)
widget.pack(**pack_info)
def draw_figure_w_toolbar(canvas, fig, canvas_toolbar):
if canvas.children:
for child in canvas.winfo_children():
child.destroy()
if canvas_toolbar.children:
for child in canvas_toolbar.winfo_children():
child.destroy()
figure_canvas_agg = FigureCanvasTkAgg(fig, master=canvas)
figure_canvas_agg.draw_idle()
toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
toolbar.update()
figure_canvas_agg.get_tk_widget().pack(side='right', fill='both', expand=1)
layout=[
[
sg.Frame(
'Controls',
expand_x=True,
layout=[[
sg.Canvas(key='controls_cv')
]])
],
[
sg.Canvas(
key='fig_cv',
size=(1024, 525), #(W,H)
expand_x=True,
expand_y=True,
background_color='black')
],
]
window = sg.Window('Data Visualization Tool',
layout=layout,
location=(0, 0),
resizable=True,
margins=(0, 0),
finalize=True)
fig = Figure()
def get_data_ax(x_data, y_data, y_label, marker_sym=None):
ax = fig.add_subplot()
lines = ax.step(
x_data,
y_data,
marker=marker_sym,
where='post')
return ax, lines
cursor_handle_list = []
axes_handle_list = []
def on_move(event, flag=False, c_flag=False):
global cursor_present_flag, cursor_handle_list, axes_handle_list
if event.dblclick:
def change_cursor_position(handle_obj):
for handles in handle_obj:
handles.set_xdata(float(event.xdata))
if event.inaxes is not None:
ax = event.inaxes
if ax not in axes_handle_list:
cursor_handle = ax.axvline(event.xdata, color ='red', lw = .75, alpha = 0.65, visible=True)
cursor_handle_list.append(cursor_handle)
axes_handle_list.append(ax)
else:
change_cursor_position(cursor_handle_list)
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig,
window['controls_cv'].TKCanvas)
window.refresh()
fig.canvas.mpl_connect("button_press_event", on_move)
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
num_rows = 2
gs = GridSpec(num_rows, 1, hspace=0)
ax0, lines = get_data_ax(t, s, 'test')
ax0.set_subplotspec(gs[0])
fig.add_subplot(ax0)
ax, lines = get_data_ax(t, s, 'test')
ax.set_subplotspec(gs[1])
fig.add_subplot(ax)
ax.get_shared_x_axes().join(ax0, ax)
graph = FigureCanvasTkAgg(fig, master=window['fig_cv'].TKCanvas)
cursor_multi = MultiCursor(graph, [ax,], color='r', lw=2.0, vertOn=True)
graph.draw()
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, fig,
window['controls_cv'].TKCanvas)
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
I tried using axvline with matplotlib events but the results were not satisfactory
i saw somewhere that defining the return value of MultiCursor as gloabl should fix the issue but that did not happen in my case
next i tried simple putting plt.var_name as a variable for catching the returned object from MultiCursor but even this did not yeild me any result
Note: The code with mpl_connect and on_move callback with axvline integration were just an workaround i was trying as MultiCursor was not working you can discard that part if not required
It's because you created the MultiCursor widget before you created the FigureCanvasTkAgg for the axes objects. So do like this.
draw_figure_w_toolbar(window['fig_cv'].TKCanvas, ...)
cursor_multi = MultiCursor(None, [ax,], ...)
Also note that the first argument of the MultiCursor() is recommended to be set to None because it is ignored. Also your code contain many problems, such as an unused object graph.

Why are my plots not appearing with set_data using Tkinter?

I am trying to improve my plotting function. I want to plot data using my plotGraph function coming from an EEG board in real-time, pulling samples from an LSL # 250Hz. Previously, I had a functional version using the regular self.ax.plot(x,y), clearing the data with self.ax.clear() every time the plot needed to refresh. Nonetheless, some profiling showed that my code was taking way too much time to plot in comparison to the rest of it.
One of the suggestions I got was to use set_data instead of plot and clear. I have multiple lines of data that I want to plot simultaneously, so I tried following Matplotlib multiple animate multiple lines, which you can see below (adapted code). Also, I was told to use self.figure.canvas.draw_idle(), which I tried, but I'm not sure if I did it correctly.
Unfortunately, it didn't work, the graph is not updating and I can't seem to find why. I'm aware that the source I just mentioned uses animation.FuncAnimation but I'm not sure that would be the problem. Is it?
Any ideas of why none of my lines are showing in my canvas' graph?
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
class AppWindow:
def plotGraph(self, x, y):
for lnum,line in enumerate(self.lines):
line.set_data(x[:], y[:, lnum])
self.figure.canvas.draw_idle()
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def __init__(self):
self.root = tk.Tk() #start of application
self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg =
bg_color, highlightthickness=0)
self.canvas.pack(fill = 'both', expand = True)
self.figure = plt.figure(figsize = (5,6), dpi = 100)
self.figure.patch.set_facecolor(sc_color)
self.ax = self.figure.add_subplot(111)
self.ax.clear()
self.line, = self.ax.plot([], [], lw=1, color = tx_color)
self.line.set_data([],[])
#place graph
self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
self.chart_type.get_tk_widget().pack()
self.lines = []
numchan = 8 #let's say I have 8 channels
for index in range(numchan):
lobj = self.ax.plot([],[], lw=2, color=tx_color)[0]
self.lines.append(lobj)
for line in self.lines:
line.set_data([],[])
def start(self):
self.root.mainloop()
You chart is empty because you are plotting empty arrays:
line.set_data([],[])
If you fill in the line arrays, the chart plots correctly.
Try this code. It updates the chart with new random data every second.
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import random
bg_color='grey'
tx_color='green'
sc_color='linen'
numchan = 8
chlen = 100
xvals=[(x-40)/20 for x in range(chlen)] # X coordinates
chcolors= ['gold','blue','green','maroon','red','brown','purple','cyan']
class AppWindow:
def plotGraph(self):
self.figure.canvas.draw_idle()
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def UpdateChannelData(self): # callback with new data
# fake random data
for i,ch in enumerate(self.chdata):
for p in range(len(ch)):
ch[p] += (random.random()-.5)/100
self.lines[i].set_data(xvals, ch)
self.plotGraph()
self.root.after(100, self.UpdateChannelData) # simulate next call
def __init__(self):
global chzero
self.root = tk.Tk() #start of application
self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg = bg_color, highlightthickness=0)
self.canvas.pack(fill = 'both', expand = True)
self.figure = plt.figure(figsize = (5,6), dpi = 100)
self.figure.patch.set_facecolor(sc_color)
self.ax = self.figure.add_subplot(111)
self.ax.clear()
self.line, = self.ax.plot([], [], lw=1, color = tx_color)
self.line.set_data([],[])
#place graph
self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
self.chart_type.get_tk_widget().pack()
self.lines = []
#numchan = 8 #let's say I have 8 channels
for index in range(numchan):
lobj = self.ax.plot([],[], lw=1, color=chcolors[index])[0]
self.lines.append(lobj)
# set flat data
self.chdata = [[0 for x in range(chlen)] for ch in range(numchan)]
self.root.after(1000, self.UpdateChannelData) # start data read
def start(self):
self.root.mainloop()
AppWindow().start()
Output:

How do I change the subplot parameters having a Figure in a window in Tkinter ? As for example I want to add the xlabel and ylabel

I have an application that should get data from a sensor into a live graph, a subplot that is added into a Figure.
I have now a problem after adding the subplot that I don't know how to change the plot parameters as xlabel, ylabel. This works if I import plt, but not if I import a Figure that will be further added to the window in Tkinter.
#file livegraph.py
import matplotlib.animation as animation
import datetime
#this is a draft for the liveGraph class
#the objective is to get live data from a sensor
class liveGraph:
#by default define the interval as being 1000 mSec
intervalAnim = 1000
def __init__(self,fig):
self.xax = 0
self.xs = []
self.ys = []
self.ax = fig.add_subplot(111)
self.ax.set_xlabel('teeeest')
#fig.title('Graph test')
#fig.set_xlabel("Time")
#fig.ylabel("% SMS")
self.anim = animation.FuncAnimation(fig, self.animate, interval = self.intervalAnim)
def animate(self,i):
self.xs.append(self.xax)
self.ys.append(datetime.datetime.now().second)
self.xax+=1
self.ax.clear()
self.ax.plot(self.xs,self.ys)
if self.xax > 90:
self.anim.event_source.stop()
from tkinter import *
from matplotlib import style
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from livegraph import liveGraph
# Define the main_screen as a tkinter
app_screen = Tk() # create a GUI window
app_screen.geometry("1920x1080") # set the configuration of GUI window
app_screen.resizable(width=True,height=True)
app_screen.title("Testare izolator") # set the title of GUI window
style.use('bmh')
#figure represents the graphic part of the system
figure = Figure(figsize=(10, 5), facecolor='white',frameon=True)
figure.suptitle('This is the figure title', fontsize=12)
#figure.add_gridspec(10,10)
#this are some parameters that I can easily change if I am using plt
# plt.title('Graph test')
#plt.xlabel("Time")
#plt.ylabel("% SMS")
#x = [ 0, 1, 2, 3, 4 ]
#y = [ 0, 1, 2, 3, 4 ]
#lines = plt.plot(x, y)
#plt.grid()
#plt.axis([0,10,0,10])
#plt.setp(lines, color= "b")
canvas = FigureCanvasTkAgg(figure, app_screen)
canvas.get_tk_widget().pack(side=TOP, anchor =NW, padx=100, pady=10)
newAnimation = liveGraph(figure)
app_screen.mainloop() # start the GUI
You should use self.ax. to add elements
self.ax.set_xlabel('teeeest')
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
but then there is other problem because self.ax.clear() removes these elements.
First method:
If you use self.ax.clear() then you remove labels and you have to put labels again and again
def animate(self, i):
self.xs.append(self.xax)
#self.ys.append(datetime.datetime.now().second)
self.ys.append(random.randint(0, 10))
self.xax += 1
self.ax.clear()
self.ax.plot(self.xs,self.ys)
if self.xax > 90:
self.anim.event_source.stop()
self.ax.set_xlabel('teeeest')
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
Second method:
To add elements only once you have to remove self.ax.clear() and instead of plot() you should create empty plot in `init
self.ax = fig.add_subplot(111)
self.ax.set_xlabel('teeeest')
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
self.line, = self.ax.plot([], [])
and in animation use set_data() to update data in existing plot
self.line.set_data(self.xs, self.ys)
but it will not rescale plot and you will have to do it manually (if you want to rescale it)
self.ax.relim() # recalculate limits
self.ax.autoscale_view(True,True,True) # rescale using limits
Full code for first method
import matplotlib.animation as animation
import datetime
import random
#this is a draft for the liveGraph class
#the objective is to get live data from a sensor
class liveGraph:
#by default define the interval as being 1000 mSec
intervalAnim = 1000
def __init__(self, fig):
self.xax = 0
self.xs = []
self.ys = []
self.ax = fig.add_subplot(111)
self.ax.set_xlabel('teeeest')
self.anim = animation.FuncAnimation(fig, self.animate, interval=self.intervalAnim)
def animate(self, i):
self.xs.append(self.xax)
#self.ys.append(datetime.datetime.now().second)
self.ys.append(random.randint(0, 10))
self.xax += 1
self.ax.clear()
self.ax.plot(self.xs,self.ys)
if self.xax > 90:
self.anim.event_source.stop()
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
from tkinter import *
from matplotlib import style
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#from livegraph import liveGraph
# Define the main_screen as a tkinter
app_screen = Tk() # create a GUI window
app_screen.geometry("1920x1080") # set the configuration of GUI window
app_screen.resizable(width=True, height=True)
app_screen.title("Testare izolator") # set the title of GUI window
style.use('bmh')
#figure represents the graphic part of the system
figure = Figure(figsize=(10, 5), facecolor='white', frameon=True)
figure.suptitle('This is the figure title', fontsize=12)
#figure.add_gridspec(10,10)
#this are some parameters that I can easily change if I am using plt
#plt.title('Graph test')
#plt.xlabel("Time")
#plt.ylabel("% SMS")
#x = [ 0, 1, 2, 3, 4 ]
#y = [ 0, 1, 2, 3, 4 ]
#lines = plt.plot(x, y)
#plt.grid()
#plt.axis([0,10,0,10])
#plt.setp(lines, color= "b")
canvas = FigureCanvasTkAgg(figure, app_screen)
canvas.get_tk_widget().pack(side=TOP, anchor=NW, padx=100, pady=10)
newAnimation = liveGraph(figure)
#canvas.draw()
app_screen.mainloop() # start the GUI
Full code for second method
import matplotlib.animation as animation
import datetime
import random
#this is a draft for the liveGraph class
#the objective is to get live data from a sensor
class liveGraph:
#by default define the interval as being 1000 mSec
intervalAnim = 1000
def __init__(self, fig):
self.xax = 0
self.xs = []
self.ys = []
self.ax = fig.add_subplot(111)
self.ax.set_xlabel('teeeest')
self.ax.set_title('Graph test')
self.ax.set_xlabel("Time")
self.ax.set_ylabel("% SMS")
# create empty plot at start
self.line, = self.ax.plot([], [])
self.anim = animation.FuncAnimation(fig, self.animate, interval=self.intervalAnim)
def animate(self, i):
self.xs.append(self.xax)
#self.ys.append(datetime.datetime.now().second)
self.ys.append(random.randint(0, 2))
self.xax += 1
# update data in existing plot
self.line.set_data(self.xs, self.ys)
# rescale plot (if you need it)
self.ax.relim() # recalculate limits
self.ax.autoscale_view(True,True,True) # rescale using limits
if self.xax > 90:
self.anim.event_source.stop()
from tkinter import *
from matplotlib import style
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
#from livegraph import liveGraph
# Define the main_screen as a tkinter
app_screen = Tk() # create a GUI window
app_screen.geometry("1920x1080") # set the configuration of GUI window
app_screen.resizable(width=True, height=True)
app_screen.title("Testare izolator") # set the title of GUI window
style.use('bmh')
#figure represents the graphic part of the system
figure = Figure(figsize=(10, 5), facecolor='white', frameon=True)
figure.suptitle('This is the figure title', fontsize=12)
#figure.add_gridspec(10,10)
#this are some parameters that I can easily change if I am using plt
#plt.title('Graph test')
#plt.xlabel("Time")
#plt.ylabel("% SMS")
#x = [ 0, 1, 2, 3, 4 ]
#y = [ 0, 1, 2, 3, 4 ]
#lines = plt.plot(x, y)
#plt.grid()
#plt.axis([0,10,0,10])
#plt.setp(lines, color= "b")
canvas = FigureCanvasTkAgg(figure, app_screen)
canvas.get_tk_widget().pack(side=TOP, anchor=NW, padx=100, pady=10)
newAnimation = liveGraph(figure)
#canvas.draw()
app_screen.mainloop() # start the GUI

Python, quitting matplotlib FuncAmination() when animation is fed by incoming sensor data

I am getting data to a Raspberry Pi from some sensors.
Once the animation starts, I have not found a way to get it to stop the animation and then execute the rest of the code in the program.
I have tried quit() and animation.event_source.stop() to no avail. I read the documentation and it looks like the method animation.FuncAnimation() is some sort of loop that calls animate() and never ends in my case. Here are a few versions of my code below. Nothing changes between version below the commented out line.
from gpiozero import MCP3008
from timeit import default_timer
import time
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
# This is specific to the ADC I am using for my sensor
ch2 = MCP3008(2)
vals = []
timer = []
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
#this is the fuction used as a parameter for the animation.FuncAnimation
def animate(i):
timer.append(default_timer())
vals.append(ch2.value)
ax1.clear()
ax1.plot(timer,vals)
#______________________________________
try:
ani = animation.FuncAnimation(fig, animate, interval = 50)
plt.show()
except KeyboardInterrupt:
plt.close("all")
#The plot created below is for saving the final set of collected data
plt.plot(timer,vals)
plt.xlabel("Time(s)")
plt.ylabel("V")
plt.savefig('/home/pi/programs/pics/plot.jpg')
plt.close('all')
quit()
The idea was that you would press control c, then the rest of the code would execute and the program would end, but the animation keeps running until I keyboard interrupt multiple times, and the rest of the code(under except) never runs. I have also tried...
from gpiozero import MCP3008
from timeit import default_timer
import time
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
# This is specific to the ADC I am using for my sensor
ch2 = MCP3008(2)
vals = []
timer = []
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
#this is the fuction used as a parameter for the animation.FuncAnimation
def animate(i):
timer.append(default_timer())
vals.append(ch2.value)
ax1.clear()
ax1.plot(timer,vals)
#______________________________________
ani = animation.FuncAnimation(fig, animate, interval = 50)
plt.show()
commmand = input('Type q and press enter to quit')
if commmand == 'q':
plt.close("all")
#The plot created below is for saving the final set of collected data
plt.plot(timer,vals)
plt.xlabel("Time(s)")
plt.ylabel("V")
plt.savefig('/home/pi/programs/pics/plot.jpg')
plt.close('all')
quit()
I also tried putting print statements in various places after the plt.show after the line where ani is assigned, and the code never gets past that point.
Any tips?
The code after plt.show() will only execute, once the window that is shown is closed. At that point you do not have the figure available in pyplot to use plt.savefig any more. However, you may very well create a new plot, like you're doing already in the code and the second version of the code should run fine once you close the matplotlib window.
#from gpiozero import MCP3008 # not available here
from timeit import default_timer
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# emulate sensor
class MCP3008():
def __init__(self, r):
self.x = 0.5
self.r = r
def value(self):
self.x = self.r*self.x*(1.-self.x)
return self.x
ch2 = MCP3008(3.62)
vals = []
timer = []
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
#this is the fuction used as a parameter for the animation.FuncAnimation
def animate(i):
timer.append(default_timer())
vals.append(ch2.value())
ax1.clear()
ax1.plot(timer,vals, marker=".", ls="")
ani = animation.FuncAnimation(fig, animate, interval = 50)
plt.show()
plt.close("all")
#The plot created below is for saving the final set of collected data
plt.plot(timer,vals)
plt.xlabel("Time(s)")
plt.ylabel("V")
plt.savefig('plot.jpg')
plt.close('all')
If you want to keep the plot open and save the plot upon a key press, the following would be an option. It saves the actualy plot in the state when the q key is pressed. (Also, the axes is not cleared every iteration, but only the line data is updated, just to show that approach).
#from gpiozero import MCP3008 # not available here
from timeit import default_timer
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# emulate sensor
class MCP3008():
def __init__(self, r):
self.x = 0.5
self.r = r
def value(self):
self.x = self.r*self.x*(1.-self.x)
return self.x
ch2 = MCP3008(3.62)
vals = []
timer = []
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
line, = ax1.plot([],[], marker=".", ls="")
ax1.set_xlabel("Time(s)")
ax1.set_ylabel("V")
#this is the fuction used as a parameter for the animation.FuncAnimation
def animate(i):
timer.append(default_timer())
vals.append(ch2.value())
line.set_data(timer,vals)
ax1.relim()
ax1.autoscale_view()
ani = animation.FuncAnimation(fig, animate, interval = 50)
def press(event):
if event.key == 'q':
ani.event_source.stop()
fig.savefig("plot.png")
print("Plot saved")
cid = fig.canvas.mpl_connect('key_press_event', press)
plt.show()

Dynamically adding a vertical line to matplotlib plot

I'm trying to add vertical lines to a matplotlib plot dynmically when a user clicks on a particular point.
import matplotlib.pyplot as plt
import matplotlib.dates as mdate
class PointPicker(object):
def __init__(self,dates,values):
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111)
self.lines2d, = self.ax.plot_date(dates, values, linestyle='-',picker=5)
self.fig.canvas.mpl_connect('pick_event', self.onpick)
self.fig.canvas.mpl_connect('key_press_event', self.onpress)
def onpress(self, event):
"""define some key press events"""
if event.key.lower() == 'q':
sys.exit()
def onpick(self,event):
x = event.mouseevent.xdata
y = event.mouseevent.ydata
print self.ax.axvline(x=x, visible=True)
x = mdate.num2date(x)
print x,y,type(x)
if __name__ == '__main__':
import numpy as np
import datetime
dates=[datetime.datetime.now()+i*datetime.timedelta(days=1) for i in range(100)]
values = np.random.random(100)
plt.ion()
p = PointPicker(dates,values)
plt.show()
Here's an (almost) working example. When I click a point, the onpick method is indeed called and the data seems to be correct, but no vertical line shows up. What do I need to do to get the vertical line to show up?
Thanks
You need to update the canvas drawing (self.fig.canvas.draw()):
def onpick(self,event):
x = event.mouseevent.xdata
y = event.mouseevent.ydata
L = self.ax.axvline(x=x)
self.fig.canvas.draw()

Categories