How to get legend location in matplotlib - python

I'm trying to get the legend location in matplotlib. It seems like Legend.get_window_extent() should provide this, but it returns the same value regardless of where the legend is located. Here is an example:
from matplotlib import pyplot as plt
def get_legend_pos(loc):
plt.figure()
plt.plot([0,1],label='Plot')
legend=plt.legend(loc=loc)
plt.draw()
return legend.get_window_extent()
if __name__=='__main__':
# Returns a bbox that goes from (0,0) to (1,1)
print get_legend_pos('upper left')
# Returns the same bbox, even though legend is in a different location!
print get_legend_pos('upper right')
What is the correct way to get the legend location?

You would need to replace plt.draw() by
plt.gcf().canvas.draw()
or, if you have a figure handle, fig.canvas.draw(). This is needed because the legend position is only determined when the canvas is drawn, beforehands it just sits in the same place.
Using plt.draw() is not sufficient, because the drawing the legend requires a valid renderer from the backend in use.

TL DR; Try this:
def get_legend_pos(loc):
plt.figure()
plt.plot([0,1],label='Plot')
legend=plt.legend(loc=loc)
plt.draw()
plt.pause(0.0001)
return legend.get_window_extent()
Here is why
So I tried your code in Jupyter and I can reproduce the behavior with option
%matplotlib notebook
However for
%matplotlib inline
I am getting correct response
Bbox(x0=60.0, y0=230.6, x1=125.69999999999999, y1=253.2)
Bbox(x0=317.1, y0=230.6, x1=382.8, y1=253.2)
It looks like in the first case the legend position is not evaluated until the execution finishes. Here is an example that proves it, in the first cell I execute
fig = plt.figure()
plt.plot([0,1],label='Plot')
legend=plt.legend(loc='upper left')
plt.draw()
print(legend.get_window_extent())
Outputs Bbox(x0=0.0, y0=0.0, x1=1.0, y1=1.0).
In the next cell re-evaluate the last expression
print(legend.get_window_extent())
Outputs Bbox(x0=88.0, y0=396.2, x1=175.725, y1=424.0)
You probably just need to add plt.pause() to enforce the evaluation.

Related

Fig layout made tighter interactively by clicking 'adjustment' -> 'tight layout' many times, but unable to reproduce it in my code

A strange thing is happening. I am interactively changing how tight the figure is after showing a plot, by clicking 'adjustment' -> 'tight layout' many times. I know I could copy the 6 parameters and use them to put in my code fig.subplots_adjust(), but the thing is that the layout changes, but there is a point in which the parameters do not!! So, I'm unable to reproduce the effect .... Here I'll show you what I mean:
In my code, I've plt.tight_layout() before plt.show().
Here is the outcome I got:
Now, I click on 'tight layout'. Parameters change, and the subplots look bigger and nearer:
Again, parameters change, and the subplots look bigger and nearer:
Again, subplots are nearer BUT only one parameter changed, and the change is almost meaningless - top was 0.950, and now it's 0.951:
Now, if I export the values and put before plt.show()
plt.subplots_adjust(
top=0.95,
bottom=0.062,
left=0.012,
right=0.988,
hspace=0.249,
wspace=0.0)
I get:
The figure is not as tight as I wanted it to be!! And the subplots aren't as big as I want!! I put the exact same parameters. Anyone knows how to solve it?
TOY EXAMPLE TO REPRODUCE THE ISSUE:
import matplotlib.pyplot as plt
limit = 3
fig, axes = plt.subplots(nrows=limit, ncols=limit, sharex=True, sharey=True, )
for i in range(limit):
for j in range(limit):
ax = axes[i, j]
ax.set_xbound(lower=-2, upper=2)
ax.set_ybound(lower=-2, upper=2)
ax.grid()
ax.set_aspect('equal', adjustable='box')
# show
fig = plt.gcf()
fig.canvas.manager.window.showMaximized()
fig.canvas.set_window_title('Test')
plt.tight_layout() # TODO: comment this line to see the difference if tight layout is not specified
# TODO: uncomment the following block to see that nothing happens if tight layout and the parameters are set
# plt.subplots_adjust(
# top=0.951,
# bottom=0.062,
# left=0.012,
# right=0.988,
# hspace=0.249,
# wspace=0.0
# )
plt.show()
Side note: I'm currently using matplotlib 3.1.1

