Why are my plots not appearing with set_data using Tkinter? - python

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:

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.

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

Embedding a Matplotlib plot into PyQt

I have been trying unsuccessfully to embed a matplotlib into PyQt for the past few days and before you say anything, I have been to every site possible to help me.
When I run the code it produces a 1 by 1 graph but doesn't actually graph anything
from __future__ import unicode_literals
import sys
import os
import random
from matplotlib.backends import qt4_compat
use_pyside = qt4_compat.QT_API == qt4_compat.QT_API_PYSIDE
if use_pyside:
from PySide import QtGui, QtCore
else:
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import numpy as np
import scipy.io
import os
import pandas as pd
progname = os.path.basename(sys.argv[0])
progversion = "0.1"
class MyMplCanvas(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
# We want the axes cleared every time plot() is called
self.axes.hold(False)
self.compute_initial_figure()
#
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class MyStaticMplCanvas(MyMplCanvas):
def extract_data(self, name):
#setting up lists
Stim_trig = []
Stim_order = []
Sch_wav = []
data = scipy.io.loadmat(name)
for k,v in data.items():
#Sends Sch_wav data to a list
if "Sch_wav" in k:
for d in (((v[0])[0])[4]):
Sch_wav.append(d[0])
#Sends StimTrig to a list
if k=="StimTrig":
for g in (((v[0])[0])[4]):
Stim_trig.append(g[0])
Stim_trig.append(Stim_trig[-1]+1)
#Sends Stim order to a list
for w in (((v[0])[0])[5]):
Stim_order.append(w[0])
superdata = []
#Prepares grouping stimuli and trigger
for i in range(len(Stim_trig)-1):
fire = []
for p in Sch_wav:
if p > Stim_trig[i] and p < Stim_trig[i+1]:
fire.append(p - Stim_trig[i])
superdata.append([Stim_order[i],fire])
#sorts all the data
superdata.sort()
alladdedup = [[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[62]]
count = 0
for d in superdata:
if d[0] == (alladdedup[count])[0]:
for j in d[1]:
((alladdedup)[count]).append(j)
else:
count += 1
#places time stamps of triggers in lists for each trigger
for l in alladdedup:
l.pop(0)
l.sort()
#removes title and sorts data
ffmsb = []
#finds number of firings for each milisecond bin
for v in alladdedup:
fmsb = []
for b in range(100):
msbc = b/100
msb = []
for t in v:
if t > msbc and t < msbc + 0.01:
msb.append(t)
fmsb.append(len(msb))
ffmsb.append(fmsb)
#returns list of stimuli firings per milisecond bin
return ffmsb
#End of Sorting Code
#Start of Graphing Code
def stimuli_graph(self):
#Set file to use and the stimuli wanted. In this case stimuli 1 is being used
filename = ("654508_rec02_all.mat"[1])
self.extract_data(filename)
#Creates parameters for index
numberlist = []
for i in range(100):
numberlist.append(i/100)
#Adjusts the y-axis max according to the y-axis of a graphed stimuli e.g. If plotted graph has y-axis of 10
# then it'll be adjusted by 1.3. So it'll change to 13
x = 0
for i in filename:
if i > x:
x = i*1.3
#Dataframes the data from extract_data
c = pd.Series(filename, index = numberlist)
neurons = pd.DataFrame((c))
#This is where the data gets graphed. I know this is where the problem occurs, I just don't know where
#This code is a mess and for that I am sorry
times = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
ax = neurons.plot(
kind = 'bar',
title = 'Number of neurons firing for stimuli '+str(1),
legend = False,
xlim = (0, 100),
ylim = (0, x),
width = 1,
position = 0,
edgecolor = 'white',
xticks = (np.arange(min(times), max(times), 10)) #Makes the x-axis label in increments of 0.1 instead of 0.01
)
ax.set_xlabel('Time (s)')
ax.set_ylabel('Neurons Firing')
t = arange(0, 100, 1)
self.axes.plot(t, neurons)
class ApplicationWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setWindowTitle("application main window")
self.file_menu = QtGui.QMenu('&File', self)
self.file_menu.addAction('&Quit', self.fileQuit,
QtCore.Qt.CTRL + QtCore.Qt.Key_Q)
self.menuBar().addMenu(self.file_menu)
self.help_menu = QtGui.QMenu('&Help', self)
self.menuBar().addSeparator()
self.menuBar().addMenu(self.help_menu)
self.main_widget = QtGui.QWidget(self)
l = QtGui.QVBoxLayout(self.main_widget)
sc = MyStaticMplCanvas(self.main_widget, width=5, height=4, dpi=100)
l.addWidget(sc)
self.main_widget.setFocus()
self.setCentralWidget(self.main_widget)
self.statusBar().showMessage("All hail matplotlib!", 2000)
def fileQuit(self):
self.close()
def closeEvent(self, ce):
self.fileQuit()
qApp = QtGui.QApplication(sys.argv)
aw = ApplicationWindow()
aw.setWindowTitle("%s" % progname)
aw.show()
sys.exit(qApp.exec_())
#qApp.exec_()
The data used in the graph is extracted from an external file

Tkinter and pyplot running out of memory

I'm running a Tkinter script that updates a plot every 5 seconds. It calls the function that plots it every 5 seconds. After not that long python starts using a lot of memory, I checked in task manager. The memory usage keeps increasing really fast. It starts a new file every 24 hours so there is a limit to the number of lines in the file.
The file starts empty.
I tried increasing the 5s time span but it does the same thing. Maybe a little slower,
also tried tried plotting every 3 rows or so but the same thing happened again.
Any idea what is causing such high memory usage and how to fix?
Thanks!
data = np.genfromtxt(filename)
time_data = data[:,0]
room_temp_data_celsius = data[:,1]
rad_temp_data_celsius = data[:,2]
fan_state_data = data[:,3]
threshold_data = data[:,4]
hysteresis_data = data[:,5]
threshold_up = [] #empty array
threshold_down = []#empty array
for i in range(0,len(threshold_data)):
threshold_up.append(threshold_data[i]+hysteresis_data[i])
threshold_down.append(threshold_data[i]-hysteresis_data[i])
# Time formatting
dts = map(datetime.datetime.fromtimestamp, time_data)
fds = matplotlib.dates.date2num(dts)
hfmt = matplotlib.dates.DateFormatter('%H:%M')
# Temperature conversion
room_temp_data_fahrenheit = map(celsius_to_fahrenheit, room_temp_data_celsius)
rad_temp_data_fahrenheit = map(celsius_to_fahrenheit, rad_temp_data_celsius)
threshold_data_fahrenheit = map(celsius_to_fahrenheit, threshold_data)
threshold_up_fahrenheit = map(celsius_to_fahrenheit, threshold_up)
threshold_down_fahrenheit = map(celsius_to_fahrenheit, threshold_down)
f = plt.figure()
a = f.add_subplot(111)
a.plot(fds,room_temp_data_fahrenheit, fds, rad_temp_data_fahrenheit, 'r')
a.plot(fds,fan_state_data*(max(rad_temp_data_fahrenheit)+4),'g_')
a.plot(fds, threshold_up_fahrenheit, 'y--')
a.plot(fds, threshold_down_fahrenheit, 'y--')
plt.xlabel('Time (min)')
plt.ylabel('Temperature '+unichr(176)+'F')
plt.legend(["Room Temperature","Radiator","Fan State","Threshold Region"], loc="upper center", ncol=2)
plt.ylim([min(room_temp_data_fahrenheit)-5, max(rad_temp_data_fahrenheit)+5])
plt.grid()
a.xaxis.set_major_formatter(hfmt)
data_graph = FigureCanvasTkAgg(f, master=root)
data_graph.show()
data_graph.get_tk_widget().grid(row=6,column=0, columnspan=3)
root.after(WAIT_TIME, control)
It's not clear to me from your code how your plots are changing with time. So I don't have any specific suggestion for your existing code. However, here is a basic example of how to embed an animated matplotlib figure in a Tkinter app. Once you grok how it works, you should be able to adapt it to your situation.
import matplotlib.pyplot as plt
import numpy as np
import Tkinter as tk
import matplotlib.figure as mplfig
import matplotlib.backends.backend_tkagg as tkagg
pi = np.pi
sin = np.sin
class App(object):
def __init__(self, master):
self.master = master
self.fig = mplfig.Figure(figsize = (5, 4), dpi = 100)
self.ax = self.fig.add_subplot(111)
self.canvas = canvas = tkagg.FigureCanvasTkAgg(self.fig, master)
canvas.get_tk_widget().pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
self.toolbar = toolbar = tkagg.NavigationToolbar2TkAgg(canvas, master)
toolbar.update()
self.update = self.animate().next
master.after(10, self.update)
canvas.show()
def animate(self):
x = np.linspace(0, 6*pi, 100)
y = sin(x)
line1, = self.ax.plot(x, y, 'r-')
phase = 0
while True:
phase += 0.1
line1.set_ydata(sin(x + phase))
newx = x+phase
line1.set_xdata(newx)
self.ax.set_xlim(newx.min(), newx.max())
self.ax.relim()
self.ax.autoscale_view(True, True, True)
self.fig.canvas.draw()
self.master.after(10, self.update)
yield
def main():
root = tk.Tk()
app = App(root)
tk.mainloop()
if __name__ == '__main__':
main()
The main idea here is that plt.plot should only be called once. It returns a Line2D object, line1. You can then manipulate the plot by calling line1.set_xdata and/or line1.set_ydata. This "technique" for animation comes from the Matplotlib Cookbook.
Technical note:
The generator function, animate was used here to allow the state of the plot to be saved and updated without having to save state information in instance attributes. Note that it is the generator function's next method (not the generator self.animate) which is being called repeatedly:
self.update = self.animate().next
master.after(10, self.update)
So we are advancing the plot frame-by-frame by calling the generator, self.animate()'s, next method.

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