matplotlib animation in wxpython panel - python

I'm struggling on an application combining wxPython and matplotlib.
I want to embed an animated matplotlib object in an wxPanel. The Data should be added on runtime.
My Module Code:
(i cant get the correct formatting, see http://pastebin.com/PU5QFEzG)
'''
a panel to display a given set of data in a wxframe as a heatmap, using pcolor
from the matplotlib
#author: me
'''
import wx
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas #todo: OW 26.10.15 needed?
class plotPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figure = plt.Figure()
self.subplot = self.figure.add_subplot(111)
plt.title('test')
self.canvas = FigureCanvas(self, -1, self.figure) #ToDo: OW 26.10.15 Verstehen
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
self.dataSet = []
self.animator = animation.FuncAnimation(self.figure,self.anim, interval=1000)
def anim(self, a):
if(len(self.dataSet) == 0):
return 0
i = a % len(self.dataSet)
obj = self.subplot.pcolor(self.dataSet[i], cmap='RdBu')
return obj
def add_data(self, data):
self.dataSet.append(data)
#
# Code for a standalone test run
#
class TestFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent,title=title,size=(1000,1000))
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Status Bar')
if __name__ == '__main__':
from numpy.random import rand #todo: OW 26.10.15 remove
app = wx.App(redirect=False)
frame = TestFrame(None, 'Debug Frame')
panel = plotPanel(frame)
frame.Show()
C = rand(10,10)
panel.add_data(C)
C = rand(10,10)
panel.add_data(C)
C = rand(10,10)
panel.add_data(C)
app.MainLoop()
Im now struggeling on adding more Details to the Graph, eg a colorbar or a title.
If I add self.subplot.title = 'test' in the anim_Fkt, i get "'str' object has no attribute 'get_animated'". If i try plt.title('test'), it has no effect.
Whats the correct way to add a title or a colorbar or a legend?

To add the features to an embedded matplotlib graph, you have to use object-oriented matplotlib API. A matplotlib graph consists of a figure (a matplotlib.figure.Figure instance), which has one or several axes (matplotlib.axes.Axes instances) in it. Each of them, in their turn, contains Drawables (lines, images, scatter plots etc).
A title can be added (or modified) to the Figure or to each Axes with setter methods, such as Figure.suptitle or Axes.set_title (or, in your case, self.figure.suptitle() or self.subplot.set_title).
The colorbar is a little bit trickier, as it needs a data object (a mappable) to be created: we can create it only in the anim function. Also, wo do not want to create many colorbar instances; once created, we only need to update it. Achieving that is easy: instantiate self.colorbar with None in constructor, and then check it against None in animation function: if it is None, then create colorbar, if it is not, then update it:
class plotPanel(wx.Panel):
def __init__(self, parent):
...
self.colorbar = None
def anim(self, a):
...
self.subplot.set_title("Frame %i" % a)
if self.colorbar is None:
self.colorbar = self.figure.colorbar(obj)
else:
self.colorbar.update_normal(obj)
self.colorbar.update_normal(obj)
return obj

Related

Matplotlib FuncAnimation does not update the plot embedded in wx Panel

