Skip to content
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

Add mpl_args and PlotGeometry stroke_width #3183

Merged
merged 6 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 81 additions & 37 deletions src/metpy/plots/declarative.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,14 @@ def lookup_map_feature(feature_name):
return feat.with_scale(scaler)


def plot_kwargs(data):
def plot_kwargs(data, args):
"""Set the keyword arguments for MapPanel plotting."""
if hasattr(data.metpy, 'cartopy_crs'):
# Conditionally add cartopy transform if we are on a map.
kwargs = {'transform': data.metpy.cartopy_crs}
else:
kwargs = {}
kwargs.update(args)
return kwargs


Expand Down Expand Up @@ -103,6 +104,19 @@ def __dir__(self):
dir(type(self))
)

mpl_args = Dict(allow_none=True)
mpl_args.__doc__ = """Supply a dictionary of valid Matplotlib keyword arguments to modify
how the plot variable is drawn.

Using this attribute you must choose the appropriate keyword arguments (kwargs) based on
what you are plotting (e.g., contours, color-filled contours, image plot, etc.). This is
available for all plot types (ContourPlot, FilledContourPlot, RasterPlot, ImagePlot,
BarbPlot, ArrowPlot, PlotGeometry, and PlotObs). For PlotObs, the kwargs are those to
specify the StationPlot object. NOTE: Setting the mpl_args trait will override
any other trait that corresponds to a specific kwarg for the particular plot type
(e.g., linecolor, linewidth).
"""


class Panel(MetPyHasTraits):
"""Draw one or more plots."""
Expand Down Expand Up @@ -942,20 +956,17 @@ def _build(self):
"""Build the plot by calling any plotting methods as necessary."""
x_like, y_like, imdata = self.plotdata

kwargs = plot_kwargs(imdata)
kwargs = plot_kwargs(imdata, self.mpl_args)

# If we're on a map, we use min/max for y and manually figure out origin to try to
# avoid upside down images created by images where y[0] > y[-1], as well as
# specifying the transform
kwargs['extent'] = (x_like[0], x_like[-1], y_like.min(), y_like.max())
kwargs['origin'] = 'upper' if y_like[0] > y_like[-1] else 'lower'
kwargs.setdefault('cmap', self._cmap_obj)
kwargs.setdefault('norm', self._norm_obj)

self.handle = self.parent.ax.imshow(
imdata,
cmap=self._cmap_obj,
norm=self._norm_obj,
**kwargs
)
self.handle = self.parent.ax.imshow(imdata, **kwargs)


@exporter.export
Expand Down Expand Up @@ -999,11 +1010,12 @@ def _build(self):
"""Build the plot by calling any plotting methods as necessary."""
x_like, y_like, imdata = self.plotdata

kwargs = plot_kwargs(imdata)
kwargs = plot_kwargs(imdata, self.mpl_args)
kwargs.setdefault('linewidths', self.linewidth)
kwargs.setdefault('colors', self.linecolor)
kwargs.setdefault('linestyles', self.linestyle)

self.handle = self.parent.ax.contour(x_like, y_like, imdata, self.contours,
colors=self.linecolor, linewidths=self.linewidth,
linestyles=self.linestyle, **kwargs)
self.handle = self.parent.ax.contour(x_like, y_like, imdata, self.contours, **kwargs)
if self.clabels:
self.handle.clabel(inline=1, fmt='%.0f', inline_spacing=8,
use_clabeltext=True, fontsize=self.label_fontsize)
Expand All @@ -1024,11 +1036,11 @@ def _build(self):
"""Build the plot by calling any plotting methods as necessary."""
x_like, y_like, imdata = self.plotdata

kwargs = plot_kwargs(imdata)
kwargs = plot_kwargs(imdata, self.mpl_args)
kwargs.setdefault('cmap', self._cmap_obj)
kwargs.setdefault('norm', self._norm_obj)

self.handle = self.parent.ax.contourf(x_like, y_like, imdata, self.contours,
cmap=self._cmap_obj, norm=self._norm_obj,
**kwargs)
self.handle = self.parent.ax.contourf(x_like, y_like, imdata, self.contours, **kwargs)


@exporter.export
Expand All @@ -1046,11 +1058,11 @@ def _build(self):
"""Build the raster plot by calling any plotting methods as necessary."""
x_like, y_like, imdata = self.plotdata

kwargs = plot_kwargs(imdata)
kwargs = plot_kwargs(imdata, self.mpl_args)
kwargs.setdefault('cmap', self._cmap_obj)
kwargs.setdefault('norm', self._norm_obj)

self.handle = self.parent.ax.pcolormesh(x_like, y_like, imdata,
cmap=self._cmap_obj, norm=self._norm_obj,
**kwargs)
self.handle = self.parent.ax.pcolormesh(x_like, y_like, imdata, **kwargs)


@exporter.export
Expand Down Expand Up @@ -1225,7 +1237,11 @@ def _build(self):
"""Build the plot by calling needed plotting methods as necessary."""
x_like, y_like, u, v = self.plotdata

kwargs = plot_kwargs(u)
kwargs = plot_kwargs(u, self.mpl_args)
kwargs.setdefault('color', self.color)
kwargs.setdefault('pivot', self.pivot)
kwargs.setdefault('length', self.barblength)
kwargs.setdefault('zorder', 2)

# Conditionally apply the proper transform
if 'transform' in kwargs and self.earth_relative:
Expand All @@ -1236,7 +1252,7 @@ def _build(self):
self.handle = self.parent.ax.barbs(
x_like[wind_slice], y_like[wind_slice],
u.values[wind_slice], v.values[wind_slice],
color=self.color, pivot=self.pivot, length=self.barblength, zorder=2, **kwargs)
**kwargs)


