Есть ли способ в matplotlib получить значения художника, которые необходимы для создания экземпляра такого типа художника? - PullRequest
3 голосов
/ 17 июня 2020

Я имею в виду следующее: с учетом экземпляра generi c matplotlib Artist существует ли общий c способ получить значения позиционных аргументов этого художника для создания экземпляра другого художника однотипные? Я знаю, что существует метод properties, но, по моему опыту, он возвращает только аргументы ключевого слова, но не позиционные.

Я узнал о модуле inspect, и мне удалось использовать его, чтобы хотя бы получить имена позиционных аргументов для данного типа исполнителя, но я не получил и далее, потому что соответствующие атрибуты художников обычно имеют разные имена.

import inspect
from matplotlib.lines import Line2D


artist = Line2D([0, 1], [2, 3])
sig = inspect.signature(type(artist))
args = {}
for param in sig.parameters.values():
    if (
        param.default == param.empty
        and param.kind != inspect.Parameter.VAR_KEYWORD
    ):
        args[param.name] = getattr(artist, param.name)

new_artist = type(artist)(**args)

1 Ответ

0 голосов
/ 18 июня 2020

Хорошо, после бесчисленных часов исследований, застревания во всевозможных кроличьих норах и обдумывания одних и тех же идей (некоторые из них задокументированы здесь ), я наконец сдался. Мой последний подход, который я публикую здесь, заключался в том, чтобы вручную жестко запрограммировать функции, которые могли бы извлекать соответствующую информацию, в зависимости от типа исполнителя. Но даже этот подход в конечном итоге потерпел неудачу, потому что:

  1. с использованием вспомогательных функций , которые должны копировать свойства от одного художника к другому, например artist.update_from() к сожалению:

    • либо скопируйте несовместимые свойства, то есть, если вы запустите метод после того, как вы добавили нового исполнителя в другие оси, он вызовет ошибки
    • или, по-видимому, не скопирует никакие properties, как и в случае с AxesImages, например,

    , это означает, что вам также придется придумать свой собственный способ индивидуального копирования этой информации. Это снова было бы чрезвычайно громоздко, но, что более важно:

  2. Для некоторых типов художников просто невозможно получить все необходимые 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()

enter image description here

Я действительно не Я понимаю, почему, хотя matplotlib категорически запрещает повторное использование одного и того же художника в разных фигурах / осях (для чего, вероятно, есть веская техническая причина), они в то же время делают это невозможным , если нет как минимум очень неудобно / хакерский перемещать или копировать художников.

Боги Matplotlib, пожалуйста, для всего хорошего, введите стандартный способ копирования художников

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...