Skip to content

Commit a31cadd

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

File tree

11 files changed

+1021
-1166
lines changed

11 files changed

+1021
-1166
lines changed
Lines 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.
Lines 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/axis.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,6 +2364,7 @@ def set_view_interval(self, vmin, vmax, ignore=False):
23642364
:meth:`~matplotlib.axes.Axes.set_ylim`.
23652365
23662366
"""
2367+
23672368
if ignore:
23682369
self.axes.viewLim.intervaly = vmin, vmax
23692370
else:

lib/matplotlib/colorbar.py

Lines changed: 153 additions & 41 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
@@ -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,38 @@ 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+
238+
class ColorbarLogLocator(ticker.LogLocator):
239+
""" LogLocator for Colorbarbar
240+
"""
241+
def __init__(self, colorbar, vmin, vmax, *args, **kwargs):
242+
self.vmin = vmin
243+
self.vmax = vmax
244+
self._colorbar = colorbar
245+
ticker.LogLocator.__init__(self, *args, **kwargs)
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+
ticks = ticker.LogLocator.tick_values(self, vmin, vmax)
251+
return ticks[(ticks >= vmin) & (ticks <= vmax)]
252+
253+
219254
class ColorbarBase(cm.ScalarMappable):
220255
'''
221256
Draw a colorbar in an existing axes.
@@ -346,45 +381,132 @@ def draw_all(self):
346381
and do all the drawing.
347382
'''
348383

384+
# sets self._boundaries and self._values in real data units.
385+
# takes into account extend values:
349386
self._process_values()
387+
# sets self.vmin and vmax in data units, but just for
388+
# the part of the colorbar that is not part of the extend
389+
# patch:
350390
self._find_range()
391+
# returns the X and Y mesh, *but* this was/is in normalized
392+
# units:
351393
X, Y = self._mesh()
352394
C = self._values[:, np.newaxis]
353395
self._config_axes(X, Y)
354396
if self.filled:
355397
self._add_solids(X, Y, C)
398+
# self._set_view_limits()
356399

357400
def config_axis(self):
358401
ax = self.ax
359402
if self.orientation == 'vertical':
360-
ax.xaxis.set_ticks([])
361403
# location is either one of 'bottom' or 'top'
362404
ax.yaxis.set_label_position(self.ticklocation)
363405
ax.yaxis.set_ticks_position(self.ticklocation)
406+
if (isinstance(self.norm, colors.LogNorm)
407+
and self._use_adjustable()):
408+
ax.set_xscale('log')
409+
ax.set_yscale('log')
410+
ax.xaxis.set_ticks([])
411+
ax.xaxis.set_ticks([], minor=True)
412+
364413
else:
365-
ax.yaxis.set_ticks([])
366414
# location is either one of 'left' or 'right'
367415
ax.xaxis.set_label_position(self.ticklocation)
368416
ax.xaxis.set_ticks_position(self.ticklocation)
417+
if (isinstance(self.norm, colors.LogNorm)
418+
and self._use_adjustable()):
419+
ax.set_xscale('log')
420+
ax.set_yscale('log')
421+
ax.yaxis.set_ticks([])
422+
ax.yaxis.set_ticks([], minor=True)
369423

370424
self._set_label()
371425

426+
def _get_ticker_locator_formatter(self):
427+
locator = self.locator
428+
formatter = self.formatter
429+
if locator is None:
430+
if self.boundaries is None:
431+
if isinstance(self.norm, colors.NoNorm):
432+
nv = len(self._values)
433+
base = 1 + int(nv / 10)
434+
locator = ticker.IndexLocator(base=base, offset=0)
435+
elif isinstance(self.norm, colors.BoundaryNorm):
436+
b = self.norm.boundaries
437+
locator = ticker.FixedLocator(b, nbins=10)
438+
elif isinstance(self.norm, colors.LogNorm):
439+
locator = ColorbarLogLocator(self, self.vmin, self.vmax)
440+
elif isinstance(self.norm, colors.SymLogNorm):
441+
# The subs setting here should be replaced
442+
# by logic in the locator.
443+
locator = ticker.SymmetricalLogLocator(
444+
subs=np.arange(1, 10),
445+
linthresh=self.norm.linthresh,
446+
base=10)
447+
else:
448+
if mpl.rcParams['_internal.classic_mode']:
449+
locator = ticker.MaxNLocator()
450+
else:
451+
locator = ColorbarAutoLocator(self)
452+
else:
453+
b = self._boundaries[self._inside]
454+
locator = ticker.FixedLocator(b, nbins=10)
455+
_log.debug('locator: %r', locator)
456+
return locator, formatter
457+
458+
def _use_adjustable(self):
459+
"""
460+
Return if we should use an adjustable tick locator or a fixed
461+
one. (check is used twice so factored out here...)
462+
"""
463+
return (self.boundaries is None
464+
and self.values is None
465+
and ((type(self.norm) == colors.Normalize)
466+
or (type(self.norm) == colors.LogNorm)))
467+
372468
def update_ticks(self):
373469
"""
374470
Force the update of the ticks and ticklabels. This must be
375471
called whenever the tick locator and/or tick formatter changes.
376472
"""
377473
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)
474+
# get the locator and formatter. Defaults to
475+
# self.locator if not None..
476+
locator, formatter = self._get_ticker_locator_formatter()
477+
478+
if self._use_adjustable():
479+
_log.debug('Using adjustable locator on colorbar')
480+
_log.debug('locator: %r', locator)
481+
#self._find_range()
482+
if self.orientation == 'vertical':
483+
ax.yaxis.set_major_locator(locator)
484+
ax.yaxis.set_major_formatter(formatter)
485+
if type(self.norm) == colors.LogNorm:
486+
ax.yaxis.set_minor_locator(ColorbarLogLocator(self,
487+
self.vmin, self.vmax, 10., 'all'))
488+
ax.yaxis.set_minor_formatter(ticker.NullFormatter())
489+
490+
else:
491+
ax.xaxis.set_major_locator(locator)
492+
ax.xaxis.set_major_formatter(formatter)
493+
if type(self.norm) == colors.LogNorm:
494+
ax.xaxis.set_minor_locator(ColorbarLogLocator(self,
495+
self.vmin, self.vmax, 10., 'all'))
496+
ax.xaxis.set_minor_formatter(ticker.NullFormatter())
383497

384498
else:
385-
ax.xaxis.set_ticks(ticks)
386-
ax.set_xticklabels(ticklabels)
387-
ax.xaxis.get_major_formatter().set_offset_string(offset_string)
499+
_log.debug('Using fixed locator on colorbar')
500+
ticks, ticklabels, offset_string = self._ticker(locator, formatter)
501+
if self.orientation == 'vertical':
502+
ax.yaxis.set_ticks(ticks)
503+
ax.set_yticklabels(ticklabels)
504+
ax.yaxis.get_major_formatter().set_offset_string(offset_string)
505+
506+
else:
507+
ax.xaxis.set_ticks(ticks)
508+
ax.set_xticklabels(ticklabels)
509+
ax.xaxis.get_major_formatter().set_offset_string(offset_string)
388510

389511
def set_ticks(self, ticks, update_ticks=True):
390512
"""
@@ -520,6 +642,7 @@ def _add_solids(self, X, Y, C):
520642
# since the axes object should already have hold set.
521643
_hold = self.ax._hold
522644
self.ax._hold = True
645+
_log.debug('Setting pcolormesh')
523646
col = self.ax.pcolormesh(*args, **kw)
524647
self.ax._hold = _hold
525648
#self.add_observer(col) # We should observe, not be observed...
@@ -574,39 +697,12 @@ def add_lines(self, levels, colors, linewidths, erase=True):
574697
self.ax.add_collection(col)
575698
self.stale = True
576699

