Skip to content

Commit daf3dca

Browse files
committed
Cyclic cmaps, units accepts iterables
1 parent b561488 commit daf3dca

14 files changed

Lines changed: 157 additions & 3988 deletions

docs/showcase.rst

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,7 +1543,7 @@ for usage details.
15431543
15441544
.. image:: showcase/showcase_100_1.png
15451545
:width: 436px
1546-
:height: 4409px
1546+
:height: 4333px
15471547

15481548

15491549
Table of color cycles
@@ -1743,7 +1743,7 @@ adding a number to the end of the color string.
17431743
Diverging colormaps are easy to modify. Just use the ``cut`` argument to
17441744
`~proplot.colortools.Colormap`; this is great when you want to have a
17451745
sharper cutoff between negative and positive values for a diverging
1746-
colormap. Again, see `~proplot.axes.wrapper_cmap` for details.
1746+
colormap.
17471747

17481748
.. code:: ipython3
17491749
@@ -1764,12 +1764,36 @@ colormap. Again, see `~proplot.axes.wrapper_cmap` for details.
17641764
:height: 314px
17651765

17661766

1767+
Cyclic colormaps are also easy to modify. Just use the ``shift``
1768+
argument to `~proplot.colortools.Colormap` to rotate the colors in
1769+
your map. This will throw an error if the colormap is not recognized as
1770+
cyclic; if you’d like to shift colors for an on-the-fly colormap, make
1771+
sure to use ``cyclic=True`` to designate it so.
1772+
1773+
.. code:: ipython3
1774+
1775+
import proplot as plot
1776+
import numpy as np
1777+
f, axs = plot.subplots(ncols=3, innercolorbars='b', axwidth=2)
1778+
data = (np.random.rand(50,50)-0.48).cumsum(axis=1).cumsum(axis=0) - 50
1779+
for ax,shift in zip(axs,(0, 90, 180)):
1780+
m = ax.contourf(data, cmap='twilight', cmap_kw={'shift':shift}, levels=12)
1781+
ax.format(xlabel='x axis', ylabel='y axis', title=f'shift = {shift}',
1782+
suptitle='Rotating the colors in a cyclic colormap')
1783+
ax.bpanel.colorbar(m, locator='null')
1784+
1785+
1786+
1787+
.. image:: showcase/showcase_118_0.png
1788+
:width: 652px
1789+
:height: 287px
1790+
1791+
17671792
It is also easy to change the “gamma” of perceptually uniform colormap
17681793
on-the-fly. The “gamma” controls how the luminance and saturation
17691794
channels vary for a `~proplot.colortools.PerceptuallyUniformColromap`
17701795
map. A gamma larger than 1 emphasizes high luminance, low saturation
1771-
colors, and vice versa. Again, see `~proplot.axes.wrapper_cmap` for
1772-
details.
1796+
colors, and vice versa.
17731797

17741798
.. code:: ipython3
17751799
@@ -1788,7 +1812,7 @@ details.
17881812
17891813
17901814
1791-
.. image:: showcase/showcase_118_0.png
1815+
.. image:: showcase/showcase_120_0.png
17921816
:width: 652px
17931817
:height: 424px
17941818

@@ -1817,7 +1841,7 @@ reversed diverging colormaps by their “reversed” name – for example,
18171841
18181842
18191843
1820-
.. image:: showcase/showcase_120_0.png
1844+
.. image:: showcase/showcase_122_0.png
18211845
:width: 544px
18221846
:height: 478px
18231847

@@ -1846,7 +1870,7 @@ for details.
18461870
18471871
18481872
1849-
.. image:: showcase/showcase_123_0.png
1873+
.. image:: showcase/showcase_125_0.png
18501874
:width: 517px
18511875
:height: 356px
18521876

@@ -1870,7 +1894,7 @@ for details.
18701894
18711895
18721896
1873-
.. image:: showcase/showcase_124_1.png
1897+
.. image:: showcase/showcase_126_1.png
18741898
:width: 634px
18751899
:height: 318px
18761900

@@ -1909,7 +1933,7 @@ by the `~proplot.colortools.ColorDictSpecial` class.
19091933
19101934
19111935
1912-
.. image:: showcase/showcase_127_0.png
1936+
.. image:: showcase/showcase_129_0.png
19131937
:width: 436px
19141938
:height: 603px
19151939

docs/showcase/showcase_100_1.png

-10.1 KB
Loading

docs/showcase/showcase_118_0.png

22.8 KB
Loading

docs/showcase/showcase_120_0.png

21.8 KB
Loading

docs/showcase/showcase_122_0.png

31.9 KB
Loading

docs/showcase/showcase_32_1.png

-107 Bytes
Loading

proplot/axistools.py

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -395,12 +395,12 @@ def Scale(scale, *args, **kwargs):
395395
raise ValueError(f'Unknown scale {scale}. Options are {", ".join(scales.keys())}.')
396396
return scale
397397

