问题描述
I created an array of Line2D-objects with matplotlib that I'd like to use in various plots. However, using the same artist in multiple plots doesn't work since I get:
RuntimeError: Can not put single artist in more than one figure
As I found out, the artists once attached to an axis, cannot be attached to another anymore. Well, my idea was to simply copy the array containing the lines with copy() but it won't work. The copied array still refers to the same objects. I suppose, that is because you simply cannot copy artists (?).
Is there any way to avoid the recalculation of the Line2Ds and to only have to calculate them once?
I so dearly wish there was a copy
method in the Artist Class of matplotlibs
!
Perhaps, someone wants to adapt the code below into the Artist Class
in order to have a kind of transfer
method to copy one Artist
object into another.
Wrong way to go
Using the standard copy
or deepcopy
functions for objects in the package copy
does not work.
For instance, it is useless to script:
import matplotlib.pyplot as plt
import numpy as np
import copy
x = np.linspace(0, 2*np.pi, 100)
fig = plt.figure()
ax1 = plt.subplot(211)
ax2 = plt.subplot(212)
# create a Line2D object
line1, = ax1.plot(x, np.sin(x))
# copy the Line2D object
line2 = copy.deepcopy(line1) #ERROR!
Solutions:
So, the only way I found to copy an Artist
object, is to create an empty object of the desired type, and then transfer via loop all necessary attributes from the original object into the new created one.
The tool to transfer attributes values from one object to the other is this function I defined:
# -- Function to attributes copy
#It copies the attributes given in attr_list (a sequence of the attributes names as
# strings) from the object 'obj1' into the object 'obj2'
#It should work for any objects as long as the attributes are accessible by
# 'get_attribute' and 'set_attribute' methods.
def copy_attributes(obj2, obj1, attr_list):
for i_attribute in attr_list:
getattr(obj2, 'set_' + i_attribute)( getattr(obj1, 'get_' + i_attribute)() )
In this function the most important parameter is attr_list
. This is a list of the names of the attributes that we want to copy from obj1
into obj2
, for example, for an object Artist.Line2D
, it could be attr_list = ('xdata', 'ydata', 'animated', 'antialiased', 'color', 'dash_capstyle', 'dash_joinstyle', 'drawstyle')
As any Artist
object has different attributes, the key in this transfer process is to generate the list of attributes names with the right attributes to be transferred.
There are two ways to generate the list of the attributes names:
First option: we specify the attributes to be selected. That is, we hard code a list with all attributes we want to transfer. It is more arduous than the second option. We have to fully specify the attributes for every type of object: these are usually long lists. It is only recommendable, when we deal with only one type of
Artist
object.Second option: we specify the attributes that are not selected. That is, we write an "exceptions list" with the attributes that we do not want to transfer, we automatically choose all transferable attributes of the object, but those in our "exception list". This is the quickest option, and we can use it with different types of
Artist
objects simultaneously.
First option: specifying the list of transferred attributes
We just write an assignment to define the list of attributes we want to transfer, as we have shown above.
The drawback of this option, is that it cannot be immediately extended to different Artist
objects, for instance, Line2D and Circle. Because we have to hard code different lists of attributes names, one for each type of Artist
object.
Full Example
I show an example for Line2D Artist class, as in the question specified.
import matplotlib.pyplot as plt
import numpy as np
# -- Function to attributes copy
#It copies the attributes given in attr_list (a sequence of the attributes names as
# strings) from the object 'obj1' into the object 'obj2'
#It should work for any objects as long as the attributes are accessible by
# 'get_attribute' and 'set_attribute' methods.
def copy_attributes(obj2, obj1, attr_list):
for i_attribute in attr_list:
getattr(obj2, 'set_' + i_attribute)( getattr(obj1, 'get_' + i_attribute)() )
#Creating a figure with to axes
fig = plt.figure()
ax1 = plt.subplot(211)
ax2 = plt.subplot(212)
plt.tight_layout() #Tweak to improve subplot layout
#create a Line2D object 'line1' via plot
x = np.linspace(0, 2*np.pi, 100)
line1, = ax1.plot(x, np.sin(x))
ax1.set_xlabel('line1 in ax1') #Labelling axis
#Attributes of the old Line2D object that must be copied to the new object
#It's just a strings list, you can add or take away attributes to your wishes
copied_line_attributes = ('xdata', 'ydata', 'animated', 'antialiased', 'color',
'dash_capstyle', 'dash_joinstyle',
'drawstyle', 'fillstyle', 'linestyle', 'linewidth',
'marker', 'markeredgecolor', 'markeredgewidth', 'markerfacecolor',
'markerfacecoloralt', 'markersize', 'markevery', 'pickradius',
'solid_capstyle', 'solid_joinstyle', 'visible', 'zorder')
#Creating an empty Line2D object
line2 = plt.Line2D([],[])
#Copying the list of attributes 'copied_line_attributes' of line1 into line2
copy_attributes(line2, line1, copied_line_attributes)
#Setting the new axes ax2 with the same limits as ax1
ax2.set_xlim(ax1.get_xlim())
ax2.set_ylim(ax1.get_ylim())
#Adding the copied object line2 to the new axes
ax2.add_artist(line2)
ax2.set_xlabel('line2 in ax2') #Labelling axis
plt.show()
Output
Second Option: specifying the list of NOT transferred attributes
In this case, we specify the names of the attributes, that we do not want to transfer: we make a exception list
. We gather automatically all transferable attributes of the Artist
object and exclude the names of our exception list
.
The advantage is that usually for different Artist
objects the excluded attributes are the same short list and, consequently, this option can be more quickly scripted. In the example below, the list is as short as except_attributes = ('transform', 'figure')
The key function in this case is list_transferable_attributes
as shown below:
#Returns a list of the transferable attributes, that is the attributes having
# both a 'get' and 'set' method. But the methods in 'except_attributes' are not
# included
def list_transferable_attributes(obj, except_attributes):
obj_methods_list = dir(obj)
obj_get_attr = []
obj_set_attr = []
obj_transf_attr =[]
for name in obj_methods_list:
if len(name) > 4:
prefix = name[0:4]
if prefix == 'get_':
obj_get_attr.append(name[4:])
elif prefix == 'set_':
obj_set_attr.append(name[4:])
for attribute in obj_set_attr:
if attribute in obj_get_attr and attribute not in except_attributes:
obj_transf_attr.append(attribute)
return obj_transf_attr
Full Example
import matplotlib.pyplot as plt
import numpy as np
# -- Function to copy, or rather, transfer, attributes
#It copies the attributes given in attr_list (a sequence of the attributes names as
# strings) from the object 'obj1' into the object 'obj2'
#It should work for any objects as long as the attributes are accessible by
# 'get_attribute' and 'set_attribute' methods.
def copy_attributes(obj2, obj1, attr_list):
for i_attribute in attr_list:
getattr(obj2,
'set_' + i_attribute)( getattr(obj1, 'get_' + i_attribute)() )
# #Returns a list of pairs (attribute string, attribute value) of the given
# # attributes list 'attr_list' of the given object 'obj'
# def get_attributes(obj, attr_list):
# attr_val_list = []
# for i_attribute in attr_list:
# i_val = getattr(obj, 'get_' + i_attribute)()
# attr_val_list.append((i_attribute, i_val))
#
# return attr_val_list
#Returns a list of the transferable attributes, that is the attributes having
# both a 'get' and 'set' method. But the methods in 'except_attributes' are not
# included
def list_transferable_attributes(obj, except_attributes):
obj_methods_list = dir(obj)
obj_get_attr = []
obj_set_attr = []
obj_transf_attr =[]
for name in obj_methods_list:
if len(name) > 4:
prefix = name[0:4]
if prefix == 'get_':
obj_get_attr.append(name[4:])
elif prefix == 'set_':
obj_set_attr.append(name[4:])
for attribute in obj_set_attr:
if attribute in obj_get_attr and attribute not in except_attributes:
obj_transf_attr.append(attribute)
return obj_transf_attr
#Creating a figure with to axes
fig = plt.figure()
ax1 = plt.subplot(211) #First axes
ax2 = plt.subplot(212) #Second axes
plt.tight_layout() #Optional: Tweak to improve subplot layout
#create an artist Line2D object 'line1' via plot
x = np.linspace(0, 2*np.pi, 100)
line1, = ax1.plot(x, np.sin(x))
#create an artist Circle object
circle1 = plt.Circle([1,0], 0.5, facecolor='yellow', edgecolor='k')
#Adding the object to the first axes
ax1.add_patch(circle1)
#Labelling first axis
ax1.set_xlabel('line1 and circle1 in ax1')
#Methods that we should not copy from artist to artist
except_attributes = ('transform', 'figure')
#Obtaining the names of line2D attributes that can be transfered
transferred_line_attributes = list_transferable_attributes(line1, except_attributes)
#Obtaining the names of Circle attributes that can be transfered
transferred_circle_attributes = list_transferable_attributes(circle1, except_attributes)
#Creating an empty Line2D object
line2 = plt.Line2D([],[])
circle2 = plt.Circle([],[])
#Copying the list of attributes 'transferred_line_attributes' of line1 into line2
copy_attributes(line2, line1, transferred_line_attributes)
copy_attributes(circle2, circle1, transferred_circle_attributes)
#attr_val_list_line2 = get_attributes(line2, line1_attr_list)
#Setting the new axes ax2 with the same limits as ax1
ax2.set_xlim(ax1.get_xlim())
ax2.set_ylim(ax1.get_ylim())
#Adding the copied object line2 to the new axes
ax2.add_line(line2) #.add_artist(line2) also possible
ax2.add_patch(circle2) #.add_artist(circle2) also possible
ax2.set_xlabel('line2 and circle2 in ax2') #Labelling axis
plt.show()
Output
这篇关于复制 matplotlib 艺术家的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!