Box gets truncated using zoomed_inset_axes - python

I want to use zoomed_inset_axes but the box gets truncated as soon as it passes the frame of the main figure. I could not get any better with
f.tight_layout()
f.subplots_adjust(bottom=...)
'figure.autolayout': True
not even with hidden (white) text using f.text somewhere outside.
Does anyone know how to do this properly?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes, mark_inset
X = np.random.normal(.5,10,1000)
Y = np.random.normal(.5,10,1000)
f, ax = plt.subplots(1, figsize=(10,6))
ax.scatter(X,Y)
# # Setup zoom window
axins = zoomed_inset_axes(ax, 2, loc="center", bbox_to_anchor=(0,0))
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
axins.set_xlim([-15,0])
axins.set_ylim([-12,-3])
# # Plot zoom window
axins.scatter(X,Y)
f.tight_layout()
f.savefig('test.png', dpi=70)

Using subplots_adjust goes in the right direction. Don't use tight_layout afterwards as this would overwrite any settings done via subplots_adjust.
You may decide to opt for something like
fig.subplots_adjust(left=0.2, bottom=0.2)
to make some space for the inset in the lower left corner of the figure.
Then you need to position the inset. Since here you're working in the lower left corner, this is relatively easy. The loc parameter needs to be set to the lower left corner and you may stick to the bbox_to_anchor=(0,0) position. Then just add some padding via borderpad=3 (in units of font size), such that the inset axes' labels are still visible,
zoomed_inset_axes(ax, 2, loc='lower left', bbox_to_anchor=(0,0), borderpad=3)
Complete code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes, mark_inset
X = np.random.normal(.5,10,1000)
Y = np.random.normal(.5,10,1000)
fig, ax = plt.subplots(1, figsize=(10,6))
fig.subplots_adjust(left=0.2, bottom=0.2)
ax.scatter(X,Y)
# # Setup zoom window
axins = zoomed_inset_axes(ax, 2, loc='lower left', bbox_to_anchor=(0,0), borderpad=3)
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
axins.set_xlim([-15,0])
axins.set_ylim([-12,-3])
# # Plot zoom window
axins.scatter(X,Y)
#fig.savefig('test.png', dpi=70)
plt.show()
In general, you have a lot of options to position and size the inset. I recently created a new example on the matplotlib page: Inset Locator Demo, which is currently only available in the devdocs, to show the interplay between the different parameters (in that case for inset_axes - but it totally applies to zoomed_inset_axes as well).

Related

How to place clip art behind plotted data in matplotlib

