Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Inner colorbar & Outer colorbar #691

Closed
wants to merge 3 commits into from

5 participants

Jae-Joon Lee Michael Droettboom Phil Elson Eric Firing Benjamin Root
Jae-Joon Lee
Owner

Create colorbar at the inner- of outer- side of axes. Unlike the original colorbar command, the location of the original axes is not changed.

import numpy as np
import matplotlib.pyplot as plt

arr = np.arange(100).reshape((10,10))
ax = plt.subplot(111)
im = ax.imshow(arr)

from mpl_toolkits.axes_grid1.colorbar import inner_colorbar
cb = inner_colorbar(ax, im, loc=2)

plt.show()

See examples/axes_grid/demo_colorbar.py.

Alt outer_colorbar

Alt inner_colorbar

Eric Firing efiring commented on the diff
lib/mpl_toolkits/axes_grid1/colorbar.py
((171 lines not shown))
+ required arguments are:
+
+ *ax*
+ parent axes object a new
+ colorbar axes will be attached
+
+ *mappable*
+ the :class:`~matplotlib.image.Image`,
+ :class:`~matplotlib.contour.ContourSet`, etc. to
+ which the colorbar applies; this argument is mandatory for the
+ :meth:`~matplotlib.figure.Figure.colorbar` method but optional for the
+ :func:`~matplotlib.pyplot.colorbar` function, which sets the
+ default to the current image.
+
+ *loc*
+ location code as in Legend
Eric Firing Owner
efiring added a note

Requiring a numeric code is rather unfriendly and harms readability of user code. It would be nice to have location specifications and codes factored out, and encourage use of names. Even in mpl code, names would be better where possible. One concise naming method for such location is map-oriented: e.g. NE is upper right, CE is center right, C is center, etc. Or one could use T for top, B for bottom, L for left, R for right, and C for center.

Benjamin Root Collaborator

My vote would be to be consistent with how legends are done (but without "best"). So, map names would be best.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Benjamin Root WeatherGod commented on the diff
lib/mpl_toolkits/axes_grid1/colorbar.py
@@ -791,7 +791,7 @@ def make_axes(parent, **kw):
return cax, kw
-def colorbar(mappable, cax=None, ax=None, **kw):
Benjamin Root Collaborator

I am really not a big fan of this renaming of these functions. I would rather see a new keyword added to colorbar that would specify whether it is inner or outer (and default to outer).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Michael Droettboom
Owner

I agree with the other comments here. If those were addressed, I think this would be a worthwhile addition.

Also, this should have a regression test.

Phil Elson pelson commented on the diff
lib/matplotlib/transforms.py
@@ -631,13 +631,15 @@ def expanded(self, sw, sh):
a = np.array([[-deltaw, -deltah], [deltaw, deltah]])
return Bbox(self._points + a)
- def padded(self, p):
+ def padded(self, p, pady=None):
Phil Elson Collaborator
pelson added a note

I can't help but see one word with this variable name, I think the variable name ypadding would probably make things clearer (I would personally go for y_padding, but that is probably a bit inconsistent with other variable names in matplotlib).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Phil Elson
Collaborator

I find it a real shame to see that such a fantastic feature wont be available in the mainstream mpl colorbar. I don't know the history, but was it, and is it still, entirely necessary to take a fork of the standard colorbar classes? Essentially, what we are seeing is a divergence of functionality (for instance, one class can do inner colorbars, the other can do hatched colorbars - as a user, just pray you don't want both), and an added complexity to the end user about how to produce a colorbar in matplotlib (if you want to do x and y you want this kind of colorbar, but if you want to do z you need this kind of colorbar).

Is there any way we can reconcile and factor in the necessary changes from mpl_toolkits.axes_grid1.colorbar back into matplotlib.colorbar?

Phil Elson pelson commented on the diff
lib/matplotlib/offsetbox.py
((13 lines not shown))
bbox_to_anchor = self.get_bbox_to_anchor()
x0, y0 = self._get_anchored_bbox(self.loc,
bbox,
bbox_to_anchor,
- borderpad)
+ borderpadx, borderpady)
Phil Elson Collaborator
pelson added a note

Again, I have readability issues with this. xborderpad is better, x_border_pad is best (but most inconsistent with the rest of the code).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Phil Elson
Collaborator

