From 6757b32802c0364f47bf2aeb35f93cb59b263922 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Wed, 5 Mar 2025 06:22:29 -0600 Subject: [PATCH] Use config options to set arrow width and spacing --- mpas_analysis/default.cfg | 10 ++- mpas_analysis/polar_regions.cfg | 26 +++++--- mpas_analysis/shared/plot/climatology_map.py | 44 +++++++------ mpas_analysis/shared/plot/colormap.py | 65 +++++++++++-------- mpas_analysis/shared/plot/vertical_section.py | 14 ++-- 5 files changed, 93 insertions(+), 66 deletions(-) diff --git a/mpas_analysis/default.cfg b/mpas_analysis/default.cfg index c45c4e324..3fcd11d33 100755 --- a/mpas_analysis/default.cfg +++ b/mpas_analysis/default.cfg @@ -1576,9 +1576,13 @@ normArgsResult = {'vmin': -100, 'vmax': 200.} contourLevelsResult = np.arange(-100., 201.0, 10.) contourThicknessResult = 0.5 contourColorResult = black -# Add arrows to contour lines -# whether to include arrows on the contour lines showing the direction of flow -arrowsOnContourResult = True +# The spacing (in meters) between arrows on contours in projection plots +# (None to disable) +arrowSpacingResult = 8e5 +# The width (in meters) of arrows on contours in projection plots (None to +# disable) +arrowWidthResult = 1.5e4 + # colormap for differences colormapNameDifference = cmo.balance # whether the colormap is indexed or continuous diff --git a/mpas_analysis/polar_regions.cfg b/mpas_analysis/polar_regions.cfg index c5b56ee17..7baa1877e 100644 --- a/mpas_analysis/polar_regions.cfg +++ b/mpas_analysis/polar_regions.cfg @@ -626,14 +626,18 @@ colormapTypeResult = continuous normTypeResult = symLog # A dictionary with keywords for the norm normArgsResult = {'linthresh': 30., 'linscale': 0.5, 'vmin': -150., 'vmax': 150.} -colorbarTicksResult = [-100.,-40., -20., -10., 0., 10., 20., 40., 100.] +colorbarTicksResult = [-150., -100.,-60., -40., -20., -10., 0., 10., 20., 40., 60., 100., 150.] # Adding contour lines to the figure contourLevelsResult = np.arange(-150., 150.1, 10.) contourThicknessResult = 0.5 contourColorResult = black -# Add arrows to contour lines -# whether to include arrows on the contour lines showing the direction of flow -arrowsOnContourResult = True +# The spacing (in meters) between arrows on contours in projection plots +# (None to disable) +arrowSpacingResult = 8e5 +# The width (in meters) of arrows on contours in projection plots (None to +# disable) +arrowWidthResult = 1.5e4 + # colormap for differences colormapNameDifference = cmo.balance # whether the colormap is indexed or continuous @@ -661,14 +665,18 @@ colormapTypeResult = continuous normTypeResult = symLog # A dictionary with keywords for the norm normArgsResult = {'linthresh': 1., 'linscale': 0.5, 'vmin': -20., 'vmax': 20.} -colorbarTicksResult = [-20., -10.,-4., -2., -1., 0., 1., 2., 4., 10., 20.] +colorbarTicksResult = [-20., -10.,-4., -2., -1., -0.5, 0., 0.5, 1., 2., 4., 10., 20.] # Adding contour lines to the figure -contourLevelsResult = np.arange(-5., 5.1, 0.5) +contourLevelsResult = np.arange(-20., 20.1, 0.5) contourThicknessResult = 0.5 contourColorResult = black -# Add arrows to contour lines -# whether to include arrows on the contour lines showing the direction of flow -arrowsOnContourResult = True +# The spacing (in meters) between arrows on contours in projection plots +# (None to disable) +arrowSpacingResult = 8e4 +# The width (in meters) of arrows on contours in projection plots (None to +# disable) +arrowWidthResult = 1.5e3 + # colormap for differences colormapNameDifference = cmo.balance # whether the colormap is indexed or continuous diff --git a/mpas_analysis/shared/plot/climatology_map.py b/mpas_analysis/shared/plot/climatology_map.py index d458f7681..f7fc6dd27 100644 --- a/mpas_analysis/shared/plot/climatology_map.py +++ b/mpas_analysis/shared/plot/climatology_map.py @@ -125,8 +125,8 @@ def plot_polar_comparison( # ------- # Xylar Asay-Davis, Milena Veneziani - def do_subplot(ax, field, title, colormap, norm, levels, ticks, contours, - lineWidth, lineColor, arrows): + def _do_subplot(ax, field, title, colormap, norm, levels, ticks, contours, + lineWidth, lineColor, **kwargs): """ Make a subplot within the figure. """ @@ -230,14 +230,14 @@ def do_subplot(ax, field, title, colormap, norm, levels, ticks, contours, plotProjection)) ax = plt.subplot(subplots[0], projection=projection) - do_subplot(ax=ax, field=modelArray, title=modelTitle, **dictModelRef) + _do_subplot(ax=ax, field=modelArray, title=modelTitle, **dictModelRef) if refArray is not None: ax = plt.subplot(subplots[1], projection=projection) - do_subplot(ax=ax, field=refArray, title=refTitle, **dictModelRef) + _do_subplot(ax=ax, field=refArray, title=refTitle, **dictModelRef) ax = plt.subplot(subplots[2], projection=projection) - do_subplot(ax=ax, field=diffArray, title=diffTitle, **dictDiff) + _do_subplot(ax=ax, field=diffArray, title=diffTitle, **dictDiff) fig.canvas.draw() plt.tight_layout(pad=4.) @@ -343,8 +343,8 @@ def plot_global_comparison( # ------- # Xylar Asay-Davis, Milena Veneziani - def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, - lineWidth, lineColor, arrows): + def _plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, + lineWidth, lineColor, **kwargs): ax.set_extent(extent, crs=projection) @@ -428,16 +428,16 @@ def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, axes = [] ax = plt.subplot(subplots[0], projection=projection) - plot_panel(ax, modelTitle, modelArray, **dictModelRef) + _plot_panel(ax, modelTitle, modelArray, **dictModelRef) axes.append(ax) if refArray is not None: ax = plt.subplot(subplots[1], projection=projection) - plot_panel(ax, refTitle, refArray, **dictModelRef) + _plot_panel(ax, refTitle, refArray, **dictModelRef) axes.append(ax) ax = plt.subplot(subplots[2], projection=projection) - plot_panel(ax, diffTitle, diffArray, **dictDiff) + _plot_panel(ax, diffTitle, diffArray, **dictDiff) axes.append(ax) _add_stats(modelArray, refArray, diffArray, Lats, axes) @@ -543,7 +543,7 @@ def plot_projection_comparison( # ------- # Xylar Asay-Davis - def add_arrow_to_line_2d(ax, poly, arrow_spacing=8e5, arrow_width=1.5e4): + def _add_arrow_to_line_2d(ax, poly, arrow_spacing, arrow_width): """ https://stackoverflow.com/a/27637925/7728169 Add arrows to a matplotlib.lines.Line2D at selected locations. @@ -568,8 +568,8 @@ def add_arrow_to_line_2d(ax, poly, arrow_spacing=8e5, arrow_width=1.5e4): arrows.append(p) return arrows - def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, - lineWidth, lineColor, arrows): + def _plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, + lineWidth, lineColor, arrowSpacing, arrowWidth): title = limit_title(title, maxTitleLength) ax.set_title(title, **plottitle_font) @@ -611,10 +611,11 @@ def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, cs = ax.contour(x_center, y_center, array, levels=contours, colors=lineColor, linewidths=lineWidth) # add arrows to streamlines - if arrows is not None: + if arrowSpacing is not None and arrowWidth is not None: for path in cs.get_paths(): for poly in path.to_polygons(): - add_arrow_to_line_2d(ax, poly) + _add_arrow_to_line_2d(ax, poly, arrowSpacing, + arrowWidth) # create an axes on the right side of ax. The width of cax will be 5% # of ax and the padding between cax and ax will be fixed at 0.05 inch. divider = make_axes_locatable(ax) @@ -677,7 +678,8 @@ def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, # set up land colormap if not useCartopyCoastline: colorList = [(0.8, 0.8, 0.8), (0.8, 0.8, 0.8)] - landColorMap = cols.LinearSegmentedColormap.from_list('land', colorList) + landColorMap = cols.LinearSegmentedColormap.from_list('land', + colorList) # locations of centers for contour plots xCenter = 0.5 * (x[1:] + x[0:-1]) @@ -688,14 +690,14 @@ def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, extent = [x[0], x[-1], y[0], y[-1]] ax = plt.subplot(subplots[0], projection=projection) - plot_panel(ax, modelTitle, modelArray, **dictModelRef) + _plot_panel(ax, modelTitle, modelArray, **dictModelRef) if refArray is not None: ax = plt.subplot(subplots[1], projection=projection) - plot_panel(ax, refTitle, refArray, **dictModelRef) + _plot_panel(ax, refTitle, refArray, **dictModelRef) ax = plt.subplot(subplots[2], projection=projection) - plot_panel(ax, diffTitle, diffArray, **dictDiff) + _plot_panel(ax, diffTitle, diffArray, **dictDiff) if fileout is not None: savefig(fileout, config) @@ -791,7 +793,7 @@ def _add_land_lakes_coastline(ax, ice_shelves=True): ax.add_feature(land_50m, zorder=2) if ice_shelves: ice_50m = cartopy.feature.NaturalEarthFeature( - 'physical', 'antarctic_ice_shelves_polys', '50m', edgecolor='k', - facecolor='lightgray', linewidth=0.5) + 'physical', 'antarctic_ice_shelves_polys', '50m', + edgecolor='k', facecolor='lightgray', linewidth=0.5) ax.add_feature(ice_50m, zorder=3) ax.add_feature(lakes_50m, zorder=4) diff --git a/mpas_analysis/shared/plot/colormap.py b/mpas_analysis/shared/plot/colormap.py index 008d2824c..a2aa520aa 100644 --- a/mpas_analysis/shared/plot/colormap.py +++ b/mpas_analysis/shared/plot/colormap.py @@ -62,6 +62,11 @@ def setup_colormap(config, configSectionName, suffix=''): 'lineWidth' is the width of contour lines or ``None`` if not specified 'lineColor' is the color of contour lines or ``None`` if not specified + + `arrowWidth` is the width of the arrows or ``None`` if not specified + + `arrowSpacing` is the spacing between arrows or ``None`` if not + specified """ # Authors # ------- @@ -69,7 +74,7 @@ def setup_colormap(config, configSectionName, suffix=''): register_custom_colormaps() - option = 'colormapType{}'.format(suffix) + option = f'colormapType{suffix}' if config.has_option(configSectionName, option): colormapType = config.get(configSectionName, option) if colormapType == 'indexed': @@ -88,35 +93,43 @@ def setup_colormap(config, configSectionName, suffix=''): levels = None ticks = None - option = 'contourLevels{}'.format(suffix) + contours = None + lineWidth = None + lineColor = None + arrowWidth = None + arrowSpacing = None + + option = f'contourLevels{suffix}' if config.has_option(configSectionName, option): contours = config.getexpression(configSectionName, option, use_numpyfunc=True) if isinstance(contours, str) and contours == 'none': contours = None - else: - contours = None - option = 'contourThickness{}'.format(suffix) + option = f'contourThickness{suffix}' if config.has_option(configSectionName, option): lineWidth = config.getfloat(configSectionName, option) - else: - lineWidth = None - option = 'contourColor{}'.format(suffix) + option = f'contourColor{suffix}' if config.has_option(configSectionName, option): lineColor = config.get(configSectionName, option) - else: - lineColor = None - option = 'arrowsOnContour{}'.format(suffix) + + option = f'arrowWidth{suffix}' if config.has_option(configSectionName, option): - arrows = config.getboolean(configSectionName, option) - else: - arrows = None + arrowWidth = config.getexpression(configSectionName, option) + if isinstance(arrowWidth, str) and arrowWidth == 'None': + arrowWidth = None + option = f'arrowsSpacing{suffix}' + if config.has_option(configSectionName, option): + arrowSpacing = config.getexpression(configSectionName, option) + if isinstance(arrowSpacing, str) and arrowSpacing == 'None': + arrowSpacing = None + return {'colormap': colormap, 'norm': norm, 'levels': levels, 'ticks': ticks, 'contours': contours, 'lineWidth': lineWidth, - 'lineColor': lineColor, 'arrows': arrows} + 'lineColor': lineColor, 'arrowWidth': arrowWidth, + 'arrowSpacing': arrowSpacing} def register_custom_colormaps(): @@ -354,12 +367,12 @@ def _setup_colormap_and_norm(config, configSectionName, suffix=''): register_custom_colormaps() colormap = plt.get_cmap(config.get(configSectionName, - 'colormapName{}'.format(suffix))) + f'colormapName{suffix}')) - normType = config.get(configSectionName, 'normType{}'.format(suffix)) + normType = config.get(configSectionName, f'normType{suffix}') kwargs = config.getexpression(configSectionName, - 'normArgs{}'.format(suffix)) + f'normArgs{suffix}') if normType == 'symLog': norm = cols.SymLogNorm(**kwargs) @@ -368,12 +381,12 @@ def _setup_colormap_and_norm(config, configSectionName, suffix=''): elif normType == 'linear': norm = cols.Normalize(**kwargs) else: - raise ValueError('Unsupported norm type {} in section {}'.format( - normType, configSectionName)) + raise ValueError(f'Unsupported norm type {normType} in section ' + f'{configSectionName}') try: ticks = config.getexpression( - configSectionName, 'colorbarTicks{}'.format(suffix), + configSectionName, f'colorbarTicks{suffix}', use_numpyfunc=True) except configparser.NoOptionError: ticks = None @@ -413,15 +426,15 @@ def _setup_indexed_colormap(config, configSectionName, suffix=''): # Xylar Asay-Davis, Milena Veneziani, Greg Streletz colormap = plt.get_cmap(config.get(configSectionName, - 'colormapName{}'.format(suffix))) + f'colormapName{suffix}')) indices = config.getexpression(configSectionName, - 'colormapIndices{}'.format(suffix), + f'colormapIndices{suffix}', use_numpyfunc=True) try: levels = config.getexpression( - configSectionName, 'colorbarLevels{}'.format(suffix), + configSectionName, f'colorbarLevels{suffix}', use_numpyfunc=True) except configparser.NoOptionError: levels = None @@ -440,7 +453,7 @@ def _setup_indexed_colormap(config, configSectionName, suffix=''): raise ValueError('length mismatch between indices and ' 'colorbarLevels') colormap = cols.ListedColormap(colormap(indices), - 'colormapName{}'.format(suffix)) + f'colormapName{suffix}') colormap.set_under(underColor) colormap.set_over(overColor) @@ -448,7 +461,7 @@ def _setup_indexed_colormap(config, configSectionName, suffix=''): try: ticks = config.getexpression( - configSectionName, 'colorbarTicks{}'.format(suffix), + configSectionName, f'colorbarTicks{suffix}', use_numpyfunc=True) except configparser.NoOptionError: ticks = levels diff --git a/mpas_analysis/shared/plot/vertical_section.py b/mpas_analysis/shared/plot/vertical_section.py index 231023c09..e7f417350 100644 --- a/mpas_analysis/shared/plot/vertical_section.py +++ b/mpas_analysis/shared/plot/vertical_section.py @@ -938,15 +938,15 @@ def plot_vertical_section( if colormapDict['levels'] is None: plotHandle = plt.tripcolor(maskedTriangulation, fieldMasked, - cmap=colormapDict['colormap'], - norm=colormapDict['norm'], - rasterized=True, shading='gouraud') + cmap=colormapDict['colormap'], + norm=colormapDict['norm'], + rasterized=True, shading='gouraud') else: plotHandle = plt.tricontourf(maskedTriangulation, fieldMasked, - cmap=colormapDict['colormap'], - norm=colormapDict['norm'], - levels=colormapDict['levels'], - extend='both') + cmap=colormapDict['colormap'], + norm=colormapDict['norm'], + levels=colormapDict['levels'], + extend='both') cbar = plt.colorbar(plotHandle, orientation='vertical',