Python matplotlib中是否有任何方法可以获取艺术家的值,这些值是实例化此类艺术家所必需的?
我的意思是:给定一个泛型matplotlib的实例,是否有一种泛型方法来获取该艺术家的位置参数值,从而实例化相同类型的另一个艺术家?我知道有-方法,但根据我的经验,它只返回关键字参数,而不返回位置参数 我已经了解了这个模块,并且我设法使用它至少获得了给定艺术家类型的位置参数的名称,但是我没有得到更多,因为艺术家的相应属性通常有不同的名称Python matplotlib中是否有任何方法可以获取艺术家的值,这些值是实例化此类艺术家所必需的?,python,matplotlib,Python,Matplotlib,我的意思是:给定一个泛型matplotlib的实例,是否有一种泛型方法来获取该艺术家的位置参数值,从而实例化相同类型的另一个艺术家?我知道有-方法,但根据我的经验,它只返回关键字参数,而不返回位置参数 我已经了解了这个模块,并且我设法使用它至少获得了给定艺术家类型的位置参数的名称,但是我没有得到更多,因为艺术家的相应属性通常有不同的名称 import inspect from matplotlib.lines import Line2D artist = Line2D([0, 1], [2,
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)
好吧,经过无数个小时的研究,陷入各种各样的兔子洞,在相同的想法中盘旋(其中一些被记录在案),我现在终于放弃了。我在这里发布的最后一种方法实际上是手动硬编码函数,根据艺术家的类型检索相关信息。但即使这种方法最终也失败了,因为:
artist.update_from()
不幸的是:
- 也可以复制不兼容的属性,也就是说,如果在将新艺术家添加到其他轴后运行该方法,则会引发错误
- 或者看起来根本不复制任何属性,例如
axesimage
args
和kwargs
。这是一个例子实际上,用于初始化该类的KWARG和arg中只有两个不能从艺术家实例本身以任何方式再次访问。这真是令人难以置信的沮丧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
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()
重复测试.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
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诸神,拜托,尽管这很好,但还是引入了一种复制艺术家的标准方法。这看起来有点像XY问题。你只是想复制对象吗?如果是这样,我建议使用标准库的
copy
模块。你是对的,这基本上是一个xy问题。你们猜对了,我真的想抄写给艺术家。但因为我读到不建议将艺术家从一个人物复制到另一个人物,所以我想从头开始创建一个具有相同属性的新艺术家。我的意思是,如果我使用copy
,matplotlib不会抱怨艺术家是在另一个人物中创作的,不应该被移动吗?嗯,我从未尝试过,但我同意复制是有问题的。使用Artist.set\u figure
方法可以“移动”副本,但这可能只适用于尚未在图形上的副本。您是否完全按照此处所示创建了艺术家?如中所示,您是通过构造而不是通过类似于plt.plot()
?这看起来很相关:。对于properties()
中的每个键,您基本上必须setattr(新艺术家,姓名,getattr(旧艺术家,姓名))
。谢谢您的建议!不过,这可以简单得多,因为有一些方便的函数可以处理这一点,例如artist.update\u from()
,如文档所示。不过,对于大多数艺术家来说,这主要只处理kwargs
,而不处理args
。出于某些原因,没有方便的方法返回艺术家的参数。如果您想确保mpl开发者看到/解决此问题,请打开问题/拉取请求或将其重新发布到Distance.matplotlib.org。谢谢您的建议,我将尝试。