Matplotlib needs careful timing? (Or is there a flag to show plotting is done?)

The following code does not work as expected:
import matplotlib.pyplot as plt
plt.plot(range(100000), '.')
plt.draw()
ax = plt.gca()
lblx = ax.get_xticklabels()
lblx[1]._text = 'hello!'
ax.set_xticklabels(lblx)
plt.draw()
plt.show()
I'm getting the following figure:
I imagine that the reason is that the automatic xticklabels did not have time to be fully created when get_xticklabels() was called. And indeed, by adding plt.pause(1)
import matplotlib.pyplot as plt
plt.plot(range(100000), '.')
plt.draw()
plt.pause(1)
ax = plt.gca()
lblx = ax.get_xticklabels()
lblx[1]._text = 'hello!'
ax.set_xticklabels(lblx)
plt.draw()
plt.show()
would give the expected
I'm not very happy with this state (having to manually insert delays). And my main concern is: How can I know how much time I need to wait? Surely it depends on number of figure elements, machine strength, etc..
So my question is: Is there some flag to know that matplotlib has finished drawing all the elements? Or is there a better way to do what I'm doing?
First it should be noted that you may make an arbitrarily short pause
plt.pause(1e-18)
The issue here is that plt.draw() calls plt.gcf().canvas.draw_idle(). This means that the figure is drawn at some arbitrary point when there is time to do it - hence the _idle.
Instead you would probably want to draw the figure at the specific point in the code you need.
plt.gcf().canvas.draw()
Full code:
import matplotlib.pyplot as plt
plt.plot(range(100000), '.')
plt.gcf().canvas.draw()
ax = plt.gca()
lblx = ax.get_xticklabels()
lblx[1]._text = 'hello!'
ax.set_xticklabels(lblx)
plt.show()

python matplotlib: how to move the scale to the other side of the axis?

I have this weird thing with the scale of the axis showing out of the figure like:
And what I want to have:
How can I move the scale to the other side of the axis?
x=range(len(ticks))
plt.plot(x,phase1,'r^-',label='$\Delta \phi(U1,I1)$')
plt.plot(x,phase2,'go-',label='$\Delta \phi(U2,I2)$')
plt.plot(x,phase3,'b*-',label='$\Delta \phi(U3,I3)$')
plt.xticks(x,ticks,rotation=45)
plt.xlabel('Messung')
plt.ylabel('$\Delta \phi [^\circ]$')
plt.legend()
plt.show()
The tick_params of your axis can be used to control axes label and ticks location. Set direction to in so that they point into the graph.
And here is a great example if you want different y-axis ranges and colours too.
from matplotlib import pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.tick_params(direction='in', length=6, width=2, colors='r', right=True, labelright='on')
plt.show()
You can use plt.tick_params() to adjust the behaviour of the ticks, documentation can be found here.
For your example you want the ticks to appear inside the figure. Therefore add
plt.tick_params(direction="in")
to your code. Example:
x=range(len(ticks))
plt.plot(x,phase1,'r^-',label='$\Delta \phi(U1,I1)$')
plt.plot(x,phase2,'go-',label='$\Delta \phi(U2,I2)$')
plt.plot(x,phase3,'b*-',label='$\Delta \phi(U3,I3)$')
plt.xticks(x,ticks,rotation=45)
plt.xlabel('Messung')
plt.ylabel('$\Delta \phi [^\circ]$')
plt.legend()
plt.tick_params(direction="in") # Set ticks inside the figure
plt.show()
You can get the ticks to appear on the top and right side of the figure too as shown in your second screenshot by adding:
plt.tick_params(direction="in",top="on",right="on")
If you wanted to make all figures in your Python script have this behaviour then you can add the following at the top of your script (this might be of interest):
import matplotlib
matplotlib.rcParams['xtick.direction'] = "in"
matplotlib.rcParams['ytick.direction'] = "in"
This will save you having to call plt.tick_params() for each figure, which is helpful if you generate lots of figures.