I am trying to update a plot with serial data with matplotlib FuncAnimation. I am using the following example to embed the plot in a wx app.
Embedding a matplotlib figure inside a WxPython panel
However, the plot is not updated and only the initial plot is displayed.
In fact, the update function is never executed which is checked with print statements in try and except blocks. You may see the script here.
import wx
from matplotlib.figure import Figure as Fig
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as NavigationToolbar
from collections import deque
import serial
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib as mlp
import numpy as np
# Class that inherits wx.Panel. The purpose is to embed it into
# a wxPython App. That part can be seen in main()
class Serial_Plot(wx.Panel):
def __init__(self, parent, strPort, id=-1, dpi=None, **kwargs):
super().__init__(parent, id=id, **kwargs)
self.figure = Fig(figsize=(20,20))
self.ax = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, -1, self.figure)
self.plot_data, = self.ax.plot([1,2,3,4],[1,2,3,4])
self.toolbar = NavigationToolbar(self.canvas)
self.toolbar.Realize()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas, 1, wx.EXPAND)
sizer.Add(self.toolbar, 0, wx.RIGHT | wx.EXPAND)
self.SetSizer(sizer)
self.Fit()
# Serial communication
self.ser = serial.Serial(strPort, 115200)
# Serial data initialized as deque. The serial readings from arduino
# are set to be one value per line.
self.vals = deque()
# matplotlib function animation
anim = animation.FuncAnimation(self.figure, self.update,
interval=20)
plt.show()
self.close
def update(self, i):
try:
print('trying')
# read serial line
data = float(self.ser.readline().decode('utf-8'))
print(data)
self.vals.append(data)
# update plot data
self.plot_data.set_data(range(len(self.vals)), self.vals)
except:
print('oops')
pass
return self.plot_data
def close(self):
# close serial
self.ser.flush()
self.ser.close()
def main():
app = wx.App(False)
frame = wx.Frame(None, -1, "WX APP!")
demo_plot = Serial_Plot(frame,'COM3')
frame.Show()
app.MainLoop()
if __name__ == "__main__":
main()
As I said in my previous answer(Embedding matplotlib FuncAnimation in wxPython: Unwanted figure pop-up), trying to use animation.FuncAnimation() and plt.show() within wxpython is not going to work, because you have 2 main.loops.
If we cull all of the wx.python from your original question and pare it down to the basics, we get this:
from collections import deque
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random
vals = deque()
figure = plt.figure(figsize=(20,20))
ax = plt.axes(xlim=(0, 1000), ylim=(0, 5000))
plot_data, = ax.plot([], [])
def update(i):
data = float(random.randint(1000, 5000))
vals.append(data)
plot_data.set_data(range(len(vals)), vals)
return plot_data
anim = animation.FuncAnimation(figure, update, interval=20)
plt.show()
It works because the animation function is controlled by plt.show() i.e. matplotlib but once you introduce wxpython, it controls the main.loop not matplotlib.
I believe that you will have to use a wxpython timer to control the serial device read, feeding data into the plot, as demonstrated in my previous answer.
Embedding matplotlib FuncAnimation in wxPython: Unwanted figure pop-up

Plotting with a for loop in matplotlib, with lists

form_class = uic.loadUiType("GUI.ui")[0] # Load the UI
class MainWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, target, Afb, np, conversion, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
if self.RadioButton.isChecked():
Ids, Ugs = unipolar_steuerkennlinie(self.target, self.Afb, self.np, self.conversion)
def plot_matplotlibwidget(self, *args):
for x in range(0, 40):
self.matplotlibwidget.axes.plot([args[0][x]], [[args[1][x]]])
self.matplotlibwidget.axes.figure.canvas.draw_idle()
Hi, I have this function and I don't really know how to plot correctly. Now it shows nothing but the does change the scale.
Since it is hard to completely replicate your code and error, I've created you a simple minimal GUI that has a MPL widget and draws a few random lines.
from PyQt4 import QtGui, QtCore
import sys
import functools
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
from matplotlib.figure import Figure
class test(QtGui.QWidget):
def __init__(self,parent=None):
self.widget=QtGui.QWidget.__init__(self, parent)
# Button to plot
self.btnPlot = QtGui.QPushButton('Plot')
self.btnPlot.connect(self.btnPlot, QtCore.SIGNAL('clicked()'), self.btnPlotPressed)
# Line edit for number of lines
self.qleN = QtGui.QLineEdit(str(0))
# Create canvas
self.setupMPLWidget()
# Layout
self.hbox = QtGui.QHBoxLayout()
self.hbox.addWidget(self.btnPlot)
self.hbox.addWidget(self.qleN)
self.hbox.addWidget(self.canvas)
self.setLayout(self.hbox)
self.show()
def btnPlotPressed(self):
"""Plots a few lines."""
# Get number of buttons to add
n=int(self.qleN.text())
# Generate some data
xs,ys=self.randData(n)
# Plot
self.plot_matplotlibwidget(xs,ys)
def randData(self,n):
"""Creates n random data sets."""
ys=[]
xs=[]
for i in range(n):
xs.append(np.arange(n-i))
ys.append(np.random.random(size=xs[-1].shape))
return xs,ys
def plot_matplotlibwidget(self,*args):
"""Plots list of datasets."""
for x in range(0, len(args[0])):
self.ax.plot(args[0][x], args[1][x])
self.ax.figure.canvas.draw_idle()
def setupMPLWidget(self):
"""Sets up a MPL figure to draw on."""
# Create parenting widget
self.plotFrame = QtGui.QWidget()
self.plotFrame.setMaximumWidth(1)
# Create Figure
self.fig = Figure( dpi=100)
#self.fig.set_size_inches(100,100,forward=True)
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.plotFrame)
self.ax = self.fig.add_subplot(111)
def main():
#Creating application
app = QtGui.QApplication(sys.argv)
main_win = test()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
It has a little QLE where you can specify how many lines you want to draw. I hope this helps.

