Current version of matplotlib do not allow box-forced anymore, how should I do the same thing as the answer?
I am using matplotlib 3.1.0. After I ploted another set of data on the same plot with twinx() function, I want to change the aspect ratio of the actual plot area to 1.
Normally I do this and it works for non-twinx axis
ratio = 1
xleft, xright = ax.get_xlim()
ybottom, ytop = ax.get_ylim()
ax.set_aspect(abs((xright - xleft) / (ybottom - ytop)) * ratio)
For twinx axis, the above code do not work, but will not raise any error either.
Then I found an answer here
The code basically used the same method to set aspect ratio to 1, only with box-forced option.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 1.6, 50) + 50.0
fig, ax = plt.subplots()
ax2 = ax.twinx()
XLIM = [50.0, 51.6]
YLIM = [0.0, 1.1, 0.0, 11.0]
ax.plot(x, np.sin(x - 50.0), 'b')
ax2.plot(x, np.cos(x - 50.0) * 10., 'r')
# set aspect to 1
ax.set(adjustable='box-forced',
xlim=XLIM, ylim=YLIM[:2],
xticks=np.arange(XLIM[0], XLIM[1], 0.2),
yticks=np.arange(YLIM[0], YLIM[1] + 0.1, 0.1)[:-1],
aspect=(XLIM[1] - XLIM[0]) / (YLIM[1] - YLIM[0]))
ax2.set(adjustable='box-forced',
ylim=YLIM[2:],
yticks=np.arange(YLIM[2], YLIM[3] + 1.0, 1.0),
aspect=(XLIM[1] - XLIM[0]) / (YLIM[3] - YLIM[2]))
ax.grid(True, which='major', linestyle='solid')
plt.show()
This code in my python don't work, raises
ValueError: 'box-forced' is not a valid value for adjustable; supported values are 'box', 'datalim'
And if I change that to 'box', it gives
RuntimeError: Adjustable 'box' is not allowed in a twinned Axes. Use 'datalim' instead.
I am not sure from when the box-forced was removed.
Now how should we set the aspect ratio in a 'box' manner?
Thanks!
For reference: matplotlib.axes.Axes.set_adjustable
As I just commented on a respective matplotlib issue,
"aspect" in matplotlib always refers to the data, not the axes box. Therefore setting the aspect for twinned or shared axes and letting the box be adjustable actually only makes sense when the scales are the same - or differ by an offset (as opposed to any other linear or nonlinear function). Matplotlib does not perform any check on this, so it disallows for adjustable='box' in such case.
It seems to me that using aspect here is merely a workaround for getting a fixed ratio for the axes box. Matplotlib does not provide any clear codepath for that as of now, but one could e.g. force the axes box into a square space by adjusting the subplot parameters
import numpy as np
import matplotlib.pyplot as plt
def squarify(fig):
w, h = fig.get_size_inches()
if w > h:
t = fig.subplotpars.top
b = fig.subplotpars.bottom
axs = h*(t-b)
l = (1.-axs/w)/2
fig.subplots_adjust(left=l, right=1-l)
else:
t = fig.subplotpars.right
b = fig.subplotpars.left
axs = w*(t-b)
l = (1.-axs/h)/2
fig.subplots_adjust(bottom=l, top=1-l)
x = np.linspace(0,1.6,50) + 50.0
fig, ax = plt.subplots()
ax2 = ax.twinx()
ax.set(xlim = [50.0, 51.6], ylim = [0.0, 1.1])
ax2.set(ylim = [0.0, 11.0])
ax.plot(x,np.sin(x-50.0),'b')
ax2.plot(x,np.cos(x-50.0)*10.,'r')
ax.grid(True, which='major',linestyle='solid')
squarify(fig)
fig.canvas.mpl_connect("resize_event", lambda evt: squarify(fig))
plt.show()
Also see this answer for more than one subplot.
If you want to use mpl_toolkits and make your hands dirty, this answer would be a good read.
Thanks to #ImportanceOfBeingErnest, but to make this work in several subplots, I found another way inspired by your answer:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import Divider, Size
from mpl_toolkits.axes_grid1.axes_divider import AxesDivider
def make_patch_spines_invisible(ax):
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
def demo_fixed_size_axes():
fig, axs = plt.subplots(1, 2, figsize=(12, 9))
axs[0].plot([1, 2, 3])
axs[1].plot([1, 2, 3.5])
ax3 = axs[1].twinx()
ax3.plot([1, 2, 3], [1, 25, 30])
axs[1].spines['right'].set_visible(False)
make_patch_spines_invisible(ax4Alt)
ax4Alt.spines['right'].set_visible(True)
for ax in fig.get_axes():
figPos = AxesDivider(ax).get_position()
h = [Size.Fixed(4)] # has to be fixed
v = h
divider = Divider(fig, figPos, h, v, aspect=False)
ax.set_axes_locator(divider.new_locator(nx=0, ny=0))
if __name__ == "__main__":
demo_fixed_size_axes()
plt.show()
The disadvantage is that one has to decide which size to use in inches.
I do not fully understand my code though...
Related
The ProPlot Python package adds additional features to the Matplotlib library, including colourmap manipulations. One feature that is particularly attractive to me is the ability to rotate/shift colourmaps. To give you an example:
import proplot as pplot
import matplotlib.pyplot as plt
import numpy as np
state = np.random.RandomState(51423)
data = state.rand(30, 30).cumsum(axis=1)
fig, axes = plt.subplots(ncols=3, figsize=(9, 4))
fig.patch.set_facecolor("white")
axes[0].pcolormesh(data, cmap="Blues")
axes[0].set_title("Blues")
axes[1].pcolormesh(data, cmap="Blues_r")
axes[1].set_title("Reversed Blues")
axes[2].pcolormesh(data, cmap="Blues_s")
axes[2].set_title("Rotated Blues")
plt.tight_layout()
plt.show()
In the third column, you see the 180° rotated version of Blues. Currently ProPlot suffers from a bug that doesn't allow the user to revert the plotting style to Matplotlib's default style, so I was wondering if there was an easy way to rotate a colourmap in Matplotlib without resorting to ProPlot. I always found cmap manipulations in Matplotlib a bit arcane, so any help would be much appreciated.
If what you are trying to do is shift the colormaps, this can be done (relatively) easily:
def shift_cmap(cmap, frac):
"""Shifts a colormap by a certain fraction.
Keyword arguments:
cmap -- the colormap to be shifted. Can be a colormap name or a Colormap object
frac -- the fraction of the colorbar by which to shift (must be between 0 and 1)
"""
N=256
if isinstance(cmap, str):
cmap = plt.get_cmap(cmap)
n = cmap.name
x = np.linspace(0,1,N)
out = np.roll(x, int(N*frac))
new_cmap = matplotlib.colors.LinearSegmentedColormap.from_list(f'{n}_s', cmap(out))
return new_cmap
demonstration:
x = np.linspace(0,1,100)
x = np.vstack([x,x])
cmap1 = plt.get_cmap('Blues')
cmap2 = shift_cmap(cmap1, 0.25)
fig, (ax1, ax2) = plt.subplots(2,1)
ax1.imshow(x, aspect='auto', cmap=cmap1)
ax2.imshow(x, aspect='auto', cmap=cmap2)
To reverse a ListedColormap, there is a built-in reversed() but for the intended rotation, we have to create our own function.
#fake data generation
import numpy as np
np.random.seed(123)
#numpy array containing x, y, and color
arr = np.random.random(30).reshape(3, 10)
from matplotlib import pyplot as plt
from matplotlib.colors import ListedColormap
def rotate_cm(co_map, deg=180):
#define a function where the colormap is rotated by a certain degree
#180° shifts by 50%, 360° no change
n = co_map.N
#if rotating in the opposite direction feels more intuitive, reverse the sign here
deg = -deg%360
if deg < 0:
deg += 360
cutpoint = n * deg // 360
new_col_arr = [co_map(i) for i in range(cutpoint, n)] + [co_map(i) for i in range(cutpoint)]
return ListedColormap(new_col_arr)
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(21,7))
#any listed colormap
my_cm = plt.cm.get_cmap("inferno")
#normal color map
cb1 = ax1.scatter(*arr[:2,:], c=arr[2,:], cmap=my_cm, marker="o")
plt.colorbar(cb1, ax=ax1)
ax1.set_title("regular colormap")
#reversed colormap
cb2 = ax2.scatter(*arr[:2,:], c=arr[2,:], cmap=my_cm.reversed(), marker="o")
plt.colorbar(cb2, ax=ax2)
ax2.set_title("reversed colormap")
#rotated colormap
cb3 = ax3.scatter(*arr[:2,:], c=arr[2,:], cmap=rotate_cm(my_cm, 90), marker="o")
#you can also combine the rotation with reversed()
#cb3 = ax3.scatter(*arr[:2,:], c=arr[2,:], cmap=rotate_cm(my_cm, 90).reversed(), marker="o")
plt.colorbar(cb3, ax=ax3)
ax3.set_title("colormap rotated by 90°")
plt.show()
Sample output:
I am trying to recreate this plot created with R in Python:
This is where I got:
This is the code I used:
from matplotlib.ticker import ScalarFormatter
fig, ax = plt.subplots(figsize=(10,8))
sns.regplot(x='Platform2',y='Platform1',data=duplicates[['Platform2','Platform1']].dropna(thresh=2), scatter_kws={'s':80, 'alpha':0.5})
plt.ylabel('Platform1', labelpad=15, fontsize=15)
plt.xlabel('Platform2', labelpad=15, fontsize=15)
plt.title('Sales of the same game in different platforms', pad=30, size=20)
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xticks([1,2,5,10,20])
ax.set_yticks([1,2,5,10,20])
ax.get_xaxis().set_major_formatter(ScalarFormatter())
ax.get_yaxis().set_major_formatter(ScalarFormatter())
ax.set_xlim([0.005, 25.])
ax.set_ylim([0.005, 25.])
plt.show()
I think I am missing some conceptual knowledge behind the logarithmic values I plotted here. Since I did not change the values themselves but the scale of the graph I think I am doing something wrong. When I tried changing the values themselves I was not successful.
What I wanted was to show the regression line like the one in the R plot and also show the 0s in the x and y axes. The logarithmic nature of the plot does not allow me to add the 0 limits in the x and y axes. I found this StackOverflow entry: LINK but I was not able to make it work. Maybe if someone can rephrase it or if someone has any suggestions on how to move forward it would be great!
Thanks!
Seaborn's regplot creates either a line in linear space (y ~ x), or (with logx=True) a linear regression of the form y ~ log(x). Your question asks for a linear regression of the form log(y) ~ log(x).
This can be accomplished by calling regplot with the log of the input data.
However, this will change the data axes showing the log of the data instead of the data themselves. With a special tick formatter (taking the power of the value), these tick values can be converted again to the original data format.
Note that both the calls to set_xticks() and set_xlim() will need their values converted to log space for this to work. The calls to set_xscale('log') need to be removed.
The code below also changes most plt. calls to ax. calls, and adds the ax as argument to sns.regplot(..., ax=ax).
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
sns.set()
p1 = 10 ** np.random.uniform(-2, 1, 1000)
p2 = 10 ** np.random.uniform(-2, 1, 1000)
duplicates = pd.DataFrame({'Platform1': 0.6 * p1 + 0.4 * p2, 'Platform2': 0.1 * p1 + 0.9 * p2})
fig, ax = plt.subplots(figsize=(10, 8))
data = duplicates[['Platform2', 'Platform1']].dropna(thresh=2)
sns.regplot(x=np.log10(data['Platform2']), y=np.log10(data['Platform1']),
scatter_kws={'s': 80, 'alpha': 0.5}, ax=ax)
ax.set_ylabel('Platform1', labelpad=15, fontsize=15)
ax.set_xlabel('Platform2', labelpad=15, fontsize=15)
ax.set_title('Sales of the same game in different platforms', pad=30, size=20)
ticks = np.log10(np.array([1, 2, 5, 10, 20]))
ax.set_xticks(ticks)
ax.set_yticks(ticks)
formatter = lambda x, pos: f'{10 ** x:g}'
ax.get_xaxis().set_major_formatter(formatter)
ax.get_yaxis().set_major_formatter(formatter)
lims = np.log10(np.array([0.005, 25.]))
ax.set_xlim(lims)
ax.set_ylim(lims)
plt.show()
To create a jointplot similar to the example in R (to set the figure size, use sns.jointplot(...., height=...), the figure will always be square):
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
sns.set()
p1 = 10 ** np.random.uniform(-2.1, 1.3, 1000)
p2 = 10 ** np.random.uniform(-2.1, 1.3, 1000)
duplicates = pd.DataFrame({'Platform1': 0.6 * p1 + 0.4 * p2, 'Platform2': 0.1 * p1 + 0.9 * p2})
data = duplicates[['Platform2', 'Platform1']].dropna(thresh=2)
g = sns.jointplot(x=np.log10(data['Platform2']), y=np.log10(data['Platform1']),
scatter_kws={'s': 80, 'alpha': 0.5}, kind='reg', height=10)
ax = g.ax_joint
ax.set_ylabel('Platform1', labelpad=15, fontsize=15)
ax.set_xlabel('Platform2', labelpad=15, fontsize=15)
g.fig.suptitle('Sales of the same game in different platforms', size=20)
ticks = np.log10(np.array([.01, .1, 1, 2, 5, 10, 20]))
ax.set_xticks(ticks)
ax.set_yticks(ticks)
formatter = lambda x, pos: f'{10 ** x:g}'
ax.get_xaxis().set_major_formatter(formatter)
ax.get_yaxis().set_major_formatter(formatter)
lims = np.log10(np.array([0.005, 25.]))
ax.set_xlim(lims)
ax.set_ylim(lims)
plt.tight_layout()
plt.show()
I wonder if it is possible with corrplot (from the biokit package) to have a colobar with patterns.
In this example below, for the colobar, I would like to have 5 bubbles with differents sizes associated to the matrix's values ([-1, -0.5, 0, +0.5, +1]).
Ideas ?
thanks a lot
import numpy as np
import pandas as pd
from biokit.viz import corrplot
from matplotlib import pyplot as plt
import string
letters = string.ascii_uppercase[0:15]
df = pd.DataFrame(dict(( (k, np.random.random(10)+ord(k)-65) for k in letters)))
df = df.corr()
c = corrplot.Corrplot(df)
c.plot(colorbar=True, upper='circle', rotation=60, cmap='Oranges', fontsize=12)
plt.tight_layout()
plt.show()
Adding filled shapes to a colorbar seems not to be part of the standard interface. However, a legend could serve your goals. Some shapes are supported directly by the legend, others need a special handler, as described in this post.
It seems first some circles need to be created, for which color etc. can be set.
To tell the handler which shape exactly is meant, I'm misusing the label parameter.
From the source of biokit's corrplot, we learn that the ellipse is rotated either + or -45°, and that it is scaled by the absolute value of the correlation.
The following code puts everything together. The colorbar is drawn as a reference, but can be left out once everything is checked. The legend is positioned outside the main plot via bbox_to_anchor=(x, y). These coordinates are in axes coordinates. The ideal location depends on the size of the other elements, so some experimentation could be useful. I didn't draw the corrplot itself, as I don't have it installed, but you can replace the dummy scatter plot with it.
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse, Circle
import matplotlib as mpl
class HandlerEllipse(mpl.legend_handler.HandlerPatch):
def create_artists(self, legend, orig_handle,
xdescent, ydescent, width, height, fontsize, trans):
d = float(orig_handle.get_label())
center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
radius = (height + ydescent) * 1.8
p = Ellipse(xy=center, width=radius, height=radius * (1 - abs(d)), angle=45 if d > 0 else -45)
self.update_prop(p, orig_handle, legend)
p.set_transform(trans)
return [p]
#values = [1, 0.5, 0, -0.5, -1]
values = [1, 0.75, 0.5, 0.25, 0, -0.25, -0.5, -0.75, -1]
cmap = plt.cm.Oranges # or plt.cm.PiYG
norm = mpl.colors.Normalize(vmin=-1, vmax=1)
fig, ax = plt.subplots()
plt.scatter([0,1], [0,1], c=[-1,1], cmap=cmap, norm=norm) # a dummy plot as a stand-in for the corrplot
char = plt.colorbar(ticks=values)
shapes = [Circle((0.5, 0.5), 1, facecolor=cmap(norm(d)), edgecolor='k', alpha=1, zorder=2, linewidth=1, label=d)
for d in values]
plt.legend(handles=shapes, labels=values, title='Correlation', framealpha=1,
bbox_to_anchor=(1.25, 1), loc='upper left',
handler_map={Circle: HandlerEllipse()})
plt.tight_layout() # make sure legend and colorbar fit nicely in the plot
plt.show()
My in my "real world" problem i want to recalculate the x y values written in the tick labeling of my figure after i have zoomed in it in such a way that the origin is always at (0,0) and obviously the relative distances of the values on the x and y axis stay the same.
My problem was solved in this thread:
Initial solution
The solution includes the creation of one invisible axis that holds the plot and one visible axis that gets different tick lables after zooming.
In my case i want to superimpose multiple countor and coutourf plots. For only one of those plots i want to add a colorbar to the figure!
But when i create the colorbar in my script, the two axis objects i have created shift relative to each other. Without the colorbar they are perfectly aligned!
Here is a MWE that roughly recreates the behavior:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import mlab, cm
# Default delta is large because that makes it fast, and it illustrates
# the correct registration between image and contours.
delta = 0.5
extent = (-3, 4, -4, 3)
x = np.arange(-3.0, 4.001, delta)
y = np.arange(-4.0, 3.001, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = (Z1 - Z2) * 10
levels = np.arange(-2.0, 1.601, 0.4) # Boost the upper limit to avoid truncation errors.
norm = cm.colors.Normalize(vmax=abs(Z).max(), vmin=-abs(Z).max())
cmap = cm.PRGn
# ax is empty
fig, ax = plt.subplots()
ax.set_navigate(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=2)
ax2.contourf(X, Y, Z, levels,
cmap=cm.get_cmap(cmap, len(levels) - 1),
norm=norm,
)
ax2.axis("off")
ax.set_xlim(ax2.get_xlim())
ax.set_ylim(ax2.get_ylim())
#
# Declare and register callbacks
def on_lims_change(axes):
# change limits of ax, when ax2 limits are changed.
a=ax2.get_xlim()
ax.set_xlim(0, a[1]-a[0])
a=ax2.get_ylim()
ax.set_ylim(0, a[1]-a[0])
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm ) #Do not show unnecessary parts of the colormap
sm._A = []
cb = plt.colorbar(sm,extend="both", label="units")
cb.ax.tick_params(labelsize=10)
ax2.callbacks.connect('xlim_changed', on_lims_change)
ax2.callbacks.connect('ylim_changed', on_lims_change)
ax.axis('scaled')
plt.axis('scaled')
# Show
plt.show()
Now the contourplot seems to be shifted realtive to the visible axis. I found a few hints online that suggest, that the "colorbar box automatically eats up space from the axes to which it is attached"
Link1
Link2
But i do not really know what i need to do to change this behavior nor do i understand if my issue is related.
Please note, that the part:
ax.axis('scaled')
plt.axis('scaled')
is necessary as i need to keep the aspect ratio exactly like it is in the data set!
Thank you in advance!
You can change the position of ax (the empty axes with the labels) to the position of ax2 (the axes showing the data) after adding the colorbar via
ax.set_position(ax2.get_position())
Alternatively, create the colorbar by "steeling" the space from both axes,
cb = fig.colorbar(sm,ax=[ax,ax2], extend="both", label="units")
Both solutions are found in the answers to this linked question.
The following are some additional improvements outside the actual scope of the question:
ax.axis('scaled')
ax2.axis('scaled')
Additionally, put the ax on top if the ax2, such that the contourf plot does not overlap the axes spines.
# put `ax` on top, to let the contours not overlap the shown axes
ax.set_zorder(2)
ax.patch.set_visible(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=1)
Complete code:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import mlab, cm
delta = 0.5
extent = (-3, 4, -4, 3)
x = np.arange(-3.0, 4.001, delta)
y = np.arange(-4.0, 3.001, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = (Z1 - Z2) * 10
levels = np.arange(-2.0, 1.601, 0.4)
norm = cm.colors.Normalize(vmax=abs(Z).max(), vmin=-abs(Z).max())
cmap = cm.PRGn
# ax is empty
fig, ax = plt.subplots()
ax.set_navigate(False)
# put `ax` on top, to let the contours not overlap the shown axes
ax.set_zorder(2)
ax.patch.set_visible(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=1)
ax2.contourf(X, Y, Z, levels,
cmap=cm.get_cmap(cmap, len(levels) - 1),
norm=norm,
)
ax2.axis("off")
ax.set_xlim(ax2.get_xlim())
ax.set_ylim(ax2.get_ylim())
#
# Declare and register callbacks
def on_lims_change(axes):
# change limits of ax, when ax2 limits are changed.
a=ax2.get_xlim()
ax.set_xlim(0, a[1]-a[0])
a=ax2.get_ylim()
ax.set_ylim(0, a[1]-a[0])
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm )
sm._A = []
cb = fig.colorbar(sm,ax=[ax,ax2], extend="both", label="units")
cb.ax.tick_params(labelsize=10)
ax2.callbacks.connect('xlim_changed', on_lims_change)
ax2.callbacks.connect('ylim_changed', on_lims_change)
ax.axis('scaled')
ax2.axis('scaled')
#ax.set_position(ax2.get_position())
# Show
plt.show()
I have an patch collection that I'd like to display a color map for. Because of some manipulations I do on top of the colormap, it's not possible for me to define it using a matplotlib.colorbar instance. At least not as far as I can tell; doing so strips some manipulations I do with my colors that blank out patches lacking data:
cmap = matplotlib.cm.YlOrRd
colors = [cmap(n) if pd.notnull(n) else [1,1,1,1]
for n in plt.Normalize(0, 1)([nullity for _, nullity in squares])]
# Now we draw.
for i, ((min_x, max_x, min_y, max_y), _) in enumerate(squares):
square = shapely.geometry.Polygon([[min_x, min_y], [max_x, min_y],
[max_x, max_y], [min_x, max_y]])
ax0.add_patch(descartes.PolygonPatch(square, fc=colors[i],
ec='white', alpha=1, zorder=4))
So I define a matplotlib.colorbar.ColorbarBase instance instead, which works:
matplotlib.colorbar.ColorbarBase(ax1, cmap=cmap, orientation='vertical',
norm=matplotlib.colors.Normalize(vmin=0, vmax=1))
Which results in e.g.:
The problem I have is that I want to reduce the size of this colorbar (specifically, the shrink it down to a specific vertical size, say, 500 pixels), but I don't see any obvious way of doing this. If I had a colorbar instance, I could adjust this easily using its axis property arguments, but ColorbarBase lacks these.
For further reference:
The example my implementation is based on.
The source code in question (warning: lengthy).
The size and shape is defined with the axis. This is a snippet from code I have where I group 2 plots together and add a colorbar at the top independently. I played with the values in that add_axes instance until I got a size that worked for me:
cax = fig.add_axes([0.125, 0.925, 0.775, 0.0725]) #has to be as a list - starts with x, y coordinates for start and then width and height in % of figure width
norm = mpl.colors.Normalize(vmin = low_val, vmax = high_val)
mpl.colorbar.ColorbarBase(cax, cmap = self.cmap, norm = norm, orientation = 'horizontal')
The question may be a bit old, but I found another solution that can be of help for anyone who is not willing to manually create a colorbar axes for the ColorbarBase class.
The solution below uses the matplotlib.colorbar.make_axes class to create a dependent sub_axes from the given axes. That sub_axes can then be supplied for the ColorbarBase class for the colorbar creation.
The code is derived from the matplotlib code example describe in here
Here is a snippet code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import matplotlib.colorbar as mcbar
from matplotlib import ticker
import matplotlib.colors as mcolors
# Make some illustrative fake data:
x = np.arange(0, np.pi, 0.1)
y = np.arange(0, 2 * np.pi, 0.1)
X, Y = np.meshgrid(x, y)
Z = np.cos(X) * np.sin(Y) * 10
colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] # R -> G -> B
n_bins = [3, 6, 10, 100] # Discretizes the interpolation into bins
cmap_name = 'my_list'
fig, axs = plt.subplots(2, 2, figsize=(9, 7))
fig.subplots_adjust(left=0.02, bottom=0.06, right=0.95, top=0.94, wspace=0.05)
for n_bin, ax in zip(n_bins, axs.ravel()):
# Create the colormap
cm = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bin)
# Fewer bins will result in "coarser" colomap interpolation
im = ax.imshow(Z, interpolation='nearest', origin='lower', cmap=cm)
ax.set_title("N bins: %s" % n_bin)
cax, cbar_kwds = mcbar.make_axes(ax, location = 'right',
fraction=0.15, shrink=0.5, aspect=20)
cbar = mcbar.ColorbarBase(cax, cmap=cm,
norm=mcolors.Normalize(clip=False),
alpha=None,
values=None,
boundaries=None,
orientation='vertical', ticklocation='auto', extend='both',
ticks=n_bins,
format=ticker.FormatStrFormatter('%.2f'),
drawedges=False,
filled=True,
extendfrac=None,
extendrect=False, label='my label')
if n_bin <= 10:
cbar.locator = ticker.MaxNLocator(n_bin)
cbar.update_ticks()
else:
cbar.locator = ticker.MaxNLocator(5)
cbar.update_ticks()
fig.show()