577-
def _ticker(self):
700+
def _ticker(self, locator, formatter):
578701
'''
579702
Return the sequence of ticks (colorbar data locations),
580703
ticklabels (strings), and the corresponding offset string.
581704
'''
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)
705+
# locator, formatter = _get_ticker_locator_formatter()
610706
if isinstance(self.norm, colors.NoNorm) and self.boundaries is None:
611707
intv = self._values[0], self._values[-1]
612708
else:
@@ -851,12 +947,28 @@ def _mesh(self):
851947
y = self._uniform_y(self._central_N())
852948
else:
853949
y = self._proportional_y()
950+
# if boundaries and values are None, then we can go ahead and
951+
# scale this up for Auto tick location. Otherwise we
952+
# want to keep normalized between 0 and 1 and use manual tick
953+
# locations.
954+
if self._use_adjustable():
955+
y = self.norm.inverse(y)
956+
x = self.norm.inverse(x)
957+
else:
958+
dy = 1.0
854959
self._y = y
960+
855961
X, Y = np.meshgrid(x, y)
856962
if self._extend_lower() and not self.extendrect:
857-
X[0, :] = 0.5
963+
if self._use_adjustable():
964+
X[0, :] = self.norm.inverse(0.5)
965+
else:
966+
X[0, :] = 0.5
858967
if self._extend_upper() and not self.extendrect:
859-
X[-1, :] = 0.5
968+
if self._use_adjustable():
969+
X[-1, :] = self.norm.inverse(0.5)
970+
else:
971+
X[-1, :] = 0.5
860972
return X, Y
861973

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

0 commit comments

Comments
 (0)