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

Skip to content

Commit 1fe5820

Browse files
efiringNelleV
authored andcommitted
Improved LogNorm ticks and tick labels on colorbar
1 parent 5608c95 commit 1fe5820

File tree

2 files changed

+106
-41
lines changed

2 files changed

+106
-41
lines changed

lib/matplotlib/colorbar.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,8 @@ def __init__(self, ax, cmap=None,
310310
else:
311311
self.locator = ticks # Handle default in _ticker()
312312
if format is None:
313-
if isinstance(self.norm, colors.LogNorm):
314-
self.formatter = ticker.LogFormatterMathtext()
313+
if isinstance(self.norm, (colors.LogNorm, colors.SymLogNorm)):
314+
self.formatter = ticker.LogFormatterSciNotation()
315315
else:
316316
self.formatter = ticker.ScalarFormatter()
317317
elif cbook.is_string_like(format):
@@ -574,7 +574,12 @@ def _ticker(self):
574574
b = self.norm.boundaries
575575
locator = ticker.FixedLocator(b, nbins=10)
576576
elif isinstance(self.norm, colors.LogNorm):
577-
locator = ticker.LogLocator()
577+
locator = ticker.LogLocator(subs='all')
578+
# FIXME: we should be able to use the SymmetricalLogLocator
579+
# but we need to give it a transform, or modify the
580+
# locator to get what it needs from the Norm.
581+
# elif isinstance(self.norm, colors.SymLogNorm):
582+
# locator = ticker.SymmetricalLogLocator(...)
578583
else:
579584
if mpl.rcParams['_internal.classic_mode']:
580585
locator = ticker.MaxNLocator()

lib/matplotlib/ticker.py

Lines changed: 98 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -798,32 +798,58 @@ def _formatSciNotation(self, s):
798798

799799
class LogFormatter(Formatter):
800800
"""
801-
Format values for log axis.
801+
Base class for formatting ticks on a log or symlog scale.
802+
803+
It may be instantiated directly, or subclassed.
804+
805+
Parameters
806+
----------
807+
base : float, optional, default: 10.
808+
Base of the logarithm used in all calculations.
809+
810+
labelOnlyBase : bool, optional, default: False
811+
If True, label ticks only at integer powers of base.
812+
This is normally True for major ticks and False for
813+
minor ticks.
814+
815+
minor_thresholds : (subset, all), optional, default: (1, 0.4)
816+
If labelOnlyBase is False, these two numbers control
817+
the labeling of ticks that are not at integer powers of
818+
base; normally these are the minor ticks. The controlling
819+
parameter is the log of the axis data range. In the typical
820+
case where base is 10 it is the number of decades spanned
821+
by the axis, so we can call it 'numdec'. If ``numdec <= all``,
822+
all minor ticks will be labeled. If ``all < numdec <= subset``,
823+
then only a subset of minor ticks will be labeled, so as to
824+
avoid crowding. If ``numdec > subset`` then no minor ticks will
825+
be labeled.
826+
827+
Notes
828+
-----
829+
The `set_locs` method must be called to enable the subsetting
830+
logic controlled by the ``minor_thresholds`` parameter.
831+
832+
In some cases such as the colorbar, there is no distinction between
833+
major and minor ticks; the tick locations might be set manually,
834+
or by a locator that puts ticks at integer powers of base and
835+
at intermediate locations. For this situation, disable the
836+
minor_thresholds logic by using ``minor_thresholds=(np.inf, np.inf)``.
837+
838+
Examples
839+
--------
840+
To label a subset of minor ticks when the view limits span up
841+
to 2 decades, and all of the ticks when zoomed in to 0.5 decades
842+
or less, use ``minor_thresholds=(2, 0.5)``.
843+
844+
To label all minor ticks when the view limits span up to 1.5
845+
decades, use ``minor_thresholds=(1.5, 1.5)``.
846+
847+
802848
"""
803849
def __init__(self, base=10.0, labelOnlyBase=False,
804850
minor_thresholds=(1, 0.4)):
805-
"""
806-
`base` is used to locate the decade tick, which will be the only
807-
one to be labeled if `labelOnlyBase` is ``True``.
808851

809-
Parameters
810-
----------
811-
base : float, optional, default: 10.
812-
base of the logarithm.
813-
814-
labelOnlyBase : bool, optional, default: False
815-
whether label ticks only at integer multiples of base.
816-
This is normally True for major ticks and False for
817-
minor ticks.
818-
819-
minor_thresholds : (subset, all), optional, default: (1, 0.4)
820-
Thresholds applied to the data range measured in powers
821-
of the base (numbers of "decades", or 'numdec'), and
822-
effective only when labelOnlyBase is False. Then a
823-
subset of minor ticks will be labeled if `numdec <= subset`,
824-
and all will be labeled if `numdec <= all`.
825-
"""
826-
self._base = base + 0.0
852+
self._base = float(base)
827853
self.labelOnlyBase = labelOnlyBase
828854
self.minor_thresholds = minor_thresholds
829855
self._sublabels = None
@@ -850,17 +876,24 @@ def label_minor(self, labelOnlyBase):
850876
"""
851877
self.labelOnlyBase = labelOnlyBase
852878

853-
def set_locs(self, locs):
879+
def set_locs(self, locs=None):
880+
"""
881+
Use axis view limits to control which ticks are labeled.
882+
883+
The ``locs`` parameter is ignored in the present algorithm.
884+
885+
"""
886+
if np.isinf(self.minor_thresholds[0]):
887+
self._sublabels = None
888+
return
889+
854890
b = self._base
855891

856892
vmin, vmax = self.axis.get_view_interval()
857893
self.d = abs(vmax - vmin)
858894

859-
if not hasattr(self.axis, 'get_transform'):
860-
# This might be a colorbar dummy axis, do not attempt to get
861-
# transform
862-
numdec = 10
863-
elif hasattr(self.axis.get_transform(), 'linthresh'):
895+
if (hasattr(self.axis, 'get_transform') and
896+
hasattr(self.axis.get_transform(), 'linthresh')):
864897
t = self.axis.get_transform()
865898
linthresh = t.linthresh
866899
# Only compute the number of decades in the logarithmic part of the
@@ -882,11 +915,13 @@ def set_locs(self, locs):
882915
# Label only bases
883916
self._sublabels = set((1,))
884917
elif numdec > self.minor_thresholds[1]:
885-
# Add labels between bases at log-spaced coefficients
886-
c = np.logspace(0, 1, b//2 + 1, base=b)[1:-1]
918+
# Add labels between bases at log-spaced coefficients;
919+
# include base powers in case the locations include
920+
# "major" and "minor" points, as in colorbar.
921+
c = np.logspace(0, 1, b//2 + 1, base=b)
887922
self._sublabels = set(np.round(c))
888923
else:
889-
self._sublabels = set(np.linspace(2, b-1, b-2))
924+
self._sublabels = set(np.linspace(1, b, b-2))
890925

891926
def __call__(self, x, pos=None):
892927
"""
@@ -1809,7 +1844,22 @@ class LogLocator(Locator):
18091844

18101845
def __init__(self, base=10.0, subs=(1.0,), numdecs=4, numticks=None):
18111846
"""
1812-
place ticks on the location= base**i*subs[j]
1847+
Place ticks on the locations : subs[j] * base**i
1848+
1849+
Parameters
1850+
----------
1851+
subs : None, string, or sequence of float, optional, default (1.0,)
1852+
Gives the multiples of integer powers of the base at which
1853+
to place ticks. The default places ticks only at
1854+
integer powers of the base.
1855+
The permitted string values are ``'auto'`` and ``'all'``,
1856+
both of which use an algorithm based on the axis view
1857+
limits to determine whether and how to put ticks between
1858+
integer powers of the base. With ``'auto'``, ticks are
1859+
placed only between integer powers; with ``'all'``, the
1860+
integer powers are included. A value of None is
1861+
equivalent to ``'auto'``.
1862+
18131863
"""
18141864
if numticks is None:
18151865
if rcParams['_internal.classic_mode']:
@@ -1832,6 +1882,9 @@ def set_params(self, base=None, subs=None, numdecs=None, numticks=None):
18321882
if numticks is not None:
18331883
self.numticks = numticks
18341884

1885+
# FIXME: these base and subs functions are contrary to our
1886+
# usual and desired API.
1887+
18351888
def base(self, base):
18361889
"""
18371890
set the base of the log scaling (major tick every base**i, i integer)
@@ -1842,8 +1895,11 @@ def subs(self, subs):
18421895
"""
18431896
set the minor ticks for the log scaling every base**i*subs[j]
18441897
"""
1845-
if subs is None:
1846-
self._subs = None # autosub
1898+
if subs is None: # consistency with previous bad API
1899+
self._subs = 'auto'
1900+
elif cbook.is_string_like(subs):
1901+
# TODO: validation ('all', 'auto')
1902+
self._subs = subs
18471903
else:
18481904
self._subs = np.asarray(subs, dtype=float)
18491905

@@ -1887,13 +1943,17 @@ def tick_values(self, vmin, vmax):
18871943

18881944
numdec = math.floor(vmax) - math.ceil(vmin)
18891945

1890-
if self._subs is None: # autosub for minor ticks
1946+
if cbook.is_string_like(self._subs):
1947+
_first = 2.0 if self._subs == 'auto' else 1.0
18911948
if numdec > 10 or b < 3:
1892-
return np.array([]) # no minor ticks
1949+
if self._subs == 'auto':
1950+
return np.array([]) # no minor or major ticks
1951+
else:
1952+
subs = np.array([1.0]) # major ticks
18931953
elif numdec > 5 and b >= 6:
1894-
subs = np.arange(2.0, b, 2.0)
1954+
subs = np.arange(_first, b, 2.0)
18951955
else:
1896-
subs = np.arange(2.0, b)
1956+
subs = np.arange(_first, b)
18971957
else:
18981958
subs = self._subs
18991959

0 commit comments

Comments
 (0)