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 402b4d3

Browse filesBrowse files
committed
ENH: colorbar ticks adjustable to colorbar size
1 parent 5ee9553 commit 402b4d3
Copy full SHA for 402b4d3

File tree

Expand file treeCollapse file tree

11 files changed

+1056
-1167
lines changed
Filter options
Expand file treeCollapse file tree

11 files changed

+1056
-1167
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/axis.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axis.py
+1Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/colorbar.py
+184-41Lines 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):
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.