@exporter.export
Expand Down Expand Up @@ -1287,7 +1303,10 @@ def _build(self):
"""Build the plot by calling needed plotting methods as necessary."""
x_like, y_like, u, v = self.plotdata

kwargs = plot_kwargs(u)
kwargs = plot_kwargs(u, self.mpl_args)
kwargs.setdefault('color', self.color)
kwargs.setdefault('pivot', self.pivot)
kwargs.setdefault('scale', self.arrowscale)

# Conditionally apply the proper transform
if 'transform' in kwargs and self.earth_relative:
Expand All @@ -1298,7 +1317,7 @@ def _build(self):
self.handle = self.parent.ax.quiver(
x_like[wind_slice], y_like[wind_slice],
u.values[wind_slice], v.values[wind_slice],
color=self.color, pivot=self.pivot, scale=self.arrowscale, **kwargs)
**kwargs)

# The order here needs to match the order of the tuple
if self.arrowkey is not None:
Expand Down Expand Up @@ -1573,9 +1592,12 @@ def _build(self):
scale = 1. if self.parent._proj_obj == ccrs.PlateCarree() else 100000.
point_locs = self.parent._proj_obj.transform_points(ccrs.PlateCarree(), lon, lat)
subset = reduce_point_density(point_locs, self.reduce_points * scale)
kwargs = self.mpl_args
kwargs.setdefault('clip_on', True)
kwargs.setdefault('transform', ccrs.PlateCarree())
kwargs.setdefault('fontsize', self.fontsize)

self.handle = StationPlot(self.parent.ax, lon[subset], lat[subset], clip_on=True,
transform=ccrs.PlateCarree(), fontsize=self.fontsize)
self.handle = StationPlot(self.parent.ax, lon[subset], lat[subset], **kwargs)

for i, ob_type in enumerate(self.fields):
field_kwargs = {}
Expand Down Expand Up @@ -1673,6 +1695,17 @@ class PlotGeometry(MetPyHasTraits):
the sequence of colors as needed. Default value is black.
"""

stroke_width = Union([Instance(collections.abc.Iterable), Float()], default_value=[1],
allow_none=True)
stroke_width.__doc__ = """Stroke width(s) for polygons and lines.

A single integer or floating point value or collection of values representing the size of
the stroke width. If a collection, the first value corresponds to the first Shapely
object in `geometry`, the second value corresponds to the second Shapely object, and so on.
If `stroke_width` is shorter than `geometry`, `stroke_width` cycles back to the beginning,
repeating the sequence of values as needed. Default value is 1.
"""

marker = Unicode(default_value='.', allow_none=False)
marker.__doc__ = """Symbol used to denote points.

Expand Down Expand Up @@ -1851,27 +1884,38 @@ def _build(self):
else self.label_edgecolor)
self.label_facecolor = (['none'] if self.label_facecolor is None
else self.label_facecolor)
kwargs = self.mpl_args

# Each Shapely object is plotted separately with its corresponding colors and label
for geo_obj, stroke, fill, label, fontcolor, fontoutline in zip(
self.geometry, cycle(self.stroke), cycle(self.fill), cycle(self.labels),
cycle(self.label_facecolor), cycle(self.label_edgecolor)):
for geo_obj, stroke, strokewidth, fill, label, fontcolor, fontoutline in zip(
self.geometry, cycle(self.stroke), cycle(self.stroke_width), cycle(self.fill),
cycle(self.labels), cycle(self.label_facecolor), cycle(self.label_edgecolor)):
# Plot the Shapely object with the appropriate method and colors
if isinstance(geo_obj, (MultiPolygon, Polygon)):
self.parent.ax.add_geometries([geo_obj], edgecolor=stroke,
facecolor=fill, crs=ccrs.PlateCarree())
kwargs.setdefault('edgecolor', stroke)
kwargs.setdefault('linewidths', strokewidth)
kwargs.setdefault('facecolor', fill)
kwargs.setdefault('crs', ccrs.PlateCarree())
self.parent.ax.add_geometries([geo_obj], **kwargs)
elif isinstance(geo_obj, (MultiLineString, LineString)):
self.parent.ax.add_geometries([geo_obj], edgecolor=stroke,
facecolor='none', crs=ccrs.PlateCarree())
kwargs.setdefault('edgecolor', stroke)
kwargs.setdefault('linewidths', strokewidth)
kwargs.setdefault('facecolor', 'none')
kwargs.setdefault('crs', ccrs.PlateCarree())
self.parent.ax.add_geometries([geo_obj], **kwargs)
elif isinstance(geo_obj, MultiPoint):
kwargs.setdefault('color', fill)
kwargs.setdefault('marker', self.marker)
kwargs.setdefault('transform', ccrs.PlateCarree())
for point in geo_obj.geoms:
lon, lat = point.coords[0]
self.parent.ax.plot(lon, lat, color=fill, marker=self.marker,
transform=ccrs.PlateCarree())
self.parent.ax.plot(lon, lat, **kwargs)
elif isinstance(geo_obj, Point):
kwargs.setdefault('color', fill)
kwargs.setdefault('marker', self.marker)
kwargs.setdefault('transform', ccrs.PlateCarree())
lon, lat = geo_obj.coords[0]
self.parent.ax.plot(lon, lat, color=fill, marker=self.marker,
transform=ccrs.PlateCarree())
self.parent.ax.plot(lon, lat, **kwargs)

# Plot labels if provided
if label:
Expand Down
Binary file added tests/plots/baseline/test_colorfill_args.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading