Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ ProPlot v0.4.4 (2020-##-##)
.. rubric:: Features

- Add back `Fabio Crameri's scientific colour maps <http://www.fabiocrameri.ch/colourmaps.php>`__ (:pr:`116`).
- Permit *descending* `~proplot.styletools.BinNorm` and `~proplot.styletools.LinearSegmentedNorm`
levels (:pr:`119`).
- Permit overriding the font weight, style, and stretch in the
`~proplot.styletools.show_fonts` table (:commit:`e8b9ee38`).
- Permit hiding "unknown" colormaps and color cycles in the
Expand Down
13 changes: 7 additions & 6 deletions proplot/axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,12 +613,13 @@ def _update_title(self, obj, **kwargs):
return self.text(x, y, text, **kwextra)

def format(
self, *, title=None, top=None,
figtitle=None, suptitle=None, rowlabels=None, collabels=None,
leftlabels=None, rightlabels=None,
toplabels=None, bottomlabels=None,
llabels=None, rlabels=None, tlabels=None, blabels=None,
**kwargs):
self, *, title=None, top=None,
figtitle=None, suptitle=None, rowlabels=None, collabels=None,
leftlabels=None, rightlabels=None,
toplabels=None, bottomlabels=None,
llabels=None, rlabels=None, tlabels=None, blabels=None,
**kwargs
):
"""
Modify the axes title(s), the a-b-c label, row and column labels, and
the figure title. Called by `XYAxes.format`,
Expand Down
31 changes: 26 additions & 5 deletions proplot/styletools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2578,14 +2578,20 @@ def __init__(
# detect it... even though we completely override it.
# Check input levels
levels = np.atleast_1d(levels)
diffs = np.sign(np.diff(levels))
self._descending = False
if levels.ndim != 1:
raise ValueError('Levels must be 1-dimensional.')
elif levels.size < 2:
raise ValueError('Need at least two levels.')
elif ((levels[1:] - levels[:-1]) <= 0).any():
elif all(diffs == -1):
self._descending = True
levels = levels[::-1]
elif not all(diffs == 1):
raise ValueError(
f'Levels {levels!r} must be monotonically increasing.'
)

# Check input extend
extend = extend or 'neither'
extends = ('both', 'min', 'max', 'neither')
Expand All @@ -2594,6 +2600,7 @@ def __init__(
f'Unknown extend option {extend!r}. Options are: '
+ ', '.join(map(repr, extends)) + '.'
)

# Check input normalizer
if not norm:
norm = mcolors.Normalize()
Expand All @@ -2608,7 +2615,8 @@ def __init__(
'matplotlib.colors.BoundaryNorm.'
)

# Get color coordinates corresponding to each bin
# Normalize the level boundaries and get color coordinates
# corresponding to each bin.
x_b = norm(levels)
if isinstance(x_b, ma.core.MaskedArray):
x_b = x_b.filled(np.nan)
Expand Down Expand Up @@ -2683,6 +2691,8 @@ def __call__(self, value, clip=None):
value = np.clip(value, self._bmin, self._bmax)
xq = self._norm(value)
yq = self._y[np.searchsorted(self._x_b, xq)]
if self._descending:
yq = 1 - yq
mask = ma.getmaskarray(xq)
return ma.array(yq, mask=mask)

Expand Down Expand Up @@ -2714,16 +2724,25 @@ def __init__(self, levels, vmin=None, vmax=None, clip=False):
maximum levels.
"""
levels = np.atleast_1d(levels)
if levels.size < 2:
diffs = np.sign(np.diff(levels))
y = np.linspace(0, 1, len(levels))
self._descending = False
if levels.ndim != 1:
raise ValueError('Levels must be 1-dimensional.')
elif levels.size < 2:
raise ValueError('Need at least two levels.')
elif ((levels[1:] - levels[:-1]) <= 0).any():
elif all(diffs == -1):
self._descending = True
levels = levels[::-1]
y = y[::-1]
elif not all(diffs == 1):
raise ValueError(
f'Levels {levels!r} must be monotonically increasing.'
)
vmin, vmax = levels.min(), levels.max()
super().__init__(vmin, vmax, clip=clip) # second level superclass
self._x = levels
self._y = np.linspace(0, 1, len(levels))
self._y = y