How to display a value in a PyQt text field using matplotlib's object picking function?

I am using PyQt 4 for a basic GUI and matplotlib for a plot from which I want to read the coordinates of the plotted data points. Based on these examples (simple picking example), I have the simple problem that I cannot display the coordinates of a data point in a text field such as QtGui.QLabel(). I do not understand why I cannot call the instance Window.msg in the method onpick(). Probably it is because the instance it not given to the method. I only have a basic understanding of object oriented programming (but I am working on it), so the problem is my lack of knowledge.
My question: How to display the coordinates of chosen data (by clicking on it) from a matplotlib plot in my GUI based on PyQT (in that case in my label lbl)?
Also, it would be nice to highlight the chosen data point in the plot.
Here is my code (working):
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.initUI()
def initUI(self):
self.msg = '0'
# a figure instance to plot on
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
# a label
self.lbl = QtGui.QLabel(self.msg)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.lbl)
self.setLayout(layout)
self.plot()
def plot(self):
# random data
data = [np.random.random() for i in range(10)]
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.hold(False)
# plot data
line, = ax.plot(data, 'o', picker=5) # 5 points tolerance
self.canvas.draw()
self.canvas.mpl_connect('pick_event', Window.onpick)
def onpick(self):
thisline = self.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = self.ind
# show data
self.msg = (xdata[ind], ydata[ind])
print(self.msg)
# This does not work:
#Window.lbl.setText(self.msg)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
The self is being overlapped by the picker (not sure why). In any case this should work:
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.initUI()
def initUI(self):
self.msg = '0'
# a figure instance to plot on
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
# a label
self.lbl = QtGui.QLabel(self.msg)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.lbl)
self.setLayout(layout)
self.plot()
def changelabel(arg):
main.lbl.setText(str(arg[0])+' '+str(arg[1]))
def plot(self):
# random data
data = [np.random.random() for i in range(10)]
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.hold(False)
# plot data
line, = ax.plot(data, 'o', picker=5) # 5 points tolerance
self.canvas.draw()
self.canvas.mpl_connect('pick_event', Window.onpick)
def onpick(self):
thisline = self.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = self.ind
# show data
self.msg = (xdata[ind], ydata[ind])
print(self.msg)
# Window.changelabel(self.msg)
main.lbl.setText(str(self.msg[0])+' '+str(self.msg[1]))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
, the change is in the setText function, since I call it directly from the variable (no self or Window).
main.lbl.setText(str(self.msg[0])+' '+str(self.msg[1]))

How to refresh wx.Panel correctly?

