matplotlib storing and removing artist - python

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()

Related

replacing most recent line in jupyter matplotlib chart

I'm using matplotlib to generate a chart with a dynamic line at any point on the y-axis as a threshold (that is, the user clicks on the chart somewhere and a new line will be generated that replaces the previous one). I can add new lines with axhline(), but I can't figure out how to remove the previous line. I've seen references to Artist.remove and ax.remove.line(0), but I'm still fairly new to matplotlib and haven't been able to get anything to work.
NB: I've only been working in Jupyter, so I don't know if this will work as-is outside of Jupyter. Also, I know that the separate update() procedure isn't really necessary here, but I'll most likely need it for future functionality.
%matplotlib notebook
import matplotlib.pyplot as plt
import matplotlib.figure as fig
import numpy as np
from matplotlib.axes import Axes as ax
from matplotlib.artist import Artist as art
x = np.random.normal(size = 1000)
plt.hist(x, bins=50, alpha=0.75)
plt.gcf().canvas.draw()
green = plt.axhline(35, color='g')
print('green line = {}'.format(green))
def update(threshold, lines):
plt.gca().set_title('most recent line = {}'.format(lines))
def on_press(event):
threshold = event.ydata
lines = plt.axhline(threshold, color='r')
update(threshold, lines)
plt.gcf().canvas.mpl_connect('button_press_event', on_press)
You can get at the lines in the subplot with plt.gca().lines, so you can just add something like this to the beginning of your on_press() function:
plt.gca().lines.pop()
Or adding plt.gca().pop(0) to the update() function also seems to work.
I don't know if this is the best way to do it or I just got lucky, but I found that calling plt.delaxes() before drawing the new line got me what I needed. The axhline() is the last thing I draw on the plot before the user has a chance to interact with it, so that may be why it works.

Store multiple matplotlib figures in a dictionary and use keys to display them / modify them at will?

I have a function that generates a plot from data I have read in. The goal is to be able to open multiple plot windows, each with a unique name, and then allow other functions to alter them at will (updating the data in them, changing colors, etc.), or to re-open plots that have been closed.
What I have tried to do is this:
1) Define a class, workspace, that has an attribute Figures that is a dictionary. I intend to assign my figures to unique keys in this dictionary.
2) Now, I generate a figure, and then save it into the WS.Figures dictionary.
3) Simplest use case, the user closes the figure and wants to re-open it, without having to go through make_plot() again. (My actual function has far, far, more complexity in the plotting - it's a pain to re-create each time manually). So I tried to write the following, which does absolutely nothing, apparently.
class WorkSpace(object):
def __init__(self):
self.Figures = {}
return
WS = WorkSpace()
def make_plot(x_data,y_data,name):
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x_data,y_data)
fig.canvas.draw()
WS.Figures[name] = fig
return
def replot(name):
WS.Figures[name].canvas.draw()
return
My thinking here was that WS.Figures[name] would hold an identical copy of fig from make_plot() and so I'd just need to issue the same command to make it appear.
I was hoping this would be the absolute easiest thing to do, and that I could then use that dictionary key to refer to the plot, update axes, etc. whenever I needed. But if I can't even replot it...

Keeping animated objects from disappearing in python matplotlib animation

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.

Matplotlib unexpectedly hidden polygons

