diff --git a/docs/api.rst b/docs/api.rst index 5a240eff0..857656b6b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -51,6 +51,7 @@ Ocean tasks ClimatologyMapSST ClimatologyMapSSS ClimatologyMapMLD + ClimatologyMapSSH ClimatologyMapAntarcticMelt IndexNino34 MeridionalHeatTransport diff --git a/mpas_analysis/config.default b/mpas_analysis/config.default index caba17636..6ae09c339 100644 --- a/mpas_analysis/config.default +++ b/mpas_analysis/config.default @@ -588,11 +588,21 @@ comparisonGrids = ['latlon'] ## sea surface height (SSH) against reference model results and observations # colormap for model/observations -colormapNameResult = Spectral_r +#colormapNameResult = Spectral_r +colormapNameResult = Maximenko # color indices into colormapName for filled contours -colormapIndicesResult = [0, 20, 40, 80, 100, 120, 160, 180, 200, 240, 255] +colormapIndicesResult = numpy.array(numpy.linspace(0, 255, 38), int) # colormap levels/values for contour boundaries -colorbarLevelsResult = [-240., -200., -160., -120., -80., -40., 0., 40., 80., 120.] +colorbarLevelsResult = numpy.arange(-240., 130., 10.) +# colormap levels/values for ticks (defaults to same as levels) +colorbarTicksResult = numpy.arange(-240., 160., 40.) + +# contour line levels +contourLevelsResult = numpy.arange(-240., 130., 10.) +# contour line thickness +contourThicknessResult = 0.25 +# contour color +contourColorResult = 0.25 # colormap for differences colormapNameDifference = RdBu_r diff --git a/mpas_analysis/configuration/mpas_analysis_config_parser.py b/mpas_analysis/configuration/mpas_analysis_config_parser.py index 3bf3cf643..06f2472cf 100644 --- a/mpas_analysis/configuration/mpas_analysis_config_parser.py +++ b/mpas_analysis/configuration/mpas_analysis_config_parser.py @@ -22,8 +22,8 @@ if six.PY3: xrange = range -npallow = dict(linspace=np.linspace, xrange=xrange, range=range, - arange=np.arange, pi=np.pi, Pi=np.pi, __builtins__=None) +npallow = dict(linspace=np.linspace, xrange=xrange, range=range, array=np.array, + arange=np.arange, pi=np.pi, Pi=np.pi, int=int, __builtins__=None) class MpasAnalysisConfigParser(ConfigParser): diff --git a/mpas_analysis/ocean/climatology_map_ssh.py b/mpas_analysis/ocean/climatology_map_ssh.py index fe59d3b4d..bab7483f6 100644 --- a/mpas_analysis/ocean/climatology_map_ssh.py +++ b/mpas_analysis/ocean/climatology_map_ssh.py @@ -54,7 +54,8 @@ def __init__(self, config, mpasClimatologyTask, componentName='ocean', tags=['climatology', 'horizontalMap', fieldName]) - mpasFieldName = 'timeMonthly_avg_ssh' + mpasFieldName = 'timeMonthly_avg_pressureAdjustedSSH' + iselValues = None sectionName = self.taskName @@ -86,7 +87,8 @@ def __init__(self, config, mpasClimatologyTask, if refConfig is None: - refTitleLabel = 'Observations (AVISO sea-level anomaly)' + refTitleLabel = 'Observations (AVISO Dynamic ' \ + 'Topography, 1993-2010)' observationsDirectory = build_config_full_path( config, 'oceanObservations', @@ -123,11 +125,11 @@ def __init__(self, config, mpasClimatologyTask, comparisonGridName, remapClimatologySubtask, remapObservationsSubtask, - refConfig) + refConfig, removeMean=True) subtask.set_plot_info( outFileLabel=outFileLabel, - fieldNameInTitle='SSH', + fieldNameInTitle='Zero-mean SSH', mpasFieldName=mpasFieldName, refFieldName=refFieldName, refTitleLabel=refTitleLabel, diff --git a/mpas_analysis/ocean/meridional_heat_transport.py b/mpas_analysis/ocean/meridional_heat_transport.py index f1dbfd0fc..218203f41 100644 --- a/mpas_analysis/ocean/meridional_heat_transport.py +++ b/mpas_analysis/ocean/meridional_heat_transport.py @@ -6,8 +6,7 @@ import numpy as np import os -from mpas_analysis.shared.plot.plotting import plot_vertical_section,\ - setup_colormap, plot_1D +from mpas_analysis.shared.plot.plotting import plot_vertical_section, plot_1D from mpas_analysis.shared.io.utility import build_config_full_path, \ make_directories @@ -16,9 +15,6 @@ from mpas_analysis.shared import AnalysisTask from mpas_analysis.shared.html import write_image_xml -from mpas_analysis.shared.climatology import \ - get_unmasked_mpas_climatology_file_name - class MeridionalHeatTransport(AnalysisTask): # {{{ ''' @@ -174,7 +170,6 @@ def run_task(self): # {{{ '{}/meridionalHeatTransport_years{:04d}-{:04d}.nc'.format( outputDirectory, self.startYear, self.endYear) - if os.path.exists(outFileName): self.logger.info(' Reading results from previous analysis run...') annualClimatology = xr.open_dataset(outFileName) @@ -342,18 +337,12 @@ def run_task(self): # {{{ filePrefix = self.filePrefixes['mhtZ'] figureName = '{}/{}.png'.format(self.plotsDirectory, filePrefix) colorbarLabel = '[PW/m]' - contourLevels = config.getExpression(self.sectionName, - 'contourLevelsGlobal', - usenumpyfunc=True) - (colormapName, colorbarLevels) = setup_colormap(config, - self.sectionName, - suffix='Global') - plot_vertical_section(config, x, y, z, - colormapName, colorbarLevels, - contourLevels, colorbarLabel, - title, xLabel, yLabel, figureName, - xLim=xLimGlobal, yLim=depthLimGlobal, - invertYAxis=False, N=movingAveragePoints) + plot_vertical_section(config, x, y, z, self.sectionName, + suffix='', colorbarLabel=colorbarLabel, + title=title, xlabel=xLabel, ylabel=yLabel, + fileout=figureName, xLim=xLimGlobal, + yLim=depthLimGlobal, invertYAxis=False, + N=movingAveragePoints) self._write_xml(filePrefix) diff --git a/mpas_analysis/ocean/plot_climatology_map_subtask.py b/mpas_analysis/ocean/plot_climatology_map_subtask.py index 34b11d39d..3d68a766b 100644 --- a/mpas_analysis/ocean/plot_climatology_map_subtask.py +++ b/mpas_analysis/ocean/plot_climatology_map_subtask.py @@ -16,7 +16,7 @@ from mpas_analysis.shared import AnalysisTask from mpas_analysis.shared.plot.plotting import plot_global_comparison, \ - setup_colormap, plot_polar_projection_comparison + plot_polar_projection_comparison from mpas_analysis.shared.html import write_image_xml @@ -57,6 +57,13 @@ class PlotClimatologyMapSubtask(AnalysisTask): # {{{ The subtask for remapping the MPAS climatology for the reference run that this subtask will plot + removeMean : bool, optional + If True, a common mask for the model and reference data sets is + computed (where both are valid) and the mean over that mask is + subtracted from both the model and reference results. This is + useful for data sets where the desire is to compare the spatial + pattern but the mean offset is not meaningful (e.g. SSH) + outFileLabel : str The prefix on each plot and associated XML file @@ -106,7 +113,7 @@ class PlotClimatologyMapSubtask(AnalysisTask): # {{{ def __init__(self, parentTask, season, comparisonGridName, remapMpasClimatologySubtask, remapObsClimatologySubtask=None, - refConfig=None, depth=None): + refConfig=None, depth=None, removeMean=False): # {{{ ''' Construct one analysis subtask for each plot (i.e. each season and @@ -139,6 +146,13 @@ def __init__(self, parentTask, season, comparisonGridName, Depth the data is being plotted, 'top' for the sea surface 'bot' for the sea floor + removeMean : bool, optional + If True, a common mask for the model and reference data sets is + computed (where both are valid) and the mean over that mask is + subtracted from both the model and reference results. This is + useful for data sets where the desire is to compare the spatial + pattern but the mean offset is not meaningful (e.g. SSH) + Authors ------- Xylar Asay-Davis @@ -151,6 +165,7 @@ def __init__(self, parentTask, season, comparisonGridName, self.remapMpasClimatologySubtask = remapMpasClimatologySubtask self.remapObsClimatologySubtask = remapObsClimatologySubtask self.refConfig = refConfig + self.removeMean = removeMean subtaskName = 'plot{}_{}'.format(season, comparisonGridName) if depth is None: @@ -369,6 +384,24 @@ def run_task(self): # {{{ remappedRefClimatology = remappedRefClimatology.sel( depthSlice=str(depth), drop=True) + if self.removeMean: + if remappedRefClimatology is None: + remappedModelClimatology[self.mpasFieldName] = \ + remappedModelClimatology[self.mpasFieldName] - \ + remappedModelClimatology[self.mpasFieldName].mean() + else: + masked = remappedModelClimatology[self.mpasFieldName].where( + remappedRefClimatology[self.refFieldName].notnull()) + remappedModelClimatology[self.mpasFieldName] = \ + remappedModelClimatology[self.mpasFieldName] - \ + masked.mean() + + masked = remappedRefClimatology[self.refFieldName].where( + remappedModelClimatology[self.mpasFieldName].notnull()) + remappedRefClimatology[self.refFieldName] = \ + remappedRefClimatology[self.refFieldName] - \ + masked.mean() + if self.comparisonGridName == 'latlon': self._plot_latlon(remappedModelClimatology, remappedRefClimatology) elif self.comparisonGridName == 'antarctic': @@ -386,11 +419,6 @@ def _plot_latlon(self, remappedModelClimatology, remappedRefClimatology): mainRunName = config.get('runs', 'mainRunName') - (colormapResult, colorbarLevelsResult) = setup_colormap( - config, configSectionName, suffix='Result') - (colormapDifference, colorbarLevelsDifference) = setup_colormap( - config, configSectionName, suffix='Difference') - modelOutput = nans_to_numpy_mask( remappedModelClimatology[self.mpasFieldName].values) @@ -419,10 +447,7 @@ def _plot_latlon(self, remappedModelClimatology, remappedRefClimatology): modelOutput, refOutput, bias, - colormapResult, - colorbarLevelsResult, - colormapDifference, - colorbarLevelsDifference, + configSectionName, fileout=outFileName, title=title, modelTitle='{}'.format(mainRunName), @@ -483,15 +508,6 @@ def _plot_antarctic(self, remappedModelClimatology, self.fieldNameInTitle, season, self.startYear, self.endYear) - if config.has_option(configSectionName, 'colormapIndicesResult'): - colorMapType = 'indexed' - elif config.has_option(configSectionName, 'normTypeResult'): - colorMapType = 'norm' - else: - raise ValueError('config section {} contains neither the info' - 'for an indexed color map nor for computing a ' - 'norm'.format(configSectionName)) - plot_polar_projection_comparison( config, x, @@ -502,7 +518,6 @@ def _plot_antarctic(self, remappedModelClimatology, bias, fileout=outFileName, colorMapSectionName=configSectionName, - colorMapType=colorMapType, title=title, modelTitle='{}'.format(mainRunName), refTitle=self.refTitleLabel, diff --git a/mpas_analysis/ocean/plot_hovmoller_subtask.py b/mpas_analysis/ocean/plot_hovmoller_subtask.py index 96e54a284..8543325e8 100644 --- a/mpas_analysis/ocean/plot_hovmoller_subtask.py +++ b/mpas_analysis/ocean/plot_hovmoller_subtask.py @@ -8,8 +8,7 @@ from mpas_analysis.shared import AnalysisTask -from mpas_analysis.shared.plot.plotting import plot_vertical_section, \ - setup_colormap +from mpas_analysis.shared.plot.plotting import plot_vertical_section from mpas_analysis.shared.io import open_mpas_dataset @@ -260,18 +259,11 @@ def run_task(self): # {{{ figureName = '{}/{}.png'.format(self.plotsDirectory, self.filePrefix) - (colormapName, colorbarLevels) = setup_colormap(config, - self.sectionName) - - contourLevels = config.getExpression(self.sectionName, - 'contourLevels', - usenumpyfunc=True) - - plot_vertical_section(config, Time, depth, field, - colormapName, colorbarLevels, contourLevels, - self.unitsLabel, title, xLabel, yLabel, - figureName, linewidths=1, xArrayIsTime=True, - calendar=self.calendar) + plot_vertical_section(config, Time, depth, field, self.sectionName, + suffix='', colorbarLabel=self.unitsLabel, + title=title, xlabel=xLabel, ylabel=yLabel, + fileout=figureName, linewidths=1, + xArrayIsTime=True, calendar=self.calendar) write_image_xml( config=config, diff --git a/mpas_analysis/ocean/streamfunction_moc.py b/mpas_analysis/ocean/streamfunction_moc.py index ef310ad3b..5b7ec216c 100644 --- a/mpas_analysis/ocean/streamfunction_moc.py +++ b/mpas_analysis/ocean/streamfunction_moc.py @@ -9,8 +9,8 @@ import os from mpas_analysis.shared.constants.constants import m3ps_to_Sv -from mpas_analysis.shared.plot.plotting import plot_vertical_section, \ - timeseries_analysis_plot, setup_colormap +from mpas_analysis.shared.plot.plotting import plot_vertical_section,\ + timeseries_analysis_plot from mpas_analysis.shared.io.utility import build_config_full_path, \ make_directories, get_files_year_month @@ -197,20 +197,14 @@ def run_task(self): # {{{ mainRunName) filePrefix = self.filePrefixes[region] figureName = '{}/{}.png'.format(self.plotsDirectory, filePrefix) - contourLevels = \ - config.getExpression(self.sectionName, - 'contourLevels{}'.format(region), - usenumpyfunc=True) - (colormapName, colorbarLevels) = setup_colormap(config, - self.sectionName, - suffix=region) x = self.lat[region] y = self.depth z = self.moc[region] - plot_vertical_section(config, x, y, z, colormapName, - colorbarLevels, contourLevels, colorbarLabel, - title, xLabel, yLabel, figureName, + plot_vertical_section(config, x, y, z, self.sectionName, + suffix=region, colorbarLabel=colorbarLabel, + title=title, xlabel=xLabel, ylabel=yLabel, + fileout=figureName, N=movingAveragePointsClimatological) caption = '{} Meridional Overturning Streamfunction'.format(region) diff --git a/mpas_analysis/sea_ice/plot_climatology_map_subtask.py b/mpas_analysis/sea_ice/plot_climatology_map_subtask.py index 5ca70ff2e..5393ee826 100644 --- a/mpas_analysis/sea_ice/plot_climatology_map_subtask.py +++ b/mpas_analysis/sea_ice/plot_climatology_map_subtask.py @@ -9,8 +9,7 @@ from mpas_analysis.shared import AnalysisTask -from mpas_analysis.shared.plot.plotting import plot_polar_comparison, \ - setup_colormap +from mpas_analysis.shared.plot.plotting import plot_polar_comparison from mpas_analysis.shared.html import write_image_xml @@ -298,11 +297,6 @@ def run_task(self): # {{{ else: plotProjection = 'spstere' - (colormapResult, colorbarLevelsResult) = setup_colormap( - config, sectionName, suffix='Result') - (colormapDifference, colorbarLevelsDifference) = setup_colormap( - config, sectionName, suffix='Difference') - referenceLongitude = config.getfloat(sectionName, 'referenceLongitude') minimumLatitude = config.getfloat(sectionName, @@ -373,10 +367,7 @@ def run_task(self): # {{{ modelOutput, refOutput, difference, - colormapResult, - colorbarLevelsResult, - colormapDifference, - colorbarLevelsDifference, + sectionName, title=title, fileout=fileout, plotProjection=plotProjection, diff --git a/mpas_analysis/shared/plot/plotting.py b/mpas_analysis/shared/plot/plotting.py index d60a2cbb0..54d768322 100644 --- a/mpas_analysis/shared/plot/plotting.py +++ b/mpas_analysis/shared/plot/plotting.py @@ -13,6 +13,7 @@ from __future__ import absolute_import, division, print_function, \ unicode_literals +import matplotlib import matplotlib.pyplot as plt import matplotlib.colors as cols import xarray as xr @@ -261,7 +262,7 @@ def timeseries_analysis_plot_polar(config, dsvalues, N, title, majorTickLocs[month] = majorTickLocs[month-1] + \ ((constants.daysInMonth[month-1] * np.pi * 2.0) / 365.0) minorTickLocs[month] = minorTickLocs[month-1] + \ - (((constants.daysInMonth[month-1] + \ + (((constants.daysInMonth[month-1] + constants.daysInMonth[month]) * np.pi) / 365.0) ax.set_xticks(majorTickLocs) @@ -273,7 +274,6 @@ def timeseries_analysis_plot_polar(config, dsvalues, N, title, if titleFontSize is None: titleFontSize = config.get('plot', 'titleFontSize') - axis_font = {'size': config.get('plot', 'axisFontSize')} title_font = {'size': titleFontSize, 'color': config.get('plot', 'titleFontColor'), 'weight': config.get('plot', 'titleFontWeight')} @@ -294,10 +294,7 @@ def plot_polar_comparison( modelArray, refArray, diffArray, - cmapModelRef, - clevsModelRef, - cmapDiff, - clevsDiff, + colorMapSectionName, fileout, title=None, plotProjection='npstere', @@ -330,17 +327,8 @@ def plot_polar_comparison( diffArray : float array difference between modelArray and refArray - cmapModelRef : str - colormap of model and observations or reference run panel - - clevsModelRef : int array - colorbar values for model and observations or reference run panel - - cmapDiff : str - colormap of difference (bias) panel - - clevsDiff : int array - colorbar values for difference (bias) panel + colorMapSectionName : str + section name in ``config`` where color map info can be found. fileout : str the file name to be written @@ -383,7 +371,8 @@ def plot_polar_comparison( Xylar Asay-Davis, Milena Veneziani """ - def do_subplot(ax, field, title, cmap, norm, levels): + def do_subplot(ax, field, title, colormap, norm, levels, ticks, contours, + lineWidth, lineColor): """ Make a subplot within the figure. """ @@ -398,16 +387,28 @@ def do_subplot(ax, field, title, cmap, norm, levels): m.drawparallels(np.arange(-80., 81., 10.)) m.drawmeridians(np.arange(-180., 181., 20.), labels=[True, False, True, True]) - cs = m.contourf(x, y, field, cmap=cmap, norm=norm, - levels=levels) - cbar = m.colorbar(cs, location='right', pad="3%", spacing='uniform', - ticks=levels, boundaries=levels) + if levels is None: + plotHandle = m.pcolormesh(x, y, field, cmap=colormap, norm=norm) + else: + plotHandle = m.contourf(x, y, field, cmap=colormap, norm=norm, + levels=levels) + + if contours is not None: + matplotlib.rcParams['contour.negative_linestyle'] = 'solid' + m.contour(x, y, field, levels=contours, colors=lineColor, + linewidths=lineWidth) + + cbar = m.colorbar(plotHandle, location='right', pad="3%", + spacing='uniform', ticks=ticks, boundaries=levels) cbar.set_label(cbarlabel) if dpi is None: dpi = config.getint('plot', 'dpi') + dictModelRef = setup_colormap(config, colorMapSectionName, suffix='Result') + dictDiff = setup_colormap(config, colorMapSectionName, suffix='Difference') + if refArray is None: if figsize is None: figsize = (8, 8.5) @@ -432,21 +433,15 @@ def do_subplot(ax, field, title, cmap, norm, levels): fig.suptitle(title, y=0.95, **title_font) axis_font = {'size': config.get('plot', 'axisFontSize')} - normModelRef = cols.BoundaryNorm(clevsModelRef, cmapModelRef.N) - normDiff = cols.BoundaryNorm(clevsDiff, cmapDiff.N) - ax = plt.subplot(subplots[0]) - do_subplot(ax=ax, field=modelArray, title=modelTitle, cmap=cmapModelRef, - norm=normModelRef, levels=clevsModelRef) + do_subplot(ax=ax, field=modelArray, title=modelTitle, **dictModelRef) if refArray is not None: ax = plt.subplot(subplots[1]) - do_subplot(ax=ax, field=refArray, title=refTitle, cmap=cmapModelRef, - norm=normModelRef, levels=clevsModelRef) + do_subplot(ax=ax, field=refArray, title=refTitle, **dictModelRef) ax = plt.subplot(subplots[2]) - do_subplot(ax=ax, field=diffArray, title=diffTitle, cmap=cmapDiff, - norm=normDiff, levels=clevsDiff) + do_subplot(ax=ax, field=diffArray, title=diffTitle, **dictDiff) plt.tight_layout(pad=4.) if vertical: @@ -460,25 +455,24 @@ def do_subplot(ax, field, title, cmap, norm, levels): def plot_global_comparison( - config, - Lons, - Lats, - modelArray, - refArray, - diffArray, - cmapModelRef, - clevsModelRef, - cmapDiff, - clevsDiff, - fileout, - title=None, - modelTitle='Model', - refTitle='Observations', - diffTitle='Model-Observations', - cbarlabel='units', - titleFontSize=None, - figsize=(8, 13), - dpi=None): + config, + Lons, + Lats, + modelArray, + refArray, + diffArray, + colorMapSectionName, + fileout, + title=None, + modelTitle='Model', + refTitle='Observations', + diffTitle='Model-Observations', + cbarlabel='units', + titleFontSize=None, + figsize=(8, 13), + dpi=None, + lineWidth=1, + lineColor='black'): """ Plots a data set as a longitude/latitude map. @@ -498,17 +492,8 @@ def plot_global_comparison( diffArray : float array difference between modelArray and refArray - cmapModelRef : str - colormap of model and observations or reference run panel - - clevsModelRef : int array - colorbar values for model and observations or reference run panel - - cmapDiff : str - colormap of difference (bias) panel - - clevsDiff : int array - colorbar values for difference (bias) panel + colorMapSectionName : str + section name in ``config`` where color map info can be found. fileout : str the file name to be written @@ -538,11 +523,42 @@ def plot_global_comparison( the number of dots per inch of the figure, taken from section ``plot`` option ``dpi`` in the config file by default + lineWidth : int, optional + the line width of contour lines (if specified) + + lineColor : str, optional + the color contour lines (if specified) + Authors ------- Xylar Asay-Davis, Milena Veneziani """ + def plot_panel(title, array, colormap, norm, levels, ticks, contours, + lineWidth, lineColor): + plt.title(title, y=1.06, **axis_font) + m.drawcoastlines() + m.fillcontinents(color='grey', lake_color='white') + m.drawparallels(np.arange(-80., 80., 20.), + labels=[True, False, False, False]) + m.drawmeridians(np.arange(-180., 180., 60.), + labels=[False, False, False, True]) + + if levels is None: + plotHandle = m.pcolormesh(x, y, array, cmap=colormap, norm=norm) + else: + plotHandle = m.contourf(x, y, array, cmap=colormap, norm=norm, + levels=levels, extend='both') + + if contours is not None: + matplotlib.rcParams['contour.negative_linestyle'] = 'solid' + m.contour(x, y, array, levels=contours, colors=lineColor, + linewidths=lineWidth) + + cbar = m.colorbar(plotHandle, location='right', pad="5%", + spacing='uniform', ticks=ticks, boundaries=ticks) + cbar.set_label(cbarlabel) + # set up figure if dpi is None: dpi = config.getint('plot', 'dpi') @@ -560,52 +576,20 @@ def plot_global_comparison( urcrnrlon=181, resolution='l') x, y = m(Lons, Lats) # compute map proj coordinates - normModelRef = cols.BoundaryNorm(clevsModelRef, cmapModelRef.N) - normDiff = cols.BoundaryNorm(clevsDiff, cmapDiff.N) + dictModelRef = setup_colormap(config, colorMapSectionName, suffix='Result') + dictDiff = setup_colormap(config, colorMapSectionName, suffix='Difference') if refArray is not None: plt.subplot(3, 1, 1) - plt.title(modelTitle, y=1.06, **axis_font) - m.drawcoastlines() - m.fillcontinents(color='grey', lake_color='white') - m.drawparallels(np.arange(-80., 80., 20.), - labels=[True, False, False, False]) - m.drawmeridians(np.arange(-180., 180., 60.), - labels=[False, False, False, True]) - cs = m.contourf(x, y, modelArray, cmap=cmapModelRef, norm=normModelRef, - levels=clevsModelRef, extend='both') - cbar = m.colorbar(cs, location='right', pad="5%", spacing='uniform', - ticks=clevsModelRef, boundaries=clevsModelRef) - cbar.set_label(cbarlabel) + + plot_panel(modelTitle, modelArray, **dictModelRef) if refArray is not None: plt.subplot(3, 1, 2) - plt.title(refTitle, y=1.06, **axis_font) - m.drawcoastlines() - m.fillcontinents(color='grey', lake_color='white') - m.drawparallels(np.arange(-80., 80., 20.), - labels=[True, False, False, False]) - m.drawmeridians(np.arange(-180., 180., 40.), - labels=[False, False, False, True]) - cs = m.contourf(x, y, refArray, cmap=cmapModelRef, norm=normModelRef, - levels=clevsModelRef, extend='both') - cbar = m.colorbar(cs, location='right', pad="5%", spacing='uniform', - ticks=clevsModelRef, boundaries=clevsModelRef) - cbar.set_label(cbarlabel) + plot_panel(refTitle, refArray, **dictModelRef) plt.subplot(3, 1, 3) - plt.title(diffTitle, y=1.06, **axis_font) - m.drawcoastlines() - m.fillcontinents(color='grey', lake_color='white') - m.drawparallels(np.arange(-80., 80., 20.), - labels=[True, False, False, False]) - m.drawmeridians(np.arange(-180., 180., 40.), - labels=[False, False, False, True]) - cs = m.contourf(x, y, diffArray, cmap=cmapDiff, norm=normDiff, - levels=clevsDiff, extend='both') - cbar = m.colorbar(cs, location='right', pad="5%", spacing='uniform', - ticks=clevsDiff, boundaries=clevsModelRef) - cbar.set_label(cbarlabel) + plot_panel(diffTitle, diffArray, **dictDiff) if (fileout is not None): plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1) @@ -624,7 +608,6 @@ def plot_polar_projection_comparison( diffArray, fileout, colorMapSectionName, - colorMapType='norm', title=None, modelTitle='Model', refTitle='Observations', @@ -662,22 +645,6 @@ def plot_polar_projection_comparison( colorMapSectionName : str section name in ``config`` where color map info can be found. - colorMapType : {'norm', 'indexed'}, optional - The type of color map, either a matplotlib norm or indices into a color - map. - - If 'norm', the following options must be defined for suffixes - ``Result`` and ``Difference``: - ``colormapName``, ``normType``, - ``normArgs``, ``colorbarTicks`` - - If 'indexed', these options are required: - ``colormapName``, ``colormapIndices``, - ``colorbarLevels`` - - The colorbar for each panel will be constructed from these options - - title : str, optional the subtitle of the plot @@ -716,21 +683,31 @@ def plot_polar_projection_comparison( Xylar Asay-Davis """ - def plot_panel(ax, title, array, cmap, norm, ticks): + def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours, + lineWidth, lineColor): plt.title(title, y=1.06, **axis_font) - mesh = plt.pcolormesh(x, y, array, cmap=cmap, norm=norm) + if levels is None: + plotHandle = plt.pcolormesh(x, y, array, cmap=colormap, norm=norm) + else: + plotHandle = plt.contourf(x, y, array, cmap=colormap, norm=norm, + levels=levels, extend='both') plt.pcolormesh(x, y, landMask, cmap=landColorMap) plt.contour(xCenter, yCenter, landMask.mask, (0.5,), colors='k', linewidths=0.5) + if contours is not None: + matplotlib.rcParams['contour.negative_linestyle'] = 'solid' + plt.contour(x, y, array, levels=contours, colors=lineColor, + linewidths=lineWidth) + # 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) cax = divider.append_axes("right", size="5%", pad=0.05) - cbar = plt.colorbar(mesh, cax=cax) + cbar = plt.colorbar(plotHandle, cax=cax) cbar.set_label(cbarlabel) if ticks is not None: cbar.set_ticks(ticks) @@ -757,36 +734,8 @@ def plot_panel(ax, title, array, cmap, norm, ticks): figsize = (22, 7.5) subplots = [131, 132, 133] - if colorMapType == 'norm': - (cmapModelRef, normModelRef) = _setup_colormap_and_norm( - config, colorMapSectionName, suffix='Result') - (cmapDiff, normDiff) = _setup_colormap_and_norm( - config, colorMapSectionName, suffix='Difference') - - if config.has_option(colorMapSectionName, 'colorbarTicksResult'): - colorbarTicksResult = config.getExpression(colorMapSectionName, - 'colorbarTicksResult', - usenumpyfunc=True) - else: - colorbarTicksResult = None - if config.has_option(colorMapSectionName, 'colorbarTicksDifference'): - colorbarTicksDifference = config.getExpression( - colorMapSectionName, 'colorbarTicksDifference', - usenumpyfunc=True) - else: - colorbarTicksDifference = None - - elif colorMapType == 'indexed': - - (cmapModelRef, colorbarTicksResult) = setup_colormap( - config, colorMapSectionName, suffix='Result') - (cmapDiff, colorbarTicksDifference) = setup_colormap( - config, colorMapSectionName, suffix='Difference') - - normModelRef = cols.BoundaryNorm(colorbarTicksResult, cmapModelRef.N) - normDiff = cols.BoundaryNorm(colorbarTicksDifference, cmapDiff.N) - else: - raise ValueError('colorMapType must be one of {norm, indexed}') + dictModelRef = setup_colormap(config, colorMapSectionName, suffix='Result') + dictDiff = setup_colormap(config, colorMapSectionName, suffix='Difference') # set up figure fig = plt.figure(figsize=figsize, dpi=dpi) @@ -808,17 +757,14 @@ def plot_panel(ax, title, array, cmap, norm, ticks): yCenter = 0.5*(y[1:] + y[0:-1]) ax = plt.subplot(subplots[0]) - plot_panel(ax, modelTitle, modelArray, cmapModelRef, normModelRef, - colorbarTicksResult) + plot_panel(ax, modelTitle, modelArray, **dictModelRef) if refArray is not None: ax = plt.subplot(subplots[1]) - plot_panel(ax, refTitle, refArray, cmapModelRef, normModelRef, - colorbarTicksResult) + plot_panel(ax, refTitle, refArray, **dictModelRef) ax = plt.subplot(subplots[2]) - plot_panel(ax, diffTitle, diffArray, cmapDiff, normDiff, - colorbarTicksDifference) + plot_panel(ax, diffTitle, diffArray, **dictDiff) if (fileout is not None): plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1) @@ -949,28 +895,27 @@ def plot_1D(config, xArrays, fieldArrays, errArrays, def plot_vertical_section( - config, - xArray, - depthArray, - fieldArray, - colormapName, - colorbarLevels, - contourLevels, - colorbarLabel=None, - title=None, - xlabel=None, - ylabel=None, - fileout='moc.png', - figsize=(10, 4), - dpi=None, - xLim=None, - yLim=None, - linewidths=2, - invertYAxis=True, - xArrayIsTime=False, - N=None, - maxXTicks=20, - calendar='gregorian'): # {{{ + config, + xArray, + depthArray, + fieldArray, + colorMapSectionName, + suffix='', + colorbarLabel=None, + title=None, + xlabel=None, + ylabel=None, + fileout='moc.png', + figsize=(10, 4), + dpi=None, + xLim=None, + yLim=None, + linewidths=2, + invertYAxis=True, + xArrayIsTime=False, + N=None, + maxXTicks=20, + calendar='gregorian'): # {{{ """ Plots a data set as a x distance (latitude, longitude, @@ -986,7 +931,8 @@ def plot_vertical_section( control plotting xArray : float array - x array (latitude, longitude, or spherical distance; or, time for a Hovmoller plot) + x array (latitude, longitude, or spherical distance; or, time for a + Hovmoller plot) depthArray : float array depth array [m] @@ -994,17 +940,11 @@ def plot_vertical_section( fieldArray : float array field array to plot - colormapName : str - colormap of plot - - colorbarLevels : int array - colorbar levels of plot - - contourLevels : int levels - levels of contours to be drawn + colorMapSectionName : str + section name in ``config`` where color map info can be found. - colorbarLabel : str, optional - label of the colorbar + suffix : str, optional + the suffix used for colorbar config options title : str, optional title of plot @@ -1040,9 +980,10 @@ def plot_vertical_section( N : int, optional the number of points over which to perform a moving average NOTE: this option is mostly intended for use when xArrayIsTime is True, - although it will work with other data as well. Also, the moving average - calculation is based on number of points, not actual x axis values, so for - best results, the values in the xArray should be equally spaced. + although it will work with other data as well. Also, the moving + average calculation is based on number of points, not actual x axis + values, so for best results, the values in the xArray should be equally + spaced. maxXTicks : int, optional the maximum number of tick marks that will be allowed along the x axis. @@ -1058,7 +999,8 @@ def plot_vertical_section( Milena Veneziani, Mark Petersen, Xylar Asay-Davis, Greg Streletz """ - # verify that the dimensions of fieldArray are consistent with those of xArray and depthArray + # verify that the dimensions of fieldArray are consistent with those of + # xArray and depthArray if len(xArray) != fieldArray.shape[1]: raise ValueError('size mismatch between xArray and fieldArray') elif len(depthArray) != fieldArray.shape[0]: @@ -1069,12 +1011,15 @@ def plot_vertical_section( dpi = config.getint('plot', 'dpi') plt.figure(figsize=figsize, dpi=dpi) - if N is not None and N != 1: # compute moving averages with respect to the x dimension + # compute moving averages with respect to the x dimension + if N is not None and N != 1: movingAverageDepthSlices = [] for nVertLevel in range(len(depthArray)): depthSlice = fieldArray[[nVertLevel]][0] - depthSlice = xr.DataArray(depthSlice) # in case it's not an xarray already - mean = pd.Series.rolling(depthSlice.to_series(), N, center=True).mean() + # in case it's not an xarray already + depthSlice = xr.DataArray(depthSlice) + mean = pd.Series.rolling(depthSlice.to_series(), N, + center=True).mean() mean = xr.DataArray.from_series(mean) mean = mean[int(N/2.0):-int(round(N/2.0)-1)] movingAverageDepthSlices.append(mean) @@ -1083,21 +1028,23 @@ def plot_vertical_section( x, y = np.meshgrid(xArray, depthArray) # change to zMid - if colorbarLevels is None: - normModelObs = None - else: - normModelObs = cols.BoundaryNorm(colorbarLevels, colormapName.N) + colormapDict = setup_colormap(config, colorMapSectionName, suffix=suffix) - cs = plt.contourf(x, y, fieldArray, cmap=colormapName, norm=normModelObs, - levels=colorbarLevels, extend='both') + cs = plt.contourf(x, y, fieldArray, cmap=colormapDict['colormap'], + norm=colormapDict['norm'], + levels=colormapDict['levels'], extend='both') + contourLevels = colormapDict['contours'] if contourLevels is not None: if len(contourLevels) == 0: - contourLevels = None # automatic calculation of contour levels - plt.contour(x, y, fieldArray, levels=contourLevels, colors='k', linewidths=linewidths) + # automatic calculation of contour levels + contourLevels = None + plt.contour(x, y, fieldArray, levels=contourLevels, colors='k', + linewidths=linewidths) cbar = plt.colorbar(cs, orientation='vertical', spacing='uniform', - ticks=colorbarLevels, boundaries=colorbarLevels) + ticks=colormapDict['ticks'], + boundaries=colormapDict['ticks']) if colorbarLabel is not None: cbar.set_label(colorbarLabel) @@ -1152,13 +1099,28 @@ def setup_colormap(config, configSectionName, suffix=''): suffix: str, optional suffix of colormap related options + colorMapType + Returns ------- - colormap : srt - new colormap + colormapDict : dict + A dictionary of colormap information. - colorbarLevels : int array - colorbar levels + 'colormap' specifies the name of the new colormap + + 'norm' is a matplotlib norm object used to normalize the colormap + + 'levels' is an array of contour levels or ``None`` if not using indexed + color map + + 'ticks' is an array of values where ticks should be placed + + 'contours' is an array of contour values to plot or ``None`` if none + have been specified + + 'lineWidth' is the width of contour lines or ``None`` if not specified + + 'lineColor' is the color of contour lines or ``None`` if not specified Authors ------- @@ -1167,38 +1129,42 @@ def setup_colormap(config, configSectionName, suffix=''): _register_custom_colormaps() - colormap = plt.get_cmap(config.get(configSectionName, - 'colormapName{}'.format(suffix))) - - indices = config.getExpression(configSectionName, - 'colormapIndices{}'.format(suffix), - usenumpyfunc=True) + if config.has_option(configSectionName, + 'colormapIndices{}'.format(suffix)): + (colormap, norm, levels, ticks) = _setup_indexed_colormap( + config, configSectionName, suffix=suffix) + elif config.has_option(configSectionName, 'normType{}'.format(suffix)): + (colormap, norm, ticks) = _setup_colormap_and_norm( + config, configSectionName, suffix=suffix) + levels = None + else: + raise ValueError('config section {} contains neither the info' + 'for an indexed color map nor for computing a ' + 'norm'.format(configSectionName)) + + option = 'contourLevels{}'.format(suffix) + if config.has_option(configSectionName, option): + contours = config.getExpression(configSectionName, + option, + usenumpyfunc=True) + else: + contours = None - try: - colorbarLevels = config.getExpression(configSectionName, - 'colorbarLevels{}'.format(suffix), - usenumpyfunc=True) - except(configparser.NoOptionError): - colorbarLevels = None + option = 'contourThickness{}'.format(suffix) + if config.has_option(configSectionName, option): + lineWidth = config.getfloat(configSectionName, option) + else: + lineWidth = None - if colorbarLevels is not None: - # set under/over values based on the first/last indices in the colormap - underColor = colormap(indices[0]) - overColor = colormap(indices[-1]) - if len(colorbarLevels)+1 == len(indices): - # we have 2 extra values for the under/over so make the colormap - # without these values - indices = indices[1:-1] - elif len(colorbarLevels)-1 != len(indices): - # indices list must be either one element shorter - # or one element longer than colorbarLevels list - raise ValueError('length mismatch between indices and colorbarLevels') - colormap = cols.ListedColormap(colormap(indices), - 'colormapName{}'.format(suffix)) - colormap.set_under(underColor) - colormap.set_over(overColor) + option = 'contourColor{}'.format(suffix) + if config.has_option(configSectionName, option): + lineColor = config.get(configSectionName, option) + else: + lineColor = None - return (colormap, colorbarLevels) + return {'colormap': colormap, 'norm': norm, 'levels': levels, + 'ticks': ticks, 'contours': contours, 'lineWidth': lineWidth, + 'lineColor': lineColor} def plot_xtick_format(plt, calendar, minDays, maxDays, maxXTicks): @@ -1273,6 +1239,9 @@ def _setup_colormap_and_norm(config, configSectionName, suffix=''): norm : ``SymLogNorm`` object the norm used to normalize the colormap + ticks : array of float + the tick marks on the colormap + Authors ------- Xylar Asay-Davis @@ -1296,7 +1265,93 @@ def _setup_colormap_and_norm(config, configSectionName, suffix=''): raise ValueError('Unsupported norm type {} in section {}'.format( normType, configSectionName)) - return (colormap, norm) + try: + ticks = config.getExpression( + configSectionName, 'colorbarTicks{}'.format(suffix), + usenumpyfunc=True) + except(configparser.NoOptionError): + ticks = None + + return (colormap, norm, ticks) + + +def _setup_indexed_colormap(config, configSectionName, suffix=''): + + ''' + Set up a colormap from the registry + + Parameters + ---------- + config : instance of ConfigParser + the configuration, containing a [plot] section with options that + control plotting + + configSectionName : str + name of config section + + suffix: str, optional + suffix of colormap related options + + colorMapType + + Returns + ------- + colormap : srt + new colormap + + norm : ``SymLogNorm`` object + the norm used to normalize the colormap + + ticks : array of float + the tick marks on the colormap + + Authors + ------- + Xylar Asay-Davis, Milena Veneziani, Greg Streletz + ''' + + colormap = plt.get_cmap(config.get(configSectionName, + 'colormapName{}'.format(suffix))) + + indices = config.getExpression(configSectionName, + 'colormapIndices{}'.format(suffix), + usenumpyfunc=True) + + try: + levels = config.getExpression( + configSectionName, 'colorbarLevels{}'.format(suffix), + usenumpyfunc=True) + except(configparser.NoOptionError): + levels = None + + if levels is not None: + # set under/over values based on the first/last indices in the colormap + underColor = colormap(indices[0]) + overColor = colormap(indices[-1]) + if len(levels)+1 == len(indices): + # we have 2 extra values for the under/over so make the colormap + # without these values + indices = indices[1:-1] + elif len(levels)-1 != len(indices): + # indices list must be either one element shorter + # or one element longer than colorbarLevels list + raise ValueError('length mismatch between indices and ' + 'colorbarLevels') + colormap = cols.ListedColormap(colormap(indices), + 'colormapName{}'.format(suffix)) + colormap.set_under(underColor) + colormap.set_over(overColor) + + norm = cols.BoundaryNorm(levels, colormap.N) + + try: + ticks = config.getExpression( + configSectionName, 'colorbarTicks{}'.format(suffix), + usenumpyfunc=True) + except(configparser.NoOptionError): + ticks = levels + + return (colormap, norm, levels, ticks) def _date_tick(days, pos, calendar='gregorian', includeMonth=True): @@ -1426,5 +1481,27 @@ def _register_custom_colormaps(): plt.register_cmap(name, colorMap) + name = 'Maximenko' + colorArray = np.array([ + [-1, 0., 0.45882352941, 0.76470588235], + [-0.666667, 0., 0.70196078431, 0.90588235294], + [-0.333333, 0.3294117647, 0.87058823529, 1.], + [0., 0.76470588235, 0.94509803921, 0.98039215686], + [0.333333, 1., 1., 0.], + [0.666667, 1., 0.29411764705, 0.], + [1, 1., 0., 0.]], float) + + colorCount = 255 + colorList = np.ones((colorCount, 4), float) + x = colorArray[:, 0] + for cIndex in range(3): + colorList[:, cIndex] = np.interp( + np.linspace(-1., 1., colorCount), + x, colorArray[:, cIndex+1]) + + colorMap = cols.LinearSegmentedColormap.from_list( + name, colorList, N=255) + + plt.register_cmap(name, colorMap) # vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python