I am creating a image viewer using wxPython. I want to view multiple images individually, so I wrote a code below (partially).
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
class CanvasPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figs = []
self.axes = []
self.canvases = []
self.panelsizers = []
def draw(self, data):
"""data is 2D numpy array"""
fig = Figure()
self.figs.append(fig)
ax = fig.add_subplot(111)
ax.imshow(data, interpolation='none')
self.axes.append(ax)
canvas = FigureCanvas(self, wx.ID_ANY, fig)
self.canvases.append(canvas)
panelsizer = wx.BoxSizer()
panelsizer.Add(canvas, 1, wx.GROW)
self.panelsizers.append(panelsizer)
self.SetSizer(panelsizer)
This works almost perfectly except a slight problem.
When I ran the code and open a image, the window looks like below.
enter image description here
This window consists of three wx.Panels and the center one is CanvasPanel. You can see that the size of CanvasPanel is a bit small even though proportion=1 and style=wx.GROW. Moreover, when I resize this window by dragging the corner of window, it looks like below.
enter image description here
The size of CanvasPanel changes correctly! Why? And how can I revise my code to fit the CanvasPanel in the viewer without resizing.

Reopening a GTK & matplotlib window - GTK window is blank

My program (developed with GTK using glade) receives some data and has the option to display a seperate window containing a matplotlib scatterplot that represents the data.
My problem is that if the user closes the graph window and reopens it, no graph is displayed. It is just a blank GTK Window. I'm sure there is a simple fix, but there aren't many resources available that are relevant to my issue (or GTK and matlplotlib integration for that matter).
I have created a Module for my scatterplot so I can easily reuse it. I am just trying to get it to work, so the code isn't structured perfectly.
##Scatterplot Module:
import gtk
import matplotlib
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
from matplotlib.figure import Figure
class ScatterPlot:
def __init__(self):
self.window = gtk.Window()
self.window.connect("destroy", lambda x: self.destroy())
self.window.set_default_size(500,400)
self.is_hidden = False
self.figure = Figure(figsize = (5,4), dpi=100)
self.ax = self.figure
self.ax = self.ax.add_subplot(111)
self.canvas = FigureCanvas(self.figure)
self.window.add(self.canvas)
self.Xs = list()
self.Ys = list()
def set_axis(self, xLimit = (0,384) , yLimit = (0,100)):
self.ax.set_xlim(xLimit)
self.ax.set_ylim(yLimit)
def plot(self, xs, ys):
self.Xs.extend([xs])
self.Ys.extend([ys])
self.ax.plot(xs,ys,'bo')
def update(self):
self.window.add(self.canvas)
def set_title(self, title):
self.ax.set_title(title)
def show(self):
self.window.show_all()
self.is_hidden = False
def hide(self):
self.window.hide()
self.is_hidden = True
def destroy(self):
self.window.destroy()
I call the module like so:
class GUI:
def __init__(self):
self.scatterplot = scatterplot.ScatterPlot()
#When the user presses the "Graph" button it calls the following function
def graph():
self.scatterplot.plot(someDataX, someDataY)
self.scatterplot.set_axis()
self.scatterplot.set_title("Some Title")
self.scatterplot.show()
(This was just an example of what my code looks like.)
When the scatterplot is closed, I am calling self.window.destroy instead of self.window.hide. When reopening is attempted, I call the same graph() function but, as stated above, the GTK Window does not display the graph. (When I first open it, it displays perfectly)
My speculations:
Should I be calling .hide() instead of .destroy()?
Is there a piece of code in scatterplot's constructor that needs to be called again to create the plot?
Or should I just re-instantiate the plot every time graph() is called?
My Solution:
From:
class ScatterPlot:
def __init__(self):
#remove the following two lines
self.canvas = FigureCanvas(self.figure)
self.window.add(self.canvas)
Move the two lines of code to show()
def show(self):
self.canvas = FigureCanvas(self.figure)
self.window.add(self.canvas)
self.window.show_all()
self.is_hidden = False
Moving these two lines of code allows the graph to be displayed when re-opening the window.
Sidenote: Calling both .destroy() or .show() when closing the window will work. I'm not sure which one is better though.

Categories