I am trying to use matplotlib's animation feature to animate multiple objects. By default, the animation feature erases old instances of the object each time it calls your animation function and you change the position of the line or patch or whatever. (Of course, this is the whole point of animation). But I have 2 different objects to be animated: a line that sweeps a polar plot in azimuth, and scatter markers. I don't want the markers to disappear. I want them to persist throughout the animation once they've been drawn.
I've tried a couple different things like appending the 'marker' scatter points to a list and returning it, but I'm having trouble with the return of the animate function. Here is the basic code without trying to append to a list. I trimmed down a lot of the figure and axis setup. As this code sits, the line sweeps around the plot and when it reaches a point where a marker is, the marker flashes then disappears.
fig = plt.figure(1)
ax = fig.add_subplot(111,projection='polar')
ax.set_theta_zero_location('N',offset=0)
ax.set_theta_direction(-1)
line, = ax.plot((0,0),(0,0))
list = []
def animate(i):
line.set_data((0,line_az_time[i][0]),(0,300))
if(line_az_time[i][1] in marker_time):
marker = plt.scatter(marker_az[0],marker_range[0])
list.append(marker)
del marker_az[0]
del marker_range[0]
return line, list # wrong return type?
return line, list # how to handle empty list before 1st marker appended?
def init():
line.set_data((0,0),(0,300))
return line,
ani = animation.FuncAnimation(fig,animate,np.arange(len(line_az_time)),
blit=True,interval=0,repeat=True,init_func=init)
plt.show()
'line_az_time' is a nested list of the format [[angle1, time1],[angle2,time2]...]
Basically it saves an angle and the time that angle occurs. the 'marker_xxx' variables are the angles, ranges, and times of target detections. When the line is getting animated, it is checking if a detection is occuring at that time and angle. The times and detections are always sequential, so I just delete them once they've been plotted.
Basically all I need is keeping the 'marker' scatter points to persist throughout the animation. As stated before, I tried appending them to a list and returning the list instead of 'marker' itself. That way, they all get plotted once they've occurred. But that didn't work. I think I am confused by what type of variable to pack them into and how to write the 'return' lines once I append them to a variable.
Or if there is another way, I'd be open to that as well.
Related
I'm trying to determine what the limits of data points are on a matplotlib Axes or Figure, but I can't find any way.
I'm gonna give an example, but the original image is much more complex:
By using Axes.get_xbound() or Axes.get_xlim() I get (-265.6, 6000.0) but I would want to get (0,5570).
I'm asking this because on this part of code I only have access to the Figure or Axes object.
Something like this:
def plot_detail():
fig, ax = plt.subplots(1)
# Code
# ...
return fig,ax
def main():
fig,ax = plot_detail()
print(ax.get_xbound())
print(ax.get_xlim())
# Here I would need the data limits
# Any Idea how?
First, just as a side note, from the fact that you want the data at point in the code where you only have the plot (Figure and Axes), it seems to me that there was at least one not-so-great design decision made while designing/writing your code. If I could see the whole code I could likely recommend a better approach. That said, it is understandable that sometimes we don't anticipate all the needs of our code, and then sometimes (depending on the size of the program) it may not be worth the effort for a redesign/rewrite of part of the code.
So to get the data (in order to know the x-limits of the data itself, and not just of the plot) ... You can do this by getting the lines.Line2D objects from the Axes object.
Even though it appears you are plotting a bar graph, there should still be a line2D object in there. That object contains the xy data.
xdata = ax.get_lines()[0].get_xdata()
print('xdata limits:',xdata[0],xdata[-1])
HTH.
I have encountered a strange issue with matplotlib artists.
Disclaimer: Unfortunately I only ever used matplotlib in jupyter notebooks and in a tkinter GUI (the latter is where I found this) so I do not know how to write simple code that will replicate the issue. However I do not think sample is code is absolutely needed in this case.
Now the issue:
In the interest of speeding up plotting in a GUI I do not plot everything anew whenever elements of the plot change but rather make use of methods like set_ydata and canvas.draw. Sometimes it is also necessary to remove lines altogether which can be accomplished with artist.remove. Here is the problem: When I have one or several artist(s) stored in a list I can successfully remove them from the plot by iterating over the list and calling remove. If however I store the reference directly (as an attribute of the class that is managing plots), calling remove does not do anything.
As sketch of the code suppose we have
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot()
the first case is generated by something like
artist_list = list()
for x in range(5):
line = ax.axhline(x)
artist_list.append(line)
and can be removed by
for line in artist_list:
line.remove()
artist_list = list()
(the last is needed for this to work).
whereas the second would be
line = ax.axhline(1)
line.remove()
which does not remove the line from the plot (even if del line or line = None are added).
It seems that storing the artist in a list and then assigning that variable to a new empty list is somehow a more complete removal than reassigning the variable that stores the artist directly or even deleting it. Does somebody know what is going here? How could a line be removed if it is simply stored as line rather than in a list?
As can be seen from the below piece of code, removing a line is pretty easy. Indeed, you just call .remove() on the object in question and redraw the canvas.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set(title="Click to remove line", xlim=(0,2))
line=ax.axvline(1)
def remove_line(event):
line.remove()
fig.canvas.draw()
fig.canvas.mpl_connect("button_press_event", remove_line)
plt.show()
I have a list of 2D arrays that I plot them using a slider from matplotlib.
I added some control buttons to my plot, so I want to click a play button, and the plot changes with some time interval.
Here's the function that is called by the button:
def play(mouse_event):
for x in range(len(listOfMoments)):
image.set_data(listOfMoments[x])
time.sleep(0.5)
print(x)
The print(x) shows that x is increasing normally, however, it plots only the last array of the list, after the increment finishes.
My question is: How can I make it plot the array one by one, as it expected to be?
I should mention also that I tried the while loop and got the same results.
You need to tell the backend to update the figure after changing the data each time through the loop:
fig.canvas.draw()
Here's the documentation reference.
If you don't have a reference to the Figure object, you can probably retrieve it with fig = plt.gcf().
Whenever I plot something in matplotlib, moving the mouse over the plot shows at the bottom the x and y position of the cursor. This is very convenient when exploring data.
Now, If I set the ticklabels to something else, the x position of the cursor at the bottom of the plot is empty, while the gui keeps track of the y position. Is there a way to keep getting the x position?
This is a simple example of this happening:
import numpy as np
fig, ax = plt.subplots()
x=np.linspace(0,np.pi)
y=np.sin(x)
ax.plot(x,y)
ax.set_xticks([0,np.pi/2,np.pi])
ax.set_xticklabels(['0','pi/2','pi'])
plt.show()
No, there is no easy way to do this.
When you set the labels with a list of strings you set the xaxis.major_formatter to be FixedFormatter (doc) which has some peculiar behavior to make it work (it always returns '', unless you give it a third argument which given when it labels the x-axis, but not otherwise). The marking of where the cursor is is generated using the major_formatter and when you call a FixedFormatter it returns '', which is what is displayed.
If you really want this, you have to write your own call back, see Color values in imshow for matplotlib? for a starting point. (or subclass axes and re-implement format_coord (around line 2903 in axes.py in current matplotlib master branch)
I have a little app that allows me to change an input value with a tKinter scale widget and see how a graph reacts to different changes in inputs. Every time I move the scale, it's bound to an event that redoes the calculations for a list and replots. It's kind of slow.
Now, I'm replotting the entire thing, but it's stacking one axis on top of the other, hundreds after a few minutes of use.
deltaPlot = Figure(figsize=(4,3.5), dpi=75, frameon=False)
c = deltaPlot.add_subplot(111)
c.set_title('Delta')
deltaDataPlot = FigureCanvasTkAgg(deltaPlot, master=master)
deltaDataPlot.get_tk_widget().grid(row=0,rowspan=2)
and the main loop runs
c.cla()
c.plot(timeSpread,tdeltas,'g-')
deltaDataPlot.show()
It's clearing the initial plot, but like I said the axes are stacking (because it's redrawing one each time, corresponding to the slightly altered data points). Anyone know a fix?
To improve speed there are a couple of things you could do:
Either Run the remove method on the line produced by plot:
# inside the loop
line, = c.plot(timeSpread,tdeltas,'g-')
deltaDataPlot.show()
...
line.remove()
Or Re-use the line, updating its coordinates appropriately:
# outside the loop
line, = c.plot(timeSpread,tdeltas,'g-')
# inside the loop
deltaDataPlot.show()
line.set_data(timeSpread,tdeltas)
The documentation of Line2d can be found here.
You might also like to read the cookbook article on animation.
HTH