Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ab8bc46

Browse files
committed
initial multivar colorbar
1 parent 22e122d commit ab8bc46

2 files changed

Lines changed: 257 additions & 18 deletions

File tree

lib/matplotlib/colorbar.py

Lines changed: 191 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import logging
1515

1616
import numpy as np
17+
from collections.abc import Sequence
1718

1819
import matplotlib as mpl
1920
from matplotlib import _api, cbook, collections, colors, contour, ticker
@@ -1675,6 +1676,73 @@ def _cbar_cla(self):
16751676
self.ax.cla()
16761677

16771678

1679+
class MultivarColorbar(Sequence):
1680+
def __init__(self, axes, mappable=None, **kwargs):
1681+
if isinstance(mappable, mpl.colorizer.Colorizer):
1682+
mappable = mcolorizer.ColorizingArtist(mappable)
1683+
1684+
self.mappable = mappable
1685+
self.colorizer = mappable.colorizer
1686+
cmap = self.colorizer.cmap
1687+
norm = self.colorizer.norm
1688+
n = cmap.n_variates
1689+
1690+
self._colorbars = [Colorbar(axes[i],
1691+
norm=norm.norms[i],
1692+
cmap=cmap[i],
1693+
**kwargs,
1694+
) for i in range(n)]
1695+
1696+
mappable.colorbar = self
1697+
mappable.colorbar_cid = mappable.callbacks.connect(
1698+
'changed', self.update_normals)
1699+
1700+
self.axes = axes
1701+
1702+
def _set_colorbar_info(self, colorbar_info):
1703+
if colorbar_info is not None:
1704+
parents = colorbar_info['parents']
1705+
for a in parents:
1706+
a._colorbars.append(self)
1707+
1708+
def update_normals(self, mappable=None):
1709+
[c.update_normal() for c in self._colorbars]
1710+
1711+
def remove(self):
1712+
if hasattr(self, '_colorbar_info'):
1713+
parents = self._colorbar_info['parents']
1714+
for a in parents:
1715+
if self in a._colorbars:
1716+
a._colorbars.remove(self)
1717+
1718+
for ax in self.axes:
1719+
ax.remove()
1720+
1721+
self.mappable.callbacks.disconnect(self.mappable.colorbar_cid)
1722+
self.mappable.colorbar = None
1723+
self.mappable.colorbar_cid = None
1724+
1725+
try:
1726+
ax = self.mappable.axes
1727+
if ax is None:
1728+
return
1729+
except AttributeError:
1730+
return
1731+
try:
1732+
subplotspec = self.ax.get_subplotspec().get_gridspec()._subplot_spec
1733+
except AttributeError: # use_gridspec was False
1734+
pos = ax.get_position(original=True)
1735+
ax._set_position(pos)
1736+
else: # use_gridspec was True
1737+
ax.set_subplotspec(subplotspec)
1738+
1739+
def __getitem__(self, index):
1740+
return self._colorbars[index]
1741+
1742+
def __len__(self):
1743+
return len(self._colorbars)
1744+
1745+
16781746
def _normalize_location_orientation(location, orientation):
16791747
if location is None:
16801748
location = _get_ticklocation_from_orientation(orientation)
@@ -1777,19 +1845,8 @@ def _get_bbox_shrink_parents(parents, location, fraction,
17771845
return pbcb
17781846

17791847

1780-
def _make_axes_helper(loc_settings, fraction, shrink, aspect, kwargs, parents):
1781-
"""
1782-
Help function for `make_axes` and `make_bivar_axes`.
1783-
1784-
`make_axes` and `make_bivar_axes` are identical
1785-
except for the fact that `make_axes` also deals with:
1786-
1. the aspect
1787-
2. orientation.
1848+
def _make_axes_get_pbcb(loc_settings, fraction, shrink, aspect, kwargs, parents):
17881849

1789-
`make_bivar_axes` on the other hand has no concept of orientation
1790-
and the aspect is handled by the `BivarColormap` instance, not during
1791-
creation of the axes.
1792-
"""
17931850
location = loc_settings['location']
17941851
kwargs['location'] = location
17951852

@@ -1803,11 +1860,7 @@ def _make_axes_helper(loc_settings, fraction, shrink, aspect, kwargs, parents):
18031860
# shrink the parents and get the bbox for the colorbar
18041861
pbcb = _get_bbox_shrink_parents(parents, location, fraction,
18051862
pad, shrink, anchor, panchor)
1806-
cax = fig.add_axes(pbcb, label="<colorbar>")
1807-
for a in parents:
1808-
a._colorbars.append(cax) # tell the parent it has a colorbar
1809-
1810-
cax._colorbar_info = dict(
1863+
colorbar_info = dict(
18111864
parents=parents,
18121865
location=location,
18131866
shrink=shrink,
@@ -1817,7 +1870,30 @@ def _make_axes_helper(loc_settings, fraction, shrink, aspect, kwargs, parents):
18171870
aspect=aspect,
18181871
pad=pad)
18191872

1820-
cax.set_anchor(anchor)
1873+
return fig, pbcb, colorbar_info
1874+
1875+
1876+
def _make_axes_helper(loc_settings, fraction, shrink, aspect, kwargs, parents):
1877+
"""
1878+
Help function for `make_axes` and `make_bivar_axes`.
1879+
1880+
`make_axes` and `make_bivar_axes` are identical
1881+
except for the fact that `make_axes` also deals with:
1882+
1. the aspect
1883+
2. orientation.
1884+
1885+
`make_bivar_axes` on the other hand has no concept of orientation
1886+
and the aspect is handled by the `BivarColormap` instance, not during
1887+
creation of the axes.
1888+
"""
1889+
fig, pbcb, colorbar_info = _make_axes_get_pbcb(loc_settings, fraction, shrink,
1890+
aspect, kwargs, parents)
1891+
cax = fig.add_axes(pbcb, label="<colorbar>")
1892+
for a in colorbar_info["parents"]:
1893+
a._colorbars.append(cax) # tell the parent it has a colorbar
1894+
1895+
cax._colorbar_info = colorbar_info
1896+
cax.set_anchor(colorbar_info["anchor"])
18211897
return cax
18221898

18231899

@@ -1892,6 +1968,103 @@ def make_bivar_axes(parents, location=None, fraction=0.15,
18921968
return cax, kwargs
18931969

18941970

1971+
@_docstring.interpd
1972+
def make_multivar_axes(parents, n_variates, n_major, location=None, orientation=None,
1973+
fraction=0.15, shrink=1.0, aspect=20, **kwargs):
1974+
"""
1975+
Create an `~.axes.Axes` suitable for a mulitvariate colorbar.
1976+
1977+
The Axes is placed in the figure of the *parents* Axes, by resizing and
1978+
repositioning *parents*.
1979+
1980+
Parameters
1981+
----------
1982+
parents : `~matplotlib.axes.Axes` or iterable or `numpy.ndarray` of `~.axes.Axes`
1983+
The Axes to use as parents for placing the colorbar.
1984+
%(_make_axes_kw_doc)s
1985+
1986+
Returns
1987+
-------
1988+
cax : `~matplotlib.axes.Axes`
1989+
The child Axes.
1990+
kwargs : dict
1991+
The reduced keyword dictionary to be passed when creating the colorbar
1992+
instance.
1993+
"""
1994+
loc_settings = _normalize_location_orientation(location, orientation)
1995+
# put appropriate values into the kwargs dict for passing back to
1996+
# the Colorbar class
1997+
orientation = loc_settings['orientation']
1998+
kwargs['orientation'] = orientation
1999+
kwargs['ticklocation'] = loc_settings['location']
2000+
fig, pbcb, colorbar_info = _make_axes_get_pbcb(loc_settings, fraction, shrink,
2001+
aspect, kwargs, parents)
2002+
major_pad = 0.1
2003+
minor_pad = 0.6
2004+
2005+
# get the shape of the grid of new colorbars
2006+
if n_major == -1:
2007+
n_major = n_variates
2008+
n_minor = 1
2009+
else:
2010+
n_minor = n_variates // n_major
2011+
if n_major * n_minor < n_variates:
2012+
n_minor += 1
2013+
2014+
# adjust aspect
2015+
aspect = aspect/n_major
2016+
if loc_settings["location"] in ('top', 'bottom'):
2017+
aspect = 1.0 / aspect
2018+
2019+
# define how the bbox needs to be split
2020+
major_width = (1-major_pad) / n_major
2021+
if n_major > 1:
2022+
major_space = major_pad / (n_major - 1)
2023+
else:
2024+
major_space = 0
2025+
major_split = np.empty(2 * (n_major - 1))
2026+
major_split[0::2] = major_width
2027+
major_split[1::2] = major_space
2028+
major_split = np.cumsum(major_split)
2029+
2030+
minor_width = (1-minor_pad) / n_minor
2031+
if n_minor > 1:
2032+
minor_space = minor_pad / (n_minor - 1)
2033+
else:
2034+
minor_space = 0
2035+
minor_split = np.empty(2 * (n_minor - 1))
2036+
minor_split[0::2] = minor_width
2037+
minor_split[1::2] = minor_space
2038+
minor_split = np.cumsum(minor_split)
2039+
2040+
# make the colorbar axes
2041+
caxes = []
2042+
v = orientation == "vertical"
2043+
pbcbs = pbcb.splitx(*minor_split) if v else pbcb.splity(*minor_split)[::-1]
2044+
for i, mpcbc in enumerate(pbcbs):
2045+
if i % 2 == 1:
2046+
continue
2047+
fbcbs = mpcbc.splity(*major_split)[::-1] if v else mpcbc.splitx(*major_split)
2048+
for j, bbox in enumerate(fbcbs):
2049+
if j % 2 == 1:
2050+
continue
2051+
if len(caxes) < n_variates:
2052+
cax = fig.add_axes(bbox, label="<colorbar>")
2053+
caxes.append(cax)
2054+
2055+
for cax in caxes:
2056+
for a in colorbar_info["parents"]:
2057+
a._colorbars.append(cax) # tell the parent it has a colorbar
2058+
2059+
cax.set_anchor(colorbar_info["anchor"])
2060+
2061+
cax.set_box_aspect(aspect)
2062+
cax.set_aspect('auto')
2063+
cax._colorbar_info = colorbar_info
2064+
2065+
return caxes, kwargs, colorbar_info
2066+
2067+
18952068
def _make_axes_gridspec_helper(loc_settings, fraction, shrink, aspect, kwargs, parent):
18962069
"""
18972070
Help function for `make_axes_gridspec` and `make_bivar_axes_gridspec`.

lib/matplotlib/figure.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,72 @@ def bivar_colorbar(
14201420
cax.get_figure(root=False).stale = True
14211421
return cb
14221422

1423+
@_docstring.interpd
1424+
def multivar_colorbar(
1425+
self, mappable, *, caxes=None, ax=None, use_gridspec=True,
1426+
n_major=-1, **kwargs):
1427+
1428+
if isinstance(mappable, mpl.colorizer.Colorizer):
1429+
mappable = mcolorizer.ColorizingArtist(mappable)
1430+
if not isinstance(mappable.colorizer.cmap, mcolors.MultivarColormap):
1431+
raise ValueError("A multivariate colorbar can only be used together "
1432+
"with a multivariate colormap, not "
1433+
f"{type(mappable.colorizer.cmap)}")
1434+
1435+
n_variates = mappable.colorizer.cmap.n_variates
1436+
1437+
if ax is None:
1438+
ax = getattr(mappable, "axes", None)
1439+
1440+
cbar_info = None
1441+
if caxes is None:
1442+
if ax is None:
1443+
raise ValueError(
1444+
'Unable to determine Axes to steal space for Colorbar. '
1445+
'Either provide the *cax* argument to use as the Axes for '
1446+
'the Colorbar, provide the *ax* argument to steal space '
1447+
'from it, or add *mappable* to an Axes.')
1448+
fig = ( # Figure of first Axes; logic copied from make_axes.
1449+
[*ax.flat] if isinstance(ax, np.ndarray)
1450+
else [*ax] if np.iterable(ax)
1451+
else [ax])[0].get_figure(root=False)
1452+
current_ax = fig.gca()
1453+
if (fig.get_layout_engine() is not None and
1454+
not fig.get_layout_engine().colorbar_gridspec):
1455+
use_gridspec = False
1456+
if (use_gridspec
1457+
and isinstance(ax, mpl.axes._base._AxesBase)
1458+
and ax.get_subplotspec()):
1459+
caxes, kwargs, cbar_info = cbar.make_multivar_axes_gridspec(ax,
1460+
n_variates, n_major, **kwargs)
1461+
else:
1462+
caxes, kwargs, cbar_info = cbar.make_multivar_axes(ax,
1463+
n_variates, n_major, **kwargs)
1464+
# make_axes calls add_{axes,subplot} which changes gca; undo that.
1465+
fig.sca(current_ax)
1466+
for cax in caxes:
1467+
cax.grid(visible=False, which='both', axis='both')
1468+
1469+
if (hasattr(mappable, "get_figure") and
1470+
(mappable_host_fig := mappable.get_figure(root=True)) is not None):
1471+
# Warn in case of mismatch
1472+
if mappable_host_fig is not self._root_figure:
1473+
_api.warn_external(
1474+
f'Adding colorbar to a different Figure '
1475+
f'{repr(mappable_host_fig)} than '
1476+
f'{repr(self._root_figure)} which '
1477+
f'fig.colorbar is called on.')
1478+
NON_COLORBAR_KEYS = [ # remove kws that cannot be passed to Colorbar
1479+
'fraction', 'pad', 'shrink', 'anchor', 'panchor']
1480+
1481+
cb = cbar.MultivarColorbar(caxes, mappable, **{
1482+
k: v for k, v in kwargs.items() if k not in NON_COLORBAR_KEYS})
1483+
cb._set_colorbar_info(cbar_info)
1484+
1485+
for cax in caxes:
1486+
cax.get_figure(root=False).stale = True
1487+
return cb
1488+
14231489
def subplots_adjust(self, left=None, bottom=None, right=None, top=None,
14241490
wspace=None, hspace=None):
14251491
"""

0 commit comments

Comments
 (0)