My problem is:
I have Matplotlib figure in PyGTK application, that is constatly updated each few seconds. I've added abbility to save figure to disk as PNG file. After calling figure.savefig(filename, other parameters) my figure in application stops being updated.
Figure initialization phase:
# setup matplotlib stuff on empty space in vbox4
figure = Figure()
canvas = FigureCanvasGTK(figure) # a gtk.DrawingArea
canvas.show()
self.win.get_widget('vbox4').pack_start(canvas, True, True) # this will be aded to last place
self.win.get_widget('vbox4').reorder_child(canvas, 1) #place plot to space where it should be
Figure is being updated this way (this called each few seconds in separate thread):
def _updateGraph(self, fig, x, x1, y):
#Various calculations done here
fig.clf()#repaint plot: delete current and formate a new one
axis = fig.add_subplot(111)
#axis.set_axis_off()
axis.grid(True)
#remove ticks and labels
axis.get_xaxis().set_ticks_position("none")
for i in range(len(axis.get_xticklabels())): axis.get_xticklabels()[i].set_visible(False)
axis.get_yaxis().set_ticks_position("none")
axis.plot(numpy.array(x),numpy.array(y)/(1.0**1), "k-" ,alpha=.2)
axis.set_title('myTitle')
fig.autofmt_xdate()
fig.canvas.draw()
everything works as expected. But after calling:
figure.savefig(fileName, bbox_inches='tight', pad_inches=0.05)
File have been saved, BUT my figure on screen stops being updated.
Any ideas how do I save figure to disk and still be able to update my fig on screen ?
Have you tried updating the line data instead of recreating the figure? This assumes the number of datapoints doesn't change each frame. It might help issue of things refusing to update, and at the least it will be faster.
def _updateGraph(self, fig, x, x1, y):
#Various calculations done here
ydata = numpy.array(y)/(1.0**1)
# retrieved the saved line object
line = getattr(fig, 'animated_line', None);
if line is None:
# no line object so create the subplot and axis and all
fig.clf()
axis = fig.add_subplot(111)
axis.grid(True)
#remove ticks and labels
axis.get_xaxis().set_ticks_position("none")
for i in range(len(axis.get_xticklabels())):
axis.get_xticklabels()[i].set_visible(False)
axis.get_yaxis().set_ticks_position("none")
xdata = numpy.array(x);
line = axis.plot(xdata, ydata, "k-" ,alpha=.2)
axis.set_title('myTitle')
fig.autofmt_xdate()
# save the line for later reuse
fig.animated_line = line
else:
line.set_ydata(ydata)
fig.canvas.draw()
I have found a work-a-round to this. As my figure refuses to be updated after calling figure.savefig() so i found a way how to work a round it. My figure is within HBox2 container (GUI is created with Glade 3.6.7) as first element
# some stuff going
figure.saveFig(fileName)
# WORK-A-ROUND: delete figure after calling savefig()
box = self.win.get_widget('hbox2')
box.remove(box.get_children()[0])
self._figPrepare()
def _figPrepare(self): #initialize graph
figure = Figure()
canvas = FigureCanvasGTK(figure) # a gtk.DrawingArea
canvas.show()
figure.clf()
gui.w().set("figure", figure)
self.win.get_widget('hbox2').pack_start(canvas, True, True) # this will be aded to last place
self.win.get_widget('hbox2').reorder_child(canvas, 0) #place plot to space where it should be
I know this is not best practice, and probably is slow, but it work OK for me. Hope someone else will find this useful
from http://matplotlib.org/examples/user_interfaces/embedding_in_gtk2.html
what seems to help is the "agg" not sure what that means but fixed this bug for me :)
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
Related
Hello and thanks in advance for any tips and advice. I'm trying to create an animated scatterplot that changes dot color based on model results that I'm reading into python. The model results would be much easier to interpret if I could display an updating time stamp along with the animation.
I tried to incorporate the answer for to this post:
Matplotlib animating multiple lines and text, but I think the fact that I'm using a scatter dataset instead of lines complicates the way the data needs to be returned, and I'm not sure how to correct the problem.
This code alternately flashes the time step and the scatter animation, which makes for a distracting and useless visual.
# load result
testModel.addResult(testDye,type='dye')
xs = testModel.grid['x']
ys = testModel.grid['y']
zs = testModel.grid['z']
# graphing
fig = plt.figure()
ax = fig.add_subplot(111)
gcf = plt.gcf()
scat = ax.scatter(xs,ys)
timetext = gcf.text(0.2, 0.2, '', size = 14)
def animate(i):
print i
actDye = testModel.getResult(type='dye',layer=1,tIndex=i)
scat.set_array((actDye/1000)*100) #update colors of points
timetext.set_text(i)
return scat,
def init():
actDye = testModel.getResult(type='dye',layer=1,tIndex=0)
scat.set_array((actDye/1000)*100) #update colors of points
return scat,
ani = animation.FuncAnimation(fig,animate,np.arange(0,200),init_func=init, interval=500, blit=True)
plt.show()
I think that returning timetext along with scat, would fix the problem (like it did for the other poster), but I can't get the syntax right. Switching to this block of code gives me the error that the 'PathCollection' object is not iterable.
def animate(i):
print i
actDye = testModel.getResult(type='dye',layer=1,tIndex=i)
scat.set_array((actDye/1000)*100) #update colors of points
timetext.set_text(i)
return tuple(scat,) + (timetext,)
What should I be doing differently? Thanks!
Here's the code that I'm using so far:
class Main:
app = QtGui.QApplication(sys.argv)
QtCore.QObject.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()"))
mainWidget = Gui.PointPredictorGui()
app.setActiveWindow(mainWidget)
mainWidget.show()
app.exec()
class PointPredictorGui(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self)
frame = QtGui.QFrame()
hobo = QtGui.QHBoxLayout()
self.graphFigure = plt.Figure()
self.graphCanvas = FigureCanvas(self.graphFigure)
self.ax1 = self.graphFigure.add_subplot(111)
self.ax2 = self.ax1.twinx()
self.check1 = QtGui.QCheckBox("1")
self.check2 = QtGui.QCheckBox("2")
hobo.addWidget(self.graphCanvas)
hobo.addWidget(self.check1)
hobo.addWidget(self.check2)
frame.setLayout(hobo)
self.setCentralWidget(frame)
self.check1.stateChanged.connect(self.updateGraph)
self.check2.stateChanged.connect(self.updateGraph)
def updateGraph(self):
if self.check1.isChecked(): self.ax1.plot([1,2,3,4,5], [1,2,3,4,5], '--')
elif not self.check1.isChecked(): self.eraseMethod()
if self.check2.isChecked(): self.ax2.plot([1,2,3,4,5], [500,400,300,200,100], '--')
elif not self.check2.isChecked(): self.eraseMethod()
self.graphCanvas.draw()
def eraseMethod(self):
self.graphFigure.clear()
What I am trying to get to happen is that I want line attributed to axis 1 or 2 to display dependent on whether or not the corresponding checkbox is, well, checked.
When both are checked:
When only the second one is checked:
You get the idea.
The problem I'm having is that I can't seem to either implement this the right way to facilitate what I want to do, or figure out which method to use from which class. Pretty much whatever I've tried results in no change at all or the graph portion disappearing entirely.
To recap, this is what I want to do:
Lines appear when checkboxes get checked (done)
Lines disappear when checkboxes get unchecked (not done)
Also, redrawing the entire thing every time is fine if that's what I need to resort to.
I figured it out.
self.graphFigure = plt.figure()
fig, self.ax1 = plt.subplots()
self.ax2 = self.ax1.twinx()
self.graphCanvas = FigureCanvas(fig)
This is the setup the graph widget needs to have in order to make what I was trying to do possible. Why? I'm not quite sure, maybe someone else could answer that.
What I do know though is that redrawing the graph on an axis by axis basis is required. Here's my update method:
def updateGraph(self):
plt.sca(self.ax2)
plt.cla()
plt.sca(self.ax1)
plt.cla()
if self.check1.isChecked():
self.ax1.plot([1,2,3,4,5], [1,2,3,4,5], '--')
if self.check2.isChecked():
self.ax2.plot(self.line2.get_xdata(), self.line2.get_ydata(), '--')
self.graphCanvas.draw_idle()
By the way, here's what self.line2 is: self.line1 = matplotlib.lines.Line2D([1,2,3,4,5], [1,2,3,4,5])
I tried both ways (the list and the line object) to see if any weird errors came up.
Back to redrawing the graph, I had to clear one of the axes ( plt.cla() ), then plot to it ( self.axis.plot(stuffx, stuffy) ), then redraw the graph. To make sure both of the axes got updated each time the update method was called I had to switch between the axes( plt.sca(axis to be cleared) ) and repeat the process, redrawing afterwards.
I hope this helps anyone else that came across this issue.
Based on the John Hunter's answer to a question regarding using patches in animations here, I am under the impression that:
I can create a patch object, with its animated stated being True.
Add it to an existing axis (let's say the axis object is called ax) using the add_patch method.
Then when I want to draw the patch, I do: ax.draw_artist(patch).
Doing this, I am faced with the following error:
File "environment.py", line 254, in animation_function
ax.draw_artist(patches[index])
File "A:\Anaconda\lib\site-packages\matplotlib\axes\_base.py", line 2121, in draw_artist
assert self._cachedRenderer is not None
AssertionError
The top level code is organized as follows:
a function creates patches from data, and then adds them to an axis object -- basically, I get a list of patch objects back, patches, where each patch has been added to ax; I think the issue might be here, since the patch objects in patches are not really connected to ax..., they were added to it, but passed by copy, not reference?
the animation function uses the number (let's say n) received from FuncAnimation to reference relevant patch objects, and then calls ax.draw_artist(patches[n])
At first I was doing the following:
patches = []
...
patch = mpatches.PathPatch(...)
patch.set_animated(True)
ax.add_patch(patch)
patches.append(patch)
...
ax.draw_artist(patches[n])
Then, after reading the documentation, which suggests that a patch object (possibly now connected to an axes object?) is returned, I tried the following:
patches = []
...
patch = mpatches.PathPatch(...)
patch.set_animated(True)
ax_patch = ax.add_patch(patch)
patches.append(ax_patch)
...
ax.draw_artist(patches[n])
However, the issue is still the same.
Can you comment on what you think the issue might be, or where I might need to provide additional information in order to figure out the issue?
EDIT: the top-level function where the error arises from.
def create_animation_from_data(self, vertex_marker_radius=0.25, labels = ['a', 'a', 'b', 'b'], unit_line=0.5, colours=['red', 'blue', 'green', 'orange']):
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal', autoscale_on=False, xlim=(-2, 100), ylim=(-2, 100))
ax.grid()
print "Initializing patches..."
patches, patch_indices_per_timestep, num_polys, num_timesteps = self.make_patches_from_environment(ax, vertex_marker_radius, labels, unit_line, colours)
def animation_function(n):
relevant_patch_indices = patch_indices_per_timestep[n]
for polygon_based_index_group in relevant_patch_indices:
for index in polygon_based_index_group:
patches[index].draw(fig.canvas.get_renderer())
return patches,
print "Beginning animation..."
ani = animation.FuncAnimation(fig, animation_function, blit=True)
plt.show()
You're understanding everything correctly, but missing a couple of lower-level steps. The renderer hasn't been initialized yet, and you're getting an error that reflects that.
In a nutshell, you can't use draw_artist before the plot has been drawn the first time. You'll need to call fig.canvas.draw() (in some cases you can get away with just fig.canvas.get_renderer()) at least once before you can use draw_artist.
If you're running into this problem, it's often because you're trying to go "against the grain" and do things that are better not to be handled directly.
What exactly are you trying to do? There's likely an easier way to handle this (e.g. if you're trying to grab a background, put this part of your code in a callback to the draw event).
Let me back up and explain what's happening. Matplotlib Artists draw on a FigureCanvas (e.g. fig.canvas) using an instance of Renderer (e.g. fig.canvas.renderer). The renderer is backend-specific and low-level. You normally won't touch it directly.
ax.draw_artist is a lower-level function than fig.canvas.draw. More specifically, it's shorthand for artist.draw(renderer).
Initializing the renderer is relatively slow, so it's cached and reused unless the figure's size or dpi changes. This is what the error you're getting is saying: The canvas doesn't have a renderer yet.
You have a few different options. You could manually initialize the renderer (the easy way is to call fig.canvas.get_renderer()). However, sometimes you're want to get something (such as the size of a text object) that isn't defined until after it's been drawn. In those cases, you'll need a "full" fig.canvas.draw().
Usually, though, running into things like this is a sign that there's an easier way to do it. Often it's best to put code that needs a draw to have happened into a callback to the draw event. (Especially if it's something that depends on the exact size of the figure -- e.g. a background for blitting).
Update based on code sample
If you're using the matplotlib.animation framework, then you don't need to draw the artists inside of the update function. The animation framework will take care of that step for you.
It sounds like you're trying to display only a subset of the artists you've plotted at each timestep?
If so, you might consider toggling their visibility instead. As a quick example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation
def main():
fig, ax = plt.subplots()
p = Plotter(ax)
# Note: We need to save a referce to the animation object, otherwise it
# will be garbage collected and never properly animate.
anim = matplotlib.animation.FuncAnimation(fig, p.update,
init_func=p.anim_init)
plt.show()
class Plotter(object):
def __init__(self, ax):
self.ax = ax
def anim_init(self):
self.triangles = [self.random_triangle() for _ in range(10)]
# Initially, show a blank plot...
for tri in self.triangles:
tri.set_visible(False)
return self.triangles
def update(self, i):
"""Animation step."""
# Hide all triangles
for tri in self.triangles:
tri.set(visible=False)
# pick 2 at random to show (e.g. your patch_indices_per_timestep)
for tri in np.random.choice(self.triangles, size=2):
tri.set(visible=True)
return self.triangles
def random_triangle(self):
x, y = np.random.random((2,3))
x += 10 * np.random.random(1)
y += 10 * np.random.random(1)
return self.ax.fill(x, y)[0]
main()
I'm trying to use blitting with pylab to animate my plots at a fast frame rate; the code below seems to work fine, but plots new data on top of the old rather than replotting, so that I end up with a figure filling up with lines rather than one animated line (in each subplot). Any suggestions to get a single animated line (in each subfigure) at as fast a frame rate as possible greatly appreciated.
import pylab
import time
import threading
import random
import math
time_series, cos_series, sin_series = [], [], []
MAX = 100
# This generates new data for the plot
def data_generator():
while True:
time.sleep(0.1)
ts = time.time()
time_series.append(ts)
cos_series.append(math.sin( ts ))
sin_series.append(math.cos( ts ))
if len(cos_series) > MAX:
cos_series.pop(0)
if len(sin_series) > MAX:
sin_series.pop(0)
if len(time_series) > MAX:
time_series.pop(0)
if __name__ == '__main__':
# Run the receiving function in a separate thread.
thread = threading.Thread(target=data_generator)
thread.daemon = True
thread.start()
fig = pylab.figure()
ax = fig.add_subplot(211)
bx = fig.add_subplot(212)
ax.grid(True)
bx.grid(True)
print(len(time_series),len(sin_series),len(cos_series))
fig.show()
fig.canvas.draw()
line1, = ax.plot(time_series, sin_series, '-.k', animated=True)
line2, = bx.plot(time_series, cos_series, 'r+-', animated=True)
ax.legend(['Sin(x)'])
bx.legend(['Cos(x)'])
ax.set_ylim([-1,1])
bx.set_ylim([-1,1])
background_a = [fig.canvas.copy_from_bbox(ax.bbox)]
background_b = [fig.canvas.copy_from_bbox(bx.bbox)]
timer = 0
t_start = time.time()
# Continuously update plot
while True:
time.sleep(0.1)
line1.set_data(time_series,sin_series)
ax.set_xlim([time_series[0],time_series[-1]])
line2.set_data(time_series,cos_series)
bx.set_xlim([time_series[0],time_series[-1]])
ax.draw_artist(line1)
bx.draw_artist(line2)
fig.canvas.restore_region(background_a)
fig.canvas.restore_region(background_b)
fig.canvas.blit(ax.bbox)
fig.canvas.blit(bx.bbox)
timer += 1
print('FPS = ',timer/(time.time() - t_start))
There are two problems with your code.
Firstly, when you do this:
background_a = [fig.canvas.copy_from_bbox(ax.bbox)]
background_b = [fig.canvas.copy_from_bbox(bx.bbox)]
you shouldn't put your buffer objects in a list - restore_region just takes the buffer objects directly, so you should just do this instead:
background_a = fig.canvas.copy_from_bbox(ax.bbox)
background_b = fig.canvas.copy_from_bbox(bx.bbox)
Secondly, in your rendering loop you need to restore the background before you draw any of your updated line artists on top, otherwise you'll always be drawing the background on top of your moving lines. Move those lines above your draw_artist calls, like this:
fig.canvas.restore_region(background_a)
fig.canvas.restore_region(background_b)
ax.draw_artist(line1)
bx.draw_artist(line2)
fig.canvas.blit(ax.bbox)
fig.canvas.blit(bx.bbox)
Now everything should work fine.
Update
If you want the x-axis to also be updated during the animation, things get a little bit more complicated. Firstly you'll need to set the x-axis to be animated for both sets of axes:
ax = fig.add_subplot(211)
bx = fig.add_subplot(212)
ax.xaxis.set_animated(True)
bx.xaxis.set_animated(True)
The axis bounding box (ax.bbox) doesn't contain the tick labels, so in order to get a large enough region to restore during the rendering loop you'll need to cache a larger region of the figure canvas, e.g. the whole figure bounding box:
figbackground = fig.canvas.copy_from_bbox(fig.bbox)
And to restore the background:
fig.canvas.restore_region(figbackground)
At each timepoint you need to force the x-axis to be re-drawn as well as the lines:
ax.draw_artist(line1)
bx.draw_artist(line2)
ax.xaxis.draw(fig.canvas.renderer)
bx.xaxis.draw(fig.canvas.renderer)
And finally, when you do the blitting you need to use the axes clipboxes, which contain the tick labels, rather than the bounding boxes, which do not:
fig.canvas.blit(ax.clipbox)
fig.canvas.blit(bx.clipbox)
With these changes the tick labels and the x-gridlines will get updated, but so far I haven't figured out how exactly to get the y-gridlines and the legend to be drawn correctly. Hopefully this gives you some idea of how to go about doing this.
Also tcaswell is right to suggest looking at the Animation class - for your case it might work out to be a lot simpler, although I think it's also good to have an understand of how blitting works under the hood.
I am plotting data in a plot using wxPython where the data limits on the y- axis are changing with the data. I would like to change the axis dynamically without redrawing the whole canvas like canvas.draw() rather I'd like to use blitting for this as I do for the plot itself.
What I got to work is the changing y-axis, and I get the yticks animated with the plot, unfortunately the ylabels are gone and I cant find the solution. The reason is setting the get_yaxis().set_animated(True) setting for the axis.
I put together a little working example in the following.
What am I missing here?
import matplotlib
matplotlib.use('WXAgg')
import wx
import pylab as p
import numpy as npy
from time import sleep
ax = p.subplot(111)
canvas = ax.figure.canvas
x = npy.arange(0,2*npy.pi,0.01)
line, = p.plot(x, npy.sin(x), animated=True)
ax.get_yaxis().set_animated(True)
def update_line(*args):
if update_line.background is None:
update_line.background = canvas.copy_from_bbox(ax.bbox)
for i in range(20):
canvas.restore_region(update_line.background)
line.set_ydata((i/10.0)*npy.sin(x))
ax.set_ylim(-1*i/5.0-0.5,i/5.0+0.5)
ax.draw_artist(ax.get_yaxis())
ax.draw_artist(line)
canvas.blit(ax.bbox)
sleep(0.1)
print 'end'
update_line.cnt = 0
update_line.background = None
wx.EVT_IDLE(wx.GetApp(), update_line)
p.show()
Basically I am looking for something like get_ylabels().set_animated(True) but I cant find it.
It looks like the labels are drawn but the blit command doesn't copy them over to the canvas because the bounding box only includes the inner part of the axes.
For me changing update_line.background = canvas.copy_from_bbox(ax.bbox) to update_line.background = canvas.copy_from_bbox(ax.get_figure().bbox) and canvas.blit(ax.bbox) to canvas.blit(ax.clipbox) made it work.