|
| 1 | +from collections.abc import MutableMapping |
| 2 | +import functools |
| 3 | + |
1 | 4 | import numpy as np
|
2 | 5 |
|
3 | 6 | import matplotlib
|
@@ -534,3 +537,90 @@ def set_color(self, c):
|
534 | 537 | """
|
535 | 538 | self.set_edgecolor(c)
|
536 | 539 | self.stale = True
|
| 540 | + |
| 541 | + |
| 542 | +class SpinesProxy: |
| 543 | + """ |
| 544 | + A proxy to broadcast ``set_*`` method calls to all contained `.Spines`. |
| 545 | +
|
| 546 | + The supported methods are determined dynamically based on the contained |
| 547 | + spines. If not all spines support a given method, it's executed only on |
| 548 | + the subset of spines that support it. |
| 549 | + """ |
| 550 | + def __init__(self, spine_dict): |
| 551 | + self._spine_dict = spine_dict |
| 552 | + |
| 553 | + def __getattr__(self, name): |
| 554 | + broadcast_targets = [spine for spine in self._spine_dict.values() |
| 555 | + if hasattr(spine, name)] |
| 556 | + if not name.startswith('set_') or not broadcast_targets: |
| 557 | + raise ValueError(f"'SpinesProxy' object has no attribute '{name}'") |
| 558 | + |
| 559 | + def x(_targets, _funcname, *args, **kwargs): |
| 560 | + for spine in _targets: |
| 561 | + getattr(spine, _funcname)(*args, **kwargs) |
| 562 | + x = functools.partial(x, broadcast_targets, name) |
| 563 | + x.__doc__ = broadcast_targets[0].__doc__ |
| 564 | + return x |
| 565 | + |
| 566 | + def __dir__(self): |
| 567 | + names = [] |
| 568 | + for spine in self._spine_dict.values(): |
| 569 | + names.extend(name |
| 570 | + for name in dir(spine) if name.startswith('set_')) |
| 571 | + return list(sorted(set(names))) |
| 572 | + |
| 573 | + |
| 574 | +class Spines(MutableMapping): |
| 575 | + r""" |
| 576 | + The container of all `.Spine`\s in an Axes. |
| 577 | +
|
| 578 | + Individual spines can be accessed via attribute access of the spine names:: |
| 579 | +
|
| 580 | + spines.top.set_visible(False) |
| 581 | +
|
| 582 | + For backward compatibility, it's also possible to use item access:: |
| 583 | +
|
| 584 | + spines['top'].set_visible(False) |
| 585 | +
|
| 586 | + Setting spine properties can be broadcasted to all spines using the proxy |
| 587 | + ``spines.all``: |
| 588 | +
|
| 589 | + spines.all.set_visible(False) |
| 590 | +
|
| 591 | + Attributes |
| 592 | + ---------- |
| 593 | + all : SpinesProxy |
| 594 | + A proxy to broadcast ``set_*`` method calls to all contained `.Spines`. |
| 595 | +
|
| 596 | + """ |
| 597 | + def __init__(self, **kwargs): |
| 598 | + self._dict = kwargs |
| 599 | + self.all = SpinesProxy(self._dict) |
| 600 | + |
| 601 | + @classmethod |
| 602 | + def from_dict(cls, d): |
| 603 | + return cls(**d) |
| 604 | + |
| 605 | + def __getstate__(self): |
| 606 | + return self._dict |
| 607 | + |
| 608 | + def __setstate__(self, state): |
| 609 | + self.__init__(**state) |
| 610 | + |
| 611 | + def __getitem__(self, key): |
| 612 | + return self._dict[key] |
| 613 | + |
| 614 | + def __setitem__(self, key, value): |
| 615 | + # TODO: Do we want to deprecate adding spines? |
| 616 | + self._dict[key] = value |
| 617 | + |
| 618 | + def __delitem__(self, key): |
| 619 | + # TODO: Do we want to deprecate deleting spines? |
| 620 | + del self._dict[key] |
| 621 | + |
| 622 | + def __iter__(self): |
| 623 | + return iter(self._dict) |
| 624 | + |
| 625 | + def __len__(self): |
| 626 | + return len(self._dict) |
0 commit comments