|
24 | 24 | import six
|
25 | 25 | from six.moves import xrange, zip
|
26 | 26 |
|
| 27 | +import logging |
27 | 28 | import warnings
|
28 | 29 |
|
29 | 30 | import numpy as np
|
|
43 | 44 |
|
44 | 45 | from matplotlib import docstring
|
45 | 46 |
|
| 47 | +_log = logging.getLogger(__name__) |
| 48 | + |
46 | 49 | make_axes_kw_doc = '''
|
47 | 50 |
|
48 | 51 | ============= ====================================================
|
@@ -216,6 +219,38 @@ def _set_ticks_on_axis_warn(*args, **kw):
|
216 | 219 | warnings.warn("Use the colorbar set_ticks() method instead.")
|
217 | 220 |
|
218 | 221 |
|
| 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 | + |
219 | 254 | class ColorbarBase(cm.ScalarMappable):
|
220 | 255 | '''
|
221 | 256 | Draw a colorbar in an existing axes.
|
@@ -346,45 +381,132 @@ def draw_all(self):
|
346 | 381 | and do all the drawing.
|
347 | 382 | '''
|
348 | 383 |
|
| 384 | + # sets self._boundaries and self._values in real data units. |
| 385 | + # takes into account extend values: |
349 | 386 | 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: |
350 | 390 | self._find_range()
|
| 391 | + # returns the X and Y mesh, *but* this was/is in normalized |
| 392 | + # units: |
351 | 393 | X, Y = self._mesh()
|
352 | 394 | C = self._values[:, np.newaxis]
|
353 | 395 | self._config_axes(X, Y)
|
354 | 396 | if self.filled:
|
355 | 397 | self._add_solids(X, Y, C)
|
| 398 | + # self._set_view_limits() |
356 | 399 |
|
357 | 400 | def config_axis(self):
|
358 | 401 | ax = self.ax
|
359 | 402 | if self.orientation == 'vertical':
|
360 |
| - ax.xaxis.set_ticks([]) |
361 | 403 | # location is either one of 'bottom' or 'top'
|
362 | 404 | ax.yaxis.set_label_position(self.ticklocation)
|
363 | 405 | 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 | + |
364 | 413 | else:
|
365 |
| - ax.yaxis.set_ticks([]) |
366 | 414 | # location is either one of 'left' or 'right'
|
367 | 415 | ax.xaxis.set_label_position(self.ticklocation)
|
368 | 416 | 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) |
369 | 423 |
|
370 | 424 | self._set_label()
|
371 | 425 |
|
| 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 | + |
372 | 468 | def update_ticks(self):
|
373 | 469 | """
|
374 | 470 | Force the update of the ticks and ticklabels. This must be
|
375 | 471 | called whenever the tick locator and/or tick formatter changes.
|
376 | 472 | """
|
377 | 473 | 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()) |
383 | 497 |
|
384 | 498 | 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) |
388 | 510 |
|
389 | 511 | def set_ticks(self, ticks, update_ticks=True):
|
390 | 512 | """
|
@@ -520,6 +642,7 @@ def _add_solids(self, X, Y, C):
|
520 | 642 | # since the axes object should already have hold set.
|
521 | 643 | _hold = self.ax._hold
|
522 | 644 | self.ax._hold = True
|
| 645 | + _log.debug('Setting pcolormesh') |
523 | 646 | col = self.ax.pcolormesh(*args, **kw)
|
524 | 647 | self.ax._hold = _hold
|
525 | 648 | #self.add_observer(col) # We should observe, not be observed...
|
@@ -574,39 +697,12 @@ def add_lines(self, levels, colors, linewidths, erase=True):
|
574 | 697 | self.ax.add_collection(col)
|
575 | 698 | self.stale = True
|
576 | 699 |
|
577 |
| - def _ticker(self): |
| 700 | + def _ticker(self, locator, formatter): |
578 | 701 | '''
|
579 | 702 | Return the sequence of ticks (colorbar data locations),
|
580 | 703 | ticklabels (strings), and the corresponding offset string.
|
581 | 704 | '''
|
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() |
610 | 706 | if isinstance(self.norm, colors.NoNorm) and self.boundaries is None:
|
611 | 707 | intv = self._values[0], self._values[-1]
|
612 | 708 | else:
|
@@ -851,12 +947,28 @@ def _mesh(self):
|
851 | 947 | y = self._uniform_y(self._central_N())
|
852 | 948 | else:
|
853 | 949 | 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 |
854 | 959 | self._y = y
|
| 960 | + |
855 | 961 | X, Y = np.meshgrid(x, y)
|
856 | 962 | 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 |
858 | 967 | 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 |
860 | 972 | return X, Y
|
861 | 973 |
|
862 | 974 | def _locate(self, x):
|
|
0 commit comments