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

Skip to content

Commit 402b4d3

Browse files
committed
ENH: colorbar ticks adjustable to colorbar size
1 parent 5ee9553 commit 402b4d3

File tree

11 files changed

+1056
-1167
lines changed

11 files changed

+1056
-1167
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
@@ -2449,6 +2449,7 @@ def set_view_interval(self, vmin, vmax, ignore=False):
24492449
:meth:`~matplotlib.axes.Axes.set_ylim`.
24502450
24512451
"""
2452+
24522453
if ignore:
24532454
self.axes.viewLim.intervaly = vmin, vmax
24542455
else:

lib/matplotlib/colorbar.py

Lines changed: 184 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
@@ -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)
@@ -357,34 +426,119 @@ def draw_all(self):
357426
def config_axis(self):
358427
ax = self.ax
359428
if self.orientation == 'vertical':
360-
ax.xaxis.set_ticks([])
361429
# location is either one of 'bottom' or 'top'
362430
ax.yaxis.set_label_position(self.ticklocation)
363431
ax.yaxis.set_ticks_position(self.ticklocation)
432+
if (isinstance(self.norm, colors.LogNorm)
433+
and self._use_adjustable()):
434+
ax.set_xscale('log')
435+
ax.set_yscale('log')
436+
ax.xaxis.set_ticks([])
437+
ax.xaxis.set_ticks([], minor=True)
438+
364439
else:
365-
ax.yaxis.set_ticks([])
366440
# location is either one of 'left' or 'right'
367441
ax.xaxis.set_label_position(self.ticklocation)
368442
ax.xaxis.set_ticks_position(self.ticklocation)
443+
if (isinstance(self.norm, colors.LogNorm)
444+
and self._use_adjustable()):
445+
ax.set_xscale('log')
446+
ax.set_yscale('log')
447+
ax.yaxis.set_ticks([])
448+
ax.yaxis.set_ticks([], minor=True)
369449

370450
self._set_label()
371451

452+
def _get_ticker_locator_formatter(self):
453+
"""
454+
This code looks at the norm being used by the colorbar
455+
and decides what locator and formatter to use. If ``locator`` has
456+
already been set by hand, it just returns
457+
``self.locator, self.formatter``.
458+
"""
459+
locator = self.locator
460+
formatter = self.formatter
461+
if locator is None:
462+
if self.boundaries is None:
463+
if isinstance(self.norm, colors.NoNorm):
464+
nv = len(self._values)
465+
base = 1 + int(nv / 10)
466+
locator = ticker.IndexLocator(base=base, offset=0)
467+
elif isinstance(self.norm, colors.BoundaryNorm):
468+
b = self.norm.boundaries
469+
locator = ticker.FixedLocator(b, nbins=10)
470+
elif isinstance(self.norm, colors.LogNorm):
471+
locator = ColorbarLogLocator(self)
472+
elif isinstance(self.norm, colors.SymLogNorm):
473+
# The subs setting here should be replaced
474+
# by logic in the locator.
475+
locator = ticker.SymmetricalLogLocator(
476+
subs=np.arange(1, 10),
477+
linthresh=self.norm.linthresh,
478+
base=10)
479+
else:
480+
if mpl.rcParams['_internal.classic_mode']:
481+
locator = ticker.MaxNLocator()
482+
else:
483+
locator = ColorbarAutoLocator(self)
484+
else:
485+
b = self._boundaries[self._inside]
486+
locator = ticker.FixedLocator(b, nbins=10)
487+
_log.debug('locator: %r', locator)
488+
return locator, formatter
489+
490+
def _use_adjustable(self):
491+
"""
492+
Return if we should use an adjustable tick locator or a fixed
493+
one. (check is used twice so factored out here...)
494+
"""
495+
return (self.boundaries is None
496+
and self.values is None
497+
and ((type(self.norm) == colors.Normalize)
498+
or (type(self.norm) == colors.LogNorm)))
499+
372500
def update_ticks(self):
373501
"""
374502
Force the update of the ticks and ticklabels. This must be
375503
called whenever the tick locator and/or tick formatter changes.
376504
"""
377505
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)
506+
# get the locator and formatter. Defaults to
507+
# self.locator if not None..
508+
locator, formatter = self._get_ticker_locator_formatter()
509+
510+
if self._use_adjustable():
511+
_log.debug('Using adjustable locator on colorbar')
512+
_log.debug('locator: %r', locator)
513+
#self._find_range()
514+
if self.orientation == 'vertical':
515+
ax.yaxis.set_major_locator(locator)
516+
ax.yaxis.set_major_formatter(formatter)
517+
if type(self.norm) == colors.LogNorm:
518+
ax.yaxis.set_minor_locator(ColorbarLogLocator(self,
519+
base=10., subs='auto'))
520+
ax.yaxis.set_minor_formatter(ticker.NullFormatter())
521+
522+
else:
523+
ax.xaxis.set_major_locator(locator)
524+
ax.xaxis.set_major_formatter(formatter)
525+
if type(self.norm) == colors.LogNorm:
526+
ax.xaxis.set_minor_locator(ColorbarLogLocator(self,
527+
base=10., subs='auto'))
528+
ax.xaxis.set_minor_formatter(ticker.NullFormatter())
383529

384530
else:
385-
ax.xaxis.set_ticks(ticks)
386-
ax.set_xticklabels(ticklabels)
387-
ax.xaxis.get_major_formatter().set_offset_string(offset_string)
531+
_log.debug('Using fixed locator on colorbar')
532+
ticks, ticklabels, offset_string = self._ticker(locator, formatter)
533+
if self.orientation == 'vertical':
534+
ax.yaxis.set_ticks(ticks)
535+
ax.set_yticklabels(ticklabels)
536+
ax.yaxis.get_major_formatter().set_offset_string(offset_string)
537+
538+
else:
539+
ax.xaxis.set_ticks(ticks)
540+
ax.set_xticklabels(ticklabels)
541+
ax.xaxis.get_major_formatter().set_offset_string(offset_string)
388542

389543
def set_ticks(self, ticks, update_ticks=True):
390544
"""
@@ -520,6 +674,7 @@ def _add_solids(self, X, Y, C):
520674
# since the axes object should already have hold set.
521675
_hold = self.ax._hold
522676
self.ax._hold = True
677+
_log.debug('Setting pcolormesh')
523678
col = self.ax.pcolormesh(*args, **kw)
524679
self.ax._hold = _hold
525680
#self.add_observer(col) # We should observe, not be observed...
@@ -575,39 +730,11 @@ def add_lines(self, levels, colors, linewidths, erase=True):
575730
self.ax.add_collection(col)
576731
self.stale = True
577732

