Хорошо, после бесчисленных часов исследований, застревания во всевозможных кроличьих норах и обдумывания одних и тех же идей (некоторые из них задокументированы здесь ), я наконец сдался. Мой последний подход, который я публикую здесь, заключался в том, чтобы вручную жестко запрограммировать функции, которые могли бы извлекать соответствующую информацию, в зависимости от типа исполнителя. Но даже этот подход в конечном итоге потерпел неудачу, потому что:
с использованием вспомогательных функций , которые должны копировать свойства от одного художника к другому, например artist.update_from()
к сожалению:
- либо скопируйте несовместимые свойства, то есть, если вы запустите метод после того, как вы добавили нового исполнителя в другие оси, он вызовет ошибки
- или, по-видимому, не скопирует никакие properties, как и в случае с
AxesImages
, например,
, это означает, что вам также придется придумать свой собственный способ индивидуального копирования этой информации. Это снова было бы чрезвычайно громоздко, но, что более важно:
- Для некоторых типов художников просто невозможно получить все необходимые
args
и kwargs
вообще из экземпляр. Это, например, случай с FancyArrows
. Буквально ни один из kwargs и только два аргумента, которые используются для инициализации этого класса, могут быть снова доступны любым способом из самого экземпляра художника . Это так невероятно расстраивает.
В любом случае, вот моя попытка жестко закодировать методы дублирования, возможно, они будут кому-то полезны.
дубликат. py
from matplotlib.patches import Ellipse, Rectangle, FancyArrow
from matplotlib.text import Annotation, Text
from matplotlib.lines import Line2D
from matplotlib.image import AxesImage
from matplotlib.artist import Artist
from matplotlib.axes import Axes
from matplotlib_scalebar.scalebar import ScaleBar
from typing import Callable
def _get_ellipse_args(ellipse: Ellipse) -> list:
return [ellipse.center, ellipse.width, ellipse.height]
def _get_rectangle_args(rectangle: Rectangle) -> list:
return [rectangle.get_xy(), rectangle.get_width(), rectangle.get_height()]
def _get_fancy_arroy_args(arrow: FancyArrow) -> list:
return [*arrow.xy, arrow.dx, arrow.dy]
def _get_scalebar_args(scalebar: ScaleBar) -> list:
return [scalebar.dx]
def _get_line2d_args(line: Line2D) -> list:
return line.get_data()
def _get_text_args(text: Text) -> list:
return []
def _get_annotation_args(text: Text) -> list:
return [text.get_text(), text.xy]
class Duplicator:
_arg_fetchers = {
Line2D: _get_line2d_args,
# Ellipse: _get_ellipse_args,
Rectangle: _get_rectangle_args,
Text: _get_text_args,
Annotation: _get_annotation_args,
ScaleBar: _get_scalebar_args,
AxesImage: lambda: None,
}
def args(self, artist):
return self._arg_fetchers[type(artist)](artist)
@classmethod
def duplicate(
cls, artist: Artist, other_ax: Axes,
duplication_method: Callable = None
) -> Artist:
if duplication_method is not None:
cls._arg_fetchers[type(artist)] = duplication_method
if type(artist) in cls._arg_fetchers:
if isinstance(artist, AxesImage):
duplicate = other_ax.imshow(artist.get_array())
# duplicate.update_from(artist) has no effect on AxesImage
# instances for some reason.
# duplicate.update(artist.properties()) causes an
# AttributeError for some other reason.
# Thus it seems kwargs need to be set individually for
# AxesImages.
duplicate.set_cmap(artist.get_cmap())
duplicate.set_clim(artist.get_clim())
else:
duplicate = type(artist)(*cls.args(cls, artist))
# this unfortunately copies properties that should not be
# copied, resulting in the artist being absent in the new axes
# duplicate.update_from(artist)
other_ax.add_artist(duplicate)
return duplicate
else:
raise TypeError(
'There is no duplication method for this type of artist',
type(artist)
)
@classmethod
def can_duplicate(cls, artist: Artist) -> bool:
return type(artist) in cls._arg_fetchers
duplicate_test.py
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
from matplotlib.text import Annotation
from matplotlib.lines import Line2D
from duplicate import Duplicator, _get_ellipse_args
fig, (ax1, ax2, ax3) = plt.subplots(1, 3)
# determine artists that were there before we manually added some
default_artists1 = set(ax1.get_children())
default_artists2 = set(ax2.get_children())
# add several artists to ax1
ax1.add_line(Line2D([0, 1], [2, 3], lw=4, color='red'))
ax1.add_patch(Ellipse((1, 1), 1, 1))
ax1.imshow(np.random.uniform(0, 1, (10, 10)))
ax2.add_patch(Ellipse((3, 5), 2, 4, fc='purple'))
ax2.add_artist(Annotation('text', (1, 1), fontsize=20))
# set axes limits, optional, but usually necessary
for ax in [ax1, ax2]:
ax.relim()
ax.autoscale_view()
ax2.axis('square')
for ax in [ax2, ax3]:
ax.set_xlim(ax1.get_xlim())
ax.set_ylim(ax1.get_ylim())
# determine artists that were added manually
new_artists1 = set(ax1.get_children()) - default_artists1
new_artists2 = set(ax2.get_children()) - default_artists2
new_artists = new_artists1 | new_artists2
# declare our own arg fetchers for artists types that may not be
# covered by the Duplicator class
arg_fetchers = {Ellipse: _get_ellipse_args}
# duplicate artists to ax3
for artist in new_artists:
if Duplicator.can_duplicate(artist):
Duplicator.duplicate(artist, ax3)
else:
Duplicator.duplicate(artist, ax3, arg_fetchers[type(artist)])
fig.show()
Я действительно не Я понимаю, почему, хотя matplotlib категорически запрещает повторное использование одного и того же художника в разных фигурах / осях (для чего, вероятно, есть веская техническая причина), они в то же время делают это невозможным , если нет как минимум очень неудобно / хакерский перемещать или копировать художников.
Боги Matplotlib, пожалуйста, для всего хорошего, введите стандартный способ копирования художников