Skip to content

Commit 5c48c10

Browse files
committed
ENH: colorbar ticks adjustable to colorbar size
1 parent c9f80ef commit 5c48c10

File tree

6 files changed

+933
-839
lines changed

6 files changed

+933
-839
lines changed
+8
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.

lib/matplotlib/colorbar.py

+104-40
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
@@ -43,6 +44,8 @@
4344

4445
from matplotlib import docstring
4546

47+
_log = logging.getLogger(__name__)
48+
4649
make_axes_kw_doc = '''
4750
4851
============= ====================================================
@@ -216,6 +219,22 @@ def _set_ticks_on_axis_warn(*args, **kw):
216219
warnings.warn("Use the colorbar set_ticks() method instead.")
217220

218221

222+
class ColorbarAutoLocator(ticker.MaxNLocator):
223+
""" AutoLocator for Colorbar
224+
"""
225+
226+
def __init__(self, colorbar, *args, **kwargs):
227+
self._colorbar = colorbar
228+
nbins = 'auto'
229+
steps = [1, 2, 2.5, 5, 10]
230+
ticker.MaxNLocator.__init__(self, nbins=nbins, steps=steps)
231+
232+
def tick_values(self, vmin, vmax):
233+
vmin = max(vmin, self._colorbar.norm.vmin)
234+
vmax = min(vmax, self._colorbar.norm.vmax)
235+
return ticker.MaxNLocator.tick_values(self, vmin, vmax)
236+
237+
219238
class ColorbarBase(cm.ScalarMappable):
220239
'''
221240
Draw a colorbar in an existing axes.
@@ -346,8 +365,15 @@ def draw_all(self):
346365
and do all the drawing.
347366
'''
348367

368+
# sets self._boundaries and self._values in real data units.
369+
# takes into account extend values:
349370
self._process_values()
371+
# sets self.vmin and vmax in data units, but just for
372+
# the part of the colorbar that is not part of the extend
373+
# patch:
350374
self._find_range()
375+
# returns the X and Y mesh, *but* this was/is in normalized
376+
# units:
351377
X, Y = self._mesh()
352378
C = self._values[:, np.newaxis]
353379
self._config_axes(X, Y)
@@ -369,22 +395,76 @@ def config_axis(self):
369395

370396
self._set_label()
371397

398+
def _get_ticker_locator_formatter(self):
399+
locator = self.locator
400+
formatter = self.formatter
401+
if locator is None:
402+
if self.boundaries is None:
403+
if isinstance(self.norm, colors.NoNorm):
404+
nv = len(self._values)
405+
base = 1 + int(nv / 10)
406+
locator = ticker.IndexLocator(base=base, offset=0)
407+
elif isinstance(self.norm, colors.BoundaryNorm):
408+
b = self.norm.boundaries
409+
locator = ticker.FixedLocator(b, nbins=10)
410+
elif isinstance(self.norm, colors.LogNorm):
411+
locator = ticker.LogLocator(subs='all')
412+
elif isinstance(self.norm, colors.SymLogNorm):
413+
# The subs setting here should be replaced
414+
# by logic in the locator.
415+
locator = ticker.SymmetricalLogLocator(
416+
subs=np.arange(1, 10),
417+
linthresh=self.norm.linthresh,
418+
base=10)
419+
else:
420+
if mpl.rcParams['_internal.classic_mode']:
421+
locator = ticker.MaxNLocator()
422+
else:
423+
locator = ColorbarAutoLocator(self)
424+
else:
425+
b = self._boundaries[self._inside]
426+
locator = ticker.FixedLocator(b, nbins=10)
427+
return locator, formatter
428+
429+
def _use_adjustable(self):
430+
"""
431+
Return if we should use an adjustable tick locator or a fixed
432+
one. (check is used twice so factored out here...)
433+
"""
434+
return (self.boundaries is None
435+
and self.values is None
436+
and not isinstance(self.norm, colors.BoundaryNorm))
437+
372438
def update_ticks(self):
373439
"""
374440
Force the update of the ticks and ticklabels. This must be
375441
called whenever the tick locator and/or tick formatter changes.
376442
"""
377443
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)
383-
444+
# get the locator and formatter. Defaults to
445+
# self.locator if not None..
446+
locator, formatter = self._get_ticker_locator_formatter()
447+
448+
if self._use_adjustable():
449+
_log.debug('Using adjustable locator on colorbar')
450+
if self.orientation == 'vertical':
451+
ax.yaxis.set_major_locator(locator)
452+
ax.yaxis.set_major_formatter(formatter)
453+
else:
454+
ax.xaxis.set_major_locator(locator)
455+
ax.xaxis.set_major_formatter(formatter)
384456
else:
385-
ax.xaxis.set_ticks(ticks)
386-
ax.set_xticklabels(ticklabels)
387-
ax.xaxis.get_major_formatter().set_offset_string(offset_string)
457+
_log.debug('Using fixed locator on colorbar')
458+
ticks, ticklabels, offset_string = self._ticker(locator, formatter)
459+
if self.orientation == 'vertical':
460+
ax.yaxis.set_ticks(ticks)
461+
ax.set_yticklabels(ticklabels)
462+
ax.yaxis.get_major_formatter().set_offset_string(offset_string)
463+
464+
else:
465+
ax.xaxis.set_ticks(ticks)
466+
ax.set_xticklabels(ticklabels)
467+
ax.xaxis.get_major_formatter().set_offset_string(offset_string)
388468

389469
def set_ticks(self, ticks, update_ticks=True):
390470
"""
@@ -574,39 +654,12 @@ def add_lines(self, levels, colors, linewidths, erase=True):
574654
self.ax.add_collection(col)
575655
self.stale = True
576656

577-
def _ticker(self):
657+
def _ticker(self, locator, formatter):
578658
'''
579659
Return the sequence of ticks (colorbar data locations),
580660
ticklabels (strings), and the corresponding offset string.
581661
'''
582-
locator = self.locator
583-
formatter = self.formatter
584-
if locator is None:
585-
if self.boundaries is None:
586-
if isinstance(self.norm, colors.NoNorm):
587-
nv = len(self._values)
588-
base = 1 + int(nv / 10)
589-
locator = ticker.IndexLocator(base=base, offset=0)
590-
elif isinstance(self.norm, colors.BoundaryNorm):
591-
b = self.norm.boundaries
592-
locator = ticker.FixedLocator(b, nbins=10)
593-
elif isinstance(self.norm, colors.LogNorm):
594-
locator = ticker.LogLocator(subs='all')
595-
elif isinstance(self.norm, colors.SymLogNorm):
596-
# The subs setting here should be replaced
597-
# by logic in the locator.
598-
locator = ticker.SymmetricalLogLocator(
599-
subs=np.arange(1, 10),
600-
linthresh=self.norm.linthresh,
601-
base=10)
602-
else:
603-
if mpl.rcParams['_internal.classic_mode']:
604-
locator = ticker.MaxNLocator()
605-
else:
606-
locator = ticker.AutoLocator()
607-
else:
608-
b = self._boundaries[self._inside]
609-
locator = ticker.FixedLocator(b, nbins=10)
662+
# locator, formatter = _get_ticker_locator_formatter()
610663
if isinstance(self.norm, colors.NoNorm) and self.boundaries is None:
611664
intv = self._values[0], self._values[-1]
612665
else:
@@ -851,12 +904,23 @@ def _mesh(self):
851904
y = self._uniform_y(self._central_N())
852905
else:
853906
y = self._proportional_y()
907+
# if boundaries and values are None, then we can go ahead and
908+
# scale this up for Auto tick location. Otherwise we
909+
# want to keep normalized between 0 and 1 and use manual tick
910+
# locations.
911+
if self._use_adjustable():
912+
dy = self.vmax - self.vmin
913+
y = y * dy + self.vmin
914+
x = x * dy
915+
else:
916+
dy = 1.0
854917
self._y = y
918+
855919
X, Y = np.meshgrid(x, y)
856920
if self._extend_lower() and not self.extendrect:
857-
X[0, :] = 0.5
921+
X[0, :] = 0.5 * dy
858922
if self._extend_upper() and not self.extendrect:
859-
X[-1, :] = 0.5
923+
X[-1, :] = 0.5 * dy
860924
return X, Y
861925

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

0 commit comments

Comments
 (0)