Is there any way we can reconcile and factor in the necessary changes from mpl_toolkits.axes_grid1.colorbar back into matplotlib.colorbar?

@leejjoon : I'm guessing the answer to this is no. If that is the case, then, once @WeatherGod is happy his comments are addressed, I don't see any value in holding this PR up (there is already some awesome functionality in axes_grid which is not available to a "native" user [such as me] which I would hope, in the future, we can port back into the core of mpl).

@leejjoon : Are you in a position to move this forwards?

Thanks,

Jae-Joon Lee
Owner

The proposed functionality uses Colorbar class from the matplotlib. But it depends on axes_grid1's functionality to place the colorbar "axes" relative to the parent axes. So, unless axes_grid1 becomes the mainstream part of matplotlib, it is not possible to put this into matplotlib.colorbar.py.

In fact, I now think that the current approach is not as good as I originally thought. I have some idea on an alternative (better) approach but not sure when I can start working on. I am not sure what is best option to do with this PR for now. Maybe we put some label on it and close?

Phil Elson
Collaborator

Maybe we put some label on it and close?

I've added the "onging" label, and will close. Thanks @leejjoon

Phil Elson pelson closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
48 examples/axes_grid/demo_colorbar.py
View
@@ -0,0 +1,48 @@
+import numpy as np
+import matplotlib.gridspec as gridspec
+import matplotlib.pyplot as plt
+
+from mpl_toolkits.axes_grid1.colorbar import outer_colorbar, inner_colorbar
+
+
+plt.figure(figsize=[6,6])
+gs = gridspec.GridSpec(3,3, hspace=0.7, wspace=0.7)
+
+locs = [1, 2, 3, 4, 6, 7, 8, 9, 10]
+arr = np.arange(100).reshape((10,10))
+for i, ss in zip(locs, gs):
+ ax = plt.subplot(ss)
+ ax.axison = False
+ im = ax.imshow(arr, interpolation="none")
+ cb = outer_colorbar(ax, mappable=im, loc=i,
+ orientation=None,
+ ticks=[0, 50],
+ length="50%", thickness="7%",
+ pad=0.2,
+ )
+
+
+
+
+plt.figure(figsize=[6,6])
+gs = gridspec.GridSpec(3,3)
+
+from matplotlib.patheffects import withStroke
+pe = [withStroke(foreground="w", linewidth=3)]
+
+locs = [1, 2, 3, 4, 6, 7, 8, 9, 10]
+arr = np.arange(100).reshape((10,10))
+for i, ss in zip(locs, gs):
+ ax = plt.subplot(ss)
+ ax.axison = False
+ im = ax.imshow(arr, interpolation="none")
+ cb = inner_colorbar(ax, mappable=im, loc=i,
+ orientation=None,
+ ticks=[0, 50],
+ length="50%", thickness="5%",
+ pad=0.2,
+ )
+ cb.ax.axis[:].major_ticklabels.set_path_effects(pe)
+
+
+plt.show()
16 lib/matplotlib/offsetbox.py
View
@@ -1045,13 +1045,20 @@ def _update_offset_func(self, renderer, fontsize=None):
def _offset(w, h, xd, yd, renderer, fontsize=fontsize, self=self):
bbox = Bbox.from_bounds(0, 0, w, h)
- borderpad = self.borderpad*fontsize
+ try:
+ borderpadx, borderpady = self.borderpad
+ except TypeError:
+ borderpadx = self.borderpad
+ borderpady = self.borderpad
+ borderpadx = borderpadx*fontsize
+ borderpady = borderpady*fontsize
+
bbox_to_anchor = self.get_bbox_to_anchor()
x0, y0 = self._get_anchored_bbox(self.loc,
bbox,
bbox_to_anchor,
- borderpad)
+ borderpadx, borderpady)
Phil Elson Collaborator
pelson added a note

