|
9 | 9 | import matplotlib.ticker as mticker |
10 | 10 | import numpy as np |
11 | 11 |
|
| 12 | +from packaging import version |
| 13 | + |
12 | 14 | from .. import constructor |
13 | 15 | from .. import scale as pscale |
14 | 16 | from .. import ticker as pticker |
15 | 17 | from ..config import rc |
16 | 18 | from ..internals import ic # noqa: F401 |
17 | 19 | from ..internals import _not_none, _pop_rc, _version_mpl, docstring, labels, warnings |
18 | 20 | from . import plot, shared |
| 21 | +import matplotlib.axis as maxis |
19 | 22 |
|
20 | 23 | __all__ = ["CartesianAxes"] |
21 | 24 |
|
@@ -373,35 +376,162 @@ def _apply_axis_sharing(self): |
373 | 376 | Enforce the "shared" axis labels and axis tick labels. If this is not |
374 | 377 | called at drawtime, "shared" labels can be inadvertantly turned off. |
375 | 378 | """ |
376 | | - # X axis |
377 | 379 | # NOTE: Critical to apply labels to *shared* axes attributes rather |
378 | 380 | # than testing extents or we end up sharing labels with twin axes. |
379 | 381 | # NOTE: Similar to how _align_super_labels() calls _apply_title_above() this |
380 | 382 | # is called inside _align_axis_labels() so we align the correct text. |
381 | 383 | # NOTE: The "panel sharing group" refers to axes and panels *above* the |
382 | 384 | # bottommost or to the *right* of the leftmost panel. But the sharing level |
383 | 385 | # used for the leftmost and bottommost is the *figure* sharing level. |
384 | | - axis = self.xaxis |
385 | | - if self._sharex is not None and axis.get_visible(): |
386 | | - level = 3 if self._panel_sharex_group else self.figure._sharex |
387 | | - if level > 0: |
388 | | - labels._transfer_label(axis.label, self._sharex.xaxis.label) |
389 | | - axis.label.set_visible(False) |
390 | | - if level > 2: |
391 | | - # WARNING: Cannot set NullFormatter because shared axes share the |
392 | | - # same Ticker(). Instead use approach copied from mpl subplots(). |
393 | | - axis.set_tick_params(which="both", labelbottom=False, labeltop=False) |
394 | | - # Y axis |
395 | | - axis = self.yaxis |
396 | | - if self._sharey is not None and axis.get_visible(): |
397 | | - level = 3 if self._panel_sharey_group else self.figure._sharey |
398 | | - if level > 0: |
399 | | - labels._transfer_label(axis.label, self._sharey.yaxis.label) |
400 | | - axis.label.set_visible(False) |
401 | | - if level > 2: |
402 | | - axis.set_tick_params(which="both", labelleft=False, labelright=False) |
| 386 | + |
| 387 | + # Get border axes once for efficiency |
| 388 | + border_axes = self.figure._get_border_axes() |
| 389 | + |
| 390 | + # Apply X axis sharing |
| 391 | + self._apply_axis_sharing_for_axis("x", border_axes) |
| 392 | + |
| 393 | + # Apply Y axis sharing |
| 394 | + self._apply_axis_sharing_for_axis("y", border_axes) |
| 395 | + |
| 396 | + def _apply_axis_sharing_for_axis( |
| 397 | + self, |
| 398 | + axis_name: str, |
| 399 | + border_axes: dict[str, plot.PlotAxes], |
| 400 | + ) -> None: |
| 401 | + """ |
| 402 | + Apply axis sharing for a specific axis (x or y). |
| 403 | +
|
| 404 | + Parameters |
| 405 | + ---------- |
| 406 | + axis_name : str |
| 407 | + Either 'x' or 'y' |
| 408 | + border_axes : dict |
| 409 | + Dictionary from _get_border_axes() containing border information |
| 410 | + """ |
| 411 | + if axis_name == "x": |
| 412 | + axis = self.xaxis |
| 413 | + shared_axis = self._sharex |
| 414 | + panel_group = self._panel_sharex_group |
| 415 | + sharing_level = self.figure._sharex |
| 416 | + label_params = ["labeltop", "labelbottom"] |
| 417 | + border_sides = ["top", "bottom"] |
| 418 | + else: # axis_name == 'y' |
| 419 | + axis = self.yaxis |
| 420 | + shared_axis = self._sharey |
| 421 | + panel_group = self._panel_sharey_group |
| 422 | + sharing_level = self.figure._sharey |
| 423 | + label_params = ["labelleft", "labelright"] |
| 424 | + border_sides = ["left", "right"] |
| 425 | + |
| 426 | + if shared_axis is None or not axis.get_visible(): |
| 427 | + return |
| 428 | + |
| 429 | + level = 3 if panel_group else sharing_level |
| 430 | + |
| 431 | + # Handle axis label sharing (level > 0) |
| 432 | + if level > 0: |
| 433 | + shared_axis_obj = getattr(shared_axis, f"{axis_name}axis") |
| 434 | + labels._transfer_label(axis.label, shared_axis_obj.label) |
| 435 | + axis.label.set_visible(False) |
| 436 | + |
| 437 | + # Handle tick label sharing (level > 2) |
| 438 | + if level > 2: |
| 439 | + label_visibility = self._determine_tick_label_visibility( |
| 440 | + axis, |
| 441 | + shared_axis, |
| 442 | + axis_name, |
| 443 | + label_params, |
| 444 | + border_sides, |
| 445 | + border_axes, |
| 446 | + ) |
| 447 | + axis.set_tick_params(which="both", **label_visibility) |
| 448 | + # Turn minor ticks off |
403 | 449 | axis.set_minor_formatter(mticker.NullFormatter()) |
404 | 450 |
|
| 451 | + def _determine_tick_label_visibility( |
| 452 | + self, |
| 453 | + axis: maxis.Axis, |
| 454 | + shared_axis: maxis.Axis, |
| 455 | + axis_name: str, |
| 456 | + label_params: list[str], |
| 457 | + border_sides: list[str], |
| 458 | + border_axes: dict[str, list[plot.PlotAxes]], |
| 459 | + ) -> dict[str, bool]: |
| 460 | + """ |
| 461 | + Determine which tick labels should be visible based on sharing rules and borders. |
| 462 | +
|
| 463 | + Parameters |
| 464 | + ---------- |
| 465 | + axis : matplotlib axis |
| 466 | + The current axis object |
| 467 | + shared_axis : Axes |
| 468 | + The axes this one shares with |
| 469 | + axis_name : str |
| 470 | + Either 'x' or 'y' |
| 471 | + label_params : list |
| 472 | + List of label parameter names (e.g., ['labeltop', 'labelbottom']) |
| 473 | + border_sides : list |
| 474 | + List of border side names (e.g., ['top', 'bottom']) |
| 475 | + border_axes : dict |
| 476 | + Dictionary from _get_border_axes() |
| 477 | +
|
| 478 | + Returns |
| 479 | + ------- |
| 480 | + dict |
| 481 | + Dictionary of label visibility parameters |
| 482 | + """ |
| 483 | + ticks = axis.get_tick_params() |
| 484 | + shared_axis_obj = getattr(shared_axis, f"{axis_name}axis") |
| 485 | + sharing_ticks = shared_axis_obj.get_tick_params() |
| 486 | + |
| 487 | + label_visibility = {} |
| 488 | + |
| 489 | + def _convert_label_param(label_param: str) -> str: |
| 490 | + # Deal with logic not being consistent |
| 491 | + # in prior mpl versions |
| 492 | + if version.parse(str(_version_mpl)) <= version.parse("3.9"): |
| 493 | + if label_param == "labeltop" and axis_name == "x": |
| 494 | + label_param = "labelright" |
| 495 | + elif label_param == "labelbottom" and axis_name == "x": |
| 496 | + label_param = "labelleft" |
| 497 | + return label_param |
| 498 | + |
| 499 | + for label_param, border_side in zip(label_params, border_sides): |
| 500 | + # Check if user has explicitly set label location via format() |
| 501 | + label_visibility[label_param] = False |
| 502 | + has_panel = False |
| 503 | + for panel in self._panel_dict[border_side]: |
| 504 | + # Check if the panel is a colorbar |
| 505 | + colorbars = [ |
| 506 | + values |
| 507 | + for key, values in self._colorbar_dict.items() |
| 508 | + if border_side in key # key is tuple (side, top | center | lower) |
| 509 | + ] |
| 510 | + if not panel in colorbars: |
| 511 | + # Skip colorbar as their |
| 512 | + # yaxis is not shared |
| 513 | + has_panel = True |
| 514 | + break |
| 515 | + # When we have a panel, let the panel have |
| 516 | + # the labels and turn-off for this axis + side. |
| 517 | + if has_panel: |
| 518 | + continue |
| 519 | + is_border = self in border_axes.get(border_side, []) |
| 520 | + is_panel = ( |
| 521 | + self in shared_axis._panel_dict[border_side] |
| 522 | + and self == shared_axis._panel_dict[border_side][-1] |
| 523 | + ) |
| 524 | + # Use automatic border detection logic |
| 525 | + # if we are a panel we "push" the labels outwards |
| 526 | + label_param_trans = _convert_label_param(label_param) |
| 527 | + is_this_tick_on = ticks[label_param_trans] |
| 528 | + is_parent_tick_on = sharing_ticks[label_param_trans] |
| 529 | + if is_panel: |
| 530 | + label_visibility[label_param] = is_parent_tick_on |
| 531 | + elif is_border: |
| 532 | + label_visibility[label_param] = is_this_tick_on |
| 533 | + return label_visibility |
| 534 | + |
405 | 535 | def _add_alt(self, sx, **kwargs): |
406 | 536 | """ |
407 | 537 | Add an alternate axes. |
|
0 commit comments