Python matplotlib logarithmic autoscale - python
I need to get a x,y plot with a logarithmic scale on the x-axes. When I zoom in the x-axes should autoscale as the (not logarithmic) y-axes.
plt.xscale('log')
plt.grid(True)
plt.autoscale(True,True,True)
plt.legend(loc='best')
plt.show()
As you can see there is no working autoscale function on the x-axes.
How can I get this to display properly?
The solution by #hashcode55 does not work as it is what I was attempting before I found this thread.
It seems to me that there is simply a "bug" in that:
plt.yscale('log')
plt.autoscale(enable=True, axis='y')
are not compatible.
Here is my sample code:
import matplotlib.pyplot as plt
import matplotlib
import random
import numpy as np
# generate some random data and add it to the plot
x = np.array(range(1,100))
y = np.maximum(np.ones(99), np.random.randn(99))
plt.plot(x, y, markersize=4, marker='.', color='red')
# format
ax = plt.gca()
plt.ylabel('LOGARITHMIC SCALE')
plt.yscale('log')
plt.minorticks_on
ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
ax.yaxis.set_minor_formatter(matplotlib.ticker.ScalarFormatter())
plt.autoscale(enable=True, axis='y')
#ax.set_ylim([np.min(y), np.max(y)])
#plot
plt.show()
which produces:
log scale, but clearly not autoscale
if I remove the comments from this line:
ax.set_ylim([np.min(y), np.max(y)])
Then it actually plots as would be expected with autoscale:
Nice, but what if I've lost reference to my y values on the plot?
while this solution/answer is a good "hack" to this sample problem, it its not a solid solution for my situation as my chart is a) live; continually updating every minute b) contains MANY plots c) is dropping off data older than past 24 hours; so such a solution would get really hacky if implemented every time something was added or removed from the plot in live session.
I would be interested in a true built-in "autoscale" solution, if such exists, that works with log y scale and I can auto update using plt.ion()
until then, what about this:
h/t #David Z
How to extract data from matplotlib plot
#if you do need to get data out of a plot, I think this should do it
gca().get_lines()[n].get_xydata()
#Alternatively you can get the x and y data sets separately:
line = gca().get_lines()[n]
xd = line.get_xdata()
yd = line.get_ydata()
implemented in our situation at hand (with an extra blue line to test multiple lines) as:
import matplotlib.pyplot as plt
import matplotlib
import random
import numpy as np
# generate some random data and add it to the plot
x = np.array(range(1,100))
y = np.maximum(np.ones(99), np.random.randn(99))
plt.plot(x, y, markersize=4, marker='.', color='red')
# test for compatibility with multilpes lines
x = np.array(range(1,100))
y = np.maximum(np.ones(99), 1.5*np.random.randn(99))
plt.plot(x, y, markersize=4, marker='.', color='blue')
# format
ax = plt.gca()
plt.ylabel('LOGARITHMIC SCALE')
plt.yscale('log')
plt.minorticks_on
ax.yaxis.set_major_formatter(matplotlib.ticker.ScalarFormatter())
ax.yaxis.set_minor_formatter(matplotlib.ticker.ScalarFormatter())
#plt.autoscale(enable=True, axis='y')
#####################################################
#force 'autoscale'
#####################################################
yd = [] #matrix of y values from all lines on plot
for n in range(len(plt.gca().get_lines())):
line = plt.gca().get_lines()[n]
yd.append(line.get_ydata())
ax.set_ylim([0.9*np.min(yd), 1.1*np.max(yd)])
#####################################################
#plot
plt.show()
which, in essence, is pulling all y data from all lines on the plot, finding the max and min; then implementing them via set_ylim; "forcing" autoscale
yields:
voila!
for my situation I had somewhat more complicated plots in the format:
plt.plot((x1,x2), (y1,y2))
creating a matrix in matrix situation producing a 'Value Error'
for that I had to flatten using:
yd = [item for sublist in yd for item in sublist]
h/t #Alex Martelli
Making a flat list out of list of lists in Python
and this was the final product:
#####################################################
#force 'autoscale'
#####################################################
yd = [] #matrix of y values from all lines on plot
for n in range(len(plt.gca().get_lines())):
line = plt.gca().get_lines()[n]
yd.append((line.get_ydata()).tolist())
yd = [item for sublist in yd for item in sublist]
ax.set_ylim([0.9*np.min(yd), 1.1*np.max(yd)])
#####################################################
If you look at the documentation the function is like-
matplotlib.pyplot.autoscale(enable=True, axis='both', tight=None)
What you are sending is an invalid argument...
just make it
plt.autoscale(True, axis = 'both')
And about tight -
If True, set view limits to data limits; if False, let the locator and
margins expand the view limits; if None, use tight scaling if the only
artist is an image, otherwise treat tight as False. The tight setting
is retained for future autoscaling until it is explicitly changed.
I had a similar problem and I was able to solve it by setting the 'log' scale before plotting. In this case, the autoscaling is working as expected.
Related
Python surface plotting
I have following table data(Please see the image) for which I want to have a surface plot in python. Using surface plotting from matplotlib, import matplotlib.pyplot as plt from matplotlib import cm from matplotlib.ticker import LinearLocator, FormatStrFormatter import numpy as np fig = plt.figure() ax = fig.gca(projection='3d') X=[2,3,5,8,20,30,50,80,100,150,175,200,250,300] Y=[2,3,4,5,10,15,20,30,40,50,80,100,125,150,175,200] Y,X=np.meshgrid(Y,X) Z=np.array([ [0.2885307,0.269452,0.259193,0.2548041,0.2731868,0.4801551,0.7992361,1.7577641,3.2611327,5.428839,19.647976,37.59729,78.0871,152.21466,268.14572,0], [0.2677955,0.2538363,0.2380033,0.2306999,0.4779794,0.9251045,1.5448972,3.508644,6.4968576,11.252151,0,0,0,0,0,0], [0.2432982,0.2283371,0.2514196,0.3392502,0,0,0,0,0,0,0,0,0,0,0,0], [0.2342575,0.3158406,0.4770729, 0.6795485,2.353042, 5.260077,9.78172,25.87004,59.52568, 0,0,0,0,0,0,0], [0.6735384, 1.3873291,2.346506, 3.5654,0,0,0,0,0,0,0,0,0,0,0,0], [1.3584715, 2.9405127,5.096819,8.155857,0,0,0,0,0,0,0,0,0,0,0,0], [3.558062,8.216592,15.768077,27.386694,0,0,0,0,0,0,0,0,0,0,0,0], [9.537899,25.202589,58.20041,0,0,0,0,0,0,0,0,0,0,0,0,0], [16.083374,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [54.936775,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [89.185974,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]) my_col = cm.jet(Z/np.amax(Z)) surf = ax.plot_surface(X, Y, Z,cmap=cm.coolwarm,linewidth=0, antialiased=False) ax.set_zlim(0, 300) ax.zaxis.set_major_locator(LinearLocator(10)) ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f')) fig.colorbar(surf, shrink=0.5, aspect=5) plt.show() I get a plot like this which is correct but not very attractive or intuitive. How can I make the visualizations more smooth and clear? Please note that I have many blanks in my data. Should I use 'zero' for the blanks or 'nan'(not a number)? For the same data, excel shows a much better graph. I appreciate your inputs in order to make python plot more visually attractive.
The difference between the matplotlib and excel plots is that matplotlib is plotting on a linear scale and excel is logarithmic (or something that looks deceptively like a log axis but actually isn't -- see below). Therefore, in the matplotlib the slopes look extremely steep, but in excel the slopes are dramatically stretched out by the log. Unfortunately, matplotlib doesn't yet have log axes working well in 3D. I'm not sure why this is, but it is a serious shortcoming. You can see a plot similar to Excel though if you take the log10 of your X and Y data before you do the plots. You can also go further to DIY your own log axes, but I've just done a shorthand for that using a tick formatter. import matplotlib.pyplot as plt from matplotlib import cm from matplotlib.ticker import LinearLocator, FormatStrFormatter, FuncFormatter from mpl_toolkits.mplot3d import axes3d import numpy as np def format_log(x, pos=None): x1 = 10**x s = "%.3f" % x1 return s[:-4] if s[-3:]=="000" else " " fig = plt.figure() ax = fig.gca(projection='3d') X=[2,3,5,8,20,30,50,80,100,150,175,200,250,300] Y=[2,3,4,5,10,15,20,30,40,50,80,100,125,150,175,200] X = np.log10(np.array(X)) Y = np.log10(np.array(Y)) Y,X=np.meshgrid(Y,X) Z=np.array([ [0.2885307,0.269452,0.259193,0.2548041,0.2731868,0.4801551,0.7992361,1.7577641,3.2611327,5.428839,19.647976,37.59729,78.0871,152.21466,268.14572,0], [0.2677955,0.2538363,0.2380033,0.2306999,0.4779794,0.9251045,1.5448972,3.508644,6.4968576,11.252151,0,0,0,0,0,0], [0.2432982,0.2283371,0.2514196,0.3392502,0,0,0,0,0,0,0,0,0,0,0,0], [0.2342575,0.3158406,0.4770729, 0.6795485,2.353042, 5.260077,9.78172,25.87004,59.52568, 0,0,0,0,0,0,0], [0.6735384, 1.3873291,2.346506, 3.5654,0,0,0,0,0,0,0,0,0,0,0,0], [1.3584715, 2.9405127,5.096819,8.155857,0,0,0,0,0,0,0,0,0,0,0,0], [3.558062,8.216592,15.768077,27.386694,0,0,0,0,0,0,0,0,0,0,0,0], [9.537899,25.202589,58.20041,0,0,0,0,0,0,0,0,0,0,0,0,0], [16.083374,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [54.936775,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [89.185974,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]) my_col = cm.jet(Z/np.amax(Z)) surf = ax.plot_surface(X, Y, Z,cmap=cm.coolwarm) ax.set_zlim(0, 300) ax.zaxis.set_major_locator(LinearLocator(10)) ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f')) ax.xaxis.set_major_formatter(FuncFormatter(format_log)) ax.yaxis.set_major_formatter(FuncFormatter(format_log)) fig.colorbar(surf, shrink=0.5, aspect=5) plt.show() Edit: After coming back to this question, I realize that the Excel plot isn't actually showing a logarithmic axis, but is instead just plotting the X and Y values given with equal spacing along the axis even those values have no clear mathematical progression. It's critical to note that this isn't a good representation of the data, since it gives the obvious impression that it's logarithmic (for the specific data presented), but it's actually not, although it requires unusually close inspection to see that. Here the gaps between adjacent numbers aren't even monotonic. So I discourage this representation, but to reproduce that Excel plot, I'd suggest making equally spaced data, but labeling it with different numbers (and just this sentence alone should be enough to discourage this approach). But here's the code and approach: fig = plt.figure() ax = fig.gca(projection='3d') x=[2,3,5,8,20,30,50,80,100,150,175,200,250,300] y=[2,3,4,5,10,15,20,30,40,50,80,100,125,150,175,200] Y,X=np.meshgrid(range(len(y)),range(len(x))) Z=np.array([ [0.2885307,0.269452,0.259193,0.2548041,0.2731868,0.4801551,0.7992361,1.7577641,3.2611327,5.428839,19.647976,37.59729,78.0871,152.21466,268.14572,0], [0.2677955,0.2538363,0.2380033,0.2306999,0.4779794,0.9251045,1.5448972,3.508644,6.4968576,11.252151,0,0,0,0,0,0], [0.2432982,0.2283371,0.2514196,0.3392502,0,0,0,0,0,0,0,0,0,0,0,0], [0.2342575,0.3158406,0.4770729, 0.6795485,2.353042, 5.260077,9.78172,25.87004,59.52568, 0,0,0,0,0,0,0], [0.6735384, 1.3873291,2.346506, 3.5654,0,0,0,0,0,0,0,0,0,0,0,0], [1.3584715, 2.9405127,5.096819,8.155857,0,0,0,0,0,0,0,0,0,0,0,0], [3.558062,8.216592,15.768077,27.386694,0,0,0,0,0,0,0,0,0,0,0,0], [9.537899,25.202589,58.20041,0,0,0,0,0,0,0,0,0,0,0,0,0], [16.083374,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [54.936775,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [89.185974,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]) my_col = cm.jet(Z/np.amax(Z)) surf = ax.plot_surface(X, Y, Z,cmap=cm.coolwarm) ax.tick_params(axis='both', which='major', labelsize=6) ax.set_zlim(0, 300) ax.xaxis.set_major_locator(IndexLocator(1, 0)) ax.xaxis.set_major_formatter(FixedFormatter([repr(v) for v in x])) ax.yaxis.set_major_locator(IndexLocator(1, 0)) ax.yaxis.set_major_formatter(FixedFormatter([repr(v) for v in y])) fig.colorbar(surf, shrink=0.5, aspect=5) If one wanted to show the specific numbers given for X and Y, one solution would be to plot with a logarithmic axis (since the numbers are spaced very approximately in a log way), and then plot the numbers specifically by lines on the axes, or alternatively, don't use these numbers instead of the usual regularly spaced numbers. (But to plot these as axes values, and space them visually at regular intervals, that is a problem.)
polar chart : showing yearly trend
I'm trying to reproduce the following chart: But I'm not sure if's actually possible to create such a plot using Python,R or Tableau. Here is my first attempt using Plotly in R: Do you have any suggestion for creating such a chart?
You can use R and de package highcharter to create a plot like this one: spiderweb plot the plot js code is in www/highcharts.com/demo/polar-spider
While I was working on creating this plot with matplotlib, someone mentioned that I can create this chart using Excel! in less than 2 minutes, so I didn't complete the code but anyway as I already figure out how should I create different elements of the plot in matplotlib, I put the code here in case anyone wants to create such a thing. import matplotlib.pyplot as plt import matplotlib.patches as patches fig1 = plt.figure() #Adding grids for rad in reversed(range(1,10)): #10 is maximum of ranks we need to show ax1 = fig1.add_subplot(111,aspect = 'equal') ax1.add_patch( patches.RegularPolygon( (0,0), #center of the shape 11, #number of vertices rad, fill=False, ls='--', )) plt.xlim(xmin = -10,xmax=10) plt.ylim(ymin = -10,ymax=10) fig1.show() #plotting the trend plt.scatter(xs,ys) #xs = list of x coordinates, the same for ys for k in range(len(xs)-1): x, y = [xs[k], xs[k+1]], [ys[k], ys[k+1]] plt.plot(x, y,color = 'b') plt.grid(False) plt.show() Result plot (As I said the code doesn't create the whole trends, labels,...but it's pretty much all you need to create the plot)
Python matplotlib plotting after date
I am having an issue plotting after a date has been plotted. the code is the following: import matplotlib.pyplot as plt from matplotlib import style x = [735412.0, 735503.0, 735594.0, 735685.0] y =['0.0', '16961000000.0', '29030000000.0', '32504000000.0'] z = ['100000', '200000000000', '3000000000000', '400000000000'] # plt.plot_date(x, y, marker='o', linestyle='-', color='b') plt.plot(y,z) # this does not print if above line is uncommented plt.gcf().autofmt_xdate() # turns bottom dates at angle plt.show() What am I doing wrong? Thanks! L
First of all, your "numbers" in y are actually strings. You would need to convert them to float using np.array(y, float) to use them on an axis. But did you really intend to plt.plot(y,z) in the same figure as the others? The values in y are not dates/times of any kind, so probably not. I suspect this should be a new figure, so you need to start a new figure with plt.figure() before you plot y vs z: plt.figure() plt.plot(y,z) and drop the plt.gcf().autofmt_xdate() after that.
matplotlib pyplot 2 plots with different axes in same figure
I have a small issue with matplotlib.pyplot and I hope someone might have come across it before. I have data that contain X,Y,e values that are the X, Y measurements of a variable and e are the errors of the measurements in Y. I need to plot them in a log log scale. I use the plt.errorbars function to plot them and then set yscale and xscale to log and this works fine. But I need to also plot a line on the same graph that needs to be in linear scale. I am able to have the plots done separately just fine but I would like to have them in the same image if possible. Do you have any ideas? I am posting what I have done for now. Cheers, Kimon tdlist = np.array([0.01,0.02,0.05,0.1,0.2,0.3,0.4,0.5,0.8,1,2,5,10,15,20,25,30,40,60,80,100,150,200,250,300,400]) freqlist=np.array([30,40,50,60,70,80,90,100,110,120,140,160,180,200,220,250,300,350,400,450]) filename=opts.filename data = reader(filename) data2 = logconv(data) #x,y,e the data. Calculating usefull sums x = data2[0] y = data2[1] e = data2[2] xoe2 = np.sum(x/e**2) yoe2 = np.sum(y/e**2) xyoe2 = np.sum(x*y/e**2) oe2 = np.sum(1/e**2) x2oe2 = np.sum(x**2/e**2) aslope = (xoe2*yoe2-xyoe2*oe2)/(xoe2**2-x2oe2*oe2) binter = (xyoe2-aslope*x2oe2)/xoe2 aerr = np.sqrt(oe2/(x2oe2*oe2-xoe2**2)) berr = np.sqrt(x2oe2/(x2oe2*oe2-xoe2**2)) print('slope is ',aslope,' +- ', aerr) print('inter is ',binter,' +- ', berr) fig = plt.figure() ax1 = fig.add_subplot(1,1,1) ax2 = fig.add_axes(ax1.get_position(), frameon=False) ax1.errorbar(data[0],data[1],yerr=data[2],fmt='o') ax1.set_xscale('log',basex=10) ax1.set_yscale('log',basey=10) ax1.set_yticks([]) ax1.set_xticks([]) ax2.plot(x,aslope*x+binter,'r') ax2.plot(x,(aslope-aerr)*x+(binter+berr),'--') ax2.plot(x,(aslope+aerr)*x+(binter-berr),'--') ax2.set_xscale('linear') ax2.set_yscale('linear') plt.xticks(np.log10(freqlist),freqlist.astype('int')) plt.yticks(np.log10(tdlist),tdlist.astype('float')) plt.xlabel('Frequency (MHz)') plt.ylabel('t_s (msec)') fitndx1 = 'Fit slope '+"{0:.2f}".format(aslope)+u"\u00B1"+"{0:.2f}".format(aerr) plt.legend(('Data',fitndx1)) plt.show() Following Molly's suggestion I managed to get closer to my goal but still not there. I am adding a bit more info for what I am trying to do and it might clarify things a bit. I am setting ax1 to the errobar plot that uses loglog scale. I need to use errorbar and not loglog plot so that I can display the errors with my points. I am using ax2 to plot the linear fit in linealinear scale. Moreover I do not want the x and y axes to display values that are 10,100,1000 powers of ten but my own axes labels that have the spacing I want therefore I am using the plt.xticks. I tried ax1.set_yticks and ax1.set_yticklabes but with no success. Below is the image I am getting. I do not have enough reputation to post an image but here is the link of it uploaded http://postimg.org/image/uojanigab/ The values of my points should be x range = 40 - 80 and y range = 5 -200 as the fit lines are now.
You can create two overlapping axes using the add_suplot method of figure. Here's an example: from matplotlib import pyplot as plt fig = plt.figure() ax1 = fig.add_subplot(1,1,1) ax2 = fig.add_axes(ax1.get_position(), frameon=False) ax1.loglog([1,10,100,1000],[1000,1,100,10]) ax2.plot([5,10,11,13],'r') plt.show() You can then turn off the x and y ticks for the linear scale plot like this: ax2.set_xticks([]) ax2.set_yticks([])
I was not able to get two sets of axis working with the errorbar function so I had to convert everything to log scale including my linear plot. Below is the code I use to get it might be useful to someone. plt.errorbar(data[0],data[1],yerr=data[2],fmt='o') plt.xscale('log',basex=10) plt.yscale('log',basey=10) plt.plot(data[0],data[0]**aslope*10**binter,'r') plt.plot(data[0],data[0]**(aslope-aerr)*10**(binter+berr),'--') plt.plot(data[0],data[0]**(aslope+aerr)*10**(binter-berr),'--') plt.xticks(freqlist,freqlist.astype('int')) plt.yticks(tdlist,tdlist.astype('float')) plt.xlabel('Frequency (MHz)') plt.ylabel('t_s (msec)') fitndx1 = 'Fit slope '+"{0:.2f}".format(aslope)+u"\u00B1"+"{0:.2f}".format(aerr) plt.legend(('Data',fitndx1)) plt.show() And here is the link to the final image http://postimg.org/image/bevj2k6nf/
Moving matplotlib legend outside of the axis makes it cutoff by the figure box
I'm familiar with the following questions: Matplotlib savefig with a legend outside the plot How to put the legend out of the plot It seems that the answers in these questions have the luxury of being able to fiddle with the exact shrinking of the axis so that the legend fits. Shrinking the axes, however, is not an ideal solution because it makes the data smaller making it actually more difficult to interpret; particularly when its complex and there are lots of things going on ... hence needing a large legend The example of a complex legend in the documentation demonstrates the need for this because the legend in their plot actually completely obscures multiple data points. http://matplotlib.sourceforge.net/users/legend_guide.html#legend-of-complex-plots What I would like to be able to do is dynamically expand the size of the figure box to accommodate the expanding figure legend. import matplotlib.pyplot as plt import numpy as np x = np.arange(-2*np.pi, 2*np.pi, 0.1) fig = plt.figure(1) ax = fig.add_subplot(111) ax.plot(x, np.sin(x), label='Sine') ax.plot(x, np.cos(x), label='Cosine') ax.plot(x, np.arctan(x), label='Inverse tan') lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0)) ax.grid('on') Notice how the final label 'Inverse tan' is actually outside the figure box (and looks badly cutoff - not publication quality!) Finally, I've been told that this is normal behaviour in R and LaTeX, so I'm a little confused why this is so difficult in python... Is there a historical reason? Is Matlab equally poor on this matter? I have the (only slightly) longer version of this code on pastebin http://pastebin.com/grVjc007
Sorry EMS, but I actually just got another response from the matplotlib mailling list (Thanks goes out to Benjamin Root). The code I am looking for is adjusting the savefig call to: fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight') #Note that the bbox_extra_artists must be an iterable This is apparently similar to calling tight_layout, but instead you allow savefig to consider extra artists in the calculation. This did in fact resize the figure box as desired. import matplotlib.pyplot as plt import numpy as np plt.gcf().clear() x = np.arange(-2*np.pi, 2*np.pi, 0.1) fig = plt.figure(1) ax = fig.add_subplot(111) ax.plot(x, np.sin(x), label='Sine') ax.plot(x, np.cos(x), label='Cosine') ax.plot(x, np.arctan(x), label='Inverse tan') handles, labels = ax.get_legend_handles_labels() lgd = ax.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5,-0.1)) text = ax.text(-0.2,1.05, "Aribitrary text", transform=ax.transAxes) ax.set_title("Trigonometry") ax.grid('on') fig.savefig('samplefigure', bbox_extra_artists=(lgd,text), bbox_inches='tight') This produces: [edit] The intent of this question was to completely avoid the use of arbitrary coordinate placements of arbitrary text as was the traditional solution to these problems. Despite this, numerous edits recently have insisted on putting these in, often in ways that led to the code raising an error. I have now fixed the issues and tidied the arbitrary text to show how these are also considered within the bbox_extra_artists algorithm.
Added: I found something that should do the trick right away, but the rest of the code below also offers an alternative. Use the subplots_adjust() function to move the bottom of the subplot up: fig.subplots_adjust(bottom=0.2) # <-- Change the 0.02 to work for your plot. Then play with the offset in the legend bbox_to_anchor part of the legend command, to get the legend box where you want it. Some combination of setting the figsize and using the subplots_adjust(bottom=...) should produce a quality plot for you. Alternative: I simply changed the line: fig = plt.figure(1) to: fig = plt.figure(num=1, figsize=(13, 13), dpi=80, facecolor='w', edgecolor='k') and changed lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,0)) to lgd = ax.legend(loc=9, bbox_to_anchor=(0.5,-0.02)) and it shows up fine on my screen (a 24-inch CRT monitor). Here figsize=(M,N) sets the figure window to be M inches by N inches. Just play with this until it looks right for you. Convert it to a more scalable image format and use GIMP to edit if necessary, or just crop with the LaTeX viewport option when including graphics.
Here is another, very manual solution. You can define the size of the axis and paddings are considered accordingly (including legend and tickmarks). Hope it is of use to somebody. Example (axes size are the same!): Code: #================================================== # Plot table colmap = [(0,0,1) #blue ,(1,0,0) #red ,(0,1,0) #green ,(1,1,0) #yellow ,(1,0,1) #magenta ,(1,0.5,0.5) #pink ,(0.5,0.5,0.5) #gray ,(0.5,0,0) #brown ,(1,0.5,0) #orange ] import matplotlib.pyplot as plt import numpy as np import collections df = collections.OrderedDict() df['labels'] = ['GWP100a\n[kgCO2eq]\n\nasedf\nasdf\nadfs','human\n[pts]','ressource\n[pts]'] df['all-petroleum long name'] = [3,5,2] df['all-electric'] = [5.5, 1, 3] df['HEV'] = [3.5, 2, 1] df['PHEV'] = [3.5, 2, 1] numLabels = len(df.values()[0]) numItems = len(df)-1 posX = np.arange(numLabels)+1 width = 1.0/(numItems+1) fig = plt.figure(figsize=(2,2)) ax = fig.add_subplot(111) for iiItem in range(1,numItems+1): ax.bar(posX+(iiItem-1)*width, df.values()[iiItem], width, color=colmap[iiItem-1], label=df.keys()[iiItem]) ax.set(xticks=posX+width*(0.5*numItems), xticklabels=df['labels']) #-------------------------------------------------- # Change padding and margins, insert legend fig.tight_layout() #tight margins leg = ax.legend(loc='upper left', bbox_to_anchor=(1.02, 1), borderaxespad=0) plt.draw() #to know size of legend padLeft = ax.get_position().x0 * fig.get_size_inches()[0] padBottom = ax.get_position().y0 * fig.get_size_inches()[1] padTop = ( 1 - ax.get_position().y0 - ax.get_position().height ) * fig.get_size_inches()[1] padRight = ( 1 - ax.get_position().x0 - ax.get_position().width ) * fig.get_size_inches()[0] dpi = fig.get_dpi() padLegend = ax.get_legend().get_frame().get_width() / dpi widthAx = 3 #inches heightAx = 3 #inches widthTot = widthAx+padLeft+padRight+padLegend heightTot = heightAx+padTop+padBottom # resize ipython window (optional) posScreenX = 1366/2-10 #pixel posScreenY = 0 #pixel canvasPadding = 6 #pixel canvasBottom = 40 #pixel ipythonWindowSize = '{0}x{1}+{2}+{3}'.format(int(round(widthTot*dpi))+2*canvasPadding ,int(round(heightTot*dpi))+2*canvasPadding+canvasBottom ,posScreenX,posScreenY) fig.canvas._tkcanvas.master.geometry(ipythonWindowSize) plt.draw() #to resize ipython window. Has to be done BEFORE figure resizing! # set figure size and ax position fig.set_size_inches(widthTot,heightTot) ax.set_position([padLeft/widthTot, padBottom/heightTot, widthAx/widthTot, heightAx/heightTot]) plt.draw() plt.show() #-------------------------------------------------- #==================================================
I tried a very simple way, just make the figure a bit wider: fig, ax = plt.subplots(1, 1, figsize=(a, b)) adjust a and b to a proper value such that the legend is included in the figure