I am writing a GUI using PyQt4, and am trying to implement a live updating pyplot chart using the matplotlib animate module.
The attached code works exactly as I am intending if an error conveniently occurs just before plt.show() is called in the main method (I have caused a div0 error here for demonstration).
If plt.show() is called however, 2 other windows are opened alongside the embedded graph (see screenshots). If any of these extra windows are closed, the embedded graph crashes. If plt.show() is removed from the code entirely, the chart live chart never works at all.
I am at a bit of a loss as to what is going on there. This behaviour is unexpected and quite confusing. It would be fantastic if anyone has any suggestions for how I might get everything working properly.
import sys
import json
import time
import math
import dateutil
import requests
from PyQt4 import QtGui, QtCore
import matplotlib.pyplot as plt
from matplotlib import style
import matplotlib.animation as animation
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
style.use('ggplot')
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.time = []
self.recorded_temp = []
self.recorded_setpoint = []
self.runtime = 0
self.element_status = ""
self.temp = 0
self.setpoint = 0
self.setWindowTitle("Distillation Control Panel")
#self.setGeometry(100, 100, 500, 100)
self.stacked_layout = QtGui.QStackedLayout()
self.connect_page = QtGui.QWidget()
self.home_page = QtGui.QWidget()
self.home_UI()
self.connect_UI()
self.stacked_layout.addWidget(self.connect_page)
self.stacked_layout.addWidget(self.home_page)
self.setLayout(self.stacked_layout)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update_gui)
self.show()
def main(self):
self.timer.start(1000)
ani = animation.FuncAnimation(self.fig, self.animate, interval=1000)
1 / 0 # Code works if an error occurs here
plt.show()
def home_UI(self):
grid = QtGui.QGridLayout()
runtime_indicator_label = QtGui.QLabel()
self.runtime = QtGui.QLabel()
runtime_indicator_label.setText("Runtime: ")
element_status_indicator = QtGui.QLabel()
self.element_status = QtGui.QLabel()
element_status_indicator.setText("Element Status: ")
temp_indicator_label = QtGui.QLabel()
self.temp = QtGui.QLabel()
temp_indicator_label.setText("Temperature: ")
setpoint_label = QtGui.QLabel()
self.setpoint = QtGui.QLabel()
setpoint_label.setText("Setpoint: ")
up_btn = QtGui.QPushButton("Increase", self)
up_btn.resize(up_btn.minimumSizeHint())
down_btn = QtGui.QPushButton("Decrease", self)
down_btn.resize(down_btn.minimumSizeHint())
self.fig = plt.figure(figsize=(15, 5))
self.canvas = FigureCanvas(self.fig)
self.toolbar = NavigationToolbar(self.canvas, self)
grid.addWidget(runtime_indicator_label, 0, 0, 2, 1)
grid.addWidget(self.runtime, 0, 1, 2, 1)
grid.addWidget(element_status_indicator, 0, 2, 2, 1)
grid.addWidget(self.element_status, 0, 3, 2, 1)
grid.addWidget(temp_indicator_label, 0, 4, 2, 1)
grid.addWidget(self.temp, 0, 5, 2, 1)
grid.addWidget(setpoint_label, 0, 6, 2, 1)
grid.addWidget(self.setpoint, 0, 7, 2, 1)
grid.addWidget(up_btn, 0, 8, 1, 1)
grid.addWidget(down_btn, 1, 8, 1, 1)
grid.addWidget(self.canvas, 3, 0, 1, 9)
grid.addWidget(self.toolbar, 4, 0, 1, 9)
self.home_page.setLayout(grid)
def connect_UI(self):
'''
User interface for connecting to distillation controller server. Enter local IP address for ESP8266
assigned by router
'''
grid = QtGui.QGridLayout()
addr = QtGui.QLineEdit()
addr.setPlaceholderText("Enter IP address for distiller server")
grid.addWidget(addr, 1, 1)
btn = QtGui.QPushButton("Connect!", self)
btn.clicked.connect(lambda: self.connect(addr.text()))
btn.setFocus()
grid.addWidget(btn, 1, 2)
self.connect_page.setLayout(grid)
def connect(self, addr):
'''
Check connection with controller server and verify address
'''
full_address = 'http://' + addr + "/connect"
try:
data = requests.get(str(full_address), timeout=1.0)
if data.status_code == requests.codes.ok:
QtGui.QMessageBox.information(self, 'test', "Connected!", QtGui.QMessageBox.Ok)
self.address = addr
self.stacked_layout.setCurrentIndex(1)
else:
# Add handling if non-200 http code returned
pass
except Exception:
QtGui.QMessageBox.information(self, 'test', "Device not found at {}. Please enter a valid address".format(addr), QtGui.QMessageBox.Ok)
self.connect_UI()
self.main()
def update_gui(self):
full_address = 'http://' + self.address + "/request_data"
data = json.loads(requests.get(full_address).text)
runtime = convertMillis(data["Runtime"])
temp = data['Temp']
setpoint = data["Setpoint"]
self.runtime.setText(runtime)
self.element_status.setText(data["Element status"])
self.temp.setText(str(temp))
self.setpoint.setText(str(setpoint))
self.time.append(dateutil.parser.parse(runtime))
self.recorded_temp.append(temp)
self.recorded_setpoint.append(setpoint)
def animate(self, i):
plt.cla()
plt.plot(self.time, self.recorded_temp)
plt.plot(self.time, self.recorded_setpoint)
plt.xlabel("Running Time", fontsize=6)
plt.ylabel("Temperature (°C)", fontsize=6)
plt.xticks(fontsize=6, rotation=45)
plt.yticks(fontsize=6)
plt.tight_layout()
plt.draw()
def convertMillis(millis):
seconds = math.floor((millis/1000)%60)
minutes = math.floor((millis/(1000*60))%60)
hours = math.floor((millis/(1000*60*60))%24)
return "{:02}:{:02}:{:02}".format(hours, minutes, seconds)
def main():
app = QtGui.QApplication(sys.argv)
GUI = Window()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I am unable to post screenshots, so here is a link to the image
Related
I'm hoping I can get some help with a problem I've been stuck on all day. I've looked through previous questions but nothing seems to quite match up with the issue I'm facing. Perhaps a fresh pair of eyes can guide me in the right direction. I'll include my code at the end of my question.
Background:
I'm working on a simple application that consists of the main window and a popup window. The main window contains only one button that opens the popup window when pressed. The popup window contains two checkbox options, an ok button, and a cancel button. When pressed, the popup's ok button returns a signal to the main window. This signal contains a list of 1s and 0s, depending on the status of the checkboxes. The purpose of the list is so that a function in the main window can determine which canvases to plot (either plot 1, plot 2, or both).
It should be noted that the main window is organized using a grid layout, and each canvas is meant to be displayed on a row in column 3 with a corresponding label in column 4. The desired outcome when creating both canvases is as shown in the figure:
Problem:
Everything works fine until the end when the signal returns to the slot at popup_input() within the main window. No errors occur but the canvases simply do not appear. Taking the code for creating the figures from popup_input() and placing it instead into open_options() does seem to work and gives the figure as shown above. However, my goal is to have the plots appear after the user makes their selection. Does anyone have any idea why the canvases are failing to appear?
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import QDialog, QApplication, QMainWindow, QLabel, QPushButton
from PyQt5.QtGui import QFont
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.ticker import FormatStrFormatter
class Options(QMainWindow):
popup_response = QtCore.pyqtSignal(object)
def __init__(self):
super().__init__()
self._title = 'Plotting Options'
self.setWindowTitle(self._title)
self.selected_plots = [1, 1] # Always have both options selected by default
self.option1 = QtWidgets.QCheckBox('Option 1')
self.option1.setChecked(True)
self.option2 = QtWidgets.QCheckBox('Option 2')
self.option2.setChecked(True)
self.checkbox_layout = QtWidgets.QHBoxLayout()
self.checkbox_layout.addWidget(self.option1)
self.checkbox_layout.addWidget(self.option2)
self.ok_button = QtWidgets.QPushButton('OK', self)
self.ok_button.clicked.connect(lambda: self.clicked_ok())
self.cancel_button = QtWidgets.QPushButton('Cancel', self)
self.cancel_button.clicked.connect(lambda: self.clicked_cancel())
self.button_layout = QtWidgets.QHBoxLayout()
self.button_layout.addWidget(self.ok_button)
self.button_layout.addWidget(self.cancel_button)
self._popup = QtWidgets.QWidget()
self.setCentralWidget(self._popup)
self.layout = QtWidgets.QGridLayout(self._popup)
self.layout.addLayout(self.checkbox_layout, 0, 0)
self.layout.addLayout(self.button_layout, 1, 0)
def finalize_selected_plots(self):
if self.option1.isChecked() == False:
self.selected_plots[0] = 0
if self.option2.isChecked() == False:
self.selected_plots[1] = 0
return self.selected_plots
def clicked_ok(self):
self.plots = self.finalize_selected_plots()
# Send selection back to Results Window
main_window = MainWindow()
static_reply = self.plots
self.popup_response.connect(main_window.popup_input)
self.popup_response.emit(static_reply)
self.plots = [1,1]
self.close()
def clicked_cancel(self):
self.close()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.exPopup = Options()
self.data_to_plot = [[1, 5, 10], [2, 4, 6], [6, 4, 2]] # Dummy values
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
self.button_options = QtWidgets.QPushButton('Plot Options', self)
self.button_options.clicked.connect(self.open_options)
self.option_layout = QtWidgets.QHBoxLayout()
self.option_layout.addWidget(self.button_options)
self.layout = QtWidgets.QGridLayout(self._main)
self.layout.addLayout(self.option_layout, 0, 2)
def open_options(self):
self.exPopup.show()
#QtCore.pyqtSlot(object)
def popup_input(self, reply):
plot_title_font_size = 15
data = self.data_to_plot
if reply[0] != 0:
self.figure_xy1 = FigureCanvas(Figure(figsize=(5, 5)))
self._figure1 = self.figure_xy1.figure.subplots()
self._figure1.grid()
self.layout.addWidget(self.figure_xy1, 1, 3)
self.figure_label1 = QLabel('Test Plot 1', self)
self.figure_label1.setFont(QFont('Times', plot_title_font_size))
self.figure_label1.setAlignment(QtCore.Qt.AlignLeft)
self.layout.addWidget(self.figure_label1, 1, 4)
x = data[0]
y = data[1]
self._figure1.plot(x, y, '-')
self._figure1.set_xlabel('x')
self._figure1.set_ylabel('y1')
self._figure1.figure.canvas.draw()
if reply[1] != 0:
self.figure_xy2 = FigureCanvas(Figure(figsize=(5, 5)))
self._figure2 = self.figure_xy2.figure.subplots()
self._figure2.grid()
self.layout.addWidget(self.figure_xy2, 2, 3)
self.figure_label2 = QLabel('Test Plot 2', self)
self.figure_label2.setFont(QFont('Times', plot_title_font_size))
self.figure_label2.setAlignment(QtCore.Qt.AlignLeft)
self.layout.addWidget(self.figure_label2, 2, 4)
x = data[0]
y = data[2]
self._figure2.plot(x, y, '-')
self._figure2.set_xlabel('x')
self._figure2.set_ylabel('y2')
self._figure2.figure.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = QtWidgets.QStackedWidget()
main_window = MainWindow()
widget.addWidget(main_window)
widget.setWindowTitle("Main Window")
widget.show()
try:
sys.exit(app.exec_())
except:
print("Exiting")
I have found a solution to my issue. With the help of jmacey over on Reddit's pyqt page, here's what I figured out:
My popup should inherit QDialog vs QMainWindow
Simplified dialog button scheme by utilizing QDialogButtonBox
Include accepted and rejected signals for my ok and cancel buttons, respectively. I also included an extra signal for clicked_ok that would perform my desired function as the dialog closes.
popup dialog should be executed from my main window. If I click ok in my dialog, then it will run my code related to plotting.
Here are my updated classes:
class Options(QDialog):
popup_response = QtCore.pyqtSignal(object)
def __init__(self, parent=None):
super().__init__(parent)
self._title = 'Plotting Options'
self.setWindowTitle(self._title)
self.setGeometry(100, 100, 300, 200)
self.selected_plots = [1, 1] # Always have both options selected by default
self.option1 = QtWidgets.QCheckBox('Option 1')
self.option1.setChecked(True)
self.option2 = QtWidgets.QCheckBox('Option 2')
self.option2.setChecked(True)
self.checkbox_layout = QtWidgets.QHBoxLayout()
self.checkbox_layout.addWidget(self.option1)
self.checkbox_layout.addWidget(self.option2)
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.clicked_ok)
self.button_box.rejected.connect(self.reject)
self.popup_layout = QtWidgets.QVBoxLayout()
self.popup_layout.addLayout(self.checkbox_layout)
self.popup_layout.addWidget(self.button_box)
self.setLayout(self.popup_layout)
def finalize_selected_plots(self):
if self.option1.isChecked() == False:
self.selected_plots[0] = 0
if self.option2.isChecked() == False:
self.selected_plots[1] = 0
return self.selected_plots
def clicked_ok(self):
print("clicked ok")
self.plots = self.finalize_selected_plots()
# Send selection back to Results Window
# main_window = MainWindow()
# static_reply = self.plots
# self.popup_response.connect(main_window.popup_input)
# self.popup_response.emit(static_reply)
self.plots = [1,1]
self.close()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
#self.exPopup = Options()
self.data_to_plot = [[1, 5, 10], [2, 4, 6], [6, 4, 2]] # Dummy values
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
self.button_options = QtWidgets.QPushButton('Plot Options', self)
#self.button_options.clicked.connect(self.open_options)
self.button_options.clicked.connect(self.popup_input)
self.option_layout = QtWidgets.QHBoxLayout()
self.option_layout.addWidget(self.button_options)
self.layout = QtWidgets.QGridLayout(self._main)
self.layout.addLayout(self.option_layout, 0, 2)
# def open_options(self):
# self.exPopup.show()
##QtCore.pyqtSlot(object)
def popup_input(self):
plot_title_font_size = 15
data = self.data_to_plot
self.grab_popup = Options(self)
if self.grab_popup.exec():
print("Pressed ok")
if reply[0] != 0:
self.figure_xy1 = FigureCanvas(Figure(figsize=(5, 5)))
self._figure1 = self.figure_xy1.figure.subplots()
self._figure1.grid()
self.layout.addWidget(self.figure_xy1, 1, 3)
self.figure_label1 = QLabel('Test Plot 1', self)
self.figure_label1.setFont(QFont('Times', plot_title_font_size))
self.figure_label1.setAlignment(QtCore.Qt.AlignLeft)
self.layout.addWidget(self.figure_label1, 1, 4)
x = data[0]
y = data[1]
self._figure1.plot(x, y, '-')
self._figure1.set_xlabel('x')
self._figure1.set_ylabel('y1')
self._figure1.figure.canvas.draw()
if reply[1] != 0:
self.figure_xy2 = FigureCanvas(Figure(figsize=(5, 5)))
self._figure2 = self.figure_xy2.figure.subplots()
self._figure2.grid()
self.layout.addWidget(self.figure_xy2, 2, 3)
self.figure_label2 = QLabel('Test Plot 2', self)
self.figure_label2.setFont(QFont('Times', plot_title_font_size))
self.figure_label2.setAlignment(QtCore.Qt.AlignLeft)
self.layout.addWidget(self.figure_label2, 2, 4)
x = data[0]
y = data[2]
self._figure2.plot(x, y, '-')
self._figure2.set_xlabel('x')
self._figure2.set_ylabel('y2')
self._figure2.figure.canvas.draw()
else:
print("Pressed Cancel")
I need to update an imshow() figure packed in tkinter. Here's a code example:
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as tk
import random
matplotlib.use('TkAgg')
def get_data():
return [[random.randint(1, 100), random.randint(1, 100)], [random.randint(1, 100), random.randint(1, 100)]]
class MainWindow:
def __init__(self, window):
self.window = window
self.figureCanvas = FigureCanvas(self.window)
self.button = tk.Button(window, text="Update", command=self._button_command)
self.button.pack()
self._pack_figure()
def _button_command(self):
self.figureCanvas.data = get_data()
self.figureCanvas.plot()
def _pack_figure(self):
canvas = FigureCanvasTkAgg(self.figureCanvas.figure, master=self.window)
canvas.get_tk_widget().pack()
canvas.draw()
class FigureCanvas:
data = []
def __init__(self, window):
self.window = window
self.figure = Figure(figsize=(6, 6))
self._create_plot_object()
def _create_plot_object(self):
self.axes = self.figure.add_subplot(111)
self.plot_object = self.axes.imshow([[0, 0], [0, 0]])
def update(self):
self.plot_object.set_data(self.data)
_window = tk.Tk()
_window.title("Snapshot")
start = MainWindow(_window)
_window.mainloop()
It plots the first image [[0, 0], [0, 0]] fine, but it doesn't re-draw after the image data has updated by plot_object.set_data(self.data)
Update
I have tried calling the following functions under FigureCanvas.update():
matplotlib.backend_bases.FigureCanvasBase(self.figure).draw_idle()
self.axes.redraw_in_frame()
A correct version of the code could look like this. I got rid of the false FigureCanvas and put everything in one class (sure you can use different classes, but don't name them confusingly). Also several other issues are fixed here (e.g. not calling the callback, supplying min and max color values).
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as tk
def get_data():
return np.random.randint(1,100,size=(2,2))
class MainWindow:
def __init__(self, window):
self.window = window
self.button = tk.Button(window, text="Update", command=self._button_command )
self.button.pack()
self._pack_figure()
self._create_plot_object()
def _button_command(self):
self.data = get_data()
self.plot()
def _pack_figure(self):
self.figure = Figure(figsize=(6, 6))
self.canvas = FigureCanvasTkAgg(self.figure, master=self.window)
self.canvas.get_tk_widget().pack()
def _create_plot_object(self):
axes = self.figure.add_subplot(111)
self.plot_object = axes.imshow([[0, 0], [0, 0]], vmin=0, vmax=100)
def plot(self):
self.plot_object.set_data(self.data)
self.canvas.draw_idle()
_window = tk.Tk()
_window.title("Snapshot")
start = MainWindow(_window)
_window.mainloop()
I'm new to Python and I want to implement a scrolling plot for a very long time series data. I've found an example from Matplotlib as follows.
http://scipy-cookbook.readthedocs.io/items/Matplotlib_ScrollingPlot.html
When I run the example from the link, I found every time I scroll the plot and release the scrollbar, the scrollbar returns to the beginning. Want to scroll to the next position? I need to start to scroll from the beginning again.
I want to understand why it happens and how to fix it.
Here's an improved version of the example. (Disclaimer: I started digging into it half an hour ago, never before used wx/matplotlib scrollbars so there might be a much better solution.)
The path I took: first I checked the wx scroll events, then found out that the canvas is FigureCanvasWxAgg derived from wxPanel, inheriting wxWindow methods. There you may find the scroll position handling methods GetScrollPos and SetScrollPos.
from numpy import arange, sin, pi, float, size
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self,parent, id, 'scrollable plot',
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER,
size=(800, 400))
self.panel = wx.Panel(self, -1)
self.fig = Figure((5, 4), 75)
self.canvas = FigureCanvasWxAgg(self.panel, -1, self.fig)
self.scroll_range = 400
self.canvas.SetScrollbar(wx.HORIZONTAL, 0, 5,
self.scroll_range)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas, -1, wx.EXPAND)
self.panel.SetSizer(sizer)
self.panel.Fit()
self.init_data()
self.init_plot()
self.canvas.Bind(wx.EVT_SCROLLWIN, self.OnScrollEvt)
def init_data(self):
# Generate some data to plot:
self.dt = 0.01
self.t = arange(0,5,self.dt)
self.x = sin(2*pi*self.t)
# Extents of data sequence:
self.i_min = 0
self.i_max = len(self.t)
# Size of plot window:
self.i_window = 100
# Indices of data interval to be plotted:
self.i_start = 0
self.i_end = self.i_start + self.i_window
def init_plot(self):
self.axes = self.fig.add_subplot(111)
self.plot_data = \
self.axes.plot(self.t[self.i_start:self.i_end],
self.x[self.i_start:self.i_end])[0]
def draw_plot(self):
# Update data in plot:
self.plot_data.set_xdata(self.t[self.i_start:self.i_end])
self.plot_data.set_ydata(self.x[self.i_start:self.i_end])
# Adjust plot limits:
self.axes.set_xlim((min(self.t[self.i_start:self.i_end]),
max(self.t[self.i_start:self.i_end])))
self.axes.set_ylim((min(self.x[self.i_start:self.i_end]),
max(self.x[self.i_start:self.i_end])))
# Redraw:
self.canvas.draw()
def update_scrollpos(self, new_pos):
self.i_start = self.i_min + new_pos
self.i_end = self.i_min + self.i_window + new_pos
self.canvas.SetScrollPos(wx.HORIZONTAL, new_pos)
self.draw_plot()
def OnScrollEvt(self, event):
evtype = event.GetEventType()
if evtype == wx.EVT_SCROLLWIN_THUMBTRACK.typeId:
pos = event.GetPosition()
self.update_scrollpos(pos)
elif evtype == wx.EVT_SCROLLWIN_LINEDOWN.typeId:
pos = self.canvas.GetScrollPos(wx.HORIZONTAL)
self.update_scrollpos(pos + 1)
elif evtype == wx.EVT_SCROLLWIN_LINEUP.typeId:
pos = self.canvas.GetScrollPos(wx.HORIZONTAL)
self.update_scrollpos(pos - 1)
elif evtype == wx.EVT_SCROLLWIN_PAGEUP.typeId:
pos = self.canvas.GetScrollPos(wx.HORIZONTAL)
self.update_scrollpos(pos - 10)
elif evtype == wx.EVT_SCROLLWIN_PAGEDOWN.typeId:
pos = self.canvas.GetScrollPos(wx.HORIZONTAL)
self.update_scrollpos(pos + 10)
else:
print "unhandled scroll event, type id:", evtype
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(parent=None,id=-1)
self.frame.Show()
self.SetTopWindow(self.frame)
return True
if __name__ == '__main__':
app = MyApp()
app.MainLoop()
You may adjust e.g. the increments for PAGEUP/PAGEDOWN if you feel it too slow.
Also if you wish, the events can be handled separately setting up the specific event handlers instead of their collection EVT_SCROLLWIN, then instead of if/elifs there will be OnScrollPageUpEvt etc.
I would like to know how to perform the following pseudocode in python when embedding a matplotlib figure inside of a wxPython FigureCanvasWxAgg instance:
the following items need to be used:
---- IMPORTS THAT CAN BE USED ----
import wx
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
-------------------------------------------------------
main_canvas;
shadow_canvas;
big_plot [a matplotlib figure instance with one big plot in it -- like the one you would make with figure.add_subplots(1,1,1)];
small_subplots [a matplotlib figure instance with, say, 2 subplots in it -- you would make with figure.add_subplots(2,1,i), where 1<=i<=2]
a function called SwapView(main_canvas,shadow_canvas,big_plot,small_subplots) that essentially swaps the figure that is currently in shadow_canvas with the one in main_canvas (so keep switching between the one with a big plot and the one with many small plots)
a function UpdateDisplay() that dynamically updates the display every time you call SwapView()
******* PSEUDOCODE *******
main_canvas.show()
shadow_canvas.hide()
main_canvas has big_plot initially
shadow_canvas has small_subplots initially
if big_plot in main_canvas:
SwapView(...) ---> should put big_plot in shadow_canvas and small_subplots in the main_canvas
else:
SwapView(...) ---> should put the small_subplots in shadow_canvas and the big_plot in main_canvas
UpdateDisplay()
******* END OF CODE *******
Here is my initial attempt at this code and unfortunately I can't find a way to find which figure is the one currently displayed.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import numpy as np
import wx
import time
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
class myframe(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,parent = None, id = -1, title = 'LoadFigure()', size = (800,800))
self.figurePanel = FigurePanel(parent = self)
canvas1 = self.figurePanel.canvas
canvas2 = self.figurePanel.enlarged_canvas
fig1 = self.figurePanel.enlarged_figure
fig2 = self.figurePanel.figure
fig1.set_canvas(canvas1) #enlarged_fig resides in canvas1
fig2.set_canvas(canvas2) #fig resides in canvas2
#Show both canvases ---> canvas2 will override canvas1, but when canvas2 hides canvas1 should show
canvas2.Show()
canvas1.Show()
self.Show()
print "Starting to swap displays!"
time.sleep(1)
for i in range(10):
print "run: %d"%i
self.SwapView(big_plot = fig1,small_plots = fig2,main_canvas = canvas1,shadow_canvas = canvas2)
time.sleep(1)
def SwapView(self,big_plot,small_plots,main_canvas,shadow_canvas):
'''
Keep swapping the main_canvas with the shadow_canvas to show either fig1 or fig2.
Initially, big_plot has main_canvas and small_plots have shadow_canvas
'''
wx.Yield()
print list(main_canvas)
print list(big_plot.get_children())
time.sleep(2)
for child in big_plot.get_children():
if child == main_canvas:
print 'big_plot has main_canvas'
big_plot.set_canvas(shadow_canvas)
small_plots.set_canvas(main_canvas)
main_canvas.draw()
wx.Yield()
main_canvas.Show()
else:
print 'big_plot has shadow_canvas'
for child in small_plots.get_children():
if child == main_canvas:
print 'small_plots has main_canvas'
small_plots.set_canvas(shadow_canvas)
big_plot.set_canvas(main_canvas)
main_canvas.draw()
wx.Yield()
main_canvas.Show()
else:
print 'small_plots has shadow_canvas'
class FigurePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figPanel = self
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.figure = Figure(figsize = (8,6.1), dpi =60)
self.ax = self.figure.add_subplot(1,1,1)
self.ax.plot([1,2,3],[1,2,3])
self.enlarged_figure = Figure(figsize = (8,6.1), dpi = 60)
self.ax1 = self.enlarged_figure.add_subplot(2,1,1)
self.ax2 = self.enlarged_figure.add_subplot(2,1,2)
self.ax1.plot([1,2,3],[1,4,9])
self.ax2.plot([1,2,3],[1,4,9])
self.canvas = FigureCanvas(self, -1, self.figure)
self.enlarged_canvas = FigureCanvas(self,-1,self.enlarged_figure)
self.Layout()
self.Fit()
if __name__ == "__main__":
app = wx.App(False)
fr = myframe()
app.MainLoop()
For anyone that might need it, here's the solution that I came up with:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import numpy as np
import wx
import time
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
class myframe(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,parent = None, id = -1, title = 'SWAP!', size = (480,390))
self.figurePanel = FigurePanel(parent = self)
self.canvas1 = self.figurePanel.canvas
self.canvas2 = self.figurePanel.enlarged_canvas
self.fig1 = self.figurePanel.enlarged_figure
self.fig2 = self.figurePanel.figure
self.fig1.set_canvas(self.canvas1) #enlarged_fig resides in canvas1
self.canvas1.Show()
self.Show()
self.canvas2.mpl_connect("button_release_event",self.OnLoadFigure) #Enable the detection of mouseclicks for the plots in the plotting window
print "Click anywhere on the figure to swap the plots!"
self.display = 1
def OnLoadFigure(self,event = None):
print "Tried to load figure"
if event != None:
self.display = self.SwapView(big_plot = self.fig1 ,small_plots = self.fig2 , display = self.display, main_canvas = self.canvas1 , shadow_canvas = 0)
def SwapView(self,big_plot = None,display = -1, small_plots = None,main_canvas = None,shadow_canvas = None):
'''
Keep swapping the main_canvas with the shadow_canvas to show either fig1 or fig2.
Initially, big_plot has main_canvas and small_plots have shadow_canvas
'''
wx.Yield()
print display
if display == 1: #Show the big plot
print 'big_plot showing'
big_plot.set_canvas(main_canvas)
main_canvas.Show()
time.sleep(0.01) #Fastest time you can pick
wx.Yield()
else:
print 'small_plots showing'
main_canvas.Hide()
wx.Yield()
self.Refresh(canvas = main_canvas)
display = not(display)
return display
def Refresh(self,canvas = None,figure = None):
wx.Yield()
if canvas != None:
print "draw"
canvas.draw()
self.Update()
self.figurePanel.Update()
wx.Yield()
class FigurePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figPanel = self
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.figure = Figure(figsize = (8,6.1), dpi =60)
self.ax = self.figure.add_subplot(1,1,1)
self.ax.plot([1,2,3],[1,2,3])
self.enlarged_figure = Figure(figsize = (8,6.1), dpi = 60)
self.ax1 = self.enlarged_figure.add_subplot(2,1,1)
self.ax2 = self.enlarged_figure.add_subplot(2,1,2)
self.ax1.plot([1,2,3],[1,4,9])
self.ax2.plot([1,2,3],[1,4,9])
self.canvas = FigureCanvas(self, -1, self.figure)
self.enlarged_canvas = FigureCanvas(self,-1,self.enlarged_figure)
self.Layout()
self.Fit()
if __name__ == "__main__":
app = wx.App(False)
fr = myframe()
app.MainLoop()
To make the display change, click on the figure.
My code opens a PyQt window with a matplotlib plot inside of it. At the top there are 10 buttons, each with their own data values to plot. I want to know how can I clear the previous plot that is currently showing and then plot the new one when I click on the buttons.
I use PyQt4 and Python 3.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import scipy.io
import sys
import os
from matplotlib.backends.backend_qt4agg import (FigureCanvasQTAgg as FigureCanvas)
from matplotlib.figure import Figure
from PyQt4.QtGui import *
from PyQt4 import QtCore
plt.style.use('ggplot')
def Extract_Data(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(1000):
msbc = b/1000
msb = []
for t in v:
if t > msbc and t < msbc + 0.001:
msb.append(t)
fmsb.append(len(msb))
ffmsb.append(fmsb)
#returns list of stimuli firings per milisecond bin
return(ffmsb)
class DisplayWidget(QMainWindow):
def __init__(self, parent=None):
super(DisplayWidget, self).__init__( parent )
self.initUI()
def initUI(self):
self.main_frame = QWidget()
self.canvas = PlotCanvas(self)
self.canvas.setParent(self.main_frame)
self.one = QPushButton('Intensity 1')
self.two = QPushButton('Intensity 2')
self.three = QPushButton('Intensity 3')
self.four = QPushButton('Intensity 4')
self.five = QPushButton('Intensity 5')
self.six = QPushButton('Intensity 6')
self.seven = QPushButton('Intensity 7')
self.eight = QPushButton('Intensity 8')
self.nine = QPushButton('Intensity 9')
self.ten = QPushButton('Intensity 10')
self.one.connect(self.one, QtCore.SIGNAL('clicked()'), self.setIntensity_one)
self.two.connect(self.two, QtCore.SIGNAL('clicked()'), self.setIntensity_two)
self.three.connect(self.three, QtCore.SIGNAL('clicked()'), self.setIntensity_three)
self.four.connect(self.four, QtCore.SIGNAL('clicked()'), self.setIntensity_four)
self.five.connect(self.five, QtCore.SIGNAL('clicked()'), self.setIntensity_five)
self.six.connect(self.six, QtCore.SIGNAL('clicked()'), self.setIntensity_six)
self.seven.connect(self.seven, QtCore.SIGNAL('clicked()'), self.setIntensity_seven)
self.eight.connect(self.eight, QtCore.SIGNAL('clicked()'), self.setIntensity_eight)
self.nine.connect(self.nine, QtCore.SIGNAL('clicked()'), self.setIntensity_nine)
self.ten.connect(self.ten, QtCore.SIGNAL('clicked()'), self.setIntensity_ten)
grid = QGridLayout()
grid.addWidget(self.canvas, 1, 0, 10, 10) # the matplotlib canvas
grid.addWidget(self.one, 0, 0)
grid.addWidget(self.two, 0, 1)
grid.addWidget(self.three, 0, 2)
grid.addWidget(self.four, 0, 3)
grid.addWidget(self.five, 0, 4)
grid.addWidget(self.six, 0, 5)
grid.addWidget(self.seven, 0, 6)
grid.addWidget(self.eight, 0, 7)
grid.addWidget(self.nine, 0, 8)
grid.addWidget(self.ten, 0, 9)
self.main_frame.setLayout(grid)
self.setCentralWidget(self.main_frame)
self.setWindowTitle('Neurons')
self.showMaximized()
def setIntensity_one(self):
self.data(intensity = 1)
def setIntensity_two(self):
self.data(intensity = 2)
def setIntensity_three(self):
self.data(intensity = 3)
def setIntensity_four(self):
self.data(intensity = 4)
def setIntensity_five(self):
self.data(intensity = 5)
def setIntensity_six(self):
self.data(intensity = 6)
def setIntensity_seven(self):
self.data(intensity = 7)
def setIntensity_eight(self):
self.data(intensity = 8)
def setIntensity_nine(self):
self.data(intensity = 9)
def setIntensity_ten(self):
self.data(intensity = 10)
def data(self, intensity):
stimuli = (Extract_Data("654508_rec02_all.mat")[intensity])
numberlist = []
for i in range(1000):
numberlist.append(i/1000)
d = pd.Series(stimuli, index = numberlist)
self.df = pd.DataFrame(d)
self.canvas.plot_data_frame(self.df)
class PlotCanvas(FigureCanvas, DisplayWidget):
def __init__(self, parent = None, width = 12, height = 9):
self.fig = Figure(figsize = (width, height))
self.axes = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def plot_data_frame(self, df):
df.plot( ax = self.axes,
kind = 'line',
title = 'Number of neurons firing',
legend = False,
xlim = (0, 1))
self.draw()
if __name__ == "__main__":
app = QApplication( [] )
widget = DisplayWidget()
widget.show()
app.exec_()
Just call self.axes.cla() before each plot at your plot_data_frame function. This will clear the axes.