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 dbfc79c

Browse filesBrowse files
authored
Merge pull request #9903 from jklymak/enh-colorbar-ticks
ENH: adjustable colorbar ticks
2 parents 197bb59 + b74c0fd commit dbfc79c
Copy full SHA for dbfc79c

File tree

Expand file treeCollapse file tree

10 files changed

+1039
-1173
lines changed
Filter options
Expand file treeCollapse file tree

10 files changed

+1039
-1173
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
+169-47Lines changed: 169 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
2020
'''
2121

22+
import logging
2223
import warnings
2324

2425
import numpy as np
@@ -39,6 +40,8 @@
3940
import matplotlib._constrained_layout as constrained_layout
4041
from matplotlib import docstring
4142

43+
_log = logging.getLogger(__name__)
44+
4245
make_axes_kw_doc = '''
4346
4447
============= ====================================================
@@ -212,6 +215,63 @@ def _set_ticks_on_axis_warn(*args, **kw):
212215
warnings.warn("Use the colorbar set_ticks() method instead.")
213216

214217

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

404+
# sets self._boundaries and self._values in real data units.
405+
# takes into account extend values:
344406
self._process_values()
407+
# sets self.vmin and vmax in data units, but just for
408+
# the part of the colorbar that is not part of the extend
409+
# patch:
345410
self._find_range()
411+
# returns the X and Y mesh, *but* this was/is in normalized
412+
# units:
346413
X, Y = self._mesh()
347414
C = self._values[:, np.newaxis]
348415
self._config_axes(X, Y)
@@ -351,35 +418,105 @@ def draw_all(self):
351418

352419
def config_axis(self):
353420
ax = self.ax
421+
if (isinstance(self.norm, colors.LogNorm)
422+
and self._use_auto_colorbar_locator()):
423+
# *both* axes are made log so that determining the
424+
# mid point is easier.
425+
ax.set_xscale('log')
426+
ax.set_yscale('log')
427+
354428
if self.orientation == 'vertical':
355-
ax.xaxis.set_ticks([])
356-
# location is either one of 'bottom' or 'top'
357-
ax.yaxis.set_label_position(self.ticklocation)
358-
ax.yaxis.set_ticks_position(self.ticklocation)
429+
long_axis, short_axis = ax.yaxis, ax.xaxis
359430
else:
360-
ax.yaxis.set_ticks([])
361-
# location is either one of 'left' or 'right'
362-
ax.xaxis.set_label_position(self.ticklocation)
363-
ax.xaxis.set_ticks_position(self.ticklocation)
431+
long_axis, short_axis = ax.xaxis, ax.yaxis
432+
433+
long_axis.set_label_position(self.ticklocation)
434+
long_axis.set_ticks_position(self.ticklocation)
435+
short_axis.set_ticks([])
436+
short_axis.set_ticks([], minor=True)
364437

365438
self._set_label()
366439

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

498+
if self.orientation == 'vertical':
499+
long_axis, short_axis = ax.yaxis, ax.xaxis
379500
else:
380-
ax.xaxis.set_ticks(ticks)
381-
ax.set_xticklabels(ticklabels)
382-
ax.xaxis.get_major_formatter().set_offset_string(offset_string)
501+
long_axis, short_axis = ax.xaxis, ax.yaxis
502+
503+
if self._use_auto_colorbar_locator():
504+
_log.debug('Using auto colorbar locator on colorbar')
505+
_log.debug('locator: %r', locator)
506+
long_axis.set_major_locator(locator)
507+
long_axis.set_major_formatter(formatter)
508+
if type(self.norm) == colors.LogNorm:
509+
long_axis.set_minor_locator(_ColorbarLogLocator(self,
510+
base=10., subs='auto'))
511+
long_axis.set_minor_formatter(
512+
ticker.LogFormatter()
513+
)
514+
else:
515+
_log.debug('Using fixed locator on colorbar')
516+
ticks, ticklabels, offset_string = self._ticker(locator, formatter)
517+
long_axis.set_ticks(ticks)
518+
long_axis.set_ticklabels(ticklabels)
519+
long_axis.get_major_formatter().set_offset_string(offset_string)
383520

384521
def set_ticks(self, ticks, update_ticks=True):
385522
"""
@@ -515,6 +652,7 @@ def _add_solids(self, X, Y, C):
515652
# since the axes object should already have hold set.
516653
_hold = self.ax._hold
517654
self.ax._hold = True
655+
_log.debug('Setting pcolormesh')
518656
col = self.ax.pcolormesh(*args, **kw)
519657
self.ax._hold = _hold
520658
#self.add_observer(col) # We should observe, not be observed...
@@ -568,39 +706,11 @@ def add_lines(self, levels, colors, linewidths, erase=True):
568706
self.ax.add_collection(col)
569707
self.stale = True
570708

571-
def _ticker(self):
709+
def _ticker(self, locator, formatter):
572710
'''
573711
Return the sequence of ticks (colorbar data locations),
574712
ticklabels (strings), and the corresponding offset string.
575713
'''
576-
locator = self.locator
577-
formatter = self.formatter
578-
if locator is None:
579-
if self.boundaries is None:
580-
if isinstance(self.norm, colors.NoNorm):
581-
nv = len(self._values)
582-
base = 1 + int(nv / 10)
583-
locator = ticker.IndexLocator(base=base, offset=0)
584-
elif isinstance(self.norm, colors.BoundaryNorm):
585-
b = self.norm.boundaries
586-
locator = ticker.FixedLocator(b, nbins=10)
587-
elif isinstance(self.norm, colors.LogNorm):
588-
locator = ticker.LogLocator(subs='all')
589-
elif isinstance(self.norm, colors.SymLogNorm):
590-
# The subs setting here should be replaced
591-
# by logic in the locator.
592-
locator = ticker.SymmetricalLogLocator(
593-
subs=np.arange(1, 10),
594-
linthresh=self.norm.linthresh,
595-
base=10)
596-
else:
597-
if mpl.rcParams['_internal.classic_mode']:
598-
locator = ticker.MaxNLocator()
599-
else:
600-
locator = ticker.AutoLocator()
601-
else:
602-
b = self._boundaries[self._inside]
603-
locator = ticker.FixedLocator(b, nbins=10)
604714
if isinstance(self.norm, colors.NoNorm) and self.boundaries is None:
605715
intv = self._values[0], self._values[-1]
606716
else:
@@ -840,17 +950,29 @@ def _mesh(self):
840950
transposition for a horizontal colorbar are done outside
841951
this function.
842952
'''
953+
# if boundaries and values are None, then we can go ahead and
954+
# scale this up for Auto tick location. Otherwise we
955+
# want to keep normalized between 0 and 1 and use manual tick
956+
# locations.
957+
843958
x = np.array([0.0, 1.0])
844959
if self.spacing == 'uniform':
845960
y = self._uniform_y(self._central_N())
846961
else:
847962
y = self._proportional_y()
963+
if self._use_auto_colorbar_locator():
964+
y = self.norm.inverse(y)
965+
x = self.norm.inverse(x)
848966
self._y = y
849967
X, Y = np.meshgrid(x, y)
968+
if self._use_auto_colorbar_locator():
969+
xmid = self.norm.inverse(0.5)
970+
else:
971+
xmid = 0.5
850972
if self._extend_lower() and not self.extendrect:
851-
X[0, :] = 0.5
973+
X[0, :] = xmid
852974
if self._extend_upper() and not self.extendrect:
853-
X[-1, :] = 0.5
975+
X[-1, :] = xmid
854976
return X, Y
855977

856978
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.