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.
Related
I have a python / matplotlib application that frequently updates a plot with new data coming in from a measurement instrument. The plot window should not change from background to foreground (or vice versa) with respect to other windows on my desktop when the plot is updated with new data.
This worked as desired with Python 3 on a machine running Ubuntu 16.10 with matplotlib 1.5.2rc. However, on a different machine with Ubuntu 17.04 and matplotlib 2.0.0, the figure window pops to the front every time the plot is updated with new data.
How can I control the window foreground/background behavior and keep the window focus when updating the plot with new data?
Here's a code example illustrating my plotting routine:
import matplotlib
import matplotlib.pyplot as plt
from time import time
from random import random
print ( matplotlib.__version__ )
# set up the figure
fig = plt.figure()
plt.xlabel('Time')
plt.ylabel('Value')
plt.ion()
# plot things while new data is generated:
t0 = time()
t = []
y = []
while True:
t.append( time()-t0 )
y.append( random() )
fig.clear()
plt.plot( t , y )
plt.pause(1)
matplotlib was changed somewhere from version 1.5.2rc to 2.0.0 such that pyplot.show() brings the window to the foreground (see here). The key is therefore to avoid calling pyplot.show() in the loop. The same goes for pyplot.pause().
Below is a working example. This will still bring the window to the foreground at the beginning. But the user may move the window to the background, and the window will stay there when the figure is updated with new data.
Note that the matplotlib animation module might be a good choice to produce the plot shown in this example. However, I couldn't make the animation work with interactive plot, so it blocks further execution of other code. That's why I could not use the animation module in my real-life application.
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import time
from random import random
print ( matplotlib.__version__ )
# set up the figure
plt.ion()
fig = plt.figure()
ax = plt.subplot(1,1,1)
ax.set_xlabel('Time')
ax.set_ylabel('Value')
t = []
y = []
ax.plot( t , y , 'ko-' , markersize = 10 ) # add an empty line to the plot
fig.show() # show the window (figure will be in foreground, but the user may move it to background)
# plot things while new data is generated:
# (avoid calling plt.show() and plt.pause() to prevent window popping to foreground)
t0 = time.time()
while True:
t.append( time.time()-t0 ) # add new x data value
y.append( random() ) # add new y data value
ax.lines[0].set_data( t,y ) # set plot data
ax.relim() # recompute the data limits
ax.autoscale_view() # automatic axis scaling
fig.canvas.flush_events() # update the plot and take care of window events (like resizing etc.)
time.sleep(1) # wait for next loop iteration
For the tkinter backend (matplotlib.use("TkAgg")), using flush_events is not sufficient: you also need to call fig.canvas.draw_idle() before each fig.canvas.flush_events(). As #samlaf wrote, the same holds for the Qt5Agg backend.
I am plotting multiple arrows using matplotlib pyplot arrow function. It's plottting just fine. After a set of arrows have been plotted, I need to recompute the arrow locations and plot them again. I am using draw for that. So essentially I draw and plot arrows in a loop and clear the plot using cla() after a time interval. Here's what I don't understand: when I substitute time.sleep(1) function with raw_input() that waits for a key input, it works just fine but when I use the time.sleep(1) there is no plot shown at all.
Here's my code(I hv removed some presumably irrelevant stuff):
import matplotlib.pyplot as PP
import time
fig = PP.figure()
fig.show()
ax = fig.add_subplot(111)
for i in range(30):
for i in range(100):
pass
# loc = some code to recalculate arrow locations
for x,y in loc.iteritems():
ax.arrow( x, y,.05,.05, fc="k", ec="k",head_width=0.04, head_length=0.04 )
PP.draw()
#raw_input()
time.sleep(1)
PP.cla()
I would like to know how I can expand the 3D Plot to fit the canvas and if one can zoom in and out of the whole cube.
My goal is to make the Plot catch the mouse inside the whole frame, so you can turn the view. Only makeing the background white isn't enough for me. I want the Plot to file the canvas. Right now the axis tick labels can escape the plot frame. By expanding the plot this would no longer be the case, so I would also like to zoom out of the cube a bit. The standard zoom only changes the axis scale.
Look at the sample below to find out what I mean.
#! coding=utf-8
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def func(X,Y):
return 1/np.pi/2*np.exp(-(X**2+Y**2)/2)
x = np.linspace(-5,5,200)
X,Y = np.meshgrid(x,x)
plt.figure()
sub = plt.subplot(111, projection='3d')
sub.plot_surface(X,Y,func(X,Y))
plt.show()
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 things using matplotlib and Basemap (within a wxpython gui). Currently, my plot code look something like this:
self.map = Basemap(llcrnrlon=lon_L, llcrnrlat=lat_D, urcrnrlon=lon_R,
urcrnrlat=lat_U, projection='lcc', lat_0=map_lat1, lon_0=map_lon1,
resolution='i', area_thresh=10000,ax=self.axes, fix_aspect=False)
m = Basemap(llcrnrlon=lon_L, llcrnrlat=lat_D, urcrnrlon=lon_R,
urcrnrlat=lat_U, projection='lcc', lat_0=map_lat1, lon_0=map_lon1,
resolution='i', area_thresh=10000,ax=self.axes)
x,y=m(some_x_data,some_y_data)
plot_handle, = self.map.plot(x,y,'bo')
plot_handle.set_xdata(x)
plot_handle.set_ydata(y)
self.figure.canvas.draw()
This plots it just fine. Now what I want to do is take a single point (single x and single y within my data) and color it a different color. I still want to use the plot_handle because I am constantly updating the map/plot -- so i don't want to just reset my data. Any help?
Thanks!
If you use scatter (doc) you can set and update the color of each point.
import matplotlib.pylab as plt
x,y = m(some_x,some_y)
c = iterator_of_colors
plt_handle, = self.map.scatter(x,y,c=c)
# some code
c[j] = new_color # update you color list
plt_handle.set_array(c) # update the colors in the graph
plt.draw()
It looks a little strange to use set_array but that is how matplotlib deals with scatter plots internally (it looks like they use the same class that is used for displaying images, only just color in markers instead of squares in the grid).
Do a new plot_handle for the specific plot with a different marker:
plot_handle1, = self.map.plot([x[i]], [y[i]], 'ro')
You'll then have to update this every time you want to change that point's position. It's not possible to use only one plot_handle and have points showing with different markers.