398-
def InvertedScaleFactory(scale, **kwargs):
398+
def InvertedScaleFactory(scale, name=None, **kwargs):
399399
"""Returns name of newly registered *inverted* version of the
400400
`~matplotlib.scale.ScaleBase` corresponding to the scale name."""
401401
# Note I use private API here; no other way to get class directly
402402
scale = Scale(scale, **kwargs) # get the class
403-
name_ = f'{scale}_inverted' # name of inverted version
403+
name_ = name or f'{scale}_inverted' # name of inverted version
404404
class Inverted(scales[scale]):
405405
name = name_
406406
def get_transform(self):
@@ -792,7 +792,6 @@ class MercatorLatitudeScale(mscale.ScaleBase):
792792
name = 'mercator'
793793
"""Registered scale name."""
794794
def __init__(self, axis, *, thresh=85.0, **kwargs):
795-
"""See `~matplotlib.scale.ScaleBase`."""
796795
super().__init__()
797796
if thresh >= 90.0:
798797
raise ValueError('Threshold "thresh" must be <=90.')
@@ -811,36 +810,28 @@ def set_default_locators_and_formatters(self, axis):
811810
axis.set_minor_formatter(Formatter('null'))
812811

813812
class _MercatorLatitudeTransform(mtransforms.Transform):
814-
"""Mercator latitude coordinate transform."""
815813
# Default attributes
816814
input_dims = 1
817815
output_dims = 1
818816
is_separable = True
819817
has_inverse = True
820818
def __init__(self, thresh):
821-
# Initialize, declare attribute
822819
super().__init__()
823820
self.thresh = thresh
824821
def transform_non_affine(self, a):
825-
"""See `~matplotlib.transforms.Transform`."""
826822
# For M N-dimensional transform, transform MxN into result
827823
# So numbers stay the same, but data will then be linear in the
828824
# result of the math below.
829825
a = np.radians(a) # convert to radians
830826
m = ma.masked_where((a < -self.thresh) | (a > self.thresh), a)
831-
# m[m.mask] = np.nan
832-
# a[m.mask] = np.nan
833827
if m.mask.any():
834828
return ma.log(np.abs(ma.tan(m) + 1.0 / ma.cos(m)))
835829
else:
836830
return np.log(np.abs(np.tan(a) + 1.0 / np.cos(a)))
837831
def inverted(self):
838-
"""See `~matplotlib.transforms.Transform`."""
839-
# Just call inverse transform class
840832
return _InvertedMercatorLatitudeTransform(self.thresh)
841833

842834
class _InvertedMercatorLatitudeTransform(mtransforms.Transform):
843-
"""Inverse Mercator latitude coordinate transform."""
844835
# As above, but for the inverse transform
845836
input_dims = 1
846837
output_dims = 1
@@ -850,31 +841,28 @@ def __init__(self, thresh):
850841
super().__init__()
851842
self.thresh = thresh
852843
def transform_non_affine(self, a):
853-
"""See `~matplotlib.transforms.Transform`."""
854844
# m = ma.masked_where((a < -self.thresh) | (a > self.thresh), a)
855845
return np.degrees(np.arctan2(1, np.sinh(a))) # always assume in first/fourth quadrant, i.e. go from -pi/2 to pi/2
856846
def inverted(self):
857-
"""See `~matplotlib.transforms.Transform`."""
858847
return _MercatorLatitudeTransform(self.thresh)
859848

860849
class SineLatitudeScale(mscale.ScaleBase):
861850
r"""
862851
The scale function is as follows:
863852
864-
.. math:
853+
.. math::
865854
866855
y = \sin(x)
867856
868857
The inverse scale function is as follows:
869858
870-
.. math:
859+
.. math::
871860
872861
x = \arcsin(y)
873862
"""
874863
name = 'sine'
875864
"""Registered scale name."""
876865
def __init__(self, axis, **kwargs):
877-
"""See `~matplotlib.scale.ScaleBase`."""
878866
super().__init__()
879867
def get_transform(self):
880868
"""See `~matplotlib.scale.ScaleBase`."""
@@ -891,7 +879,6 @@ def set_default_locators_and_formatters(self, axis):
891879
axis.set_minor_formatter(Formatter('null'))
892880

893881
class _SineLatitudeTransform(mtransforms.Transform):
894-
"""Sine latitude transform."""
895882
# Default attributes
896883
input_dims = 1
897884
output_dims = 1
@@ -901,7 +888,6 @@ def __init__(self):
901888
# Initialize, declare attribute
902889
super().__init__()
903890
def transform_non_affine(self, a):
904-
"""See `~matplotlib.transforms.Transform`."""
905891
with np.errstate(invalid='ignore'): # NaNs will always be False
906892
m = (a >= -90) & (a <= 90)
907893
if not m.all():
@@ -910,11 +896,9 @@ def transform_non_affine(self, a):
910896
else:
911897
return np.sin(np.deg2rad(a))
912898
def inverted(self):
913-
"""See `~matplotlib.transforms.Transform`."""
914899
return _InvertedSineLatitudeTransform()
915900