Again, I have readability issues with this. xborderpad is better, x_border_pad is best (but most inconsistent with the rest of the code).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
return x0+xd, y0+yd
self.set_offset(_offset)
@@ -1088,7 +1095,8 @@ def draw(self, renderer):
- def _get_anchored_bbox(self, loc, bbox, parentbbox, borderpad):
+ def _get_anchored_bbox(self, loc, bbox, parentbbox,
+ borderpadx, borderpady):
"""
return the position of the bbox anchored at the parentbbox
with the loc code, with the borderpad.
@@ -1110,7 +1118,7 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, borderpad):
c = anchor_coefs[loc]
- container = parentbbox.padded(-borderpad)
+ container = parentbbox.padded(-borderpadx, -borderpady)
anchored_box = bbox.anchored(c, container=container)
return anchored_box.x0, anchored_box.y0
6 lib/matplotlib/transforms.py
View
@@ -631,13 +631,15 @@ def expanded(self, sw, sh):
a = np.array([[-deltaw, -deltah], [deltaw, deltah]])
return Bbox(self._points + a)
- def padded(self, p):
+ def padded(self, p, pady=None):
Phil Elson Collaborator
pelson added a note

I can't help but see one word with this variable name, I think the variable name ypadding would probably make things clearer (I would personally go for y_padding, but that is probably a bit inconsistent with other variable names in matplotlib).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
"""
Return a new :class:`Bbox` that is padded on all four sides by
the given value.
"""
+ if pady is None:
+ pady = p
points = self.get_points()
- return Bbox(points + [[-p, -p], [p, p]])
+ return Bbox(points + [[-p, -pady], [p, pady]])
def translated(self, tx, ty):
"""
331 lib/mpl_toolkits/axes_grid1/colorbar.py
View
@@ -791,7 +791,7 @@ def make_axes(parent, **kw):
return cax, kw
-def colorbar(mappable, cax=None, ax=None, **kw):
Benjamin Root Collaborator