I wish to plot things on top of an image I insert into my figure. I'm not sure how to do that. Here is a simple example where I do my best to place scattered points in the foreground of mario: I specify the order with zorder and call the scatter command last. However, mario is in the foreground and the scattered points are in the background.
How can I make the scattered points appear in front of Mario?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
# load up mario
vortexRF = plt.imread('./mario.png')
imagebox = OffsetImage(vortexRF, zoom=0.08, zorder=1)
# initiate plot
fig, ax = plt.subplots()
# place mario in plot
ab = AnnotationBbox(imagebox, (0, 0), frameon=False)
cbar_ax = fig.add_axes([0.7, .42, 0.1, 0.1])
cbar_ax.add_artist(ab)
cbar_ax.axis('off')
# add scatter plot
NPoints = 1000
ax.scatter(np.random.random(NPoints), np.random.normal(0, 1, NPoints), s=3, c='purple', zorder=2)
# comment that mario should be in the background
ax.set_title("we want the purple dots to be in front of Mario")
# save figure. Mario is behind the scattered points :(
plt.savefig('marioExample')
cbar_ax = fig.add_axes(..., zorder=-1) arranges the z-order between axes. And ax.set_facecolor('none') makes the background of the scatter plot fully transparent (the default is opaque white, hiding everything behind it).
Note that everything that uses an ax is combined into one layer. An ax is either completely in front or completely to the back of another ax. Inside each ax, the elements can have their own z-orders.
To avoid copy-right issues, and to create a standalone example, the code below uses Ada Lovelace's image that comes with matplotlib.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import matplotlib.cbook as cbook
np.random.seed(1234)
# load up Ada's image
with cbook.get_sample_data('ada.png') as image_file:
vortexRF = plt.imread(image_file)
imagebox = OffsetImage(vortexRF, zoom=0.2)
# initiate plot
fig, ax = plt.subplots()
# place Ada in plot
ab = AnnotationBbox(imagebox, (0, 0), frameon=False)
cbar_ax = fig.add_axes([0.6, .42, 0.3, 0.3], zorder=-1)
cbar_ax.add_artist(ab)
cbar_ax.axis('off')
# add scatter plot
ax.scatter(np.random.normal(np.tile(np.random.uniform(0, 1, 5), 1000), .1),
np.random.normal(np.tile(np.random.uniform(0, 1, 5), 1000), .1),
c=np.tile(['fuchsia', 'gold', 'coral', 'deepskyblue', 'chartreuse'], 1000),
s=3, alpha=0.2)
# comment that Ada should be in the background
ax.set_title("we want the dots to be in front of Ada")
# make the background of the scatter plot fully transparent
ax.set_facecolor('none')
plt.show()
PS: Note that you can also add the image on the same ax as the scatter using imshow with an extent. The extent is default expressed in the same data coordinates as the plot in the order (x0, x1, y0, y1). This makes things somewhat simpler. The method using fig.add_axes, however, nicely keeps the original aspect ratio of the image.
ax.imshow(vortexRF, extent=[0.0, 0.4, 0.7, 1.1])

How to change the bounding box linewidth of a plot in python?

I am getting trouble in finding example of changing linewidth of the boundary box of a plot in Python plot.
For example, fig=plt.figure(figsize=(4.5, 4)) this command gives the dimension of the box in which python will plot the graph. But how to increase the linewidth of this boundary?
There are few options to do this depending on which boundary you are talking about.
Window. fig=plt.figure(figsize=(4.5, 4)) command embeds figure in window which is controlled by system. It's quite hard to find any solution here unless it's a hack. The only accessible option is to use root.overrideredirect(True) here:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(4.5, 4))
ax = fig.gca()
mng = plt.get_current_fig_manager()
mng.window.overrideredirect(True)
plt.show()
Figure.
Another option is to change border of matplotlib.pyplot.figure:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(4.5, 4), edgecolor='blue', linewidth=3)
ax = fig.gca()
plt.show()
Axis.
If the option is to change width of matplotlib.pyplot.axis:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(4.5, 4))
ax = fig.gca()
for axis in ['top','bottom','left','right']:
ax.spines[axis].set_linewidth(0.5)
plt.show()

matplotlib axesgrid - additional colorbar?

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)

modifying colorbar in matplotlib and seaborn