Unable to remove legend box [python]

So I need to make this plot in python. I wish to remove my legend's border. However, when I tried the different solutions other posters made, they were unable to work with mine. Please help.
This doesn't work:
plt.legend({'z$\sim$0.35', 'z$\sim$0.1','z$\sim$1.55'})
plt.legend(frameon=False)
plt.legend({'z$\sim$0.35', 'z$\sim$0.1','z$\sim$1.55'})
plt.legend.get_frame().set_linewidth(0.0)
plt.legend({'z$\sim$0.35', 'z$\sim$0.1','z$\sim$1.55'}, 'Box', 'off')
Additionally, when I plotted, I imported two different files and graphed them with a line and with circles respectively. How could I put a line or a circle within the legend key?
The plot:
It's very strange because the command :
plt.legend(frameon=False)
Should work very well.
You can also try this command, to compare :
plt.legend(frameon=None)
You can also read the documentation on this page about plt.legend
I scripted something as example to you :
import numpy as np
import matplotlib.pyplot as plt
x = np.array([0,4,8,13])
y = np.array([0,1,2,3])
fig1, ((ax1, ax2)) = plt.subplots(1, 2)
ax1.plot(x,y, label=u'test')
ax1.legend(loc='upper left', frameon=False)
ax2.plot(x,y, label=u'test2')
ax2.legend(loc='upper left', frameon=None)
plt.show()
Try this if you want to draw only one plot (without subplot)
plt.legend({'z$\sim$0.35', 'z$\sim$0.1','z$\sim$1.55'}, frameon=False)
It is enough one plt.legend. The second one rewrites the first one.
Make sure frameon = False is together with the positional argument in plt.legend(...) if you want to specify the position as well as remove the border. If these arguments are written separately or in sequential, there's an issue of overwriting and the desired effect may not be achieved.
Correct!
plt.legend(loc="lower right", frameon=False)
May not give desired effect when written like this!
plt.legend(loc="lower right") & plt.legend(frameon=False)

set_data and autoscale_view matplotlib

I have multiple lines to be drawn on the same axes, and each of them are dynamically updated (I use set_data), The issue being that i am not aware of the x and y limits of each of the lines. And axes.autoscale_view(True,True,True) / axes.set_autoscale_on(True) are not doing what they are supposed to. How do i auto scale my axes?
import matplotlib.pyplot as plt
fig = plt.figure()
axes = fig.add_subplot(111)
axes.set_autoscale_on(True)
axes.autoscale_view(True,True,True)
l1, = axes.plot([0,0.1,0.2],[1,1.1,1.2])
l2, = axes.plot([0,0.1,0.2],[-0.1,0,0.1])
#plt.show() #shows the auto scaled.
l2.set_data([0,0.1,0.2],[-1,-0.9,-0.8])
#axes.set_ylim([-2,2]) #this works, but i cannot afford to do this.
plt.draw()
plt.show() #does not show auto scaled
I have referred to these already, this , this.
In all cases I have come across, the x,y limits are known. I have multiple lines on the axes and their ranges change, keeping track of the ymax for the entire data is not practical
A little bit of exploring got me to this,
xmin,xmax,ymin,ymax = matplotlib.figure.FigureImage.get_extent(FigureImage)
But here again, i do not know how to access FigureImage from the Figure instance.
Using matplotlib 0.99.3
From the matplotlib docs for autoscale_view:
The data limits are not updated automatically when artist data are changed after the artist has been added to an Axes instance. In that case, use matplotlib.axes.Axes.relim() prior to calling autoscale_view.
So, you'll need to add two lines before your plt.draw() call after the set_data call:
axes.relim()
axes.autoscale_view(True,True,True)

Categories

Resources