Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 252ef97

Browse filesBrowse files
committed
ENH: colorbar ticks adjustable to colorbar size
1 parent fc73593 commit 252ef97
Copy full SHA for 252ef97

File tree

Expand file treeCollapse file tree

10 files changed

+1040
-1174
lines changed
Filter options
Expand file treeCollapse file tree

10 files changed

+1040
-1174
lines changed
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
The ticks for colorbar now adjust for the size of the colorbar
2+
--------------------------------------------------------------
3+
4+
Colorbar ticks now adjust for the size of the colorbar if the
5+
colorbar is made from a mappable that is not a contour or
6+
doesn't have a BoundaryNorm, or boundaries are not specified.
7+
If boundaries, etc are specified, the colorbar maintains the
8+
original behaviour.
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Colorbar ticks can now be automatic
2+
-----------------------------------
3+
4+
The number of ticks on colorbars was appropriate for a large colorbar, but
5+
looked bad if the colorbar was made smaller (i.e. via the ``shrink`` kwarg).
6+
This has been changed so that the number of ticks is now responsive to how
7+
large the colorbar is.

‎lib/matplotlib/colorbar.py

Copy file name to clipboardExpand all lines: lib/matplotlib/colorbar.py
+170-48Lines changed: 170 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import six
2525
from six.moves import xrange, zip
2626

27+
import logging
2728
import warnings
2829

2930
import numpy as np
@@ -44,6 +45,8 @@
4445
import matplotlib._constrained_layout as constrained_layout
4546
from matplotlib import docstring
4647