I am using matplotlib 1.4.3 with python 3.3. I would like to draw multiple figures with multiples sub-plot in it. I am facing some kind of bug that is really boring me:
When I use fill() and boxplot() methods whithin a figure, results of those functions are hidden if the figure is not the first one created.
This bug seems to be related somehow to polygon display and matplotlib environment state.
When a parse only one single figure, everything is working fine. When I parse multiple figures, the first one is ok. But, in every other subsequent figures, everything is all-right except wiskerbox and polygons that are hidden.
Each plot code is wrapped into a function, which accepts positional arguments, *args and **kwargs. Lets say signature are:
def myplot(t, x, *args, *kwargs):
# [...]
hFig = plt.figure()
# [...]
return hFig
As far as I understand python mechanisms, after the function call is resolved, there must be nothing alive (I do not use global variables) except what matplotlib environment has stored into its global namespace variables.
In every call, I close() my figure, I also have tried hFig.clf() in addition before leaving function, but it does not solve the problem.
Each plot is wrapped into printer (decorator) to add generic functionalities:
def myprint(func):
def inner(*args, **kwargs)
# [...]
hFig = func(*args, **kwargs)
# [...]
return inner
What I have tried so far:
Increased zscore of wiskerbox and polygons, not working;
Execute plot generation in different threads, not working;
Execute plot generation in different processes, working but I have to change my function signature because it can be pickled.
I do not want use dill and pathos, even if I would I cannot.
It looks like it is a matplotlib environment bug, because when I run different processes, this environment is recreated from scratch and it works the way it should. I would like to know if there is a way to reset matplotlib environment state within a python script. If not, what can I do for solving this issue.
Obs.: I am using GridSpecs object and subplot() method to create my figures. The problem was not present when I computed boxes myself and used add_axes() method.
Update: Here you can find a MCVE of my problem. By doing this simple example, I found the line which makes my bug happens (looks like I have old bad Matlab behaviours). It seems that plt.hold(False) alters the way of polygons and boxplot are displayed. And, as I pointed out, it was related to matplotlib global namespace variable. I just misunderstood the way this method works, and in each sub-process, it was reset.
import datetime
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gspec
def bloodyplotit(t_, x_):
hFig = plt.figure()
gs = gspec.GridSpec(1, 4, height_ratios=[1], width_ratios=[15, 2, 3, 1])
gs.update(left=0.10, right=0.90, top=0.90, bottom=0.25, hspace=0.05, wspace=0.05)
plt.hold(True)
hAxe = plt.subplot(gs[0,0])
hAxe.plot(t_, x_)
#plt.hold(False) # <------------------------ This line make the scirpt bug
hAxe = plt.subplot(gs[0,1])
hAxe.hist(x_, orientation='horizontal')
hAxe = plt.subplot(gs[0,3])
hAxe.boxplot(x_)
plt.show()
n = 1000
t = datetime.datetime.utcnow() + np.arange(n)*datetime.timedelta(minutes=1)
x = np.random.randn(1000,1)
for i in range(10):
bloodyplotit(t, x)
Here's an even more minimal script that produces the error:
x = np.random.randn(1000)
fig, ax = plt.subplots(1, 2)
ax[0].hold(True)
ax[0].boxplot(x);
ax[1].hold(False)
ax[1].boxplot(x);
As far as I can tell, this is expected behavior. According to the documentation of plt.hold,
When hold is True, subsequent plot commands will be added to the current axes. When hold is False, the current axes and figure will be cleared on the next plot command.
Boxplot is a compound object: it is created by calling multiple plotting commands. If hold is False, the axes are cleared between each of those commands, so parts of the boxplot don't show up.
Personally, I've never found a reason to toggle the hold state in 10+ years of using matplotlib. My experience is that doing it (especially globally) just causes confusion, and I'd recommend avoiding it.

matplotlib figures disappearing between show() and savefig()

I've kept a set of references to figures in a dictionary so that I could save them later if desired. I am troubled that the saved figures are blank if invoke a show() command and look at them first. Since the show() command blocks and I am not using a spyder-like interpreter, I have to close the figures before I get to savefig()
figures['myfig_1'] = figure()
...
figures['myfig_n'] = figure()
...
#show() #disabling this makes the problem go away
print "Saving:"
for fig in figures:
figure(figures[fig].number)
savefig(fig)
print "Figure " + str(figures[fig].number) + ": " + fig
The print statement here has given me the indication that the dictionary is still intact, which I think means that I have not lost the figure references (they are still returning meaningful numbers in their .number attribute.)
Another twist I have noticed is that when I have done a similar thing in a class, storing the dictionary as a member and dividing the store and save functions into their own methods, this does not happen. Is there something about the way I am closing the figures or storing the data which is making the figures loose their data?
Generally speaking, in cases like this don't use the interactive matlab-ish state machine interface to matplotlib. It's meant for interactive use.
You're trying to make a figure "active", and creating a new figure instead. It doesn't matter which figure is active, if you just retain the returned figure and/or axis objects and use them directly. (Also, don't use wildcard imports! You will regret it at some later point when you're maintaining your code!)
Just do something like this:
import matplotlib.pyplot as plt
figures = {}
figures['a'] = plt.figure()
ax = figures['a'].add_subplot(111)
ax.plot(range(10), 'ro-')
figures['b'] = plt.figure()
ax = figures['b'].add_subplot(111)
ax.plot(range(10), 'bo-')
plt.show()
for name, fig in figures.iteritems():
fig.savefig('figure-%s.png' % name)
From the documentation, whether or not the drawing elements are destroyed from show() depends on the backend, and the version of matplotlib. Not having the figures destroyed seems to be available with version 1.1.0. To figure out which backend is in use, use the get_backend() function. In my case, I was using the Qt4Agg backend. By invoking the TkAgg backend, with the call matplotlib.use('TkAgg') the figures were not destroyed before the save. Now to find out how to change the behavior of the Qt4Agg...

Categories

Resources