I want to plot a graph with pyplot. The graph is quite big, so in order to not only see many overlapping dots indicating the nodes, I have to scale the output picture.
I used:
f,ax = plt.subplots(1,1)
ax.set_aspect('equal')
zoom=30
w, h = f.get_size_inches()
f.set_size_inches(w * zoom, h * zoom)
But now I have the problem, that I have big white spaces at the edges of the picture. There nothing is drawn and it is caused because the picture is much higher than wide.
How can I avoid this?
Instead of zooming a figure, you can use figsize argument to plt.subplots. You might also be interested in plt.tight_layout.
So you can do something like:
f,ax = plt.subplots(figsize=(10, 10)) # figure size in inches
ax.set_aspect('equal')
plt.tight_layout() # fill empty space
I also removed 1,1 from call to subplots as it's not necessary when you make only one plot.
Related
I am starting to play around with creating polar plots in Matplotlib that do NOT encompass an entire circle - i.e. a "wedge" plot - by setting the thetamin and thetamax properties. This is something I was waiting for for a long time, and I am glad they have it done :)
However, I have noticed that the figure location inside the axes seem to change in a strange manner when using this feature; depending on the wedge angular aperture, it can be difficult to fine tune the figure so it looks nice.
Here's an example:
import numpy as np
import matplotlib.pyplot as plt
# get 4 polar axes in a row
fig, axes = plt.subplots(2, 2, subplot_kw={'projection': 'polar'},
figsize=(8, 8))
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig.set_facecolor('paleturquoise')
for i, theta_max in enumerate([2*np.pi, np.pi, 2*np.pi/3, np.pi/3]):
# define theta vector with varying end point and some data to plot
theta = np.linspace(0, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
axes[i//2, i%2].set_thetamin(0)
axes[i//2, i%2].set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
axes[i//2, i%2].plot(theta, data)
axes[i//2, i%2].set_ylim([0, 1])
axes[i//2, i%2].set_xlabel('Magnitude', fontsize=15)
axes[i//2, i%2].set_ylabel('Angles', fontsize=15)
fig.set_tight_layout(True)
#fig.savefig('fig.png', facecolor='skyblue')
The labels are in awkward locations and over the tick labels, but can be moved closer or further away from the axes by adding an extra labelpad parameter to set_xlabel, set_ylabel commands, so it's not a big issue.
Unfortunately, I have the impression that the plot is adjusted to fit inside the existing axes dimensions, which in turn lead to a very awkward white space above and below the half circle plot (which of course is the one I need to use).
It sounds like something that should be reasonably easy to get rid of - I mean, the wedge plots are doing it automatically - but I can't seem to figure it out how to do it for the half circle. Can anyone shed a light on this?
EDIT: Apologies, my question was not very clear; I want to create a half circle polar plot, but it seems that using set_thetamin() you end up with large amounts of white space around the image (especially above and below) which I would rather have removed, if possible.
It's the kind of stuff that normally tight_layout() takes care of, but it doesn't seem to be doing the trick here. I tried manually changing the figure window size after plotting, but the white space simply scales with the changes. Below is a minimum working example; I can get the xlabel closer to the image if I want to, but saved image file still contains tons of white space around it.
Does anyone knows how to remove this white space?
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, subplot_kw={'projection': 'polar'})
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig1.set_facecolor('skyblue')
theta_min = 0
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15)
ax1.set_ylabel('Angles', fontsize=15)
fig1.set_tight_layout(True)
#fig1.savefig('fig1.png', facecolor='skyblue')
EDIT 2: Added background color to figures to better show the boundaries, as suggested in ImportanteOfBeingErnest's answer.
It seems the wedge of the "truncated" polar axes is placed such that it sits in the middle of the original axes. There seems so be some constructs called LockedBBox and _WedgeBbox in the game, which I have never seen before and do not fully understand. Those seem to be created at draw time, such that manipulating them from the outside seems somewhere between hard and impossible.
One hack can be to manipulate the original axes such that the resulting wedge turns up at the desired position. This is not really deterministic, but rather looking for some good values by trial and error.
The parameters to adjust in this case are the figure size (figsize), the padding of the labels (labelpad, as already pointed out in the question) and finally the axes' position (ax.set_position([left, bottom, width, height])).
The result could then look like
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, figsize=(6,3.4), subplot_kw={'projection': 'polar'})
theta_min = 1.e-9
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6.)*np.abs(np.sin(3*theta)/np.sin(theta/2.))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180./np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15, labelpad=-60)
ax1.set_ylabel('Angles', fontsize=15)
ax1.set_position( [0.1, -0.45, 0.8, 2])
plt.show()
Here I've set some color to the background of the figure to better see the boundary.
I'm dynamically generating a horizontal bar plot using MatPlotLib. It works pretty well most of the time, until people try to plot a very large numbers of data points. MatPlotLib tries to squish all of the bars into the plot and they start to disappear.
The ideal solution would be to generate the plot so that every horizontal bar is one pixel in height, with 1px separating every bar. The total height of the resulting plot image would then be dependent on the number of bars. But as everything in MatPlotLib is relative, I'm getting really stuck in how to do this. Any help would be much appreciated!
One option is to generate an image with the bars as pixels.
import matplotlib.pyplot as plt
import numpy as np
dpi = 100
N = 100 # numbner of bars (approx. half the number of pixels)
w = 200 #width of plot in pixels
sp = 3 # spacing within axes in pixels
bp = 50; lp = 70 # bottom, left pixel spacing
bottom=float(bp)/(2*N+2*sp+2*bp)
top = 1.-bottom
left=float(lp)/(w+2*lp)
right=1.-left
figheight = (2*N+2*sp)/float(dpi)/(1-(1-top)-bottom) #inch
figwidth = w/float(dpi)/(1-(1-right)-left)
# this is the input array to plot
inp = np.random.rand(N)+0.16
ar = np.zeros((2*N+2*sp,w))
ninp = np.round(inp/float(inp.max())*w).astype(np.int)
for n in range(N):
ar[2*n+sp, 0: ninp[n]] = np.ones(ninp[n])
fig, ax=plt.subplots(figsize=(figwidth, figheight), dpi=dpi)
plt.subplots_adjust(left=left, bottom=bottom, right=right, top=top)
plt.setp(ax.spines.values(), linewidth=0.5)
ext = [0,inp.max(), N-0.5+(sp+0.5)/2., -(sp+0.5)/2.]
ax.imshow(ar, extent=ext, interpolation="none", cmap="gray_r", origin="upper", aspect="auto")
ax.set_xlim((0,inp.max()*1.1))
ax.set_ylabel("item")
ax.set_xlabel("length")
plt.savefig(__file__+".png", dpi=dpi)
plt.show()
This will work for any setting of dpi.
Note that the ticklabels might appear a bit off, which is an inaccuracy from matplotlib; which I don't know how to overcome.
This example shows how you can plot lines with 1 pixel width:
yinch = 2
fig, ax = plt.subplots(figsize=(3,yinch), facecolor='w')
fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
ypixels = int(yinch*fig.get_dpi())
for i in range(ypixels):
if i % 2 == 0:
c = 'k'
else:
c = 'w'
ax.plot([0,np.random.rand()], [i,i], color=c, linewidth=72./fig.get_dpi())
ax.set_ylim(0,ypixels)
ax.axis('off')
This is what the result looks like (magnified 200%):
edit:
Using a different dpi is not problem, but then using plot() becomes less useful because you cant specify the linewidth units. You can calculate the needed linewidth, but i think using barh() is more clear in that scenario.
In the example above i simply disabled the axis to focus on the 1px bars, if you remove that you can plot as normal. Spacing around it is not a problem because Matplotlib isn't bound to the 0-1 range for a Figure, but you want to add bbox_inches='tight' to your savefig to include artists outside of the normal 0-1 range. If you spend a lot of time 'precise' plotting within you axes, i think its easier to stretch the axis to span the entire figure size. You of course take a different approach but that would require you to also calculate the axes size in inches. Both angles would work, it depends or your precise case which might be more convenient.
Also be aware that old versions of Matplotlib (<2.0?) have a different default figure.dpi and savefig.dpi. You can avoid this by adding dpi=fig.get_dpi() to your savefig statement. One of many reasons to upgrade. ;)
yinch = 2
dpi = 128
fig, ax = plt.subplots(figsize=(3,yinch), facecolor='w', dpi=dpi)
fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
ypixels = int(yinch*fig.get_dpi())
for i in range(ypixels):
if i % 2 == 0:
c = '#aa0000'
else:
c = 'w'
ax.barh(i,np.random.rand(), height=1, color=c)
ax.set_title('DPI %i' % dpi)
ax.set_ylim(0,ypixels)
fig.savefig('mypic.png', bbox_inches='tight')
When plotting two (or more) subplots, there is a large areas of white spaces within the plots (on all four sides) as seen here:
Following is the code which I used to plot it.
from pylab import *
from matplotlib import rc, rcParams
import matplotlib.pyplot as plt
for kk in range(57,58):
fn_i=str(kk)
image_file_1='RedshiftOutput00'+fn_i+'_Slice_z_RadioPowerDSA.png'
image_file_2='RedshiftOutput00'+fn_i+'_Slice_z_RadioPowerTRA.png'
image_file_3='RedshiftOutput00'+fn_i+'_Slice_z_RadioPowerDSA+TRA.png'
image_1 = plt.imread(image_file_1)
image_2 = plt.imread(image_file_2)
image_3 = plt.imread(image_file_3)
ax1 = subplot(131)
plt.imshow(image_1)
plt.axis('off') # clear x- and y-axes
ax2 = subplot(132)
plt.imshow(image_2)
plt.axis('off') # clear x- and y-axes
ax3 = subplot(133)
plt.imshow(image_3)
plt.axis('off') # clear x- and y-axes
plt.savefig('RedshiftOutput00'+fn_i+'_all.png')
I am also uploading the 3 images used in this code to making the code a Minimal Working Example
1) https://drive.google.com/file/d/0B6l5iRWTUbHWSTF2R3E1THBGeVk/view?usp=sharing
2) https://drive.google.com/file/d/0B6l5iRWTUbHWaFI4dHAzcWpiOEU/view?usp=sharing
3) https://drive.google.com/file/d/0B6l5iRWTUbHWaG8xclFlcGJNaUk/view?usp=sharing
How we can remove this white space ? I tried by fixing the whole plot size, still white space is comming.
Mel's comment above (use plt.tight_layout()) works in many situations, but sometimes you need a little more control. To manipulate axes more finely (useful, e.g., when you have lots of colorbars or twin-ned axes), you can use plt.subplots_adjust() or a GridSpec object.
GridSpec objects allow you to specify the horizontal and vertical extents of individual axes, as well as their proportional width and height & spacing. subplots_adjust() moves your axes around after you've already plotted stuff on them. I prefer using the first option, but both are documented well.
It also may help to fool around with the size of your figure. If you have lots of whitespace width-wise, make the width of the figure smaller.
Here's some example code that I used to set up a recent plot:
gs = gridspec.GridSpec(
nrows=1, ncols=3, left=0.1, bottom=0.25, right=0.95, top=0.95,
wspace=0.05, hspace=0., width_ratios=[1, 1, 1])
NII_ax = plt.subplot(gs[0])
SII_ax = plt.subplot(gs[1])
OI_ax = plt.subplot(gs[2])
And the result:
Then, if you need a colorbar, adjust the right argument in GridSpec to something like 0.85, and use fig.add_axes() with a list [left_lim, bottom, width, height] and use that as the axis argument for a fig.colorbar()
I can't seem to get the horizontal gap between subplots to disappear. Any suggestions?
Code:
plt.clf()
fig = plt.figure()
for i in range(6):
ax = fig.add_subplot(3,2,i)
frame_range = [[]]
ax.set_xlim(-100000, 1300000)
ax.set_ylim(8000000, 9100000)
ax.set_aspect(1)
ax.set_xticks([])
ax.set_yticks([])
ax.set_frame_on(False)
ax.add_patch(dt.PolygonPatch(provs[0],fc = 'None', ec = 'black'))
fig.tight_layout(pad=0, w_pad=0, h_pad=0)
plt.subplots_adjust( wspace=0, hspace=0)
plt.savefig(wd + 'example.png')
Examples posted both for this code, and with ticks and frame left in.
You are setting two concurrent rules for your graphs.
One is axes aspect
ax.set_aspect(i)
This will force the plot to always respect the 1:1 proportions.
The other is setting h_space and w_space to be zero. In this case matplotlib will try to change the size of the axes to reduce the spaces to zero. As you set the aspect to be 1 whenever one of the edges touch each other the size of the axes will not change anymore. This produces the gap that keeps the graphs horizontally apart.
There two way to force them to be close to each other.
You can change the figure width to bring them closer to each other.
You can set a the spacing of the left and right edge to bring them closer to each other.
Using the example you gave, I modified few lines to illustrate what can be done with the left and right spacing.
fig = plt.figure()
for i in range(6):
ax = fig.add_subplot(3,2,i)
ax.plot(linspace(-1,1),sin(2*pi*linspace(-1,1)))
draw()
frame_range = [[]]
ax.set_aspect(1)
ax.set_xticks([])
ax.set_yticks([])
# ax.set_frame_on(False)
# ax.add_patch(dt.PolygonPatch(provs[0],fc = 'None', ec = 'black'))
fig.tight_layout(pad=0,w_pad=0, h_pad=0)
subplots_adjust(left=0.25,right=0.75,wspace=0, hspace=0)
The result should be something like the figure bellow.
It is important to keep in mind that if you resize the window the plots will be put apart again, depending if you make it taller/shorter of wider/narrower.
Hope it helps
In Matplotlib I need to draw a graph with points on the x-axis on each integer between 1 and 5000 and on the y-axis only in a very limited range.
Matplotlib automatically compacts everything to let all the data fit on a (landscape) page. In my case I would like the x-axis to be as large as possible so that all points are clearly visible. Right now there's just a thick coloured line as opposed to scattered points.
How can I do this?
(I'm saving to pdf, if that helps)
You can always try to specify the dimensions (in inches) of the figure you are creating. Something along the following line might help:
fig = plt.figure(figsize=(20, 2))
ax = fig.add_subplot(111)
ax.plot(x, y)
The figsize takes a tuple of width, height in inches.