48+
_log = logging.getLogger(__name__)
49+
4750
make_axes_kw_doc = '''
4851
4952
============= ====================================================
@@ -217,6 +220,65 @@ def _set_ticks_on_axis_warn(*args, **kw):
217220
warnings.warn("Use the colorbar set_ticks() method instead.")
218221

219222

223+
class _ColorbarAutoLocator(ticker.MaxNLocator):
224+
"""
225+
AutoLocator for Colorbar
226+
227+
This locator is just a `.MaxNLocator` except the min and max are
228+
clipped by the norm's min and max (i.e. vmin/vmax from the
229+
image/pcolor/contour object). This is necessary so ticks don't
230+
extrude into the "extend regions".
231+
"""
232+
233+
def __init__(self, colorbar):
234+
"""
235+
_ColorbarAutoLocator(colorbar)
236+
237+
This ticker needs to know the *colorbar* so that it can access
238+
its *vmin* and *vmax*. Otherwise it is the same as
239+
`~.ticker.AutoLocator`.
240+
"""
241+
242+
self._colorbar = colorbar
243+
nbins = 'auto'
244+
steps = [1, 2, 2.5, 5, 10]
245+
ticker.MaxNLocator.__init__(self, nbins=nbins, steps=steps)
246+
247+
def tick_values(self, vmin, vmax):
248+
vmin = max(vmin, self._colorbar.norm.vmin)
249+
vmax = min(vmax, self._colorbar.norm.vmax)
250+
return ticker.MaxNLocator.tick_values(self, vmin, vmax)
251+
252+
253+
class _ColorbarLogLocator(ticker.LogLocator):
254+
"""
255+
LogLocator for Colorbarbar
256+
257+
This locator is just a `.LogLocator` except the min and max are
258+
clipped by the norm's min and max (i.e. vmin/vmax from the
259+
image/pcolor/contour object). This is necessary so ticks don't
260+
extrude into the "extend regions".
261+
262+
"""
263+
def __init__(self, colorbar, *args, **kwargs):
264+
"""
265+
_ColorbarLogLocator(colorbar, *args, **kwargs)
266+
267+
This ticker needs to know the *colorbar* so that it can access
268+
its *vmin* and *vmax*. Otherwise it is the same as
269+
`~.ticker.LogLocator`. The ``*args`` and ``**kwargs`` are the
270+
same as `~.ticker.LogLocator`.
271+
"""
272+
self._colorbar = colorbar
273+
ticker.LogLocator.__init__(self, *args, **kwargs)
274+
275+
def tick_values(self, vmin, vmax):
276+
vmin = self._colorbar.norm.vmin
277+
vmax = self._colorbar.norm.vmax
278+
ticks = ticker.LogLocator.tick_values(self, vmin, vmax)
279+
return ticks[(ticks >= vmin) & (ticks <= vmax)]
280+
281+
220282
class ColorbarBase(cm.ScalarMappable):
221283
'''
222284
Draw a colorbar in an existing axes.
@@ -346,8 +408,15 @@ def draw_all(self):
346408
and do all the drawing.
347409
'''
348410

411+
# sets self._boundaries and self._values in real data units.
412+
# takes into account extend values:
349413
self._process_values()
414+
# sets self.vmin and vmax in data units, but just for
415+
# the part of the colorbar that is not part of the extend
416+
# patch:
350417
self._find_range()
418+
# returns the X and Y mesh, *but* this was/is in normalized
419+
# units:
351420
X, Y = self._mesh()
352421
C = self._values[:, np.newaxis]
353422
self._config_axes(X, Y)
@@ -356,36 +425,104 @@ def draw_all(self):
356425

357426
def config_axis(self):
358427
ax = self.ax
428+
if (isinstance(self.norm, colors.LogNorm)
429+
and self._use_auto_colorbar_locator()):
430+
# *both* axes are made log so that determining the
431+
# mid point is easier.
432+
ax.set_xscale('log')
433+
ax.set_yscale('log')
434+
359435
if self.orientation == 'vertical':
360-
ax.xaxis.set_ticks([])
361-
# location is either one of 'bottom' or 'top'
362-
ax.yaxis.set_label_position(self.ticklocation)
363-
ax.yaxis.set_ticks_position(self.ticklocation)
436+
long_axis, short_axis = ax.yaxis, ax.xaxis
364437
else:
365-
ax.yaxis.set_ticks([])
366-
# location is either one of 'left' or 'right'
367-
ax.xaxis.set_label_position(self.ticklocation)
368-
ax.xaxis.set_ticks_position(self.ticklocation)
438+
long_axis, short_axis = ax.xaxis, ax.yaxis
439+
440+
long_axis.set_label_position(self.ticklocation)
441+
long_axis.set_ticks_position(self.ticklocation)
442+
short_axis.set_ticks([])
443+
short_axis.set_ticks([], minor=True)
369444

370445
self._set_label()
371446

447+
def _get_ticker_locator_formatter(self):
448+
"""
449+
This code looks at the norm being used by the colorbar
450+
and decides what locator and formatter to use. If ``locator`` has
451+
already been set by hand, it just returns
452+
``self.locator, self.formatter``.
453+
"""
454+
locator = self.locator
455+
formatter = self.formatter
456+
if locator is None:
457+
if self.boundaries is None:
458+
if isinstance(self.norm, colors.NoNorm):
459+
nv = len(self._values)
460+
base = 1 + int(nv / 10)
461+
locator = ticker.IndexLocator(base=base, offset=0)
462+
elif isinstance(self.norm, colors.BoundaryNorm):
463+
b = self.norm.boundaries
464+
locator = ticker.FixedLocator(b, nbins=10)
465+
elif isinstance(self.norm, colors.LogNorm):
466+
locator = _ColorbarLogLocator(self)
467+
elif isinstance(self.norm, colors.SymLogNorm):
468+
# The subs setting here should be replaced
469+
# by logic in the locator.
470+
locator = ticker.SymmetricalLogLocator(
471+
subs=np.arange(1, 10),
472+
linthresh=self.norm.linthresh,
473+
base=10)
474+
else:
475+
if mpl.rcParams['_internal.classic_mode']:
476+
locator = ticker.MaxNLocator()
477+
else:
478+
locator = _ColorbarAutoLocator(self)
479+
else:
480+
b = self._boundaries[self._inside]
481+
locator = ticker.FixedLocator(b, nbins=10)
482+
_log.debug('locator: %r', locator)
483+
return locator, formatter
484+
485+
def _use_auto_colorbar_locator(self):
486+
"""
487+
Return if we should use an adjustable tick locator or a fixed
488+
one. (check is used twice so factored out here...)
489+
"""
490+
return (self.boundaries is None
491+
and self.values is None
492+
and ((type(self.norm) == colors.Normalize)
493+
or (type(self.norm) == colors.LogNorm)))
494+
372495
def update_ticks(self):
373496
"""
374497
Force the update of the ticks and ticklabels. This must be
375498
called whenever the tick locator and/or tick formatter changes.
376499
"""
377500
ax = self.ax
378-
ticks, ticklabels, offset_string = self._ticker()
379-
if self.orientation == 'vertical':
380-
ax.yaxis.set_ticks(ticks)
381-
ax.set_yticklabels(ticklabels)
382-
ax.yaxis.get_major_formatter().set_offset_string(offset_string)
501+
# get the locator and formatter. Defaults to
502+
# self.locator if not None..
503+
locator, formatter = self._get_ticker_locator_formatter()
383504

505+
if self.orientation == 'vertical':
506+
long_axis, short_axis = ax.yaxis, ax.xaxis
384507
else:
385-
ax.xaxis.set_ticks(ticks)
386-
ax.set_xticklabels(ticklabels)
387-
ax.xaxis.get_major_formatter().set_offset_string(offset_string)
388-
508+
long_axis, short_axis = ax.xaxis, ax.yaxis
509+
510+
if self._use_auto_colorbar_locator():
511+
_log.debug('Using auto colorbar locator on colorbar')
512+
_log.debug('locator: %r', locator)
513+
long_axis.set_major_locator(locator)
514+
long_axis.set_major_formatter(formatter)
515+
if type(self.norm) == colors.LogNorm:
516+
long_axis.set_minor_locator(_ColorbarLogLocator(self,
517+
base=10., subs='auto'))
518+
long_axis.set_minor_formatter(ticker.NullFormatter())
519+
else:
520+
_log.debug('Using fixed locator on colorbar')
521+
ticks, ticklabels, offset_string = self._ticker(locator, formatter)
522+
long_axis.set_ticks(ticks)
523+
long_axis.set_ticklabels(ticklabels)
524+
long_axis.get_major_formatter().set_offset_string(offset_string)
525+
389526
def set_ticks(self, ticks, update_ticks=True):
390527
"""
391528
Set tick locations.
@@ -520,6 +657,7 @@ def _add_solids(self, X, Y, C):
520657
# since the axes object should already have hold set.
521658
_hold = self.ax._hold
522659
self.ax._hold = True
660+
_log.debug('Setting pcolormesh')
523661
col = self.ax.pcolormesh(*args, **kw)
524662
self.ax._hold = _hold
525663
#self.add_observer(col) # We should observe, not be observed...
@@ -573,39 +711,11 @@ def add_lines(self, levels, colors, linewidths, erase=True):
573711
self.ax.add_collection(col)
574712
self.stale = True
575713

576-
def _ticker(self):
714+
def _ticker(self, locator, formatter):
577715
'''
578716
Return the sequence of ticks (colorbar data locations),
579717
ticklabels (strings), and the corresponding offset string.
580718
'''
581-
locator = self.locator
582-
formatter = self.formatter
583-
if locator is None:
584-
if self.boundaries is None:
585-
if isinstance(self.norm, colors.NoNorm):
586-
nv = len(self._values)
587-
base = 1 + int(nv / 10)
588-
locator = ticker.IndexLocator(base=base, offset=0)
589-
elif isinstance(self.norm, colors.BoundaryNorm):
590-
b = self.norm.boundaries
591-
locator = ticker.FixedLocator(b, nbins=10)
592-
elif isinstance(self.norm, colors.LogNorm):
593-
locator = ticker.LogLocator(subs='all')
594-
elif isinstance(self.norm, colors.SymLogNorm):
595-
# The subs setting here should be replaced
596-
# by logic in the locator.
597-
locator = ticker.SymmetricalLogLocator(
598-
subs=np.arange(1, 10),
599-
linthresh=self.norm.linthresh,
600-
base=10)
601-
else:
602-
if mpl.rcParams['_internal.classic_mode']:
603-
locator = ticker.MaxNLocator()
604-
else:
605-
locator = ticker.AutoLocator()
606-
else:
607-
b = self._boundaries[self._inside]
608-
locator = ticker.FixedLocator(b, nbins=10)
609719
if isinstance(self.norm, colors.NoNorm) and self.boundaries is None:
610720
intv = self._values[0], self._values[-1]
611721
else:
@@ -845,17 +955,29 @@ def _mesh(self):
845955
transposition for a horizontal colorbar are done outside
846956
this function.
847957
'''
958+
# if boundaries and values are None, then we can go ahead and
959+
# scale this up for Auto tick location. Otherwise we
960+
# want to keep normalized between 0 and 1 and use manual tick
961+
# locations.
962+
848963
x = np.array([0.0, 1.0])
849964
if self.spacing == 'uniform':
850965
y = self._uniform_y(self._central_N())
851966
else:
852967
y = self._proportional_y()
968+
if self._use_auto_colorbar_locator():
969+
y = self.norm.inverse(y)
970+
x = self.norm.inverse(x)
853971
self._y = y
854972
X, Y = np.meshgrid(x, y)
973+
if self._use_auto_colorbar_locator():
974+
xmid = self.norm.inverse(0.5)
975+
else:
976+
xmid = 0.5
855977
if self._extend_lower() and not self.extendrect:
856-
X[0, :] = 0.5
978+
X[0, :] = xmid
857979
if self._extend_upper() and not self.extendrect:
858-
X[-1, :] = 0.5
980+
X[-1, :] = xmid
859981
return X, Y
860982

861983
def _locate(self, x):
Loading
Binary file not shown.
Loading

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.