-
Notifications
You must be signed in to change notification settings - Fork 368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Legends for feature artists #1500
Open
poplarShift
wants to merge
12
commits into
SciTools:main
Choose a base branch
from
poplarShift:legend
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 5 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
2e4e34b
factor out stylized paths
poplarShift 5fef68d
factor out geometry operations
poplarShift b146558
simplify passed arguments
poplarShift 5a8bd09
legend handler for feature artist
poplarShift a4abc72
stickler-ci
poplarShift bd6a831
more stickler
poplarShift 049d018
remove comment
poplarShift cb3de4f
draw linestrings like polygons
poplarShift 288a8f0
decide between Rectangle and Line2D based on facecolor
poplarShift 495d96d
allow never as facecolor arg
poplarShift 3d58461
draw polygons as rectangles regardless
poplarShift 0976f53
return one artist per path as in path collection handler
poplarShift File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,10 @@ | |
import matplotlib.artist | ||
import matplotlib.collections | ||
|
||
from shapely.geometry import Polygon, LineString, LinearRing | ||
|
||
import cartopy.mpl.patch as cpatch | ||
import cartopy.crs as ccrs | ||
from .style import merge as style_merge, finalize as style_finalize | ||
|
||
|
||
|
@@ -144,16 +147,29 @@ def draw(self, renderer, *args, **kwargs): | |
return | ||
|
||
ax = self.axes | ||
feature_crs = self._feature.crs | ||
|
||
# Get geometries that we need to draw. | ||
extent = None | ||
try: | ||
extent = ax.get_extent(feature_crs) | ||
except ValueError: | ||
warnings.warn('Unable to determine extent. Defaulting to global.') | ||
geoms = self._feature.intersecting_geometries(extent) | ||
geoms, feature_crs, transform = self.get_geometry() | ||
projection = ax.projection | ||
stylised_paths = self.get_stylised_paths( | ||
geoms, feature_crs, projection, **kwargs) | ||
|
||
# Draw one PathCollection per style. We could instead pass an array | ||
# of style items through to a single PathCollection, but that | ||
# complexity does not yet justify the effort. | ||
for style, paths in stylised_paths.items(): | ||
style = style_finalize(dict(style)) | ||
# Build path collection and draw it. | ||
c = matplotlib.collections.PathCollection(paths, | ||
transform=transform, | ||
**style) | ||
c.set_clip_path(ax.patch) | ||
c.set_figure(ax.figure) | ||
c.draw(renderer) | ||
|
||
# n.b. matplotlib.collection.Collection.draw returns None | ||
return None | ||
|
||
def get_stylised_paths(self, geoms, feature_crs, projection, **kwargs): | ||
# Combine all the keyword args in priority order. | ||
prepared_kwargs = style_merge(self._feature.kwargs, | ||
self._kwargs, | ||
|
@@ -165,7 +181,6 @@ def draw(self, renderer, *args, **kwargs): | |
|
||
# Project (if necessary) and convert geometries to matplotlib paths. | ||
stylised_paths = OrderedDict() | ||
key = ax.projection | ||
for geom in geoms: | ||
# As Shapely geometries cannot be relied upon to be | ||
# hashable, we have to use a WeakValueDictionary to manage | ||
|
@@ -183,15 +198,15 @@ def draw(self, renderer, *args, **kwargs): | |
geom_key, geom) | ||
mapping = FeatureArtist._geom_key_to_path_cache.setdefault( | ||
geom_key, {}) | ||
geom_paths = mapping.get(key) | ||
geom_paths = mapping.get(projection) | ||
if geom_paths is None: | ||
if ax.projection != feature_crs: | ||
projected_geom = ax.projection.project_geometry( | ||
if projection != feature_crs: | ||
projected_geom = projection.project_geometry( | ||
geom, feature_crs) | ||
else: | ||
projected_geom = geom | ||
geom_paths = cpatch.geos_to_path(projected_geom) | ||
mapping[key] = geom_paths | ||
mapping[projection] = geom_paths | ||
|
||
if not self._styler: | ||
style = prepared_kwargs | ||
|
@@ -202,20 +217,65 @@ def draw(self, renderer, *args, **kwargs): | |
|
||
stylised_paths.setdefault(style, []).extend(geom_paths) | ||
|
||
transform = ax.projection._as_mpl_transform(ax) | ||
return stylised_paths | ||
|
||
# Draw one PathCollection per style. We could instead pass an array | ||
# of style items through to a single PathCollection, but that | ||
# complexity does not yet justify the effort. | ||
for style, paths in stylised_paths.items(): | ||
style = style_finalize(dict(style)) | ||
# Build path collection and draw it. | ||
c = matplotlib.collections.PathCollection(paths, | ||
transform=transform, | ||
**style) | ||
c.set_clip_path(ax.patch) | ||
c.set_figure(ax.figure) | ||
c.draw(renderer) | ||
def get_geometry(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And this too. |
||
ax = self.axes | ||
extent = None | ||
if ax is not None: | ||
transform = ax.projection._as_mpl_transform(ax) | ||
feature_crs = self._feature.crs | ||
# Get geometries that we need to draw. | ||
try: | ||
extent = ax.get_extent(feature_crs) | ||
except ValueError: | ||
warnings.warn('''Unable to determine extent. | ||
Defaulting to global.''') | ||
Comment on lines
+232
to
+233
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This warning should definitely not have a newline and a bunch of whitespace in it. Use plain strings, not a multiline string. |
||
else: | ||
transform = None | ||
feature_crs = ccrs.PlateCarree() | ||
|
||
# n.b. matplotlib.collection.Collection.draw returns None | ||
return None | ||
geoms = self._feature.intersecting_geometries(extent) | ||
|
||
return geoms, feature_crs, transform | ||
poplarShift marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class HandlerFeature(matplotlib.legend_handler.HandlerPathCollection): | ||
poplarShift marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def create_artists(self, legend, orig_handle, | ||
xdescent, ydescent, width, height, fontsize, trans): | ||
# Use first geometry object to determine type | ||
geom = next(orig_handle._feature.geometries()) | ||
poplarShift marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Take the first path to generate legend artist | ||
geoms, feature_crs, _ = orig_handle.get_geometry() | ||
projection = ccrs.PlateCarree() | ||
stylised_paths = orig_handle.get_stylised_paths(geoms, feature_crs, | ||
projection) | ||
style = dict(list(stylised_paths.keys())[0]) | ||
|
||
# style = deepcopy(style) | ||
if type(geom) is Polygon: | ||
p = matplotlib.patches.Rectangle( | ||
xy=(-xdescent, -ydescent), | ||
width=width, height=height, | ||
**style | ||
) | ||
elif type(geom) in (LineString, LinearRing): | ||
# color handling | ||
style.pop('facecolor') | ||
val = style.pop('edgecolor', None) | ||
if val != 'none' and style.get('color', 'none') == 'none': | ||
style['color'] = val | ||
|
||
xdata, _ = self.get_xdata(legend, xdescent, ydescent, | ||
width, height, fontsize) | ||
ydata = np.full_like(xdata, (height - ydescent) / 2) | ||
p = matplotlib.lines.Line2D(xdata, ydata, **style) | ||
|
||
p.set_transform(trans) | ||
return [p] | ||
|
||
|
||
|
||
matplotlib.legend.Legend.update_default_handler_map({ | ||
poplarShift marked this conversation as resolved.
Show resolved
Hide resolved
poplarShift marked this conversation as resolved.
Show resolved
Hide resolved
|
||
FeatureArtist: HandlerFeature()}) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this private.