{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Properly Sized Color Bars for `imshow()` Plots in Matplotlib\n",
"\n",
"[back to overview page](index.ipynb)\n",
"\n",
"By default, the size of color bars is computed to match the figure size.\n",
"However, in most cases I have seen, it would be more appropriate to scale the color bars to match the size of the actual plot.\n",
"\n",
"If you want to skip all the boring explanations, you can jump right to [the solution](#The-Proper-Solution).\n",
"\n",
"You can also have a look at [my stackoverflow answer on the topic](http://stackoverflow.com/a/33505522/).\n",
"\n",
"As always, the information on this page might be outdated:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:35.945899Z",
"iopub.status.busy": "2024-09-15T10:21:35.942582Z",
"iopub.status.idle": "2024-09-15T10:21:36.095064Z",
"shell.execute_reply": "2024-09-15T10:21:36.092042Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2024-09-15\r\n"
]
}
],
"source": [
"!date +%F"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is the date of the last change to this page. If it's older then half a year or a year, it is very likely to be outdated, so don't read it!"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:36.106490Z",
"iopub.status.busy": "2024-09-15T10:21:36.105530Z",
"iopub.status.idle": "2024-09-15T10:21:36.638275Z",
"shell.execute_reply": "2024-09-15T10:21:36.637446Z"
}
},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:36.642678Z",
"iopub.status.busy": "2024-09-15T10:21:36.642177Z",
"iopub.status.idle": "2024-09-15T10:21:36.647116Z",
"shell.execute_reply": "2024-09-15T10:21:36.646148Z"
}
},
"outputs": [],
"source": [
"plt.rcParams['image.cmap'] = 'coolwarm'\n",
"plt.rcParams['image.origin'] = 'lower'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Please check if your local settings for the \"inline\" backend are meaningful, see [Default Values for Matplotlib's \"inline\" backend](matplotlib-inline-defaults.ipynb)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Some [dummy data](http://matplotlib.org/examples/pylab_examples/pcolor_demo.html):"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:36.651219Z",
"iopub.status.busy": "2024-09-15T10:21:36.650869Z",
"iopub.status.idle": "2024-09-15T10:21:36.656224Z",
"shell.execute_reply": "2024-09-15T10:21:36.655241Z"
}
},
"outputs": [],
"source": [
"def dummy_data(xmin, xmax):\n",
" ymin, ymax = -2, 2\n",
" x, y = np.meshgrid(np.arange(xmin, xmax, 0.1), np.arange(ymin, ymax, 0.1))\n",
" z = (1 - x / 2 + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)\n",
" extent = x.min(), x.max(), y.min(), y.max()\n",
" return z, extent"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:36.659471Z",
"iopub.status.busy": "2024-09-15T10:21:36.659105Z",
"iopub.status.idle": "2024-09-15T10:21:36.664127Z",
"shell.execute_reply": "2024-09-15T10:21:36.663113Z"
}
},
"outputs": [],
"source": [
"z, extent = dummy_data(-3, 3)\n",
"zmax = np.max(np.abs(z))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The Problem"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:36.667827Z",
"iopub.status.busy": "2024-09-15T10:21:36.667376Z",
"iopub.status.idle": "2024-09-15T10:21:36.912400Z",
"shell.execute_reply": "2024-09-15T10:21:36.911483Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"im = ax.imshow(z, extent=extent, vmin=-zmax, vmax=zmax)\n",
"fig.colorbar(im);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Obviously, the color bar is too tall.\n",
"\n",
"If this doesn't bother you, stop reading."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## \"Solution\" 1: Fiddling with the Figure Size\n",
"\n",
"Since the figure seems to be too high for its width, let's just make it a little lower:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:36.917590Z",
"iopub.status.busy": "2024-09-15T10:21:36.916977Z",
"iopub.status.idle": "2024-09-15T10:21:36.922735Z",
"shell.execute_reply": "2024-09-15T10:21:36.921922Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[6.4, 4.8]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"plt.rcParams['figure.figsize']"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:36.926847Z",
"iopub.status.busy": "2024-09-15T10:21:36.926410Z",
"iopub.status.idle": "2024-09-15T10:21:37.106199Z",
"shell.execute_reply": "2024-09-15T10:21:37.105213Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots(figsize=(6.4, 3))\n",
"im = ax.imshow(z, extent=extent, vmin=-zmax, vmax=zmax)\n",
"fig.colorbar(im);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This works, but it's not a general solution.\n",
"Whenever you have a different data size, you'll have to fiddle with the figure size."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Aside: Color Bar Aspect Ratio\n",
"\n",
"By default, a color bar has an aspect ratio of 20, i.e. its height is 20 times its width (in the case of a vertical color bar).\n",
"\n",
"If you want it wider or narrower, you have to choose a lower or higher value, respectively.\n",
"To get twice the width as before, let's use an aspect ratio of 10."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:37.109784Z",
"iopub.status.busy": "2024-09-15T10:21:37.109114Z",
"iopub.status.idle": "2024-09-15T10:21:37.294753Z",
"shell.execute_reply": "2024-09-15T10:21:37.293714Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots(figsize=(6.4, 3))\n",
"im = ax.imshow(z, extent=extent, vmin=-zmax, vmax=zmax)\n",
"fig.colorbar(im, aspect=10);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Aside: Distance between Plot and Color Bar\n",
"\n",
"If you don't like the amount of space between the color bar and the actual plot (the default hardly ever looks nice), you can use the `pad` parameter:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:37.298084Z",
"iopub.status.busy": "2024-09-15T10:21:37.297746Z",
"iopub.status.idle": "2024-09-15T10:21:37.481616Z",
"shell.execute_reply": "2024-09-15T10:21:37.480645Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots(figsize=(6.4, 3))\n",
"im = ax.imshow(z, extent=extent, vmin=-zmax, vmax=zmax)\n",
"fig.colorbar(im, pad=0.02);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The value of `pad` is given as relative width of the plot (between 0.0 and 1.0).\n",
"See below for how to use a more meaningful quantity."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## \"Solution\" 2: Fiddling with `shrink`"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:37.486150Z",
"iopub.status.busy": "2024-09-15T10:21:37.485829Z",
"iopub.status.idle": "2024-09-15T10:21:37.694113Z",
"shell.execute_reply": "2024-09-15T10:21:37.693185Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"im = ax.imshow(z, extent=extent, vmin=-zmax, vmax=zmax)\n",
"fig.colorbar(im, shrink=0.71);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## \"Solution\" 3: Fiddling with Manual Placement\n",
"\n",
"* using `fig.add_axes()`\n",
"\n",
"* using GridSpec: http://worksofscience.net/matplotlib/colorbar\n",
"\n",
"* using ImageGrid: http://matplotlib.org/mpl_toolkits/axes_grid/users/overview.html#imagegrid\n",
"\n",
"The details are left as an exercise for the reader.\n",
"Spoiler alert: It will be fiddly!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Official \"Solution\" from the Matplotlib Documentation: `axes_grid1`\n",
"\n",
"In all of the above \"solutions\", we had to fiddle with some parameters. And whenever the data size changed, we had to do it all over again.\n",
"\n",
"But the official matplotlib documentation shows a way how to get the height right without fiddling:\n",
"https://matplotlib.org/stable/gallery/axes_grid1/demo_colorbar_with_axes_divider.html.\n",
"\n",
"Sadly, this is quite complicated.\n",
"And, as we'll see, it only avoids a part of the fiddling ..."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:37.697752Z",
"iopub.status.busy": "2024-09-15T10:21:37.697374Z",
"iopub.status.idle": "2024-09-15T10:21:37.716486Z",
"shell.execute_reply": "2024-09-15T10:21:37.715644Z"
}
},
"outputs": [],
"source": [
"from mpl_toolkits import axes_grid1"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:37.720169Z",
"iopub.status.busy": "2024-09-15T10:21:37.719789Z",
"iopub.status.idle": "2024-09-15T10:21:37.988871Z",
"shell.execute_reply": "2024-09-15T10:21:37.987892Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"im = ax.imshow(z, extent=extent, vmin=-zmax, vmax=zmax)\n",
"divider = axes_grid1.make_axes_locatable(ax)\n",
"cax = divider.append_axes('right', size='5%', pad=0.05)\n",
"fig.colorbar(im, cax=cax);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is better than the previous \"solutions\", because the color bar will now have the correct height, regardless of the plot width.\n",
"\n",
"However, although the height is correct, the width is given relative to the width of the image plot.\n",
"This means that if we change the width of our plot, the width of the color bar will change, too."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:37.993128Z",
"iopub.status.busy": "2024-09-15T10:21:37.992748Z",
"iopub.status.idle": "2024-09-15T10:21:37.997786Z",
"shell.execute_reply": "2024-09-15T10:21:37.996897Z"
}
},
"outputs": [],
"source": [
"narrow_z, narrow_extent = dummy_data(-2, 0)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:38.001146Z",
"iopub.status.busy": "2024-09-15T10:21:38.000759Z",
"iopub.status.idle": "2024-09-15T10:21:38.186842Z",
"shell.execute_reply": "2024-09-15T10:21:38.185839Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"im = ax.imshow(narrow_z, extent=narrow_extent, vmin=-zmax, vmax=zmax)\n",
"divider = axes_grid1.make_axes_locatable(ax)\n",
"cax = divider.append_axes('right', size='5%', pad=0.05)\n",
"fig.colorbar(im, cax=cax);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To get the width we want, we still have to do some fiddling with the `size` parameter."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The Proper Solution\n",
"\n",
"The mysterious `axes_grid1` module looked promising, but we'll have to try a little harder.\n",
"\n",
"This stuff is getting more and more complicated, so I'm putting it into a little helper function:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:38.191171Z",
"iopub.status.busy": "2024-09-15T10:21:38.190813Z",
"iopub.status.idle": "2024-09-15T10:21:38.196903Z",
"shell.execute_reply": "2024-09-15T10:21:38.196022Z"
}
},
"outputs": [],
"source": [
"from mpl_toolkits import axes_grid1\n",
"\n",
"def add_colorbar(im, aspect=20, pad_fraction=0.5, **kwargs):\n",
" \"\"\"Add a vertical color bar to an image plot.\"\"\"\n",
" divider = axes_grid1.make_axes_locatable(im.axes)\n",
" width = axes_grid1.axes_size.AxesY(im.axes, aspect=1./aspect)\n",
" pad = axes_grid1.axes_size.Fraction(pad_fraction, width)\n",
" current_ax = plt.gca()\n",
" cax = divider.append_axes('right', size=width, pad=pad)\n",
" plt.sca(current_ax)\n",
" return im.axes.figure.colorbar(im, cax=cax, **kwargs)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that you only have to pass the image object which you get from `imshow()`, the containing `Axes` and `Figure` objects are obtained automatically.\n",
"\n",
"Further, you should note that `append_axes()` updates matplotlib's internal [current axes object](http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.gca).\n",
"This might not be what you expect, especially if you want to add something like axis labels afterwards.\n",
"Therefore, the function restores the current axes object from before the call to `append_axes()`.\n",
"\n",
"OK, let's see how this works on the narrow plot from above:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:38.201338Z",
"iopub.status.busy": "2024-09-15T10:21:38.200608Z",
"iopub.status.idle": "2024-09-15T10:21:38.397617Z",
"shell.execute_reply": "2024-09-15T10:21:38.396683Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"im = ax.imshow(narrow_z, extent=narrow_extent, vmin=-zmax, vmax=zmax)\n",
"add_colorbar(im);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Good. Now the wide plot:"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:38.402525Z",
"iopub.status.busy": "2024-09-15T10:21:38.402036Z",
"iopub.status.idle": "2024-09-15T10:21:38.745835Z",
"shell.execute_reply": "2024-09-15T10:21:38.744635Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"im = ax.imshow(z, extent=extent, vmin=-zmax, vmax=zmax)\n",
"add_colorbar(im);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Very nice! Now the color bar looks the same in both cases, just as I like it.\n",
"\n",
"And there was no fiddling involved at all (but I admit that the code looks a bit frightening).\n",
"\n",
"The aspect ratio and the space between color bar and plot can still be tweaked, if desired:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:38.752288Z",
"iopub.status.busy": "2024-09-15T10:21:38.751752Z",
"iopub.status.idle": "2024-09-15T10:21:38.929498Z",
"shell.execute_reply": "2024-09-15T10:21:38.928546Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"im = ax.imshow(z, extent=extent, vmin=-zmax, vmax=zmax)\n",
"add_colorbar(im, aspect=16/9, pad_fraction=1);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I'm not saying that this looks nice, but it should illustrate how to use the `aspect` and `pad_fraction` parameters."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Does This Work with Subplots?\n",
"\n",
"I guess ... let's try, shall we?"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"execution": {
"iopub.execute_input": "2024-09-15T10:21:38.933740Z",
"iopub.status.busy": "2024-09-15T10:21:38.933370Z",
"iopub.status.idle": "2024-09-15T10:21:39.174094Z",
"shell.execute_reply": "2024-09-15T10:21:39.173091Z"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, (ax1, ax2) = plt.subplots(nrows=2)\n",
"im = ax1.imshow(z, extent=extent, vmin=-zmax, vmax=zmax)\n",
"add_colorbar(im)\n",
"ax2.plot([4, 8, 15, 16, 23, 42]);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Yes, it seems to work (although this very example doesn't look very pleasing)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Does This Work with Horizontal Color Bars?\n",
"\n",
"I don't know, I didn't try."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## More Stuff\n",
"\n",
"text labels inside the colorbar: http://stackoverflow.com/questions/15908371/matplotlib-colorbars-and-its-text-labels\n",
"\n",
"multiple subplots with colorbars: http://stackoverflow.com/questions/18266642/multiple-imshow-subplots-each-with-colorbar\n",
"\n",
"one colorbar for multiple subplots:\n",
"* http://stackoverflow.com/questions/7875688/how-can-i-create-a-standard-colorbar-for-a-series-of-plots-in-python\n",
"* http://stackoverflow.com/questions/13784201/matplotlib-2-subplots-1-colorbar"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"
\n",
" \n",
" \n",
" \n",
" \n",
" To the extent possible under law,\n",
" the person who associated CC0\n",
" with this work has waived all copyright and related or neighboring\n",
" rights to this work.\n",
"