I'm doing a bunch of work with various spherical projection plots using the Astropy WCS package, and have run into some frustrations concerning grid lines. As grid lines do not always intersect with the image bounding box or multiple intersect at the same place, they can go unlabeled or have their labels rendered illegible. I would like to be able to insert grid line labels in each line, much akin to the matplotlib.pyplot.clabel() function applied to contour plots, as in this matplotlib example. I can't embed the image as I am a new user; my apologies.
I know I can place labels using text(), figtext(), or annotate(), but since clabel() works I figure the functionality already exists, even if it hasn't been applied to grid lines. Projection plotting aside, does anyone know a way that in-line grid line labels akin to clabel() can be applied to grid lines on a plain rectangular plot?
To annotate the gridlines, you may use the positions of the major ticks (as those are the positions at which the gridlines are created).
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0,10)
y = np.sin(x)*10
fig, ax = plt.subplots()
ax.plot(x,y)
ax.grid()
for xi in ax.xaxis.get_majorticklocs():
ax.text(xi,0.8, "{:.2f}".format(xi), clip_on=True, ha="center",
transform=ax.get_xaxis_transform(), rotation=90,
bbox={'facecolor':'w', 'pad':1, "edgecolor":"None"})
for yi in ax.yaxis.get_majorticklocs():
ax.text(0.86,yi, "{:.2f}".format(yi), clip_on=True, va="center",
transform=ax.get_yaxis_transform(),
bbox={'facecolor':'w', 'pad':1, "edgecolor":"None"})
plt.show()
Related
I have a cartopy GeoAxesSubplot with some points, and potentially lines or polygons. The projection could be any that is supported by cartopy, including orthographic.
I can plot using different transformations, as explained here:
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
# Projection could be any, e.g. globe or Arctic Stereopolar...
ax = plt.axes(projection=ccrs.Mollweide())
ax.coastlines()
# Plot using the coordinate system of the Axes
a = ax.plot(0.45, 0.5, transform=ax.transAxes, marker='o', ms=10)
# Plot using the projected coordinates using cartopy.crs
b = ax.plot(0, 0, transform=ccrs.PlateCarree() , marker='o', ms=10)
I would like to transform geographical coordinates to get the cartesian coordinates of the object in the axis (e.g. a subplot). That is, the coordinates in the range [0,1] in the axes of the figure, with (0,0) in the lower-left corner, and (1,1) in the upper-right.
In the case above, b should be converted to (0.5, 0, 5) as it is in the center of the map.
Something similar can be done using transform_points, however, I have not been able to transpose to axes-coords.
A number of parameters are defined in matplotlib and cartopy to control where the object is on the map (extent, projection, center meridian, view elevation etc). Hence, introduce another library might be awkward.
Answer given e.g. here explains how the reverse is achievable, however, the example does not give the right answer for how to generate axes coords.
Keep in mind that "geographical coordinates" is not that well defined, since you're mixing two projections (Mollweide & PlateCarree) which both use "geographical coordinates". Also be careful with using the exact center, since that might accidentally look correct, even if you use incorrect coordinates.
So you might first need to convert your data to the projection of the map (projection).
Other than that the Matplotlib transformation tutorial you link to provides all the information necessary to do the transforms.
Setting up the inputs:
from matplotlib import pyplot as plt
import cartopy.crs as ccrs
# sample point coordinates in Plate-Carree
x_pc = -110.0 # longitude
y_pc = 45.0 # latitude
map_proj = ccrs.Mollweide()
data_proj = ccrs.PlateCarree()
The conversion depends on the xlim and ylim of the axes, so it's important to set use ax.set_global() first. That gives a proper mapping from the projection to the display coordinates (and subsequent axes coordinates).
fig, ax = plt.subplots(subplot_kw=dict(projection=map_proj), facecolor='w')
ax.set_global()
ax.coastlines()
b = ax.plot(x_pc, y_pc, 'go', transform=data_proj, ms=5)
# convert to map-coordinates (Mollweide)
x_mollw, y_mollw = ax.projection.transform_point(x_pc, y_pc, data_proj)
# convert to display coordinates
x_disp, y_disp = ax.transData.transform((x_mollw, y_mollw))
# convert to axes coordinates
x_axes, y_axes = ax.transAxes.inverted().transform((x_disp, y_disp))
# plot same point but using axes coordinates
ax.plot(x_axes, y_axes, 'ro', transform=ax.transAxes, ms=10, mfc='none', mew=2)
I want to plot a grid onto the background of 2D plot line, similar as it is for ECG presentations, i.e. at specific points in regular interval dots are shown, e.g. as in this image
In this example there are precisely 4 dots spaced between to major dots. Want I don't want is sth as this Plotting a grid with Matplotlib, i.e. just dotted grid lines
What I did so far (coming from ancient matlab knowledge) is this:
xg = np.linspace(iStart/fs, iEnd/fs, len(y))
yrange = ax.get_ylim()
yg = np.linspace(yrange[0], yrange[1], 4)
xx, yy = np.meshgrid(xg, yg)
gridpoints, = plt.plot(xx.reshape(1,-1),yy.reshape(1,-1),linewidth=0.3,color='0.75',marker=".",markersize=10)
But it gets me this:
What am I not getting right?
As said by jpnadas you can use plt.grid()
here an example on how you can put and customize grid
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
img = plt.imread(imagename)
_, ax = plt.subplots(ncols=1,nrows=1)
ax.imshow(img)
plt.gca().xaxis.set_major_locator(MultipleLocator(16))
plt.gca().yaxis.set_major_locator(MultipleLocator(16))
plt.gca().xaxis.set_minor_locator(MultipleLocator(32))
plt.gca().yaxis.set_minor_locator(MultipleLocator(32))
# Don't allow the axis to be on top of your data
ax.set_axisbelow(True)
# Turn on the minor TICKS, which are required for the minor GRID
ax.minorticks_on()
# Customize the major grid
ax.grid(which='major', linestyle='-', linewidth='4', color='yellow')
# Customize the minor grid
ax.grid(which='minor', linestyle=':', linewidth='2', color='blue')
plt.show()
I found my mistake. It was not an error in my thinking, but that my len(y) in the linspace of x was referencing the wrong vector, hencing producing a too fine grid, which looked like a line.
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 use autofmt_xdate to plot long x-axis labels in a readable way. The problem is, when I want to combine different subplots, the x-axis labeling of the other subplots disappears, which I do not appreciate for the leftmost subplot in the figure below (two rows high). Is there a way to prevent autofmt_xdate from quenching the other x-axis labels? Or is there another way to rotate the labels? As you can see I experimented with xticks and "rotate" as well, but the results were not satisfying because the labels were rotated around their center, which resulted in messy labeling.
Script that produces plot below:
from matplotlib import pyplot as plt
from numpy import arange
import numpy
from matplotlib import rc
rc("figure",figsize=(15,10))
#rc('figure.subplot',bottom=0.1,hspace=0.1)
rc("legend",fontsize=16)
fig = plt.figure()
Test_Data = numpy.random.normal(size=20)
fig = plt.figure()
Dimension = (2,3)
plt.subplot2grid(Dimension, (0,0),rowspan=2)
plt.plot(Test_Data)
plt.subplot2grid(Dimension, (0,1),colspan=2)
for i,j in zip(Test_Data,arange(len(Test_Data))):
plt.bar(i,j)
plt.legend(arange(len(Test_Data)))
plt.subplot2grid(Dimension, (1,1),colspan=2)
xticks = [r"%s (%i)" % (a,b) for a,b in zip(Test_Data,Test_Data)]
plt.xticks(arange(len(Test_Data)),xticks)
fig.autofmt_xdate()
plt.ylabel(r'$Some Latex Formula/Divided by some Latex Formula$',fontsize=14)
plt.plot(Test_Data)
#plt.setp(plt.xticks()[1],rotation=30)
plt.tight_layout()
#plt.show()
This is actually a feature of the autofmt_xdate method. From the documentation of the autofmt_xdate method:
Date ticklabels often overlap, so it is useful to rotate them and right align them. Also, a common use case is a number of subplots with shared xaxes where the x-axis is date data. The ticklabels are often long, and it helps to rotate them on the bottom subplot and turn them off on other subplots, as well as turn off xlabels.
If you want to rotate the xticklabels of the bottom right subplot only, use
plt.setp(plt.xticks()[1], rotation=30, ha='right') # ha is the same as horizontalalignment
This rotates the ticklabels 30 degrees and right aligns them (same result as when using autofmt_xdate) for the bottom right subplot, leaving the two other subplots unchanged.
I want to have some grid lines on a plot, but actually full-length lines are too much/distracting, even dashed light grey lines. I went and manually did some editing of the SVG output to get the effect I was looking for. Can this be done with matplotlib? I had a look at the pyplot api for grid, and the only thing I can see that might be able to get near it are the xdata and ydata Line2D kwargs.
This cannot be done through the basic API, because the grid lines are created using only two points. The grid lines would need a 'data' point at every tick mark for there to be a marker drawn. This is shown in the following example:
import matplotlib.pyplot as plt
ax = plt.subplot(111)
ax.grid(clip_on=False, marker='o', markersize=10)
plt.savefig('crosses.png')
plt.show()
This results in:
Notice how the 'o' markers are only at the beginning and the end of the Axes edges, because the grid lines only involve two points.
You could write a method to emulate what you want, creating the cross marks using a series of Artists, but it's quicker to just leverage the basic plotting capabilities to draw the cross pattern.
This is what I do in the following example:
import matplotlib.pyplot as plt
import numpy as np
NPOINTS=100
def set_grid_cross(ax, in_back=True):
xticks = ax.get_xticks()
yticks = ax.get_yticks()
xgrid, ygrid = np.meshgrid(xticks, yticks)
kywds = dict()
if in_back:
kywds['zorder'] = 0
grid_lines = ax.plot(xgrid, ygrid, 'k+', **kywds)
xvals = np.arange(NPOINTS)
yvals = np.random.random(NPOINTS) * NPOINTS
ax1 = plt.subplot(121)
ax2 = plt.subplot(122)
ax1.plot(xvals, yvals, linewidth=4)
ax1.plot(xvals, xvals, linewidth=7)
set_grid_cross(ax1)
ax2.plot(xvals, yvals, linewidth=4)
ax2.plot(xvals, xvals, linewidth=7)
set_grid_cross(ax2, in_back=False)
plt.savefig('gridpoints.png')
plt.show()
This results in the following figure:
As you can see, I take the tick marks in x and y to define a series of points where I want grid marks ('+'). I use meshgrid to take two 1D arrays and make 2 2D arrays corresponding to the double loop over each grid point. I plot this with the mark style as '+', and I'm done... almost. This plots the crosses on top, and I added an extra keyword to reorder the list of lines associated with the plot. I adjust the zorder of the grid marks if they are to be drawn behind everything.*****
The example shows the left subplot where by default the grid is placed in back, and the right subplot disables this option. You can notice the difference if you follow the green line in each plot.
If you are bothered by having grid crosses on the boarder, you can remove the first and last tick marks for both x and y before you define the grid in set_grid_cross, like so:
xticks = ax.get_xticks()[1:-1] #< notice the slicing
yticks = ax.get_yticks()[1:-1] #< notice the slicing
xgrid, ygrid = np.meshgrid(xticks, yticks)
I do this in the following example, using a larger, different marker to make my point:
***** Thanks to the answer by #fraxel for pointing this out.
You can draw on line segments at every intersection of the tickpoints. Its pretty easy to do, just grab the tick locations get_ticklocs() for both axis, then loop through all combinations, drawing short line segments using axhline and axvline, thus creating a cross hair at every intersection. I've set zorder=0 so the cross-hairs are drawn first, so that they are behind the plot data. Its easy to control the color/alpha and cross-hair size. Couple of slight 'gotchas'... do the plot before you get the tick locations.. and also the xmin and xmax parameters seem to require normalisation.
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot((0,2,3,5,5,5,6,7,8,6,6,4,3,32,7,99), 'r-',linewidth=4)
x_ticks = ax.xaxis.get_ticklocs()
y_ticks = ax.yaxis.get_ticklocs()
for yy in y_ticks[1:-1]:
for xx in x_ticks[1:-1]:
plt.axhline(y=yy, xmin=xx / max(x_ticks) - 0.02,
xmax=xx / max(x_ticks) + 0.02, color='gray', alpha=0.5, zorder=0)
plt.axvline(x=xx, ymin=yy / max(y_ticks) - 0.02,
ymax=yy / max(y_ticks) + 0.02, color='gray', alpha=0.5, zorder=0)
plt.show()