cartopy icon indicating copy to clipboard operation
cartopy copied to clipboard

Objects created with 'add_geometries' are missing the 'color', 'edgecolor', ... properties

Open pdulab opened this issue 6 years ago • 2 comments

Description

In matplotlib it is possible to change the color (face,edge,...) of an object that has already been created. This is not possible with an object created with add_geometries.

In matplotlib, we can create a plot and change its color with:

import matplolib.pyplot as plt

line, = plt.plot([1,2,3],[4,5,6],'red') #Plot in red
line.set_color('green') #Change the color to green

But in cartopy, the 'geo' object created with the following code will not have any 'color' (or similar) property.:

geo = ax.add_geometries(geometry,facecolor='ghostwhite', edgecolor='black',crs=data_transf)
geo.set_color('green') #Does not exist

This issue is in response to a stackoverflow question.

Code to reproduce

A call to matplotlib.artist.getp will show that no color, facecolor,... exist for a FeatureArtist from cartopy added with add_geometries.

For a plot, the call returns:

>>import matplotlib.artist as mart
>>mart.getp(line)
agg_filter = None
alpha = None
animated = False
antialiased = True
children = []
clip_box = TransformedBbox(     Bbox(x0=0.0, y0=0.0, x1=1.0, ...
clip_on = True
clip_path = None
color = (1.0, 0.0, 0.0, 1.0)
contains = None
dash_capstyle = butt
dash_joinstyle = round
data = (array([1, 2, 3]), array([4, 5, 6]))
drawstyle = default
figure = Figure(640x476)
fillstyle = full
gid = None
in_layout = True
label = _line0
linestyle = -
linewidth = 1.5
marker = None
markeredgecolor = (1.0, 0.0, 0.0, 1.0)
markeredgewidth = 1.0
markerfacecolor = (1.0, 0.0, 0.0, 1.0)
markerfacecoloralt = none
markersize = 6.0
markevery = None
path = Path(array([[ 1.,  4.],        [ 2.,  5.],        ...
path_effects = []
picker = None
pickradius = 5
rasterized = None

And for a geometry, it returns:

>>mart.getp(geo)
agg_filter = None
alpha = None
animated = False
children = []
clip_box = TransformedBbox(     Bbox(x0=0.0, y0=0.0, x1=1.0, ...
clip_on = True
clip_path = None
contains = None
figure = Figure(1200x990)
gid = None
in_layout = True
label = 
path_effects = []
picker = None
rasterized = None
sketch_params = None
snap = None
transform = CompositeGenericTransform(     TransformWrapper(  ...
transformed_clip_path_and_affine = (None, None)
url = None
visible = True
zorder = 1

We can see that the properties related to colors are missing.

pdulab avatar Dec 01 '18 19:12 pdulab

I'd be happy enough to enable this behaviour, but it looks like to support getp and setp, we'd need to implement all of the set_* and get_* methods. I'm quite reluctant to do this, as it would inevitably get out of date, add things we shouldn't, and miss things we don't want to miss.

The feature class has been created to be somewhat immutable, but the feature artist not so much (as they are cheap to construct, and you need one per mpl axes anyway). As a result, updating the kwargs of a FeatureArtist isn't too much of a concern IMO, so long as we don't do too much work in the __init__. Unfortunately, we have just added some work into the __init__ (in #1029) that should be removed by #1190.

The magic line in matplotlib that does the work of finding the properties of the feature is in https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/artist.py#L1284 (dir(self.o) followed by a continue if the method doesn't start with get_ or set_). There is a really great opportunity for somebody to make a significant improvement to the mpl code here - matplotlib could happly have a "what style kwargs do you support" type API rather than a method introspection one.

Alas, without that, we're forced to expose all of the methods that PathCollection exposes. What better way of doing that than using the matplotlib ArtistInspector class...

>>> import matplotlib.artist
>>> import matplotlib.collections
>>> pc = matplotlib.collections.PathCollection
>>> matplotlib.artist.ArtistInspector(pc)
<matplotlib.artist.ArtistInspector object at 0x112ee0048>
>>> i = matplotlib.artist.ArtistInspector(pc)
>>> i._
i._get_setters_and_targets(  i._get_valid_values_regex    i._replace_path(             
>>> i._get_setters_and_targets()
[('agg_filter', 'matplotlib.artist.Artist.set_agg_filter'), ('alpha', 'matplotlib.collections.Collection.set_alpha'), ('animated', 'matplotlib.artist.Artist.set_animated'), ('antialiased', 'matplotlib.collections.Collection.set_antialiased'), ('array', 'matplotlib.cm.ScalarMappable.set_array'), ('capstyle', 'matplotlib.collections.Collection.set_capstyle'), ('clim', 'matplotlib.cm.ScalarMappable.set_clim'), ('clip_box', 'matplotlib.artist.Artist.set_clip_box'), ('clip_on', 'matplotlib.artist.Artist.set_clip_on'), ('clip_path', 'matplotlib.artist.Artist.set_clip_path'), ('cmap', 'matplotlib.cm.ScalarMappable.set_cmap'), ('color', 'matplotlib.collections.Collection.set_color'), ('contains', 'matplotlib.artist.Artist.set_contains'), ('edgecolor', 'matplotlib.collections.Collection.set_edgecolor'), ('facecolor', 'matplotlib.collections.Collection.set_facecolor'), ('figure', 'matplotlib.artist.Artist.set_figure'), ('gid', 'matplotlib.artist.Artist.set_gid'), ('hatch', 'matplotlib.collections.Collection.set_hatch'), ('in_layout', 'matplotlib.artist.Artist.set_in_layout'), ('joinstyle', 'matplotlib.collections.Collection.set_joinstyle'), ('label', 'matplotlib.artist.Artist.set_label'), ('linestyle', 'matplotlib.collections.Collection.set_linestyle'), ('linewidth', 'matplotlib.collections.Collection.set_linewidth'), ('norm', 'matplotlib.cm.ScalarMappable.set_norm'), ('offset_position', 'matplotlib.collections.Collection.set_offset_position'), ('offsets', 'matplotlib.collections.Collection.set_offsets'), ('path_effects', 'matplotlib.artist.Artist.set_path_effects'), ('paths', 'matplotlib.collections.PathCollection.set_paths'), ('picker', 'matplotlib.artist.Artist.set_picker'), ('pickradius', 'matplotlib.collections.Collection.set_pickradius'), ('rasterized', 'matplotlib.artist.Artist.set_rasterized'), ('sizes', 'matplotlib.collections._CollectionWithSizes.set_sizes'), ('sketch_params', 'matplotlib.artist.Artist.set_sketch_params'), ('snap', 'matplotlib.artist.Artist.set_snap'), ('transform', 'matplotlib.artist.Artist.set_transform'), ('url', 'matplotlib.artist.Artist.set_url'), ('urls', 'matplotlib.collections.Collection.set_urls'), ('visible', 'matplotlib.artist.Artist.set_visible'), ('zorder', 'matplotlib.artist.Artist.set_zorder')]
>>> i.get_setters()
['agg_filter', 'alpha', 'animated', 'antialiased', 'array', 'capstyle', 'clim', 'clip_box', 'clip_on', 'clip_path', 'cmap', 'color', 'contains', 'edgecolor', 'facecolor', 'figure', 'gid', 'hatch', 'in_layout', 'joinstyle', 'label', 'linestyle', 'linewidth', 'norm', 'offset_position', 'offsets', 'path_effects', 'paths', 'picker', 'pickradius', 'rasterized', 'sizes', 'sketch_params', 'snap', 'transform', 'url', 'urls', 'visible', 'zorder']

I've taken a punt at an implementation in #1231.

pelson avatar Dec 03 '18 23:12 pelson

poke ... also poked the accompanying PR from way back - I think lots of folks would enjoy this functionality.

Jeitan avatar Mar 30 '22 16:03 Jeitan