I'm trying to save an image I generated using seaborn. The image is 4x4 confusion matrix ('confmat' np.array). I learned that when I save the image in vector format, certain viewers have issues resulting in white lines on colorbar, quoting from matplotlib reference:
It is known that some vector graphics viewer (svg and pdf) renders
white gaps between segments of the colorbar. This is due to bugs in
the viewers not matplotlib. As a workaround the colorbar can be
rendered with overlapping segments:
cbar = colorbar()
cbar.solids.set_edgecolor("face")
draw()
However, I have trouble doing what is suggested.
Here is what I did:
import seaborn as sns
import matplotlib.pyplot as plt
cmap=plt.cm.Blues
fig, ax = plt.subplots()
ax = sns.heatmap(confmat, annot=True, cmap=cmap)
ax.set_title('title')
ax.tick_params(
axis='both', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom='off', # ticks along the bottom edge are off
top='off', # ticks along the top edge are off
labelbottom='off', # labels along the bottom edge are off
labelleft='off',
right='off')
fig.savefig('confusion_matrix.svg', format='svg')
I tried to get colorbar using
cbar = ax.colorbar()
But get an error AttributeError: 'AxesSubplot' object has no attribute 'colorbar'.
I searched for solution and found a few questions here that suggest using plt.imshow() to get the colorbar object, but I'm completely confused about what I'm doing by now.
Can someone suggest, and if possible, explain why, the solution for implementing what matplotlib documentation has offered for colorbar?
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
x = np.random.randn(10, 10)
f, ax = plt.subplots()
sns.heatmap(x)
cbar_ax = f.axes[-1]
cbar_solids = cbar_ax.collections[0]
cbar_solids.set_edgecolor("face")
f.savefig("heatmap.svg")
Changing a colorbar with cb.solid.set_edgecolor("face") as suggested in the matplotlib docs appears to be a bit of a hack to ensure there are no white lines between elements on the colorbar. I think seaborn is designed assuming you should be able to do everything you need by passing kwargs (cbar_kws in heatmap). For example, you can pass cb_kwargs to the sns.heatmap function cbar_kws={"drawedges": "False"} but unfortunately this doesn't fix the problem.
As the Seaborn Heatmap only returns an axis handle on which the heatplot and the colorbar are plotted, you don't have direct access to the mappable object, cbar in the source code. As a result you can't apply this hack.
One solution is to just plot this using pcolormesh and colorbar. I think seaborn actually redefines matplotlib styles so should look the same,
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
cmap=plt.cm.Blues
fig, ax = plt.subplots()
confmat = np.random.rand(4, 4)
cb = ax.pcolormesh(confmat, cmap=cmap)
ax.set_title('title')
ax.tick_params(
axis='both', # changes apply to the x-axis
which='both', # both major and minor ticks are affected
bottom='off', # ticks along the bottom edge are off
top='off', # ticks along the top edge are off
labelbottom='off', # labels along the bottom edge are off
labelleft='off',
right='off')
cbar = plt.colorbar(cb)
cbar.solids.set_edgecolor("face")
plt.draw()
fig.savefig('confusion_matrix.svg', format='svg')
The result for me looks to be rid of the white lines when you zoom in.

How to change color bar to align with main plot in Matplotlib?

When plotting matrix with imshow in Matplotlib, how to change colorbar legend bar size, location, font and other parameters?
Here I created an example code
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
def plot_matrix(mat, title='example', cmap=plt.cm.Blues):
plt.imshow(mat, interpolation='nearest', cmap=cmap)
plt.grid(False)
plt.title(title)
plt.colorbar()
data = np.random.random((20, 20))
plt.figure(figsize=(8,8))
plt.tick_params(axis='both', which='major', labelsize=12)
plot_matrix(data)
In a real use case, I got complex labels and the legend bar becomes much higher then the matrix itself. I want to change the legend bar to make the plot more efficiently use the space.
I found a documentation for the matplotlib.pyplot.colorbar, however have not figure out a good way to set the size, location and font size for the color legend bar.
imshow enforces a 1:1 aspect (by default, but you can change it with aspect parameter), which makes things a little trickier. To always get consistent result, I might suggest manually specify the size of axes:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
def plot_matrix(mat, figsize, title='example', cmap=plt.cm.Blues):
f = plt.figure(figsize=figsize)
ax = plt.axes([0, 0.05, 0.9, 0.9 ]) #left, bottom, width, height
#note that we are forcing width:height=1:1 here,
#as 0.9*8 : 0.9*8 = 1:1, the figure size is (8,8)
#if the figure size changes, the width:height ratio here also need to be changed
im = ax.imshow(mat, interpolation='nearest', cmap=cmap)
ax.grid(False)
ax.set_title(title)
cax = plt.axes([0.95, 0.05, 0.05,0.9 ])
plt.colorbar(mappable=im, cax=cax)
return ax, cax
data = np.random.random((20, 20))
ax, cax = plot_matrix(data, (8,8))
Now you have the axis where the colorbar is plotted in, cax. You can do a lot of thing with that, say, rotate the labels, using plt.setp(cax.get_yticklabels(), rotation=45)

Categories