def __call__(self, value, clip=None):
"""
Expand Down Expand Up @@ -2756,6 +2775,8 @@ def __call__(self, value, clip=None):
idx[idx == len(x)] = len(x) - 1
distance = (xq - x[idx - 1]) / (x[idx] - x[idx - 1])
yq = distance * (y[idx] - y[idx - 1]) + y[idx - 1]
if self._descending:
yq = 1 - yq
mask = ma.getmaskarray(xq)
return ma.array(yq, mask=mask)

Expand Down
29 changes: 18 additions & 11 deletions proplot/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1893,9 +1893,11 @@ def cmap_changer(
continue
if 'contour' in name and 'contourf' not in name:
continue
if len(val) < 2 or any(np.diff(val) <= 0):
if len(val) < 2 or any(
np.sign(np.diff(val)) != np.sign(val[1] - val[0])
):
raise ValueError(
f'{key!r} must be monotonically increasing and '
f'{key!r} must be monotonically increasing or decreasing and '
f'at least length 2, got {val}.'
)

Expand Down Expand Up @@ -1951,7 +1953,7 @@ def cmap_changer(
if not np.iterable(levels) or len(levels) == 1:
norm = 'linear'
else:
diff = np.diff(levels)
diff = np.abs(np.diff(levels)) # permit descending
eps = diff.mean() / 1e3
if (np.abs(np.diff(diff)) >= eps).any():
norm = 'segmented'
Expand Down Expand Up @@ -2285,8 +2287,9 @@ def legend_wrapper(
)
if not np.iterable(handles): # e.g. a mappable object
handles = [handles]
if labels is not None and (not np.iterable(
labels) or isinstance(labels, str)):
if labels is not None and (
not np.iterable(labels) or isinstance(labels, str)
):
labels = [labels]

# Legend entry for colormap or scatterplot object
Expand Down Expand Up @@ -2647,10 +2650,7 @@ def colorbar_wrapper(
norm : normalizer spec, optional
Ignored if `values` is ``None``. The normalizer
for converting `values` to colormap colors. Passed to the
`~proplot.styletools.Norm` constructor. As an example, if your
values are logarithmically spaced but you want the level boundaries
to appear halfway in-between the colorbar ticks, try
``norm='log'``.
`~proplot.styletools.Norm` constructor.
norm_kw : dict-like, optional
The normalizer settings. Passed to `~proplot.styletools.Norm`.
edgecolor, linewidth : optional
Expand Down Expand Up @@ -2713,7 +2713,8 @@ def colorbar_wrapper(
'use_gridspec': True,
'orientation': orientation,
'extend': extend,
'spacing': 'uniform'})
'spacing': 'uniform'
})
kwargs.setdefault('drawedges', grid)

# Text property keyword args
Expand Down Expand Up @@ -2828,6 +2829,7 @@ def colorbar_wrapper(
# random points along the axis. If values were provided as keyword arg,
# this is colorbar from lines/colors, and we label *all* values by default.
# TODO: Handle more of the log locator stuff here instead of cmap_changer?
norm = getattr(mappable, 'norm', None)
if values is not None and locator is None:
locator = values
tickminor = False
Expand All @@ -2837,7 +2839,7 @@ def colorbar_wrapper(
if locator is not None:
break
if locator is None: # i.e. no attributes found
if isinstance(getattr(mappable, 'norm', None), mcolors.LogNorm):
if isinstance(norm, mcolors.LogNorm):
locator = 'log'
else:
locator = 'auto'
Expand Down Expand Up @@ -2973,6 +2975,11 @@ def colorbar_wrapper(
axis.set_tick_params(which=which, **kw)
axis.set_ticks_position(ticklocation)

# Invert the axis if BinNorm
# TODO: When is norm *not* BinNorm? Should be pretty much always.
if isinstance(norm, styletools.BinNorm):
axis.set_inverted(norm._descending)

# *Never* rasterize because it causes misalignment with border lines
if cb.solids:
cb.solids.set_rasterized(False)
Expand Down