I want to add another colorbar to a plot where I use AxesGrid toolkit. For example, I add a colorbar axes using ImageGrid on the left, and then I add another one on the right manually. Here is a simple example:
f = plt.figure(1)
grid = ImageGrid(f, 111, # similar to subplot(111)
nrows_ncols=(2, 2),
axes_pad=0.01,
add_all=True,
cbar_location="left",
label_mode='L',
cbar_mode="edge",
cbar_size="3%",
cbar_pad="2%",
)
for i in range(3):
m = grid[i].matshow(np.arange(100).reshape((10, 10)))
plt.colorbar(m, grid.cbar_axes[0])
m = grid[3].matshow(np.arange(100).reshape((10, 10)), cmap='plasma')
plt.colorbar(m, shrink=0.5, anchor=(0, 0))
plt.show()
How do I make the new colorbar match the position of one of the subplots in the grid exactly? I at least managed to fix the size and y-position using shrink and anchor... But it also gets a bit complicated if I try to account for the padding between subplots, and if they are rectangular rather than square...
One option is to manually place the colorbar axes according to the position of one of the axes. To this end one first needs to draw the canvas, such that the positions are known. One can then create a new axes according to coordinates of image plot. This new axes will serve as the colorbar axes.
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import ImageGrid
fig = plt.figure(1)
grid = ImageGrid(fig, 111, # similar to subplot(111)
nrows_ncols=(2, 2),
axes_pad=0.01,
add_all=True,
cbar_location="left",
label_mode='L',
cbar_mode="edge",
cbar_size="3%",
cbar_pad="2%",
)
for i in range(3):
m = grid[i].matshow(np.arange(100).reshape((10, 10)))
plt.colorbar(m, grid.cbar_axes[0])
m = grid[3].matshow(np.arange(100).reshape((10, 10)), cmap='plasma')
# first draw the figure, such that the axes are positionned
fig.canvas.draw()
#create new axes according to coordinates of image plot
trans = fig.transFigure.inverted()
g3 =grid[3].bbox.transformed(trans)
pos = [g3.x1 + g3.width*0.02, g3.y0, g3.width*0.03, g3.height ]
cax = fig.add_axes(pos) #l,b,w,h
# add colorbar to new axes
plt.colorbar(m, cax=cax)
plt.show()
This method depends on the position of the axes in the figure, once that changes, e.g. because the figure is rezised, unforseable things might happen.
A different method, which does not rely on the drawn coordinates, is to (mis)use inset axes and place the inset outside the axes. In this way the coordinates by which the inset is located are axes coordinates, so the colorbar will change its position according to the axes.
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
cax = inset_axes(grid[3], "3%", "100%", loc=3, bbox_to_anchor=(1.02,0,1,1),
bbox_transform=grid[3].transAxes, borderpad=0.0)
plt.colorbar(m, cax=cax)
Related
I use the following code to add a colorbar at the top left corner of each subplot.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
# Create figure
fig = plt.figure(figsize=(5, 2))
# Specify geometry of the grid for subplots
gs0 = gridspec.GridSpec(1, 3, wspace=0.7)
# Data
a = np.arange(3*5).reshape(3,5)
for ax_i in range(3):
# Create axes
ax = plt.subplot(gs0[ax_i])
# Plot data
plot_pcolor = plt.pcolormesh(a)
# ******** Plot a nested colorbar inside the plot ********
# Define position of the desired colorbar in axes coordinate
# [(lower left x, lower left y), (upper right x, upper right y)]
ax_coord = [(0.05, 0.5), (0.2, 0.95)]
# Transform the two points from axes coordinates to display coordinates
tr1 = ax.transAxes.transform(ax_coord)
# Create an inverse transversion from display to figure coordinates
inv = fig.transFigure.inverted()
tr2 = inv.transform(tr1)
# Position in figure coordinates [left, bottom, width, height]
datco = [tr2[0,0], tr2[0,1], tr2[1,0]-tr2[0,0], tr2[1,1]-tr2[0,1]]
# Create colorbar axes
cbar_ax = fig.add_axes(datco)
# Plot colorbar
cbar = plt.colorbar(plot_pcolor, cax=cbar_ax)
# ********************************************************
if False:
plt.subplots_adjust(left=0.15, bottom=0.2, right=0.95, top=0.8)
plt.savefig('test.png', dpi=500)
which gives the following plot:
However, if I use the subplots_adjust() function (by replacing False to True in the code above), the colorbars do not move properly:
Do you know how I can handle it?
Using the inset_axes() function from the mpl_toolkits module solves the problem. It is also possible to simply use ax.inset_axes().
Here is the new code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
# Create figure
fig = plt.figure(figsize=(5, 2))
# Specify geometry of the grid for subplots
gs0 = gridspec.GridSpec(1, 3, wspace=0.7)
# Data
a = np.arange(3*5).reshape(3,5)
for ax_i in range(3):
# Create axes
ax = plt.subplot(gs0[ax_i])
# Plot data
plot_pcolor = plt.pcolormesh(a)
axins = inset_axes(ax, width="5%", height="50%", loc='upper left')
# Plot colorbar
cbar = plt.colorbar(plot_pcolor, cax=axins)
# ********************************************************
if True:
plt.subplots_adjust(left=0.15, bottom=0.2, right=0.95, top=0.8)
plt.savefig('test.png', dpi=500)
Here is the result:
I need to achieve the following effect using matplotlib:
As you can see it's a combination of plots in different quadrants.
I do know how to generate each quadrant individually. For example, for the 'x invert' quadrant's plot I would simply use:
plt.plot(x, y)
plt.gca().invert_yaxis()
plt.show()
to draw the plot. It properly inverts the x axis. However, it would only generate top-left quadrant's plot for me.
How can I generate a combination of plots described in the above picture? Each quadrant has its own plot with different inverted axises.
My best idea was to merge it in some tool like Paint.
I don't have enough reputation to add a comment to add on to ImportanceOfBeingErnest's comment, but when you create the 4 subplots you'll want to remove the space between the plots as well as have shared axes (and clean up overlapping ticks).
There are various ways to do subplots, but I prefer gridspec. You can create a 2x2 grid with gridspec and do all of this, here's an example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure()
# lines to plot
x = np.arange(0, 10)
y = np.arange(0, 10)
# gridspec for 2 rows, 2 cols with no space between
grid = gridspec.GridSpec(nrows=2, ncols=2, hspace=0, wspace=0, figure=fig)
x_y = fig.add_subplot(grid[0, 1], zorder=3)
x_y.plot(x, y)
x_y.margins(0)
invx_y = fig.add_subplot(grid[0, 0], zorder=2, sharey=x_y)
invx_y.plot(-x, y)
invx_y.margins(0)
invx_invy = fig.add_subplot(grid[1, 0], zorder=0, sharex=invx_y)
invx_invy.plot(-x, -y)
invx_invy.margins(0)
x_invy = fig.add_subplot(grid[1, 1], zorder=1, sharey=invx_invy, sharex=x_y)
x_invy.plot(x, -y)
x_invy.margins(0)
# clean up overlapping ticks
invx_y.tick_params(labelleft=False, length=0)
invx_invy.tick_params(labelleft=False, labelbottom=False, length=0)
x_invy.tick_params(labelbottom=False, length=0)
x_y.set_xticks(x_y.get_xticks()[1:-1])
invx_y.set_xticks(invx_y.get_xticks()[1:-1])
x_invy.set_yticks(x_invy.get_yticks()[1:-1])
plt.show()
This yields the following figure:
I have an axis on which I plot some data and I have another twin axis which I use to draw grid lines at specific tick positions (other than the ticks of the original axis):
import matplotlib.pyplot as plt
import numpy as np
f, ax = plt.subplots()
ax.set_xlim([0, 1])
ax2 = ax.twiny()
ax2.set_xlim([0, 1])
ax2.set_xticks(np.linspace(0, 1, 11))
ax2.xaxis.grid()
x = np.linspace(0, 1, 100)
ax.plot(x, np.sin(x), label='sin(x)')
ax.legend()
plt.show()
Now this has the undesirable effect that the grid lines of the twin axes are drawn on top of the legend and line plot of the original axis. As far as I understand this is because matplotlib draws the axes in the order they were created and for that reason zorder won't help (because zorder only specifies the order among the artists of a single axis).
I know I could plot the data on the twin axis ax2 instead (followed by ax2.legend()) but I'd prefer to have the setup as is. Instead changing the order in which the two axes are drawn should solve the problem, but I couldn't figure out how to do that. There is f.get_axes() which seems to return the axes in the order they were created but no option to revert it.
Or maybe there exists even another solution?
You can change the zorder of the axes themselves.
ax.set_zorder(2)
ax2.set_zorder(1)
ax.patch.set_visible(False)
I have a matplotlib bar chart, which bars are colored according to some rules through a colormap. I need a colorbar on the right of the main axes, so I added a new axes with
fig, (ax, ax_cbar) = plt.subplots(1,2)
and managed to draw my color bar in the ax_bar axes, while I have my data displayed in the ax axes. Now I need to reduce the width of the ax_bar, because it looks like this:
How can I do?
Using subplots will always divide your figure equally. You can manually divide up your figure in a number of ways. My preferred method is using subplot2grid.
In this example, we are setting the figure to have 1 row and 10 columns. We then set ax to be the start at row,column = (0,0) and have a width of 9 columns. Then set ax_cbar to start at (0,9) and has by default a width of 1 column.
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8,6))
num_columns = 10
ax = plt.subplot2grid((1,num_columns), (0,0), colspan=num_columns-1)
ax_cbar = plt.subplot2grid((1,num_columns), (0,num_columns-1))
The ususal way to add a colorbar is by simply putting it next to the axes:
fig.colorbar(sm)
where fig is the figure and sm is the scalar mappable to which the colormap refers. In the case of the bars, you need to create this ScalarMappable yourself. Apart from that there is no need for complex creation of multiple axes.
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
fig , ax = plt.subplots()
x = [0,1,2,3]
y = np.array([34,40,38,50])*1e3
norm = matplotlib.colors.Normalize(30e3, 60e3)
ax.bar(x,y, color=plt.cm.plasma_r(norm(y)) )
ax.axhline(4.2e4, color="gray")
ax.text(0.02, 4.2e4, "42000", va='center', ha="left", bbox=dict(facecolor="w",alpha=1),
transform=ax.get_yaxis_transform())
sm = plt.cm.ScalarMappable(cmap=plt.cm.plasma_r, norm=norm)
sm.set_array([])
fig.colorbar(sm)
plt.show()
If you do want to create a special axes for the colorbar yourself, the easiest method would be to set the width already inside the call to subplots:
fig , (ax, cax) = plt.subplots(ncols=2, gridspec_kw={"width_ratios" : [10,1]})
and later put the colorbar to the cax axes,
fig.colorbar(sm, cax=cax)
Note that the following questions have been asked for this homework assignment already:
Point picker event_handler drawing line and displaying coordinates in matplotlib
Matplotlib's widget to select y-axis value and change barplot
Display y axis value horizontal line drawn In bar chart
How to change colors automatically once a parameter is changed
Interactively Re-color Bars in Matplotlib Bar Chart using Confidence Intervals
How to use python and matplotlib to plot a picture like following?
I know how to plot the 2D heat map, but it frustrated me a lot with plotting the bar on top of the heat map, and the bar between the color bar and heat map.
How to add those two bars on the picture, and show the number in x axis or y axis belongs to which group?
Thanks very much for all the responses.
A systematic and straightforward approach, although a bit more cumbersome at the start, is to use matplotlib.gridspec.GridSpec.
First set up the grid:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig = plt.figure()
gs = GridSpec(2, 3, width_ratios=[10, 1, 1], height_ratios=[1, 10])
This gives us a grid of 2 rows and 3 columns, where the lower left axis will be 10x10 and the other axes will be either 10x1 or 1x10 in relative sizes. These ratios can be tweaked to your liking. Note that the top center/right axes will be empty.
big_ax = fig.add_subplot(gs[1,0]) # bottom left
top_ax = fig.add_subplot(gs[0,0]) # top left
right_ax = fig.add_subplot(gs[1,1]) # bottom center
cbar_ax = fig.add_subplot(gs[1,2]) # bottom right
I will use a generic genome picture I found via google for the top and right image:
and will generate a random heatmap. I use imshow(aspect='auto') so that the image objects and heatmap take up the full space of their respective axes (otherwise they will override the height/width ratios set by gridspec).
im = plt.imread('/path/to/image.png')
# Plot your heatmap on big_ax and colorbar on cbar_ax
heatmap = big_ax.imshow(np.random.rand(10, 10), aspect='auto', origin='lower')
cbar = fig.colorbar(heatmap, cax=cbar_ax)
# Show your images on top_ax and right_ax
top_ax.imshow(im, aspect='auto')
# need to rotate my image.
# you may not have to if you have two different images
from scipy import ndimage
right_ax.imshow(ndimage.rotate(im, 90), aspect='auto')
# Clean up the image axes (remove ticks, etc.)
right_ax.set_axis_off()
top_ax.set_axis_off()
# remove spacing between axes
fig.subplots_adjust(wspace=0.05, hspace=0.05)
It's not super glamorous (especially with the default jet colormap), but you could easily use this to reproduce the figure your OP.
Edit: So if you want to generate that genome-like plot on the top and right, you could try something like this for the top bar:
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
# draw the black line
top_ax.axhline(0, color='k', zorder=-1)
# box x-coords and text labels
boxes = zip(np.arange(0.1, 1, 0.2), np.arange(0.2, 1, 0.2))
box_text = ('A1', 'B1', 'B2', 'A2')
# color indicators for boxes
colors = (0, 1, 1, 0)
# construct Rects
patches = [Rectangle(xy=(x0, -1), width=(x1-x0), height=2) for x0,x1 in boxes]
p = PatchCollection(patches, cmap='jet')
# this maps the colors in [0,1] to the cmap above
p.set_array(np.array(colors))
top_ax.add_collection(p)
# add text
[top_ax.text((x0+x1)/2., 1.2, text, ha='center')
for (x0,x1), text in zip(boxes, box_text)]
# adjust ylims
top_ax.set_ylim(-2, 2)
For something the right axis, you can do the same thing but use axvline and swap the x-coords for y-coords.
right_ax.axvline(0, color='k', zorder=-1)
patches = [Rectangle(xy=(-1, y0), width=2, height=(y1-y0)) for y0, y1 in boxes]
p = PatchCollection(patches, cmap='jet')
p.set_array(np.array(colors))
right_ax.add_collection(p)
[right_ax.text(1.2, (y0+y1)/2., text, va='center')
for (y0, y1), text in zip(boxes, box_text)]
right_ax.set_xlim(-2,2)
These modifications lead to something like: