This question already has answers here:
How can I change the x axis in matplotlib so there is no white space?
(2 answers)
Closed 4 years ago.
In the figure below (1) there is a lot of white space around the hexagonal grid that I cannot figure out how to remove. I've tried different methods i.e. tight_layout etc.
The code is
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
import matplotlib.cm as cm
import matplotlib.colors as colors
import numpy
def plot_som_hm(hm,colormap,a=1):
# setup hexagon offsets
m,n = hm.shape
offsety = .75 * 2*a
offsetx = numpy.sqrt(3) * a
evenrow = numpy.sqrt(3)/2 * a
# set up the figure
fig,ax = plt.subplots(figsize=(2,2))
ax.set_aspect('equal')
# define colormap
cmap = cm.ScalarMappable(None,colormap)
norm = colors.Normalize(vmin=hm.min(),vmax=hm.max())
# iterate over the hitmap, drawing a hexagon
xs = []
ys = []
for i in range(m):
for j in range(n):
# calculate center point for current hexagonal grid & draw it
offsetr = evenrow if i % 2 == 0 else 0
x,y = (j*offsetx+offsetr,-i*offsety)
hexg = RegularPolygon(
(x,y),numVertices=6,radius=a,facecolor=cmap.cmap(norm(hm[i][j]))
)
ax.add_patch(hexg)
xs.append(x)
ys.append(y)
# add a scatter plot so all hexagons show up & turn off ticks
ax.scatter(xs,ys,alpha=1.0)
ax.set_xticks([])
ax.set_yticks([])
# add a colorbar
sm = plt.cm.ScalarMappable(cmap=colormap,norm=norm)
sm._A = []
plt.colorbar(sm,ticks=range(int(hm.min()),int(hm.max())+1))
# and show the hitmap
plt.show()
which can be called by plot_som_hm(hitmap,'inferno',-0.5)
I am not sure if the whitespace is the result of calling subplots (figsize=(2,2)) or something else. Being relatively new to matplotlib I am not sure where the whitespace is coming from i.e. if it is the figure, the axis or even the plt so searching on Google has not provided any relevant answers.
II suggest to read the x- and y-limits to see how you can adjust them to your needs, i.e.:
print(ax.get_xlim())
and then e.g.
ax.set_xlim(0.5, 5.5)
or whatever fits.
The same then with the y-axis.
Related
I wonder if there's the possibility to display several plots or images in a non-grid constellation ? For example is there a way to display a set of images in one figure in a circular constellation along the perimeter using Matplotlib or any other python package alike ?
An axes can be created and positionned via fig.add_axes([x,y,width,height]) see documentation. Also see What are the differences between add_axes and add_subplot?
In this case we can add the axes to positions lying on a circle, creating some kind of manual radial grid of axes.
import numpy as np
import matplotlib.pyplot as plt
N = 8
t = np.linspace(0,2*np.pi, N, endpoint=False)
r = 0.37
h = 0.9 - 2*r
w = h
X,Y = r*np.cos(t)-w/2.+ 0.5, r*np.sin(t)-h/2.+ 0.5
fig = plt.figure()
axes = []
for x,y in zip(X,Y):
axes.append(fig.add_axes([x, y, w, h]))
plt.show()
I'm trying to produce a similar version of this image using Python:
I'm close but can't quite figure out how to modify a matplotlib colormap to make values <0.4 go to white. I tried masking those values and using set_bad but I ended up with a real blocky appearance, losing the nice smooth contours seen in the original image.
Result with continuous colormap (problem: no white):
Result with set_bad (problem: no smooth transition to white):
Code so far:
from netCDF4 import Dataset as NetCDFFile
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
nc = NetCDFFile('C:/myfile1.nc')
nc1 = NetCDFFile('C:/myfile2.nc')
lat = nc.variables['lat'][:]
lon = nc.variables['lon'][:]
time = nc.variables['time'][:]
uwnd = nc.variables['uwnd'][:]
vwnd = nc1.variables['vwnd'][:]
map = Basemap(llcrnrlon=180.,llcrnrlat=0.,urcrnrlon=340.,urcrnrlat=80.)
lons,lats = np.meshgrid(lon,lat)
x,y = map(lons,lats)
speed = np.sqrt(uwnd*uwnd+vwnd*vwnd)
#speed = np.ma.masked_where(speed < 0.4, speed)
#cmap = plt.cm.jet
#cmap.set_bad(color='white')
levels = np.arange(0.0,3.0,0.1)
ticks = np.arange(0.0,3.0,0.2)
cs = map.contourf(x,y,speed[0],levels, cmap='jet')
vw = plt.quiver(x,y,speed)
cbar = plt.colorbar(cs, orientation='horizontal', cmap='jet', spacing='proportional',ticks=ticks)
cbar.set_label('850 mb Vector Wind Anomalies (m/s)')
map.drawcoastlines()
map.drawparallels(np.arange(20,80,20),labels=[1,1,0,0], linewidth=0.5)
map.drawmeridians(np.arange(200,340,20),labels=[0,0,0,1], linewidth=0.5)
#plt.show()
plt.savefig('phase8_850wind_anom.png',dpi=600)
The answer to get the result smooth lies in constructing your own colormap. To do this one has to create an RGBA-matrix: a matrix with on each row the amount (between 0 and 1) of Red, Green, Blue, and Alpha (transparency; 0 means that the pixel does not have any coverage information and is transparent).
As an example the distance to some point is plotted in two dimensions. Then:
For any distance higher than some critical value, the colors will be taken from a standard colormap.
For any distance lower than some critical value, the colors will linearly go from white to the first color of the previously mentioned map.
The choices depend fully on what you want to show. The colormaps and their sizes depend on your problem. For example, you can choose different types of interpolation: linear, exponential, ...; single- or multi-color colormaps; etc..
The code:
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
# create colormap
# ---------------
# create a colormap that consists of
# - 1/5 : custom colormap, ranging from white to the first color of the colormap
# - 4/5 : existing colormap
# set upper part: 4 * 256/4 entries
upper = mpl.cm.jet(np.arange(256))
# set lower part: 1 * 256/4 entries
# - initialize all entries to 1 to make sure that the alpha channel (4th column) is 1
lower = np.ones((int(256/4),4))
# - modify the first three columns (RGB):
# range linearly between white (1,1,1) and the first color of the upper colormap
for i in range(3):
lower[:,i] = np.linspace(1, upper[0,i], lower.shape[0])
# combine parts of colormap
cmap = np.vstack(( lower, upper ))
# convert to matplotlib colormap
cmap = mpl.colors.ListedColormap(cmap, name='myColorMap', N=cmap.shape[0])
# show some example
# -----------------
# open a new figure
fig, ax = plt.subplots()
# some data to plot: distance to point at (50,50)
x,y = np.meshgrid(np.linspace(0,99,100),np.linspace(0,99,100))
z = (x-50)**2. + (y-50)**2.
# plot data, apply colormap, set limit such that our interpretation is correct
im = ax.imshow(z, interpolation='nearest', cmap=cmap, clim=(0,5000))
# add a colorbar to the bottom of the image
div = make_axes_locatable(ax)
cax = div.append_axes('bottom', size='5%', pad=0.4)
cbar = plt.colorbar(im, cax=cax, orientation='horizontal')
# save/show the image
plt.savefig('so.png')
plt.show()
The result:
I am drawing figures for scientific article.
In such article the room is scarse and the figures must be readable. Therefore an x axis must have 3 or 4 ticks maximum. The default behaviour for matplotlib is to have overlapping plenty of xticks label. It is very painful and time consuming to manually adjust the exact ticks needed, trying to prevent overlapping. I find the command ax.locator_params that seem to do the job, but it has a weird behaviour, and place ticks at unwanted position outside of the data limit.
Here is a code exploring 4 different ways to change ticks. The last two are not using locator_param(), but are bulky and doesn't work in every situation. How can I make work locator_param() or use something painless ?
Regards,
import numpy as np
import matplotlib.pyplot as plt
plt.close('all')
x = np.linspace(-0.7,0.7,num = 50)
y = np.linspace(0,1,num = 100)
ext = (x[0],x[-1],y[-1],y[0])
xx,yy = np.meshgrid(x,y,indexing = 'ij')
U = np.cos(xx**2 + yy**2)
fig, ax_l = plt.subplots(1,4)
fig.set_size_inches(4*3.45, 3, forward=True)
for ax in ax_l :
ax.set_xlabel('x')
ax.set_ylabel('y')
im = ax.imshow(U,interpolation = 'nearest', extent = ext, aspect = 'equal')
### method 1 ####
ax_l[0].locator_params(axis = 'x',tight=True, nbins=3)
### method 2 ####
ax_l[1].locator_params(axis = 'x',tight=False, nbins=3)
### method 3 ####
ticks = ax_l[2].get_xticks()
ax_l[2].set_xticks(ticks[0::2])
### method 4 ####
x_lim = ax_l[3].get_xlim()
ax_l[3].set_xticks(np.linspace(x_lim[0],x_lim[1],num=3))
fig.tight_layout()
plt.show()
I would like to draw a plot with a logarithmic y axis and a linear x axis on a square plot area in matplotlib. I can draw linear-linear as well as log-log plots on squares, but the method I use, Axes.set_aspect(...), is not implemented for log-linear plots. Is there a good workaround?
linear-linear plot on a square:
from pylab import *
x = linspace(1,10,1000)
y = sin(x)**2+0.5
plot (x,y)
ax = gca()
data_aspect = ax.get_data_ratio()
ax.set_aspect(1./data_aspect)
show()
log-log plot on a square:
from pylab import *
x = linspace(1,10,1000)
y = sin(x)**2+0.5
plot (x,y)
ax = gca()
ax.set_yscale("log")
ax.set_xscale("log")
xmin,xmax = ax.get_xbound()
ymin,ymax = ax.get_ybound()
data_aspect = (log(ymax)-log(ymin))/(log(xmax)-log(xmin))
ax.set_aspect(1./data_aspect)
show()
But when I try this with a log-linear plot, I do not get the square area, but a warning
from pylab import *
x = linspace(1,10,1000)
y = sin(x)**2+0.5
plot (x,y)
ax = gca()
ax.set_yscale("log")
xmin,xmax = ax.get_xbound()
ymin,ymax = ax.get_ybound()
data_aspect = (log(ymax)-log(ymin))/(xmax-xmin)
ax.set_aspect(1./data_aspect)
show()
yielding the warning:
axes.py:1173: UserWarning: aspect is not supported for Axes with xscale=linear, yscale=log
Is there a good way of achieving square log-linear plots despite the lack support in Axes.set_aspect?
Well, there is a sort of a workaround. The actual axis area (the area where the plot is, not including external ticks &c) can be resized to any size you want it to have.
You may use the ax.set_position to set the relative (to the figure) size and position of the plot. In order to use it in your case we need a bit of maths:
from pylab import *
x = linspace(1,10,1000)
y = sin(x)**2+0.5
plot (x,y)
ax = gca()
ax.set_yscale("log")
# now get the figure size in real coordinates:
fig = gcf()
fwidth = fig.get_figwidth()
fheight = fig.get_figheight()
# get the axis size and position in relative coordinates
# this gives a BBox object
bb = ax.get_position()
# calculate them into real world coordinates
axwidth = fwidth * (bb.x1 - bb.x0)
axheight = fheight * (bb.y1 - bb.y0)
# if the axis is wider than tall, then it has to be narrowe
if axwidth > axheight:
# calculate the narrowing relative to the figure
narrow_by = (axwidth - axheight) / fwidth
# move bounding box edges inwards the same amount to give the correct width
bb.x0 += narrow_by / 2
bb.x1 -= narrow_by / 2
# else if the axis is taller than wide, make it vertically smaller
# works the same as above
elif axheight > axwidth:
shrink_by = (axheight - axwidth) / fheight
bb.y0 += shrink_by / 2
bb.y1 -= shrink_by / 2
ax.set_position(bb)
show()
A slight stylistic comment is that import pylab is not usually used. The lore goes:
import matplotlib.pyplot as plt
pylab as an odd mixture of numpy and matplotlib imports created to make interactive IPython use easier. (I use it, too.)
I'm using quadmesh to create a simple polar projection plot. Here's a minimal script which produces basically what I'm trying to do:
from __future__ import unicode_literals
import numpy as np
import matplotlib.pyplot as plt
def make_plot(data,fig,subplot):
nphi,nt = data.shape
phi_coords = np.linspace(0,np.pi*2,nphi+1) - np.pi/2.
theta_coords = np.linspace(0,np.radians(35),nt+1)
ax = fig.add_subplot(subplot,projection='polar')
ax.set_thetagrids((45,90,135,180,225,270,315,360),(9,12,15,18,21,24,3,6))
ax.set_rgrids(np.arange(10,35,10),fmt='%s\u00b0')
theta,phi = np.meshgrid(phi_coords,theta_coords)
quadmesh = ax.pcolormesh(theta,phi,data)
ax.grid(True)
fig.colorbar(quadmesh,ax=ax)
return fig,ax
a = np.zeros((360,71)) + np.arange(360)[:,None]
b = np.random.random((360,71))
fig = plt.figure()
t1 = make_plot(a,fig,121)
t2 = make_plot(b,fig,122)
fig.savefig('test.png')
The above script creates a plot which looks like this:
I would like the colorbars to:
Not overlap the 6 label.
be scaled such that they are approximately the same height as the plot.
Is there any trick to make this work properly? (Note that this layout isn't the only one I will be using -- e.g. I might use a 1x2 layout, or a 4x4 layout ... It seems like there should be some way to scale the colorbar to the same height as the associated plot...)
This combination (and values near to these) seems to "magically" work for me to keep the colorbar scaled to the plot, no matter what size the display.
plt.colorbar(im,fraction=0.046, pad=0.04)
You can do this with a combination of the pad, shrink, and aspect kwargs:
from __future__ import unicode_literals
import numpy as np
import matplotlib.pyplot as plt
def make_plot(data,fig,subplot):
nphi,nt = data.shape
phi_coords = np.linspace(0,np.pi*2,nphi+1) - np.pi/2.
theta_coords = np.linspace(0,np.radians(35),nt+1)
ax = fig.add_subplot(subplot,projection='polar')
ax.set_thetagrids((45,90,135,180,225,270,315,360),(9,12,15,18,21,24,3,6))
ax.set_rgrids(np.arange(10,35,10),fmt='%s\u00b0')
theta,phi = np.meshgrid(phi_coords,theta_coords)
quadmesh = ax.pcolormesh(theta,phi,data)
ax.grid(True)
cb = fig.colorbar(quadmesh,ax=ax, shrink=.5, pad=.2, aspect=10)
return fig,ax,cb
a = np.zeros((360,71)) + np.arange(360)[:,None]
b = np.random.random((360,71))
fig = plt.figure()
t1 = make_plot(a,fig,121)
t2 = make_plot(b,fig,122)
figure.colorbar doc
The best value for these parameters will depend on the aspect ratio of the axes.
The size of the axes seems to not get shrink-wrapped to the polar plot, thus in the 1x2 arrangement there is a lot of space above and below the plot that are part in the axes object, but empty. The size of the color bar is keyed off of the rectangular size, not the round size, hence why the default values are not working well. There is probably a way to do the shrink-wrapping, but I do not know how to do that.
An alternate method is to force your figure to be the right aspect ratio ex:
fig.set_size_inches(10, 4) # for 1x2
fig.set_size_inches(4, 10) # for 2x1
which makes the sub plots square, so the default values more-or-less work.