I am trying to handle a list of figures with an object.
Unfortunately there seems to be a problem with plotting from a list of figures.
Please comment out the line in the example below and you see how the plotting breaks:
import matplotlib as mpl
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, FigureManagerQT
class Test:
def __init__(self):
self.figs = [mpl.figure.Figure(),mpl.figure.Figure()]
self.fig = mpl.figure.Figure()
ax = self.fig.subplots()
ax.plot([1,2],[3,4])
def show(self):
fig = self.fig # works
# fig = self.figs[0] # does not work
canvas = FigureCanvasQTAgg(fig)
figManager = FigureManagerQT(canvas, 0)
a=Test()
a.show()
Result (this is what I want):
Result with line uncommented:
In some other tests I found it might be connected with destructing the object. As a list is a mutable object, this might be the connection.
I also tried (unsuccessfully) several workarounds to copy the figure object for plotting:
I used something like fig = myCopy(self.figs[0])
in combination with a pickle-copy.
Can you please give me some explanation of what is happening and what might be a workaround?
In __init__, you give axes to self.fig and plot to this Axes object:
class Test:
def __init__(self):
self.figs = [mpl.figure.Figure(),mpl.figure.Figure()]
self.fig = mpl.figure.Figure()
ax = self.fig.subplots()
ax.plot([1,2],[3,4])
The figure objects in self.figs have no Axes object attached to them, so they're basically empty.
As a result, what you see is an empty figure:
def show(self):
fig = self.figs[0] # This is a figure with no axes
canvas = FigureCanvasQTAgg(fig)
figManager = FigureManagerQT(canvas, 0)
The problem with your logic is that it's not really meaningful to plot data in the __init__ method.
Your workflow should be:
Initialization
Figure selection
Plot
Show
I suggest that you add two methods, select_figure and plot, so as to improve the overall usability of your figure manager:
class Test:
def __init__(self):
self.fig = None
self.figures = [mpl.figure.Figure(), mpl.figure.Figure()]
def select_figure(self, index):
self.fig = self.figures[index]
def plot(self, x, y):
ax = self.fig.subplots()
ax.plot(x, y)
def show(self):
canvas = FigureCanvasQTAgg(self.fig)
figManager = FigureManagerQT(canvas, 0)
Then you can implement the workflow I described above:
test = Test()
test.select_figure(0)
test.plot([1, 2], [3, 4])
test.show()
test.select_figure(1)
test.plot([3, 4], [5, 6])
test.show()
Related
Is it possible to replace Figure in FigureCanvas (see funcs plot & plot_data)?
I thought, it's possible to use fig.set_canvas(self) (see plot func), but it doesn't work.
PS: I know, it's possible to clear figure with self.axes.clear(), but it doesn't fit me. I'd like to save Figure in some map in the future (caching)
class Canvas(FigureCanvas):
def __init__(self, parent = None, width = 5, height = 5, dpi = 100):
self.fig_width = width
self.fig_height = height
self.fig_dpi = dpi
fig = Figure(figsize=(self.fig_width, self.fig_height), dpi=self.fig_dpi)
self.axes = fig.subplots(2,3)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
def plot(self, datatest_data):
fig = self.plot_data(new_data)
fig.set_canvas(self)
def plot_data(self, new_data):
len_keys = len(new_data.index)
fig = Figure(figsize=(30, 5*len_keys), dpi=self.fig_dpi)
self.axes = fig.subplots(len_keys, 2)
.....
return fig
I'm not quite getting how to create a class for animating data. Here is the gist:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.arange(100).reshape((100, 1))
y = np.random.randn(100, 1)
xy = np.hstack((x, y))
class PlotData:
def __init__(self):
fig, ax = plt.subplots()
fig.set_size_inches((11, 9))
self.fig = fig
self.ax = ax
self.ln0, = ax.plot([], [])
def init(self):
self.ln0.set_data([], [])
return(self.ln0, )
def update(self, frame_no):
data = xy[0:frame_no + 1]
self.ln0.set_data(data[:, 0], data[:, 1])
return(self.ln0, )
if __name__ == '__main__':
my_plot = PlotData()
anim = animation.FuncAnimation(my_plot.fig, my_plot.update,
init_func=my_plot.init, blit=True,
frames=99, interval=50)
plt.show()
This only produces the init method output but not the update, so ends up a blank plot with no animation. What is going on?
For me your code works perfectly fine. The only problem is that most of the data are outside of the plotting limits. If you adjust your plot limits like this:
class PlotData:
def __init__(self):
fig, ax = plt.subplots(figsize = (11,9))
self.fig = fig
self.ax = ax
self.ax.set_xlim([0,100])
self.ax.set_ylim([-3,3])
self.ln0, = ax.plot([], [])
The line is animated just fine. If you want that the x- and y-limits are adjusted automatically, see this question on how to do it. However, if I recall correctly, this will only work properly with blit=False.
I want to display sensor data on a PyQT GUI with a matplotlib animation.
I already have a working Plot which gets updates every time I receive new sensor value from an external source with this code:
def __init__(self):
self.fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = self.fig.add_subplot(111)
self.axes.grid()
self.xdata = []
self.ydata = []
self.entry_limit = 50
self.line, = self.axes.plot([0], [0], 'r')
def update_figure_with_new_value(self, xval: float, yval: float):
self.xdata.append(xval)
self.ydata.append(yval)
if len(self.xdata) > self.entry_limit:
self.xdata.pop(0)
self.ydata.pop(0)
self.line.set_data(self.xdata, self.ydata)
self.axes.relim()
self.axes.autoscale_view()
self.fig.canvas.draw()
self.fig.canvas.flush_events()
I want now to extend the plot to show another data series with the same x-axis. I tried to achieve this with the following additions to the init-code above:
self.axes2 = self.axes.twinx()
self.y2data = []
self.line2, = self.axes2.plot([0], [0], 'b')
and in the update_figure_with_new_value() function (for test purpose I just tried to add 1 to yval, I will extend the params of the function later):
self.y2data.append(yval+1)
if len(self.y2data) > self.entry_limit:
self.y2data.pop(0)
self.line2.set_data(self.xdata, self.ydata)
self.axes2.relim()
self.axes2.autoscale_view()
But instead of getting two lines in the plot which should have the exact same movement but just shifted by one I get vertical lines for the second plot axis (blue). The first axis (red) remains unchanged and is ok.
How can I use matplotlib to update multiple axis so that they display the right values?
I'm using python 3.4.0 with matplotlib 2.0.0.
Since there is no minimal example available, it's hard to tell the reason for this undesired behaviour. In principle ax.relim() and ax.autoscale_view() should do what you need.
So here is a complete example which works fine and updates both scales when being run with python 2.7, matplotlib 2.0 and PyQt4:
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui, QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.widget = QtGui.QWidget()
self.setCentralWidget(self.widget)
self.widget.setLayout(QtGui.QVBoxLayout())
self.widget.layout().setContentsMargins(0,0,0,0)
self.widget.layout().setSpacing(0)
self.fig = Figure(figsize=(5,4), dpi=100)
self.axes = self.fig.add_subplot(111)
self.axes.grid()
self.xdata = [0]
self.ydata = [0]
self.entry_limit = 50
self.line, = self.axes.plot([], [], 'r', lw=3)
self.axes2 = self.axes.twinx()
self.y2data = [0]
self.line2, = self.axes2.plot([], [], 'b')
self.canvas = FigureCanvas(self.fig)
self.canvas.draw()
self.nav = NavigationToolbar(self.canvas, self.widget)
self.widget.layout().addWidget(self.nav)
self.widget.layout().addWidget(self.canvas)
self.show()
self.ctimer = QtCore.QTimer()
self.ctimer.timeout.connect(self.update)
self.ctimer.start(150)
def update(self):
y = np.random.rand(1)
self.update_figure_with_new_value(self.xdata[-1]+1,y)
def update_figure_with_new_value(self, xval,yval):
self.xdata.append(xval)
self.ydata.append(yval)
if len(self.xdata) > self.entry_limit:
self.xdata.pop(0)
self.ydata.pop(0)
self.y2data.pop(0)
self.line.set_data(self.xdata, self.ydata)
self.axes.relim()
self.axes.autoscale_view()
self.y2data.append(yval+np.random.rand(1)*0.17)
self.line2.set_data(self.xdata, self.y2data)
self.axes2.relim()
self.axes2.autoscale_view()
self.fig.canvas.draw()
self.fig.canvas.flush_events()
if __name__ == "__main__":
qapp = QtGui.QApplication([])
a = Window()
exit(qapp.exec_())
You may want to test this and report back if it is working or not.
I am developing an app that take lives data and plots it. I use funcanimation from Animation to plot the live graph. The problem is, i have to use clear() to get rid of the previous plotting. By doing so the configuration is also being resetted. The title that i set just show up for a second in the beginning and disappears. The plot is drawn into a canvas which is packed as a Tkinter widget.
Here is the related part of my code:
class MyPlots(plt.Figure):
def __init__(self, parent):
plt.Figure.__init__(self, figsize=(16, 12))
self.parent = parent
plt.suptitle("asdasd")
self.cList = np.array([], dtype=float)
self.vList = np.array([], dtype=float)
self.axes1 = self.add_subplot(211)
self.axes1.set_title("asdasd")
self.axes1.set_gid("A")
self.axes2 = self.add_subplot(212)
self.axes2.set_gid("B")
self.m = 0.0
self.c = 0.0
self.start_stop = True
def animate(self, i):
if self.start_stop:
self.axes1.clear()
self.axes1.set_ylim([0, 4])
self.axes1.plot(self.parent.xList, "#00A3E0", label="firat")
self.axes2.clear()
self.axes2.set_ylim([0, 3])
self.axes2.set_xlim([0, 20])
self.axes2.plot(self.cList, self.vList, "ro")
if self.m:
self.axes2.plot(self.cList, self.m * self.cList + self.c, 'b')
The solution i've found is to configure stuff in my animate function, but it seems like a clumsy and non-pythonic way. The tutorials i have seen doesnt seem to have this problem.
The funcAnimation function calls my animate func in my controller class.
def plot(self, fig):
ax = fig.gca()
This plot function is called, when dropping an item on a Qt MatPlotLib Widget. Finally everything will be updated by .draw(). The problem, which occurred is the following:
Calling an external function, that accomplishes plotting, ax has to be the current axis (fig/axis are not passed as argument(s). Therefore I had to add
pyplot.sca(ax)
Everything was fine. Just somehow, maybe becaus of updating to python(x,y) 2.7.5.1 (mpl is 1.3.1), I get this error Axes instance argument was not found in a figure. It's just in this case, when I want this external function (scipy dendrogram func) to draw on the predefined axis. I tried to follow it
[Dbg]>>> fig
<matplotlib.figure.Figure object at 0x0A119A90>
[Dbg]>>> fig.gca()
<matplotlib.axes.AxesSubplot object at 0x0A119CD0>
then stepping into the subroutine pyplot.sca(ax)
managers = _pylab_helpers.Gcf.get_all_fig_managers()
for m in managers:
if ax in m.canvas.figure.axes:
_pylab_helpers.Gcf.set_active(m)
m.canvas.figure.sca(ax)
return
raise ValueError("Axes instance argument was not found in a figure.")
The list seems to be empty
[Dbg]>>> managers
[]
Maybe some of you has an idea, what could be the problem, though remote diagnosis might be difficult. An alternate way of making dendrogram plot on the fig/axes I want it to, would be helpful, too.
Please also give a hint on what should be used to update a plot as to MatplotlibWidget, figure and axes have a draw method.
Edit: Tried to create a MWE. Isn't there anybody experiencing the same error or who can tell me what's the problem here?
import sys
from matplotlibwidget import MatplotlibWidget
from matplotlib import pyplot
from PyQt4.QtGui import QMainWindow, QApplication
import scipy.cluster.hierarchy as hac
import numpy as np
class ApplicationWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.mplwidget = MatplotlibWidget(self, title='Example',
xlabel='Observation', ylabel='Distance', hold=True)
self.mplwidget.setFocus()
self.setCentralWidget(self.mplwidget)
def plotScree(self, Z, fig):
ax = fig.gca()
ax.plot(range(len(Z)), Z[::-1,2])
def plot(self, Z, fig):
ax = fig.gca()
pyplot.sca(ax)
hac.dendrogram(Z)
app = QApplication(sys.argv)
win = ApplicationWindow()
X = np.random.random(100).reshape(25, 4)
Z = hac.linkage(X)
#win.plotScree(Z, win.mplwidget.figure)
win.plot(Z, win.mplwidget.figure)
win.show()
sys.exit(app.exec_())
The implementation of matplotlibwidget in Python(x,y) appears to be broken.
I believe the file in question is this one. If you change line 67 of that file to read self.figure = pypolt.figure(figsize=(width, height), dpi=dpi) then your code will work as you want. I've included a full copy of the modified code below so you can just copy/paste that into your project and use that matplotlibwidget instead of importing the one from python(x,y)
The problem appears to be that instantiating the Figure object directly, skips over a whole load of figure manager construction, which is why that error was being raised. I suggest you file a bug report with Python(x,y) and link to this post!
Full code with modified line (see repository link above for license)
from PyQt4.QtGui import QSizePolicy
from PyQt4.QtCore import QSize
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as Canvas
from matplotlib.figure import Figure
from matplotlib import rcParams
rcParams['font.size'] = 9
from matplotlib import pyplot
class MatplotlibWidget(Canvas):
"""
MatplotlibWidget inherits PyQt4.QtGui.QWidget
and matplotlib.backend_bases.FigureCanvasBase
Options: option_name (default_value)
-------
parent (None): parent widget
title (''): figure title
xlabel (''): X-axis label
ylabel (''): Y-axis label
xlim (None): X-axis limits ([min, max])
ylim (None): Y-axis limits ([min, max])
xscale ('linear'): X-axis scale
yscale ('linear'): Y-axis scale
width (4): width in inches
height (3): height in inches
dpi (100): resolution in dpi
hold (False): if False, figure will be cleared each time plot is called
Widget attributes:
-----------------
figure: instance of matplotlib.figure.Figure
axes: figure axes
Example:
-------
self.widget = MatplotlibWidget(self, yscale='log', hold=True)
from numpy import linspace
x = linspace(-10, 10)
self.widget.axes.plot(x, x**2)
self.wdiget.axes.plot(x, x**3)
"""
def __init__(self, parent=None, title='', xlabel='', ylabel='',
xlim=None, ylim=None, xscale='linear', yscale='linear',
width=4, height=3, dpi=100, hold=False):
self.figure = pyplot.figure(figsize=(width, height), dpi=dpi)
self.axes = self.figure.add_subplot(111)
self.axes.set_title(title)
self.axes.set_xlabel(xlabel)
self.axes.set_ylabel(ylabel)
if xscale is not None:
self.axes.set_xscale(xscale)
if yscale is not None:
self.axes.set_yscale(yscale)
if xlim is not None:
self.axes.set_xlim(*xlim)
if ylim is not None:
self.axes.set_ylim(*ylim)
self.axes.hold(hold)
Canvas.__init__(self, self.figure)
self.setParent(parent)
Canvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
Canvas.updateGeometry(self)
def sizeHint(self):
w, h = self.get_width_height()
return QSize(w, h)
def minimumSizeHint(self):
return QSize(10, 10)