578-
def _ticker(self):
733+
def _ticker(self, locator, formatter):
579734
'''
580735
Return the sequence of ticks (colorbar data locations),
581736
ticklabels (strings), and the corresponding offset string.
582737
'''
583-
locator = self.locator
584-
formatter = self.formatter
585-
if locator is None:
586-
if self.boundaries is None:
587-
if isinstance(self.norm, colors.NoNorm):
588-
nv = len(self._values)
589-
base = 1 + int(nv / 10)
590-
locator = ticker.IndexLocator(base=base, offset=0)
591-
elif isinstance(self.norm, colors.BoundaryNorm):
592-
b = self.norm.boundaries
593-
locator = ticker.FixedLocator(b, nbins=10)
594-
elif isinstance(self.norm, colors.LogNorm):
595-
locator = ticker.LogLocator(subs='all')
596-
elif isinstance(self.norm, colors.SymLogNorm):
597-
# The subs setting here should be replaced
598-
# by logic in the locator.
599-
locator = ticker.SymmetricalLogLocator(
600-
subs=np.arange(1, 10),
601-
linthresh=self.norm.linthresh,
602-
base=10)
603-
else:
604-
if mpl.rcParams['_internal.classic_mode']:
605-
locator = ticker.MaxNLocator()
606-
else:
607-
locator = ticker.AutoLocator()
608-
else:
609-
b = self._boundaries[self._inside]
610-
locator = ticker.FixedLocator(b, nbins=10)
611738
if isinstance(self.norm, colors.NoNorm) and self.boundaries is None:
612739
intv = self._values[0], self._values[-1]
613740
else:
@@ -852,12 +979,28 @@ def _mesh(self):
852979
y = self._uniform_y(self._central_N())
853980
else:
854981
y = self._proportional_y()
982+
# if boundaries and values are None, then we can go ahead and
983+
# scale this up for Auto tick location. Otherwise we
984+
# want to keep normalized between 0 and 1 and use manual tick
985+
# locations.
986+
if self._use_adjustable():
987+
y = self.norm.inverse(y)
988+
x = self.norm.inverse(x)
989+
else:
990+
dy = 1.0
855991
self._y = y
992+
856993
X, Y = np.meshgrid(x, y)
857994
if self._extend_lower() and not self.extendrect:
858-
X[0, :] = 0.5
995+
if self._use_adjustable():
996+
X[0, :] = self.norm.inverse(0.5)
997+
else:
998+
X[0, :] = 0.5
859999
if self._extend_upper() and not self.extendrect:
860-
X[-1, :] = 0.5
1000+
if self._use_adjustable():
1001+
X[-1, :] = self.norm.inverse(0.5)
1002+
else:
1003+
X[-1, :] = 0.5
8611004
return X, Y
8621005

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

0 commit comments

Comments
 (0)