From 8daf3239f9ad6b9b45417d261ebf88869d423084 Mon Sep 17 00:00:00 2001 From: Javier Vegas-Regidor Date: Thu, 8 Oct 2020 15:42:40 +0200 Subject: [PATCH 1/5] Add timeseries plotting --- bin/map_main.py | 4 +- mapgenerator/plotting/definitions.py | 4 +- mapgenerator/plotting/plotmap.py | 17 --- mapgenerator/plotting/timeseries.py | 215 +++++++++++++++++++++++++++ 4 files changed, 219 insertions(+), 21 deletions(-) create mode 100644 mapgenerator/plotting/timeseries.py diff --git a/bin/map_main.py b/bin/map_main.py index b174da7..a4c204a 100755 --- a/bin/map_main.py +++ b/bin/map_main.py @@ -29,7 +29,7 @@ mpl_logger = logging.getLogger('matplotlib') mpl_logger.setLevel('CRITICAL') from mapgenerator.plotting.config import ArgumentParser -from mapgenerator.plotting import plotmap +from mapgenerator.plotting import plotmap, timeseries import sys from datetime import datetime, timedelta @@ -76,7 +76,7 @@ class MapGenerator: if res['pltype'] == 'cross': plotmap.PlotCross(loglevel).plot(**res) elif res['pltype'] == 'timeseries': - plotmap.PlotSeries(loglevel).plot(**res) + timeseries.PlotSeries(loglevel).plot(**res) elif res['pltype'] == 'map': plotmap.PlotMap(loglevel).plot(**res) else: diff --git a/mapgenerator/plotting/definitions.py b/mapgenerator/plotting/definitions.py index 1670148..0394c3c 100644 --- a/mapgenerator/plotting/definitions.py +++ b/mapgenerator/plotting/definitions.py @@ -611,8 +611,7 @@ class MapDrawOptions(object): self.area_thresh = None # Default area threshold for maps self.resolution = None # Default resolution is intermediate self.xsize = '1.' # (when keep_aspect = False) Default horizontal size [value between 0 and 1] - self.ysize = '1.' # (when keep_aspect = False) Default vertical size [value between 0 and 1] - self.dpi = '200' # Default DPI + self.ysize = '1.' # (when keep_aspect = False) Default vertical size [value between 0 and 1] # Default DPI self.coordsopts = ".3,8,grey" self.coastsopts = ".5,grey" self.countropts = ".3,grey" @@ -699,6 +698,7 @@ class MapGenerator(object): self.debug = kwargs.get('debug', False) self.alpha = kwargs.get('alpha', None) self.shapefiles = kwargs.get('shapefiles', None) + self.dpi = kwargs.get('dpi', 200) class MapCross(MapGenerator): diff --git a/mapgenerator/plotting/plotmap.py b/mapgenerator/plotting/plotmap.py index 4532e18..c362460 100644 --- a/mapgenerator/plotting/plotmap.py +++ b/mapgenerator/plotting/plotmap.py @@ -45,13 +45,6 @@ import logging log = logging.getLogger(__name__) -class PlotSeries(): - """ Main class for plotting time series """ - - def __init__(self, loglevel='WARNING', **kwargs): - pass - - class PlotCross(): """ Main class for plotting cross sections """ @@ -404,16 +397,6 @@ class PlotMap(MapCross, MapDrawOptions): x, y = glon, glat log.info("3. GLON: %s, GLAT: %s" % (str(x.shape), str(y.shape))) -# print "map_data", map_data[0].shape - - #self.printTime("meshgrid") - #log.info("X: %s, Y: %s" % (str(x.shape), str(y.shape))) - -# # nomap option -# if self.nomap and os.path.exists("%s-%s/%s.png" % (runDate, varName, figName)) and not self.overwrite: -# print figName, " already exists." -# plt.clf() -# return figName if self.nomap: self.mgplot.frameon = False diff --git a/mapgenerator/plotting/timeseries.py b/mapgenerator/plotting/timeseries.py new file mode 100644 index 0000000..e935ace --- /dev/null +++ b/mapgenerator/plotting/timeseries.py @@ -0,0 +1,215 @@ +from ast import parse +import math +import os +import logging +from matplotlib.dates import date2num, num2date + +import matplotlib.pyplot as plt +from matplotlib.gridspec import GridSpec +import matplotlib.dates as mdates +import netCDF4 + +from mapgenerator.plotting.definitions import MapGenerator + +logger = logging.getLogger(__name__) + +class PlotSeries(MapGenerator): + """ Main class for plotting time series """ + + def __init__(self, loglevel='WARNING', **kwargs): + """ Initialize class with attributes """ + super().__init__(loglevel=loglevel, kwargs=kwargs) + self._current_fig = None + self.scalex = kwargs.get('scalex', None) + self.scaley = kwargs.get('scaley', None) + self.suptitle_fontsize = kwargs.get('suptitle_fontsize', 24) + self.title_fontsize = kwargs.get('title_fontsize', 16) + self.axis_fontsize = kwargs.get('axis_fontsize', 12) + self.plot_size = kwargs.get("plot_size", (8.0, 6.0)) + + def _close(self): + plt.close(self._current_fig) + self._current_fig = None + + def plot_cube(self, cube, coord=None, **kwargs): + if coord: + coord = cube.coord(coord) + if not coord and cube.dim_coords: + coord = cube.dim_coords[0] + if not coord and cube.dim_coords: + coord = cube.aux_coords[0] + + if coord.units.calendar: + points = date2num(coord.units.num2date(coord.points)) + else: + points = coord.points + + if 'xlabel' not in kwargs: + kwargs['xlabel'] = self._get_default_title(cube) + self._current_fig = plt.figure(figsize=self.plot_size) + self._plot_array( + points, + cube.data, + title=self._get_default_title(cube), + **kwargs, + ) + self._set_time_axis(coord) + self._current_fig.tight_layout() + suptitle = kwargs.pop('suptitle', None) + if suptitle: + plt.suptitle(suptitle, y=1.08, fontsize=self.suptitle_fontsize) + self._save_fig(self.img_template) + self._close() + + def _plot_array(self, x, y, **kwargs): + invertx = kwargs.pop('invertx', False) + inverty = kwargs.pop('inverty', False) + xlabel = kwargs.pop('xlabel', None) + ylabel = kwargs.pop('ylabel', None) + xlimits = kwargs.pop('xlimits', None) + ylimits = kwargs.pop('ylimits', None) + title = kwargs.pop('title', None) + plt.plot(x, y) + ax = plt.gca() + if xlabel: + ax.set_xlabel(xlabel, fontsize=self.axis_fontsize) + if ylabel: + ax.set_ylabel(ylabel, fontsize=self.axis_fontsize) + if title: + ax.set_title(title, fontsize=self.title_fontsize, y=1.04) + if invertx: + logger.debug('Invert x axis') + ax.invert_xaxis() + if inverty: + logger.debug('Invert y axis') + ax.invert_yaxis() + if self.scalex: + ax.set_xscale(self.scalex) + if self.scaley: + ax.set_yscale(self.scaley) + if xlimits: + if xlimits == 'tight': + ax.set_xlim(left=x.min(), right=x.max()) + elif xlimits == 'auto': + ax.set_autoscalex_on(True) + else: + ax.set_xlim(left=xlimits[0], right=xlimits[1]) + if ylimits: + if ylimits == 'tight': + ax.set_ylim(left=y.min(), right=y.max()) + elif ylimits == 'auto': + ax.set_autoscaley_on(True) + else: + ax.set_ylim(left=ylimits[0], right=ylimits[1]) + ax.grid(b=True, which='major', axis='both', alpha=0.6) + ax.grid(b=True, which='minor', axis='both', alpha=0.3) + ax.tick_params(axis='both', which='major', labelsize=self.axis_fontsize) + + + def multiplot_cube(self, cube, coord, multi_coord, ncols=2, invert=False, **kwargs): + coord = cube.coord(coord) + multi_coord = cube.coord(multi_coord) + if multi_coord.shape[0] == 1: + self.plot_cube(cube, coord, **kwargs) + return + sharex = kwargs.get('sharex', False) + sharey = kwargs.get('sharey', False) + + self._current_fig = plt.figure( + figsize=( + ncols * self.plot_size[0], + math.ceil(multi_coord.shape[0] / ncols) * self.plot_size[1]) + ) + suptitle = kwargs.pop('suptitle', '') + if suptitle: + suptitle = f"{suptitle}\n\n{self._get_default_title(cube)}" + else: + suptitle = self._get_default_title(cube) + self._current_fig.suptitle( + suptitle, y=1.0, fontsize=self.suptitle_fontsize + ) + gs = GridSpec(math.ceil(multi_coord.shape[0] / ncols), ncols) + for i, plot_cube in enumerate(cube.slices_over(multi_coord)): + if i == 0 or not (sharex or sharey): + self._current_fig.add_subplot(gs[i]) + elif sharex: + if sharey: + self._current_fig.add_subplot(gs[i], sharex=plt.gca(), sharey=plt.gca()) + kwargs.pop('invertx', None) + kwargs.pop('inverty', None) + else: + self._current_fig.add_subplot(gs[i], sharex=plt.gca()) + kwargs.pop('invertx', None) + elif sharey: + self._current_fig.add_subplot(gs[i], sharey=plt.gca()) + kwargs.pop('inverty', None) + title = plot_cube.coord(multi_coord).cell(0).point + if isinstance(title, bytes): + title = title.decode() + plt.title( + title, + fontsize=self.title_fontsize + ) + if coord.units.calendar: + points = date2num(coord.units.num2date(coord.points)) + else: + points = coord.points + if invert: + x = plot_cube.data + y = points + if 'ylabel' not in kwargs: + kwargs['ylabel'] = self._get_default_title(coord) + else: + x = points + y = plot_cube.data + if 'xlabel' not in kwargs: + kwargs['xlabel'] = self._get_default_title(coord) + + self._plot_array( + x, y, **kwargs, + ) + self._set_time_axis(coord) + + self._current_fig.tight_layout(pad=2.0) + self._save_fig(self.img_template) + self._close() + + def _save_fig(self, name): + fullname = os.path.join(self.outdir ,f"{name}.{self.filefmt}") + self._current_fig.savefig(fullname, bbox_inches='tight', pad_inches=.2, dpi=self.dpi) + + @staticmethod + def _get_default_title(cube): + if cube.attributes.get('plot_name', None): + show_name = cube.attributes['plot_name'] + elif cube.long_name and len(cube.long_name) < 45: + show_name = cube.long_name + elif cube.standard_name and len(cube.standard_name) < 45: + show_name = cube.standard_name + else: + show_name = cube.var_name + return f"{show_name} ({cube.units})" + + @staticmethod + def _set_time_axis(coord): + if not coord.units.calendar: + return + axis = plt.gca().xaxis + years = coord.cell(-1).point.year - coord.cell(0).point.year + if years < 10: + major_locator = 1 + minor_locator = None + elif years < 50: + major_locator = 5 + minor_locator = 1 + elif years < 100: + major_locator = 10 + minor_locator = 2 + else: + major_locator = 20 + minor_locator = 5 + axis.set_major_locator(mdates.YearLocator(major_locator)) + axis.set_minor_locator(mdates.YearLocator(minor_locator)) + axis.set_major_formatter(mdates.DateFormatter('%Y')) + axis.label._text = f"{coord.name()} (years)" + -- GitLab From 1f213afa230d9257e3a72909b588b962c486ae3c Mon Sep 17 00:00:00 2001 From: Javier Vegas-Regidor Date: Fri, 16 Oct 2020 15:21:42 +0200 Subject: [PATCH 2/5] Add timeseries --- mapgenerator/plotting/timeseries.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mapgenerator/plotting/timeseries.py b/mapgenerator/plotting/timeseries.py index e935ace..d9e8fd3 100644 --- a/mapgenerator/plotting/timeseries.py +++ b/mapgenerator/plotting/timeseries.py @@ -209,7 +209,8 @@ class PlotSeries(MapGenerator): major_locator = 20 minor_locator = 5 axis.set_major_locator(mdates.YearLocator(major_locator)) - axis.set_minor_locator(mdates.YearLocator(minor_locator)) + if minor_locator: + axis.set_minor_locator(mdates.YearLocator(minor_locator)) axis.set_major_formatter(mdates.DateFormatter('%Y')) axis.label._text = f"{coord.name()} (years)" -- GitLab From 9996949f224107e513cf824146ca92e02a73b753 Mon Sep 17 00:00:00 2001 From: Javier Vegas-Regidor Date: Fri, 23 Oct 2020 10:25:39 +0200 Subject: [PATCH 3/5] Add option to set up number of binds in cmaps --- mapgenerator/plotting/definitions.py | 1 + mapgenerator/plotting/plotmap.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/mapgenerator/plotting/definitions.py b/mapgenerator/plotting/definitions.py index 0394c3c..b7f1063 100644 --- a/mapgenerator/plotting/definitions.py +++ b/mapgenerator/plotting/definitions.py @@ -713,6 +713,7 @@ class MapCross(MapGenerator): # self.over = kwargs.get('over', None) # self.under = kwargs.get('under', None) self.bad = kwargs.get('bad', None) + self.N = kwargs.get('N', None) self.lat = kwargs.get('lat', []) self.lon = kwargs.get('lon', []) self.wind = kwargs.get('wind', []) diff --git a/mapgenerator/plotting/plotmap.py b/mapgenerator/plotting/plotmap.py index c362460..2dd4fdd 100644 --- a/mapgenerator/plotting/plotmap.py +++ b/mapgenerator/plotting/plotmap.py @@ -131,11 +131,15 @@ class PlotMap(MapCross, MapDrawOptions): custom_cmap = True else: try: - self.cmap = mpl.cm.get_cmap(self.colors) + if self.N: + self.cmap = mpl.cm.get_cmap(self.colors, self.N) + else: + self.cmap = mpl.cm.get_cmap(self.colors) except: self.cmap = mpl.cm.get_cmap('jet') custom_cmap = False + if self.bad: self.cmap.set_bad(self.bad) -- GitLab From c4273cbc063b6afc1ad0e13f271c415e0c32d6eb Mon Sep 17 00:00:00 2001 From: Javier Vegas-Regidor Date: Fri, 23 Oct 2020 11:45:47 +0200 Subject: [PATCH 4/5] Make default title available also for maps --- mapgenerator/plotting/definitions.py | 12 ++++++++++++ mapgenerator/plotting/plotmap.py | 2 +- mapgenerator/plotting/timeseries.py | 12 +----------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/mapgenerator/plotting/definitions.py b/mapgenerator/plotting/definitions.py index b7f1063..82e9736 100644 --- a/mapgenerator/plotting/definitions.py +++ b/mapgenerator/plotting/definitions.py @@ -700,6 +700,18 @@ class MapGenerator(object): self.shapefiles = kwargs.get('shapefiles', None) self.dpi = kwargs.get('dpi', 200) + @staticmethod + def _get_default_title(cube): + if cube.attributes.get('plot_name', None): + show_name = cube.attributes['plot_name'] + elif cube.long_name and len(cube.long_name) < 45: + show_name = cube.long_name + elif cube.standard_name and len(cube.standard_name) < 45: + show_name = cube.standard_name + else: + show_name = cube.var_name + return f"{show_name} ({cube.units})" + class MapCross(MapGenerator): """ Define common attributes to maps and cross sections """ diff --git a/mapgenerator/plotting/plotmap.py b/mapgenerator/plotting/plotmap.py index 2dd4fdd..3b07759 100644 --- a/mapgenerator/plotting/plotmap.py +++ b/mapgenerator/plotting/plotmap.py @@ -975,7 +975,7 @@ f=image" % (self.lon[0], self.lat[0], self.lon[-1], self.lat[-1], size, self.dpi def plot_cube(self, cube, **kwargs): if 'title' not in kwargs: - kwargs['title'] = '{0.long_name} ({0.units})'.format(cube) + kwargs['title'] = self._get_default_title(cube) self.aplot( cube.coord('longitude').points, diff --git a/mapgenerator/plotting/timeseries.py b/mapgenerator/plotting/timeseries.py index d9e8fd3..5fbf7b9 100644 --- a/mapgenerator/plotting/timeseries.py +++ b/mapgenerator/plotting/timeseries.py @@ -178,17 +178,7 @@ class PlotSeries(MapGenerator): fullname = os.path.join(self.outdir ,f"{name}.{self.filefmt}") self._current_fig.savefig(fullname, bbox_inches='tight', pad_inches=.2, dpi=self.dpi) - @staticmethod - def _get_default_title(cube): - if cube.attributes.get('plot_name', None): - show_name = cube.attributes['plot_name'] - elif cube.long_name and len(cube.long_name) < 45: - show_name = cube.long_name - elif cube.standard_name and len(cube.standard_name) < 45: - show_name = cube.standard_name - else: - show_name = cube.var_name - return f"{show_name} ({cube.units})" + @staticmethod def _set_time_axis(coord): -- GitLab From 68faa473134fb3bf7b42054cf968da1911491643 Mon Sep 17 00:00:00 2001 From: Javier Vegas-Regidor Date: Tue, 27 Oct 2020 10:11:07 +0100 Subject: [PATCH 5/5] Fix some plotmap issues --- mapgenerator/plotting/plotmap.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mapgenerator/plotting/plotmap.py b/mapgenerator/plotting/plotmap.py index 2e8e2e1..1273d58 100644 --- a/mapgenerator/plotting/plotmap.py +++ b/mapgenerator/plotting/plotmap.py @@ -348,9 +348,9 @@ class PlotMap(MapCross, MapDrawOptions): plt.clf() return fig_name - map_data = data.getMapData() + map_data = data.map_data # FIXME - scatter_data = data.getScatterData() or cur_scatter_data + scatter_data = data.scatter_data or cur_scatter_data if self.subplot is None: plt.clf() # params = { @@ -562,8 +562,8 @@ class PlotMap(MapCross, MapDrawOptions): norm=self.norm, zorder=10) - if data.hasWindData(): - winds = data.getWindData() + if data.wind_data: + winds = data.wind_data X, Y, U, V = delete_masked_points(x.ravel(), y.ravel(), winds['u'].ravel(), winds['v'].ravel()) #print("Wind scale is", self.wind_units, self.wind_scale) if winds.has_key('barbs'): @@ -593,12 +593,12 @@ class PlotMap(MapCross, MapDrawOptions): labelpos='S', labelsep=0.05) #self.print_time("quivers") - if data.hasContourData(): + if data.contour_data: interval = self.contours_int exclude = self.contours_exclude_vals cLowBound = -99999 cUppBound = 99999 - cdata = data.getContourData() + cdata = data.contour_data #print(cdata, type(cdata)) try: cMin = min(filter (lambda a: a > cLowBound, cdata.ravel())) -- GitLab