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()
Related
I am trying to create an animation containing a fixed sphere and a trajectory on the surface of the sphere, with a trail of the trajectory containing the last "windowSize" points in the trajectory.
Now, for the purposes of the code I will show here, I won't have an actual such trajectory, but rather just some random points changing each frame.
I am using matplotlib.animation.FuncAnimation. When I use the option blit=False, the animation works as expected. However, I would like to use blit=True to optimize performance.
When I do that, though, what happens is that nothing seems to happen in the animation, except that when I rotate the figure, then it shows an updated version of the figure (some number of frames ahead) and then freezes again.
The code below is based on this similar question.
Let me show the code I am using
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.animation
import pandas as pd
Np = 5000
windowSize = 1000
m = np.random.rand(Np, 3)
df = pd.DataFrame({ "x" : m[0:Np,0], "y" : m[0:Np,1], "z" : m[0:Np,2]})
def init_graph():
u, v = np.mgrid[0:2*np.pi:50j, 0:np.pi:50j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
z = np.cos(v)
ax.plot_surface(x, y, z, color="bisque", alpha=0.3)
return graph,
def update_graph(num):
if (num<windowSize):
graph._offsets3d = (df.x[0:num], df.y[0:num], df.z[0:num])
else:
graph._offsets3d = (df.x[(num-windowSize):num], df.y[(num-windowSize):num], df.z[(num-windowSize):num])
title.set_text('3D Test, time={}'.format(num))
return graph,
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_box_aspect((1,1,1))
title = ax.set_title('3D Test')
graph = ax.scatter(0, 0, 0)
ani = matplotlib.animation.FuncAnimation(fig, update_graph, frames=Np, init_func=init_graph, interval=200, blit=True, repeat=False)
plt.show()
m is an Np by 3 matrix, and each row represents a 3d point (in my real use case, each row is a point in a trajectory on the sphere surface, but for this demo I created m as random numbers).
I create a variable graph that contains a scatter plot, which I believe is an Artist. This is what I return from both the init_func and the updating func which are passed to FuncAnimation (as per the docs).
From what I read, you return an iterable of the Artists which will be updated in the animation. Thus I return a tuple of one element, graph,.
Now, in update_graph, the updating function for the animation, I am updating the scatter plot using graph._offsets3d, which I read in another question here on StackOverflow. I am not totally sure if this is the way to do it and I didn't find much information in the docs about whether to use this or one of the setting methods on the scatter plot.
Why doesn't blitting work with scatter plots?
If I run the following code:
import matplotlib.pyplot as plt
import numpy as np
#plt.ion()
while True:
print('loop')
x = range(10)
y = np.random.rand(10)
plt.scatter(x, y)
plt.show()
Then I see a scatter plot displayed on my screen. Then each time I close the window for the plot, it displays a new plot with new data.
However, if I uncomment the line plt.ion(), nothing is displayed at all. There is no window created, and the program just continues through the loop, printing out 'loop'.
I want to be able to display a graph, and then return to the code automatically, with the graph still displayed. How can I do this?
If you want to plot on top of the same figure window, rather than generating a new window at every iteration the following will work:
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots(1, 1)
while True:
# If wanting to see an "animation" of points added, add a pause to allow the plotting to take place
plt.pause(1)
x = range(10)
y = np.random.rand(10)
ax.scatter(x, y)
The result you see will depend on the which matplotlib backend you are using. If you're wanting to see the new points being added you should use Qt4 or Qt5
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 want to use MatPlotLib to plot a graph, where the plot changes over time. At every time step, an additional data point will be added to the plot. However, there should only be one graph displayed, whose appearance evolves over time.
In my test example, the plot is a simple linear plot (y = x). Here is what I have tried:
for i in range(100):
x = range(i)
y = range(i)
plt.plot(x, y)
plt.ion()
plt.show()
time.sleep(1)
However, what happens here is that multiple windows are created, so that by the end of the loop I have 100 windows. Also, I have noticed that for the most recent window, it is just a white window, and the plot only appears on the next step.
So, my two questions are:
1) How can I change my code so that only a single window is displayed, whose contents changes over time?
2) How can I change my code so that for the most recent timestep, the plot is actually displayed on the window, rather than it only displaying a white window?
Thanks!
(1)
You can set plt.ion() at the beginning and plot all graphs to the same window. Within the loop use plt.draw() to show the graph and plt.pause(t) to make a pause. Note that t can be very small, but the command needs to be there for the animation to work on most backends.
You might want to clear the axes before plotting new content using plt.gca().cla().
import matplotlib.pyplot as plt
plt.ion()
for i in range(100):
x = range(i)
y = range(i)
# plt.gca().cla() # optionally clear axes
plt.plot(x, y)
plt.title(str(i))
plt.draw()
plt.pause(0.1)
plt.show(block=True) # block=True lets the window stay open at the end of the animation.
Alternatively to this very simple approach, use any of the examples for animations provided in http://matplotlib.org/examples/animation/index.html
(2)
In order to get each plot in a new window, use plt.figure() and remove plt.ion(). Also only show the windows at the end:
import matplotlib.pyplot as plt
for i in range(100):
x = range(i)
y = range(i)
plt.figure()
plt.plot(x, y)
plt.title(str(i))
plt.show()
Note that you might find that in both cases the first plot is empty simply because for i=0, range(i) == [] is an empty list without any points. Even for i=1 there is only one point being plotted, but of course no line can connect a single point with itself.
I think the best way is to create one line plot and then update data in it. Then you will have single window and single graph that will continuously update.
import matplotlib.pyplot as plt
plt.ion()
fig = plt.figure(figsize=(16,8))
axes = fig.add_subplot(111)
data_plot=plt.plot(0,0)
line, = axes.plot([],[])
for i in range(100):
x = range(i)
y = range(i)
line.set_ydata(y)
line.set_xdata(x)
if len(y)>0:
axes.set_ylim(min(y),max(y)+1) # +1 to avoid singular transformation warning
axes.set_xlim(min(x),max(x)+1)
plt.title(str(i))
plt.draw()
plt.pause(0.1)
plt.show(block=True)
I am trying to use matplotlib LassoSelector to select some points from a scatter plot and produce a separate figure for selected points only. When I try to use another matplotlib widget on the second plot it doesn't work but there is no error or warning message. Below is a minimal example with LassoSelector and SpanSelector used.
I tried other widgets too; the Button widget displays the button but the action on the button press is not performed.
import numpy as np
from matplotlib.pyplot import *
from matplotlib.widgets import SpanSelector, LassoSelector
from matplotlib.path import Path
def onselect(verts):
global xys,data
#get indexes of selected points
path = Path(verts)
xysn = xys.get_offsets()
ind = np.nonzero([path.contains_point(xy) for xy in xysn])[0]
#plot the second figure
fig=figure(2)
ax=fig.add_subplot(111)
ax.hist(data[:,0][ind],10)
#this should be executed when SpanSelector is used
def action(min,max):
print min,max
#try to do SpanSelector (this fails)
span=SpanSelector(ax,action,'horizontal')
show()
#initialize a figure
fig=figure(1)
ax=fig.add_subplot(111)
#create data
data=np.array([[1,6], [4,8],[0,4],[4,2],[9,6],[10,8],[2,2],[5,5],[0,4],[4,5]])
#plot data
xys=ax.scatter(data[:,0],data[:,1])
#select point by drawing a path around them
lasso = LassoSelector(ax, onselect=onselect)
show()
matplotlib widgets are event driven, so wait for user input. The problem with you code is you are trying to create a new figure with a new event handler SpanSelector. I'm not sure if you can add new events as a result of previous ones and with SpanSelector commented out, I get the following error,
QCoreApplication::exec: The event loop is already running
So the new event, LassoSelector is not registered and user input is not picked up (and the new figure doesn't appear). It is better to create all figures and register all possible events at the beginning of the code. The following should be closer to what you want to do,
import numpy as np
from matplotlib.pyplot import *
from matplotlib.widgets import SpanSelector, LassoSelector
from matplotlib.path import Path
#this should be executed when LassoSelector is used
def onselect(verts):
global xys,data
#get indexes of selected points
path = Path(verts)
xysn = xys.get_offsets()
ind = np.nonzero([path.contains_point(xy) for xy in xysn])[0]
#Clear and update bar chart
h, b = np.histogram(data[:,0][ind],10)
for rect, bars in zip(rects, h):
rect.set_height(bars)
ax2.bar(mb, h, align='center')
draw()
#this should be executed when SpanSelector is used
def action(min,max):
print min,max
#initialize figures
fig1=figure(1)
ax1=fig1.add_subplot(111)
fig2=figure(2)
ax2=fig2.add_subplot(111)
#create data
data=np.array([[1,6],[4,8],[0,4],[4,2],[9,6],[10,8],[2,2],[5,5],[0,4],[4,5]])
#plot data
xys=ax1.scatter(data[:,0],data[:,1])
#Plot initial histogram of all data
h, b = np.histogram(data[:,0],10)
mb = [0.5*(b[i]+b[i+1]) for i in range(b.shape[0]-1)]
rects = ax2.bar(mb, h, align='center')
#Register lasso selector
lasso = LassoSelector(ax1, onselect=onselect)
#Register SpanSelector
span=SpanSelector(ax2,action,'horizontal')
show()
Note, in order to update bar charts, it's a little more tricky than plots so I used this answer here Dynamically updating a bar plot in matplotlib
For some reason, the histogram figure 2 only updates when you click on it. I would consider using a single figure with two axes for this which may be easier to work with,
fig, ax = subplots(2,1)
ax1 = ax[0]; ax2 = ax[1]