I am really not a big fan of this renaming of these functions. I would rather see a new keyword added to colorbar that would specify whether it is inner or outer (and default to outer).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+def axesgrid1_colorbar(mappable, cax=None, ax=None, **kw):
"""
Create a colorbar for a ScalarMappable instance.
@@ -799,6 +799,7 @@ def colorbar(mappable, cax=None, ax=None, **kw):
%(colorbar_doc)s
"""
import matplotlib.pyplot as plt
+ from matplotlib.colorbar import Colorbar
if ax is None:
ax = plt.gca()
if cax is None:
@@ -815,3 +816,331 @@ def on_changed(m):
mappable.set_colorbar(cb, cax)
ax.figure.sca(ax)
return cb
+
+
+def mpl_colorbar(mappable, cax=None, ax=None, **kw):
+ """
+ Create a colorbar for a ScalarMappable instance.
+
+ Documentation for the pylab thin wrapper:
+ %(colorbar_doc)s
+ """
+ import matplotlib.pyplot as plt
+ from matplotlib.colorbar import Colorbar
+ if ax is None:
+ ax = plt.gca()
+ if cax is None:
+ cax, kw = make_axes(ax, **kw)
+ cax.hold(True)
+ cb = Colorbar(cax, mappable, **kw)
+
+ def on_changed(m):
+ cb.set_cmap(m.get_cmap())
+ cb.set_clim(m.get_clim())
+ cb.update_bruteforce(m)
+
+ cbid = mappable.callbacksSM.connect('changed', on_changed)
+ mappable.set_colorbar(cb, cax)
+ ax.figure.sca(ax)
+ return cb
+
+
+colorbar = mpl_colorbar
+
+from mpl_toolkits.axes_grid1.inset_locator import inset_axes
+
+
+def _get_colorbar_params(loc, orientation, length, thickness):
+ if orientation is None:
+ if loc in [8, 9]:
+ orientation="horizontal"
+ else:
+ orientation="vertical"
+
+
+ if orientation == "vertical":
+ height = length
+ width = thickness
+ elif orientation == "horizontal":
+ height = thickness
+ width = length
+ else:
+ raise ValueError("Unknown orientation value : %s" % orientation)
+
+ if orientation == "vertical":
+ if loc in [1, 4, 5, 7]:
+ tickdir = "left"
+ elif loc in [2, 3, 6, 8, 9, 10]:
+ tickdir = "right"
+
+ elif orientation == "horizontal":
+ if loc in [3, 4, 8]:
+ tickdir = "top"
+ elif loc in [1, 2, 5, 6, 7, 9, 10]:
+ tickdir = "bottom"
+
+ return loc, orientation, height, width, tickdir
+
+
+@docstring.Substitution(colormap_kw_doc)
+def inner_colorbar(ax, mappable, loc,
+ orientation=None,
+ length="45%", thickness="5%",
+ pad=None,
+ **kw):
+ """
+ Create a colorbar at the inner side of the Axes of the given
+ location. The size and location of the original axes is not
+ changed. For example::
+
+ inner_colorbar(ax, mappable, loc=2, **kwargs)
+
+ required arguments are:
+
+ *ax*
+ parent axes object a new
+ colorbar axes will be attached
+
+ *mappable*
+ the :class:`~matplotlib.image.Image`,
+ :class:`~matplotlib.contour.ContourSet`, etc. to
+ which the colorbar applies; this argument is mandatory for the
+ :meth:`~matplotlib.figure.Figure.colorbar` method but optional for the
+ :func:`~matplotlib.pyplot.colorbar` function, which sets the
+ default to the current image.
+
+ *loc*
+ location code as in Legend
+
+ Keyword arguments are:
+
+ ============= ====================================================
+ Property Description
+ ============= ====================================================
+ *orientation* None, vertical or horizontal
+ *length* length of the colorbar.
+ *thickness* thickness of the colorbar.
+ *pad* pad bewtween original axes and colorbar in inches
+ ============= ====================================================
+
+ For *thickness* and *length*, if a float is given, it is
+ considered as a size in inches. If a string in the form of
+ '50%%' is given, the value is considered as a fraction of the
+ original axes.
+
+ Additional keyword arguments are:
+
+ %s
+
+ returns:
+ :class:`~matplotlib.colorbar.Colorbar` instance; see also its base class,
+ :class:`~matplotlib.colorbar.ColorbarBase`. Call the
+ :meth:`~matplotlib.colorbar.ColorbarBase.set_label` method
+ to label the colorbar.
+
+ """
+
+ if pad is None:
+ pad = mpl.rcParams['legend.borderaxespad']
+
+ loc, orientation, height, width, tickdir = \
+ _get_colorbar_params(loc, orientation, length, thickness)
+
+ cax = inset_axes(ax,
+ width=width,
+ height=height,
+ loc=loc,
+ bbox_to_anchor=(0, 0, 1, 1),
+ bbox_transform=ax.transAxes,
+ borderpad=pad)
+
+ cb = colorbar(mappable, cax=cax, ax=ax,
+ orientation=orientation, **kw)
+
+ if tickdir == "left":
+ cax.yaxis.tick_left()
+ elif tickdir == "right":
+ cax.yaxis.tick_right()
+ elif tickdir == "top":
+ cax.xaxis.tick_top()
+ else:
+ cax.xaxis.tick_bottom()
+
+ return cb
+
+
+@docstring.Substitution(colormap_kw_doc)
+def outer_colorbar(ax, mappable, loc,
+ orientation=None,
+ length="100%", thickness="5%",
+ pad=None,
+ **kw):
+
+ """
+ Create a colorbar at the outer side of the Axes of the given
+ location. The size and location of the original axes is not
+ changed. For example::
+
+ outer_colorbar(ax, mappable, loc=2, **kwargs)
+
+ required arguments are:
+
+ *ax*
+ parent axes object a new
+ colorbar axes will be attached
+
+ *mappable*
+ the :class:`~matplotlib.image.Image`,
+ :class:`~matplotlib.contour.ContourSet`, etc. to
+ which the colorbar applies; this argument is mandatory for the
+ :meth:`~matplotlib.figure.Figure.colorbar` method but optional for the
+ :func:`~matplotlib.pyplot.colorbar` function, which sets the
+ default to the current image.
+
+ *loc*
+ location code as in Legend
Eric Firing Owner
efiring added a note

Requiring a numeric code is rather unfriendly and harms readability of user code. It would be nice to have location specifications and codes factored out, and encourage use of names. Even in mpl code, names would be better where possible. One concise naming method for such location is map-oriented: e.g. NE is upper right, CE is center right, C is center, etc. Or one could use T for top, B for bottom, L for left, R for right, and C for center.

Benjamin Root Collaborator

My vote would be to be consistent with how legends are done (but without "best"). So, map names would be best.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ Keyword arguments are:
+
+ ============= ====================================================
+ Property Description
+ ============= ====================================================
+ *orientation* None, vertical or horizontal
+ *length* length of the colorbar.
+ *thickness* thickness of the colorbar.
+ *pad* pad bewtween original axes and colorbar in inches
+ ============= ====================================================
+
+ For *thickness* and *length*, if a float is given, it is
+ considered as a size in inches. If a string in the form of
+ '50%%' is given, the value is considered as a fraction of the
+ original axes.
+
+ Additional keyword arguments are:
+
+ %s
+
+ returns:
+ :class:`~matplotlib.colorbar.Colorbar` instance; see also its base class,
+ :class:`~matplotlib.colorbar.ColorbarBase`. Call the
+ :meth:`~matplotlib.colorbar.ColorbarBase.set_label` method
+ to label the colorbar.
+
+ """
+
+ if pad is None:
+ pad = mpl.rcParams['legend.borderaxespad']
+
+ loc, orientation, height, width, tickdir = \
+ _get_colorbar_params(loc, orientation, length, thickness)
+
+ if loc in [5, 7]:
+ bbox_to_anchor = [1, 0, 1, 1]
+ elif loc in [6]:
+ bbox_to_anchor = [-1, 0, 1, 1]
+ elif loc in [8]:
+ bbox_to_anchor = [0, -1, 1, 1]
+ elif loc in [9]:
+ bbox_to_anchor = [0, 1, 1, 1]
+ elif loc in [1, 2, 3, 4]:
+ if orientation == "vertical":
+ if loc in [1, 4]:
+ bbox_to_anchor = [1, 0, 1, 1]
+ elif loc in [2, 3]:
+ bbox_to_anchor = [-1, 0, 1, 1]
+ else: # horizontal
+ if loc in [1, 2]:
+ bbox_to_anchor = [0, 1, 1, 1]
+ elif loc in [3, 4]:
+ bbox_to_anchor = [0, -1, 1, 1]
+ else:
+ bbox_to_anchor = [0, 0, 1, 1]
+
+ try:
+ padx, pady = pad
+ except TypeError:
+ if orientation == "vertical":
+ if loc in [8, 9]:
+ padx, pady = pad, pad
+ else:
+ padx, pady = pad, 0
+ else:
+ if loc in [5, 6, 7]:
+ padx, pady = pad, pad
+ else:
+ padx, pady = 0, pad
+
+ loc_map_h = {1:4,
+ 2:3,
+ 3:2,
+ 4:1,
+ 5:6,
+ 6:7,
+ 7:6,
+ 8:9,
+ 9:8,
+ 10:10}
+ loc_map_v = {1:2,
+ 2:1,
+ 3:4,
+ 4:3,
+ 5:6,
+ 6:7,
+ 7:6,
+ 8:9,
+ 9:8,
+ 10:10}
+
+ if orientation == "horizontal":
+ loc = loc_map_h[loc]
+ else:
+ loc = loc_map_v[loc]
+ tickdir_map = {"left":"right",
+ "right":"left",
+ "top":"bottom",
+ "bottom":"top"}
+ tickdir = tickdir_map[tickdir]
+ cax = inset_axes(ax,
+ width=width,
+ height=height,
+ loc=loc,
+ bbox_to_anchor=bbox_to_anchor,
+ bbox_transform=ax.transAxes,
+ borderpad=(padx, pady))
+
+ cb = colorbar(mappable, cax=cax, ax=ax,
+ orientation=orientation, **kw)
+
+ if tickdir == "left":
+ cax.yaxis.tick_left()
+ elif tickdir == "right":
+ cax.yaxis.tick_right()
+ elif tickdir == "top":
+ cax.xaxis.tick_top()
+ else:
+ cax.xaxis.tick_bottom()
+
+ return cb
+
+
+if __name__ == '__main__':
+ import matplotlib.gridspec as gridspec
+ gs = gridspec.GridSpec(3,3)
+
+ locs = [1, 2, 3, 4, 6, 7, 8, 9, 10][:]
+ arr = np.arange(100).reshape((10,10))
+ for i, ss in zip(locs, gs):
+ ax = subplot(ss)
+ im = ax.imshow(arr, interpolation="none")
+ cb = outer_colorbar(ax, mappable=im, loc=i,
+ orientation=None,
+ #orientation="vertical",
+ #orientation="horizontal",
+ length="100%", thickness="5%",
+ #pad=0.1,
+ )
+
+
+# ax.inner_colorbar(mappable=im, loc=4,
+# orientation='vertical',
+# lenght="45%", thickness="8%")
Something went wrong with that request. Please try again.