916901
class _InvertedSineLatitudeTransform(mtransforms.Transform):
917-
"""Inverse sine latitude transform."""
918902
# Inverse of _SineLatitudeTransform
919903
input_dims = 1
920904
output_dims = 1
@@ -923,13 +907,11 @@ class _InvertedSineLatitudeTransform(mtransforms.Transform):
923907
def __init__(self):
924908
super().__init__()
925909
def transform_non_affine(self, a):
926-
"""See `~matplotlib.transforms.Transform`."""
927910
# Clipping, instead of setting invalid
928911
# NOTE: Using ma.arcsin below caused super weird errors, dun do that
929912
aa = a.copy()
930913
return np.rad2deg(np.arcsin(aa))
931914
def inverted(self):
932-
"""See `~matplotlib.transforms.Transform`."""
933915
return _SineLatitudeTransform()
934916

935917
#------------------------------------------------------------------------------#
@@ -939,7 +921,7 @@ class InverseScale(mscale.ScaleBase):
939921
r"""
940922
The scale function and inverse scale function are as follows:
941923
942-
.. math:
924+
.. math::
943925
944926
y = x^{-1}
945927
@@ -953,7 +935,6 @@ class InverseScale(mscale.ScaleBase):
953935
name = 'inverse'
954936
"""Registered scale name."""
955937
def __init__(self, axis, minpos=1e-2, **kwargs):
956-
"""See `~matplotlib.scale.ScaleBase`."""
957938
super().__init__()
958939
self.minpos = minpos
959940
def get_transform(self):
@@ -976,7 +957,6 @@ def set_default_locators_and_formatters(self, axis):
976957
axis.set_minor_formatter(Formatter('null')) # use 'minorlog' instead?
977958

978959
class _InverseTransform(mtransforms.Transform):
979-
"""Inverse transform."""
980960
# Create transform object
981961
input_dims = 1
982962
output_dims = 1
@@ -986,40 +966,37 @@ def __init__(self, minpos):
986966
super().__init__()
987967
self.minpos = minpos
988968
def transform(self, a):
989-
"""See `~matplotlib.scale.ScaleBase`."""
990969
a = np.array(a)
991970
aa = a.copy()
992971
# f = np.abs(a)<=self.minpos # attempt for negative-friendly
993972
# aa[f] = np.sign(a[f])*self.minpos
994973
aa[aa<=self.minpos] = self.minpos
995974
return 1.0/aa
996975
def transform_non_affine(self, a):
997-
"""See `~matplotlib.scale.ScaleBase`."""
998976
return self.transform(a)
999977
def inverted(self):
1000-
"""See `~matplotlib.scale.ScaleBase`."""
1001978
return _InverseTransform(self.minpos)
1002979

1003980
class DecibelScale(mscale.ScaleBase):
1004981
r"""
1005982
The scale function is as follows:
1006983
1007-
.. math:
984+
.. math::
1008985
1009986
y = 10\log_{10}(x)
1010987
1011988
The inverse scale function is as follows:
1012989
1013-
.. math:
990+
.. math::
1014991
1015992
x = 10^{y/10}
1016993
1017994
This scales axis coordinates to be linear in the "deciBel scale."
1018995
"""
1019996
# Declare name
1020997
name = 'db'
998+
"""Registered scale name."""
1021999
def __init__(self, axis, minpos=1e-300, **kwargs):
1022-
"""See `~matplotlib.scale.ScaleBase`."""
10231000
super().__init__()
10241001
self._transform = _InvertedDecibelTransform(minpos)
10251002
def limit_range_for_scale(self, vmin, vmax, minpos):
@@ -1039,7 +1016,7 @@ def get_transform(self):
10391016
return self._transform
10401017

10411018
class _DecibelTransform(mtransforms.Transform):
1042-
"""Exponential coordinate transform."""
1019+
# Create transform object
10431020
input_dims = 1
10441021
output_dims = 1
10451022
has_inverse = True
@@ -1057,7 +1034,7 @@ def inverted(self):
10571034
return _InvertedDecibelTransform(self.minpos)
10581035

10591036
class _InvertedDecibelTransform(mtransforms.Transform):
1060-
"""Inverse exponential coordinate transform."""
1037+
# Create transform object
10611038
input_dims = 1
10621039
output_dims = 1
10631040
has_inverse = True
@@ -1135,4 +1112,5 @@ def inverted(self):
11351112
mscale.register_scale(MercatorLatitudeScale)
11361113
ExpScaleFactory(-1.0/7, 1013.25, False, 'height') # scale pressure so it matches a height axis
11371114
ExpScaleFactory(-1.0/7, 1013.25, True, 'pressure') # scale height so it matches a pressure axis
1115+
InvertedScaleFactory('log', 'power')
11381116

0 commit comments

Comments
 (0)