I'm trying to construct a simple function that takes a subplot instance (matplotlib.axes._subplots.AxesSubplot) and transforms its projection to another projection, for example, to one of the cartopy.crs.CRS projections.
The idea looks something like this
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def make_ax_map(ax, projection=ccrs.PlateCarree()):
# set ax projection to the specified projection
...
# other fancy formatting
ax2.coastlines()
...
# Create a grid of plots
fig, (ax1, ax2) = plt.subplots(ncols=2)
# the first subplot remains unchanged
ax1.plot(np.random.rand(10))
# the second one gets another projection
make_ax_map(ax2)
Of course, I can just use fig.add_subplot() function:
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax1.plot(np.random.rand(10))
ax2 = fig.add_subplot(122,projection=ccrs.PlateCarree())
ax2.coastlines()
but I was wondering if there is a proper matplotlib method to change a subplot axis projection after it was defined. Reading matplotlib API didn't help unfortunately.
You can't change the projection of an existing axes, the reason is given below. However the solution to your underlying problem is simply to use the subplot_kw argument to plt.subplots() described in the matplotlib documentation here. For example, if you wanted all your subplots to have the cartopy.crs.PlateCarree projection you could do
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
# Create a grid of plots
fig, (ax1, ax2) = plt.subplots(ncols=2, subplot_kw={'projection': ccrs.PlateCarree()})
Regarding the actual question, specifying a projection when you create an axes set determines the axes class you get, which is different for each projection type. For example
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
ax1 = plt.subplot(311)
ax2 = plt.subplot(312, projection='polar')
ax3 = plt.subplot(313, projection=ccrs.PlateCarree())
print(type(ax1))
print(type(ax2))
print(type(ax3))
This code will print the following
<class 'matplotlib.axes._subplots.AxesSubplot'>
<class 'matplotlib.axes._subplots.PolarAxesSubplot'>
<class 'cartopy.mpl.geoaxes.GeoAxesSubplot'>
Notice how each axes is actually an instance of a different class.
Assuming there are multiple axes being used for 2D plotting, like...
fig = matplotlib.pyplot.Figure()
axs = fig.subplots(3, 4) # prepare for multiple subplots
# (some plotting here)
axs[0,0].plot([1,2,3])
... one can simply destroy one of them and replace it with a new one having the 3D projection:
axs[2,3].remove()
ax = fig.add_subplot(3, 4, 12, projection='3d')
ax.plot_surface(...)
Just note that unlike rest of Python, the add_subplot uses row-column indexing starting from 1 (not from 0).
EDIT: Changed my typo about indexing.
following the answer to this question :
In python, how can I inherit and override a method on a class instance, assigning this new version to the same name as the old one?
I found a hack to change the projection of an axe after creating it which seems to work at least in the simple example below, but I have no idea if this solution is the best way
from matplotlib.axes import Axes
from matplotlib.projections import register_projection
class CustomAxe(Axes):
name = 'customaxe'
def plotko(self, x):
self.plot(x, 'ko')
self.set_title('CustomAxe')
register_projection(CustomAxe)
if __name__ == '__main__':
import matplotlib.pyplot as plt
fig = plt.figure()
## use this syntax to create a customaxe directly
# ax = fig.add_subplot(111, projection="customaxe")
## change the projection after creation
ax = plt.gca()
ax.__class__ = CustomAxe
ax.plotko(range(10))
plt.show()
You can use the following function, which removes the axis and generates the axis in the specified projection, similar to dominecf answer, with the advantage that the specific subplot parameters (row, col, and index) are retrieved automatically.
import matplotlib.pyplot as plt
def update_projection(ax, axi, projection='3d', fig=None):
if fig is None:
fig = plt.gcf()
rows, cols, start, stop = axi.get_subplotspec().get_geometry()
ax.flat[start].remove()
ax.flat[start] = fig.add_subplot(rows, cols, start+1, projection=projection)
and generate a plot with all available projections
import matplotlib.projections
import numpy as np
# test data
x = np.linspace(-np.pi, np.pi, 10)
# plot all projections available
projections = matplotlib.projections.get_projection_names()
fig, ax = plt.subplots(nrows=1, ncols=len(projections), figsize=[3.5*len(projections), 4], squeeze=False)
for i, pro_i in enumerate(projections):
update_projection(ax, ax.flat[i], pro_i)
ax.flat[i].set_title(pro_i)
try:
ax.flat[i].grid(True)
ax.flat[i].plot(x, x)
except Exception as a:
print(pro_i, a)
plt.tight_layout(pad=.5)
Related
I'm making some interactive plots and I would like to add a colorbar legend. I don't want the colorbar to be in its own axes, so I want to add it to the existing axes. I'm having difficulties doing this, as most of the example code I have found creates a new axes for the colorbar.
I have tried the following code using matplotlib.colorbar.ColorbarBase, which adds a colorbar to an existing axes, but it gives me strange results and I can't figure out how to specify attributes of the colorbar (for instance, where on the axes it is placed and what size it is)
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.cm import coolwarm
import numpy as np
x = np.random.uniform(1, 10, 10)
y = np.random.uniform(1, 10, 10)
v = np.random.uniform(1, 10, 10)
fig, ax = plt.subplots()
s = ax.scatter(x, y, c=v, cmap=coolwarm)
matplotlib.colorbar.ColorbarBase(ax=ax, cmap=coolwarm, values=sorted(v),
orientation="horizontal")
Using fig.colorbar instead ofmatplotlib.colorbar.ColorbarBase still doesn't give me quite what I want, and I still don't know how to adjust the attributes of the colorbar.
fig.colorbar(s, ax=ax, cax=ax)
Let's say I want to have the colorbar in the top left corner, stretching about halfway across the top of the plot. How would I go about doing that?
Am I better off writing a custom function for this, maybe using LineCollection?
This technique is usually used for multiple axis in a figure. In this context it is often required to have a colorbar that corresponds in size with the result from imshow. This can be achieved easily with the axes grid tool kit:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
data = np.arange(100, 0, -1).reshape(10, 10)
fig, ax = plt.subplots()
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='5%', pad=0.05)
im = ax.imshow(data, cmap='bone')
fig.colorbar(im, cax=cax, orientation='vertical')
plt.show()
The colorbar has to have its own axes. However, you can create an axes that overlaps with the previous one. Then use the cax kwarg to tell fig.colorbar to use the new axes.
For example:
import numpy as np
import matplotlib.pyplot as plt
data = np.arange(100, 0, -1).reshape(10, 10)
fig, ax = plt.subplots()
cax = fig.add_axes([0.27, 0.8, 0.5, 0.05])
im = ax.imshow(data, cmap='gist_earth')
fig.colorbar(im, cax=cax, orientation='horizontal')
plt.show()
Couldn't add this as a comment, but in case anyone is interested in using the accepted answer with subplots, the divider should be formed on specific axes object (rather than on the numpy.ndarray returned from plt.subplots)
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
data = np.arange(100, 0, -1).reshape(10, 10)
fig, ax = plt.subplots(ncols=2, nrows=2)
for row in ax:
for col in row:
im = col.imshow(data, cmap='bone')
divider = make_axes_locatable(col)
cax = divider.append_axes('right', size='5%', pad=0.05)
fig.colorbar(im, cax=cax, orientation='vertical')
plt.show()
What I want to achieve with Python 3.6 is something like this :
Obviously made in paint and missing some ticks on the xAxis. Is something like this possible? Essentially, can I control exactly where to plot a histogram (and with what orientation)?
I specifically want them to be on the same axes just like the figure above and not on separate axes or subplots.
fig = plt.figure()
ax2Handler = fig.gca()
ax2Handler.scatter(np.array(np.arange(0,len(xData),1)), xData)
ax2Handler.hist(xData,bins=60,orientation='horizontal',normed=True)
This and other approaches (of inverting the axes) gave me no results. xData is loaded from a panda dataframe.
# This also doesn't work as intended
fig = plt.figure()
axHistHandler = fig.gca()
axScatterHandler = fig.gca()
axHistHandler.invert_xaxis()
axHistHandler.hist(xData,orientation='horizontal')
axScatterHandler.scatter(np.array(np.arange(0,len(xData),1)), xData)
A. using two axes
There is simply no reason not to use two different axes. The plot from the question can easily be reproduced with two different axes:
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
xData = np.random.rand(1000)
fig,(ax,ax2)= plt.subplots(ncols=2, sharey=True)
fig.subplots_adjust(wspace=0)
ax2.scatter(np.linspace(0,1,len(xData)), xData, s=9)
ax.hist(xData,bins=60,orientation='horizontal',normed=True)
ax.invert_xaxis()
ax.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax2.tick_params(axis="y", left=0)
plt.show()
B. using a single axes
Just for the sake of answering the question: In order to plot both in the same axes, one can shift the bars by their length towards the left, effectively giving a mirrored histogram.
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
xData = np.random.rand(1000)
fig,ax= plt.subplots(ncols=1)
fig.subplots_adjust(wspace=0)
ax.scatter(np.linspace(0,1,len(xData)), xData, s=9)
xlim1 = ax.get_xlim()
_,__,bars = ax.hist(xData,bins=60,orientation='horizontal',normed=True)
for bar in bars:
bar.set_x(-bar.get_width())
xlim2 = ax.get_xlim()
ax.set_xlim(-xlim2[1],xlim1[1])
plt.show()
You might be interested in seaborn jointplots:
# Import and fake data
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
data = np.random.randn(2,1000)
# actual plot
jg = sns.jointplot(data[0], data[1], marginal_kws={"bins":100})
jg.ax_marg_x.set_visible(False) # remove the top axis
plt.subplots_adjust(top=1.15) # fill the empty space
produces this:
See more examples of bivariate distribution representations, available in Seaborn.
Most of the times, we import matplotlib.pyplot as follows, and use plt to plot graphs/diagrams.
import matplotlib.pyplot as plt
x = ...
plt.plot(x, sin(x))
...
plt.savefig('/path/to/sin_x.png')
plt.plot(x, cos(x))
...
plt.savefig('/path/to/cos_x.png')
My question is how to use plt as a local variable, like this?
plt1 = get_plot()
plt1.title('y = sin(x)')
...
plt1.plot(x, sin(x))
plt1.savefig('/path/to/sin_x.png')
plt2 = get_plot()
plt2.title('y = cos(x)')
...
plt2.plot(x, cos(x))
plt2.savefig('/path/to/cos_x.png')
plt1 == plt2 # false
Or, customizations on the first figure won't affect the second one without explicitly calling plt.clf()?
This is a good example of the benefits of the matplotlib object-oriented interface over the "state-machine" interface (see, for example, here and here).
The differences:
State-machine
import matplotlib.pyplot as plt
plt.plot(x, sin(x))
plt.savefig('plot.png')
OO interface
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, sin(x)
fig.savefig('plot.png')
When using the OO interface, you can create as many figure and Axes instances, and refer back to them without interfering with other instances.
For example:
import matplotlib.pyplot as plt
# Create first figure instance
fig1 = plt.figure()
# Add an Axes instance to the figure
ax1 = fig1.add_subplot(111)
# Set the title
ax1.set_title('y = sin(x)')
# Plot your data
ax1.plot(x, sin(x))
# Save this figure
fig1.savefig('/path/to/sin_x.png')
# Now create a second figure and axes. Here I use an altenative method,
# plt.subplots(1), to show how to create the figure and axes in one step.
fig2, ax2 = plt.subplots(1)
ax2.set_title('y = cos(x)')
ax2.plot(x, cos(x))
fig2.savefig('/path/to/cos_x.png')
# If you want to, you could modify `ax1` or `fig1` here, without affecting `fig2` and `ax2`
I'm using matplotlib to produce a 3d trisurf graph. I have everything working except that I would like to invert the y-axis, so that the origin is 0,0 not 0,100. I've looked through the matplotlib axes3d API and cannot figure out how to do this. Here is my code:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
# my data, xs=xaxis, ys=yaxis, zs=zaxis
mortar_xs = []
cycles_ys = []
score_zs = []
#... populate my data for the 3 arrays: mortar_xs, cycles_ys, score_zs
# plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(mortar_xs,cycles_ys,score_zs,cmap=cm.coolwarm)
ax.set_zlim(bottom=0.0,top=1.0)
ax.legend()
ax.set_xlabel("# Mortar")
ax.set_ylabel("# Goals")
ax.set_zlabel("# Score")
plt.show()
My graph produced is the following, but I need the '# Goals' or the y-axis inverted, so that the origin is 0,0 not 0,100. If possible, I would like to do this without changing my data.
tmdavison's comment is what I was looking for:
ax.set_ylim(0,100)
Or
ax.set_ylim(100,0)
The simplest method would be to use ax.invert_yaxis()
Is it possible to set the size/position of a matplotlib subplot after the axes are created? I know that I can do:
import matplotlib.pyplot as plt
ax = plt.subplot(111)
ax.change_geometry(3,1,1)
to put the axes on the top row of three. But I want the axes to span the first two rows. I have tried this:
import matplotlib.gridspec as gridspec
ax = plt.subplot(111)
gs = gridspec.GridSpec(3,1)
ax.set_subplotspec(gs[0:2])
but the axes still fill the whole window.
Update for clarity
I want to change the position of an existing axes instance rather than set it when it is created. This is because the extent of the axes will be modified each time I add data (plotting data on a map using cartopy). The map may turn out tall and narrow, or short and wide (or something in between). So the decision on the grid layout will happen after the plotting function.
Thanks to Molly pointing me in the right direction, I have a solution:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
ax = fig.add_subplot(111)
gs = gridspec.GridSpec(3,1)
ax.set_position(gs[0:2].get_position(fig))
ax.set_subplotspec(gs[0:2]) # only necessary if using tight_layout()
fig.add_subplot(gs[2])
fig.tight_layout() # not strictly part of the question
plt.show()
You can create a figure with one subplot that spans two rows and one subplot that spans one row using the rowspan argument to subplot2grid:
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = plt.subplot2grid((3,1), (0,0), rowspan=2)
ax2 = plt.subplot2grid((3,1), (2,0))
plt.show()
If you want to change the subplot size and position after it's been created you can use the set_position method.
ax1.set_position([0.1,0.1, 0.5, 0.5])
Bu you don't need this to create the figure you described.
You can avoid ax.set_position() by using fig.tight_layout() instead which recalculates the new gridspec:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
# create the first axes without knowing of further subplot creation
fig, ax = plt.subplots()
ax.plot(range(5), 'o-')
# now update the existing gridspec ...
gs = gridspec.GridSpec(3, 1)
ax.set_subplotspec(gs[0:2])
# ... and recalculate the positions
fig.tight_layout()
# add a new subplot
fig.add_subplot(gs[2])
fig.tight_layout()
plt.show()