From 5cf9a627f0bea6ebc59e07add2b23dbbfc7a6121 Mon Sep 17 00:00:00 2001 From: Akira Sewnath Date: Mon, 20 May 2024 16:28:56 -0400 Subject: [PATCH] Adding the hvplot backend (#186) ## Description This PR expands upon the hvplot backend introduced in #158. All other diagnostic types are reformatted to have a base class and child classes for each backend. Each base class includes two methods: `data_prep` and `configure_plot`. `configure_plot` is a virtual method that is resolved once a child class for either emcpy or hvplot is instantiated. `figure_driver` is updated to reflect these changes --- requirements.txt | 1 + .../batch/base/diagnostics/density.py | 59 +++++----- .../batch/base/diagnostics/histogram.py | 53 ++++----- .../batch/base/diagnostics/horizontal_line.py | 41 +++---- .../batch/base/diagnostics/line_plot.py | 93 ++++++++------- .../batch/base/diagnostics/map_gridded.py | 69 ++++++----- .../batch/base/diagnostics/map_scatter.py | 78 ++++++------ .../batch/base/diagnostics/scatter.py | 17 ++- .../batch/base/diagnostics/vertical_line.py | 37 +++--- .../batch/base/plot_tools/figure_driver.py | 24 ++-- .../batch/emcpy/diagnostics/density.py | 91 -------------- .../batch/emcpy/diagnostics/emcpy_density.py | 50 ++++++++ .../emcpy/diagnostics/emcpy_histogram.py | 50 ++++++++ .../diagnostics/emcpy_horizontal_line.py | 50 ++++++++ .../emcpy/diagnostics/emcpy_line_plot.py | 50 ++++++++ .../emcpy/diagnostics/emcpy_map_gridded.py | 44 +++++++ .../emcpy/diagnostics/emcpy_map_scatter.py | 43 +++++++ .../batch/emcpy/diagnostics/emcpy_scatter.py | 16 ++- .../emcpy/diagnostics/emcpy_vertical_line.py | 46 ++++++++ .../batch/emcpy/diagnostics/histogram.py | 91 -------------- .../emcpy/diagnostics/horizontal_line.py | 61 ---------- .../batch/emcpy/diagnostics/line_plot.py | 111 ------------------ .../batch/emcpy/diagnostics/map_gridded.py | 70 ----------- .../batch/emcpy/diagnostics/map_scatter.py | 98 ---------------- .../batch/emcpy/diagnostics/scatter.py | 105 ----------------- .../batch/emcpy/diagnostics/vertical_line.py | 62 ---------- .../hvplot/diagnostics/hvplot_density.py | 38 ++++++ .../hvplot/diagnostics/hvplot_histogram.py | 31 +++++ .../diagnostics/hvplot_horizontal_line.py | 14 +++ .../hvplot/diagnostics/hvplot_line_plot.py | 32 +++++ .../hvplot/diagnostics/hvplot_map_gridded.py | 35 ++++++ .../hvplot/diagnostics/hvplot_map_scatter.py | 49 ++++++++ .../hvplot/diagnostics/hvplot_scatter.py | 22 ++-- .../diagnostics/hvplot_vertical_line.py | 24 ++++ .../batch/hvplot/plot_tools/create_plots.py | 93 +++++++++++++-- 35 files changed, 896 insertions(+), 952 deletions(-) delete mode 100644 src/eva/plotting/batch/emcpy/diagnostics/density.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/emcpy_density.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/emcpy_histogram.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/emcpy_horizontal_line.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/emcpy_line_plot.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/emcpy_map_gridded.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/emcpy_map_scatter.py create mode 100644 src/eva/plotting/batch/emcpy/diagnostics/emcpy_vertical_line.py delete mode 100644 src/eva/plotting/batch/emcpy/diagnostics/histogram.py delete mode 100644 src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py delete mode 100644 src/eva/plotting/batch/emcpy/diagnostics/line_plot.py delete mode 100644 src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py delete mode 100644 src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py delete mode 100644 src/eva/plotting/batch/emcpy/diagnostics/scatter.py delete mode 100644 src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py create mode 100644 src/eva/plotting/batch/hvplot/diagnostics/hvplot_density.py create mode 100644 src/eva/plotting/batch/hvplot/diagnostics/hvplot_histogram.py create mode 100644 src/eva/plotting/batch/hvplot/diagnostics/hvplot_horizontal_line.py create mode 100644 src/eva/plotting/batch/hvplot/diagnostics/hvplot_line_plot.py create mode 100644 src/eva/plotting/batch/hvplot/diagnostics/hvplot_map_gridded.py create mode 100644 src/eva/plotting/batch/hvplot/diagnostics/hvplot_map_scatter.py create mode 100644 src/eva/plotting/batch/hvplot/diagnostics/hvplot_vertical_line.py diff --git a/requirements.txt b/requirements.txt index e6cd7194..81eeb490 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,3 +22,4 @@ hvplot nbconvert bokeh geopandas +geoviews diff --git a/src/eva/plotting/batch/base/diagnostics/density.py b/src/eva/plotting/batch/base/diagnostics/density.py index 612444db..0c90ef65 100644 --- a/src/eva/plotting/batch/base/diagnostics/density.py +++ b/src/eva/plotting/batch/base/diagnostics/density.py @@ -1,32 +1,27 @@ from eva.eva_path import return_eva_path from eva.utilities.config import get from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.plots -import os import numpy as np +from abc import ABC, abstractmethod # -------------------------------------------------------------------------------------------------- -class Density(): +class Density(ABC): """Base class for creating density plots.""" def __init__(self, config, logger, dataobj): """ - Creates a density plot based on the provided configuration and data. + Creates a density plot abstract class based on the provided configuration and data. Args: config (dict): A dictionary containing the configuration for the density plot. logger (Logger): An instance of the logger for logging messages. dataobj: An instance of the data object containing input data. - This class initializes and configures a density plot based on the provided configuration and - data. The density plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). - Example: :: @@ -46,45 +41,49 @@ def __init__(self, config, logger, dataobj): density_plot = Density(config, logger, dataobj) """ + self.config = config + self.logger = logger + self.dataobj = dataobj + self.plotobj = None + self.data = None + +# -------------------------------------------------------------------------------------------------- + + def data_prep(self): + + """ Preparing data for configure_plot """ + # Get the data to plot from the data_collection # --------------------------------------------- - varstr = config['data']['variable'] + varstr = self.config['data']['variable'] var_cgv = varstr.split('::') if len(var_cgv) != 3: - logger.abort('In Density the variable \'var_cgv\' does not appear to ' + - 'be in the required format of collection::group::variable.') + self.logger.abort('In Density the variable \'var_cgv\' does not appear to ' + + 'be in the required format of collection::group::variable.') # Optionally get the channel to plot channel = None - if 'channel' in config['data']: - channel = config['data'].get('channel') + if 'channel' in self.config['data']: + channel = self.config['data'].get('channel') - data = dataobj.get_variable_data(var_cgv[0], var_cgv[1], var_cgv[2], channel) + data = self.dataobj.get_variable_data(var_cgv[0], var_cgv[1], var_cgv[2], channel) # See if we need to slice data - data = slice_var_from_str(config['data'], data, logger) + data = slice_var_from_str(self.config['data'], data, self.logger) # Density data should be flattened data = data.flatten() # Missing data should also be removed mask = ~np.isnan(data) - data = data[mask] - - # Create declarative plotting density object - # -------------------------------------------- - self.plotobj = emcpy.plots.plots.Density(data) - - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'density.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['type', 'schema', 'data'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) + self.data = data[mask] + +# -------------------------------------------------------------------------------------------------- + @abstractmethod + def configure_plot(self): + """ Virtual method for configuring plot based on selected backend """ + pass # -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/base/diagnostics/histogram.py b/src/eva/plotting/batch/base/diagnostics/histogram.py index 009d79a4..78dc9548 100644 --- a/src/eva/plotting/batch/base/diagnostics/histogram.py +++ b/src/eva/plotting/batch/base/diagnostics/histogram.py @@ -1,31 +1,27 @@ from eva.eva_path import return_eva_path from eva.utilities.config import get from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.plots -import os import numpy as np +from abc import ABC, abstractmethod # -------------------------------------------------------------------------------------------------- -class Histogram(): +class Histogram(ABC): """Base class for creating histogram plots.""" def __init__(self, config, logger, dataobj): """ - Creates a histogram plot based on the provided configuration and data. + Creates a histogram plot abstract class based on the provided configuration and data. Args: config (dict): A dictionary containing the configuration for the histogram plot. logger (Logger): An instance of the logger for logging messages. dataobj: An instance of the data object containing input data. - This class initializes and configures a histogram plot based on the provided configuration - and data. The histogram plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). Example: @@ -46,9 +42,20 @@ def __init__(self, config, logger, dataobj): histogram_plot = Histogram(config, logger, dataobj) """ + self.config = config + self.logger = logger + self.dataobj = dataobj + self.data = None + self.plotobj = None + +# -------------------------------------------------------------------------------------------------- + + def data_prep(self): + """ Preparing data for configure_plot """ + # Get the data to plot from the data_collection # --------------------------------------------- - varstr = config['data']['variable'] + varstr = self.config['data']['variable'] var_cgv = varstr.split('::') if len(var_cgv) != 3: @@ -57,34 +64,26 @@ def __init__(self, config, logger, dataobj): # Optionally get the channel to plot channel = None - if 'channel' in config['data']: - channel = config['data'].get('channel') + if 'channel' in self.config['data']: + channel = self.config['data'].get('channel') - data = dataobj.get_variable_data(var_cgv[0], var_cgv[1], var_cgv[2], channel) + data = self.dataobj.get_variable_data(var_cgv[0], var_cgv[1], var_cgv[2], channel) # See if we need to slice data - data = slice_var_from_str(config['data'], data, logger) + data = slice_var_from_str(self.config['data'], data, self.logger) # Histogram data should be flattened data = data.flatten() # Missing data should also be removed mask = ~np.isnan(data) - data = data[mask] - - # Create declarative plotting histogram object - # -------------------------------------------- - self.plotobj = emcpy.plots.plots.Histogram(data) - - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'histogram.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['type', 'schema', 'data'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) + self.data = data[mask] + +# -------------------------------------------------------------------------------------------------- + @abstractmethod + def configure_plot(self): + """ Virtual method for configuring plot based on selected backend """ + pass # -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/base/diagnostics/horizontal_line.py b/src/eva/plotting/batch/base/diagnostics/horizontal_line.py index 41544572..839933ad 100644 --- a/src/eva/plotting/batch/base/diagnostics/horizontal_line.py +++ b/src/eva/plotting/batch/base/diagnostics/horizontal_line.py @@ -1,9 +1,7 @@ from eva.eva_path import return_eva_path from eva.utilities.utils import get_schema, update_object -import emcpy.plots.plots -import os - +from abc import ABC, abstractmethod # -------------------------------------------------------------------------------------------------- @@ -14,15 +12,12 @@ class HorizontalLine(): def __init__(self, config, logger, dataobj): """ - Creates a horizontal line plot based on the provided configuration. + Creates a horizontal line plot abstract class based on the provided configuration. Args: config (dict): A dictionary containing the configuration for the horizontal line plot. logger (Logger): An instance of the logger for logging messages. - - This class initializes and configures a horizontal line plot based on the provided - configuration. The horizontal line plot is created using a declarative plotting library from - EMCPy (https://github.com/NOAA-EMC/emcpy). + dataobj: An instance of the data object containing input data. Example: @@ -38,24 +33,22 @@ def __init__(self, config, logger, dataobj): horizontal_line_plot = HorizontalLine(config, logger) """ - # Get the y value to plot - # ----------------------- - yval = config['y'] + self.logger = logger + self.config = config + self.yval = None - # Create declarative plotting HorizontalLine object - # ------------------------------------------- - self.plotobj = emcpy.plots.plots.HorizontalLine(yval) +# -------------------------------------------------------------------------------------------------- - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', - 'horizontal_line.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['type', 'schema'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) + def data_prep(self): + """ Preparing data for configure_plot """ + # Get the y value to plot + # ----------------------- + self.yval = self.config['y'] # -------------------------------------------------------------------------------------------------- + + @abstractmethod + def configure_plot(self): + """ Virtual method for configuring plot based on selected backend """ + pass diff --git a/src/eva/plotting/batch/base/diagnostics/line_plot.py b/src/eva/plotting/batch/base/diagnostics/line_plot.py index 2d733783..c80e1678 100644 --- a/src/eva/plotting/batch/base/diagnostics/line_plot.py +++ b/src/eva/plotting/batch/base/diagnostics/line_plot.py @@ -1,31 +1,27 @@ from eva.eva_path import return_eva_path from eva.utilities.config import get from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.plots -import os import numpy as np +from abc import ABC, abstractmethod # -------------------------------------------------------------------------------------------------- -class LinePlot(): +class LinePlot(ABC): """Base class for creating line plots.""" def __init__(self, config, logger, dataobj): """ - Creates a line plot based on the provided configuration. + Creates a line plot abstract class based on the provided configuration. Args: config (dict): A dictionary containing the configuration for the line plot. logger (Logger): An instance of the logger for logging messages. dataobj: An instance of the data object containing input data. - This class initializes and configures a line plot based on the provided configuration. - The line plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). Example: @@ -43,40 +39,61 @@ def __init__(self, config, logger, dataobj): line_plot = LinePlot(config, logger, None) """ + self.dataobj = dataobj + self.config = config + self.logger = logger + self.xdata = None + self.ydata = None + self.plotobj = None + + self.color = None + self.label = None + +# -------------------------------------------------------------------------------------------------- + + def data_prep(self): + """ Preparing data for configure_plot """ + # Get the data to plot from the data_collection # --------------------------------------------- - var0 = config['x']['variable'] - var1 = config['y']['variable'] + var0 = self.config['x']['variable'] + var1 = self.config['y']['variable'] var0_cgv = var0.split('::') var1_cgv = var1.split('::') if len(var0_cgv) != 3: - logger.abort('In Scatter comparison the first variable \'var0\' does not appear to ' + - 'be in the required format of collection::group::variable.') + self.logger.abort('In Scatter comparison the first variable \'var0\' ' + + 'does not appear to be in the required format of ' + + 'collection::group::variable.') if len(var1_cgv) != 3: - logger.abort('In Scatter comparison the first variable \'var1\' does not appear to ' + - 'be in the required format of collection::group::variable.') + self.logger.abort('In Scatter comparison the first variable \'var1\' ' + + 'does not appear to be in the required format of ' + + 'collection::group::variable.') # Optionally get the channel|level|datatype to plot channel = None - if 'channel' in config: - channel = config.get('channel') + if 'channel' in self.config: + channel = self.config.get('channel') level = None - if 'level' in config: - level = config.get('level') + if 'level' in self.config: + level = self.config.get('level') datatype = None - if 'datatype' in config: - datatype = config.get('datatype') - - xdata = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], - var0_cgv[2], channel, level, datatype) - ydata = dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], - var1_cgv[2], channel, level, datatype) + if 'datatype' in self.config: + datatype = self.config.get('datatype') + if 'color' in self.config: + self.color = self.config.get('color') + if 'label' in self.config: + self.label = self.config.get('label') + + xdata = self.dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2], + channel, level, datatype) + ydata = self.dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], + channel, level, datatype) # see if we need to slice data - xdata = slice_var_from_str(config['x'], xdata, logger) - ydata = slice_var_from_str(config['y'], ydata, logger) + xdata = slice_var_from_str(self.config['x'], xdata, self.logger) + ydata = slice_var_from_str(self.config['y'], ydata, self.logger) # line plot data should be flattened xdata = xdata.flatten() @@ -89,22 +106,12 @@ def __init__(self, config, logger, dataobj): ydata = ydata[mask] mask = ~np.isnan(ydata) - xdata = xdata[mask] - ydata = ydata[mask] - - # Create declarative plotting LinePlot object - # ------------------------------------------- - self.plotobj = emcpy.plots.plots.LinePlot(xdata, ydata) - - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'line_plot.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['x', 'y', 'type', 'schema', 'channel', 'level'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) - + self.xdata = xdata[mask] + self.ydata = ydata[mask] # -------------------------------------------------------------------------------------------------- + + @abstractmethod + def configure_plot(self): + """ Virtual method for configuring plot based on selected backend """ + pass diff --git a/src/eva/plotting/batch/base/diagnostics/map_gridded.py b/src/eva/plotting/batch/base/diagnostics/map_gridded.py index 6668f5fe..21ee803c 100644 --- a/src/eva/plotting/batch/base/diagnostics/map_gridded.py +++ b/src/eva/plotting/batch/base/diagnostics/map_gridded.py @@ -1,30 +1,25 @@ from eva.eva_path import return_eva_path from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.map_plots -import os +from abc import ABC, abstractmethod # -------------------------------------------------------------------------------------------------- -class MapGridded(): +class MapGridded(ABC): """Base class for creating map gridded plots.""" def __init__(self, config, logger, dataobj): """ - Creates a gridded map plot based on the provided configuration. + Creates a gridded map plot abstract class based on the provided configuration. Args: config (dict): A dictionary containing the configuration for the gridded map plot. logger (Logger): An instance of the logger for logging messages. dataobj: An instance of the data object containing input data. - This class initializes and configures a gridded map plot based on the provided - configuration. The gridded map plot is created using a declarative plotting library from - EMCPy (https://github.com/NOAA-EMC/emcpy). - Example: :: @@ -40,31 +35,41 @@ def __init__(self, config, logger, dataobj): logger = Logger() map_plot = MapGridded(config, logger, None) """ + self.collection = None + self.datavar_name = None - # prepare data based on config - lonvar_cgv = config['longitude']['variable'].split('::') - lonvar = dataobj.get_variable_data(lonvar_cgv[0], lonvar_cgv[1], lonvar_cgv[2], None) - lonvar = slice_var_from_str(config['longitude'], lonvar, logger) - latvar_cgv = config['latitude']['variable'].split('::') - latvar = dataobj.get_variable_data(latvar_cgv[0], latvar_cgv[1], latvar_cgv[2], None) - latvar = slice_var_from_str(config['latitude'], latvar, logger) - datavar_cgv = config['data']['variable'].split('::') - datavar = dataobj.get_variable_data(datavar_cgv[0], datavar_cgv[1], datavar_cgv[2], None) - datavar = slice_var_from_str(config['data'], datavar, logger) - - # create declarative plotting MapGridded object - self.plotobj = emcpy.plots.map_plots.MapGridded(latvar, lonvar, datavar) - # get defaults from schema - layer_schema = config.get("schema", - os.path.join(return_eva_path(), - 'plotting', - 'emcpy', 'defaults', - 'map_gridded.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['longitude', 'latitude', 'data', 'type', 'schema'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) + self.config = config + self.logger = logger + self.dataobj = dataobj + self.lonvar = [] + self.latvar = [] + self.datavar = [] + self.plotobj = None + +# -------------------------------------------------------------------------------------------------- + def data_prep(self): + """ Preparing data for configure_plot """ + + # prepare data based on config + lonvar_cgv = self.config['longitude']['variable'].split('::') + self.collection = lonvar_cgv[0] + self.lonvar = self.dataobj.get_variable_data(lonvar_cgv[0], lonvar_cgv[1], + lonvar_cgv[2], None) + self.lonvar = slice_var_from_str(self.config['longitude'], self.lonvar, self.logger) + latvar_cgv = self.config['latitude']['variable'].split('::') + self.latvar = self.dataobj.get_variable_data(latvar_cgv[0], latvar_cgv[1], + latvar_cgv[2], None) + self.latvar = slice_var_from_str(self.config['latitude'], self.latvar, self.logger) + datavar_cgv = self.config['data']['variable'].split('::') + self.datavar_name = datavar_cgv[1] + '::' + datavar_cgv[2] + self.datavar = self.dataobj.get_variable_data(datavar_cgv[0], datavar_cgv[1], + datavar_cgv[2], None) + self.datavar = slice_var_from_str(self.config['data'], self.datavar, self.logger) # -------------------------------------------------------------------------------------------------- + + @abstractmethod + def configure_plot(self): + """ Virtual method for configuring plot based on selected backend """ + pass diff --git a/src/eva/plotting/batch/base/diagnostics/map_scatter.py b/src/eva/plotting/batch/base/diagnostics/map_scatter.py index 81356d66..f543cb97 100644 --- a/src/eva/plotting/batch/base/diagnostics/map_scatter.py +++ b/src/eva/plotting/batch/base/diagnostics/map_scatter.py @@ -1,14 +1,13 @@ from eva.eva_path import return_eva_path from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.map_plots -import os import numpy as np +from abc import ABC, abstractmethod # -------------------------------------------------------------------------------------------------- -class MapScatter(): +class MapScatter(ABC): """Base class for creating map scatter plots.""" @@ -22,9 +21,6 @@ def __init__(self, config, logger, dataobj): logger (Logger): An instance of the logger for logging messages. dataobj: An instance of the data object containing input data. - This class initializes and configures a scatter plot on a map based on the provided - configuration. The scatter plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). Example: :: @@ -42,42 +38,46 @@ def __init__(self, config, logger, dataobj): map_scatter_plot = MapScatter(config, logger, None) """ - # prepare data based on config - # Optionally get the channel to plot + self.config = config + self.logger = logger + self.dataobj = dataobj + self.lonvar = None + self.latvar = None + self.datavar = None + self.plotobj = None + +# -------------------------------------------------------------------------------------------------- + + def data_prep(self): + """ Preparing data for configure_plot """ + channel = None - if 'channel' in config['data']: - channel = config['data'].get('channel') - lonvar_cgv = config['longitude']['variable'].split('::') - lonvar = dataobj.get_variable_data(lonvar_cgv[0], lonvar_cgv[1], lonvar_cgv[2], None) - lonvar = slice_var_from_str(config['longitude'], lonvar, logger) - latvar_cgv = config['latitude']['variable'].split('::') - latvar = dataobj.get_variable_data(latvar_cgv[0], latvar_cgv[1], latvar_cgv[2], None) - latvar = slice_var_from_str(config['latitude'], latvar, logger) - datavar_cgv = config['data']['variable'].split('::') - datavar = dataobj.get_variable_data(datavar_cgv[0], datavar_cgv[1], datavar_cgv[2], channel) - datavar = slice_var_from_str(config['data'], datavar, logger) - # scatter data should be flattened - lonvar = lonvar.flatten() - latvar = latvar.flatten() - datavar = datavar.flatten() + if 'channel' in self.config['data']: + channel = self.config['data'].get('channel') + lonvar_cgv = self.config['longitude']['variable'].split('::') + lonvar = self.dataobj.get_variable_data(lonvar_cgv[0], lonvar_cgv[1], lonvar_cgv[2], None) + lonvar = slice_var_from_str(self.config['longitude'], lonvar, self.logger) + latvar_cgv = self.config['latitude']['variable'].split('::') + latvar = self.dataobj.get_variable_data(latvar_cgv[0], latvar_cgv[1], latvar_cgv[2], None) + latvar = slice_var_from_str(self.config['latitude'], latvar, self.logger) + datavar_cgv = self.config['data']['variable'].split('::') + datavar = self.dataobj.get_variable_data(datavar_cgv[0], datavar_cgv[1], + datavar_cgv[2], channel) + datavar = slice_var_from_str(self.config['data'], datavar, self.logger) + self.lonvar = lonvar.flatten() + self.latvar = latvar.flatten() + self.datavar = datavar.flatten() # If everything is nan plotting will fail so just plot some large values - if np.isnan(datavar).all(): - datavar[np.isnan(datavar)] = 1.0e38 - - # create declarative plotting MapScatter object - self.plotobj = emcpy.plots.map_plots.MapScatter(latvar, lonvar, datavar) - # get defaults from schema - layer_schema = config.get("schema", - os.path.join(return_eva_path(), - 'plotting', - 'emcpy', 'defaults', - 'map_scatter.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['longitude', 'latitude', 'data', 'type', 'schema'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) + if np.isnan(self.datavar).all(): + self.datavar[np.isnan(self.datavar)] = 1.0e38 + +# -------------------------------------------------------------------------------------------------- + + @abstractmethod + def configure_plot(self): + """ Virtual method for configuring plot based on selected backend """ + pass # -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/base/diagnostics/scatter.py b/src/eva/plotting/batch/base/diagnostics/scatter.py index f3d743e4..ced4f573 100644 --- a/src/eva/plotting/batch/base/diagnostics/scatter.py +++ b/src/eva/plotting/batch/base/diagnostics/scatter.py @@ -1,7 +1,6 @@ from eva.eva_path import return_eva_path from eva.utilities.config import get from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import os import numpy as np from abc import ABC, abstractmethod @@ -16,33 +15,29 @@ class Scatter(ABC): def __init__(self, config, logger, dataobj): """ - Creates a scatter plot on a map based on the provided configuration. + Creates a scatter plot abstract class based on the provided configuration. Args: config (dict): A dictionary containing the configuration for the scatter plot on a map. logger (Logger): An instance of the logger for logging messages. dataobj: An instance of the data object containing input data. - This class initializes and configures a scatter plot on a map based on the provided - configuration. The scatter plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). Example: :: config = { - "longitude": {"variable": "collection::group::variable"}, - "latitude": {"variable": "collection::group::variable"}, - "data": {"variable": "collection::group::variable", - "channel": "channel_name"}, + "x": {"variable": "collection::group::variable"}, + "y": {"variable": "collection::group::variable"}, "plot_property": "property_value", "plot_option": "option_value", "schema": "path_to_schema_file.yaml" } logger = Logger() - map_scatter_plot = MapScatter(config, logger, None) + scatter_plot = Scatter(config, logger, None) """ + self.config = config self.logger = logger self.dataobj = dataobj @@ -53,6 +48,7 @@ def __init__(self, config, logger, dataobj): # -------------------------------------------------------------------------------------------------- def data_prep(self): + """ Preparing data for configure_plot """ # Get the data to plot from the data_collection # --------------------------------------------- @@ -98,6 +94,7 @@ def data_prep(self): @abstractmethod def configure_plot(self): + """ Virtual method for configuring plot based on selected backend """ pass # -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/base/diagnostics/vertical_line.py b/src/eva/plotting/batch/base/diagnostics/vertical_line.py index 6bcf94d3..0a50dc20 100644 --- a/src/eva/plotting/batch/base/diagnostics/vertical_line.py +++ b/src/eva/plotting/batch/base/diagnostics/vertical_line.py @@ -1,9 +1,7 @@ from eva.eva_path import return_eva_path from eva.utilities.utils import get_schema, update_object -import emcpy.plots.plots -import os - +from abc import ABC, abstractmethod # -------------------------------------------------------------------------------------------------- @@ -21,10 +19,6 @@ def __init__(self, config, logger, dataobj): logger (Logger): An instance of the logger for logging messages. dataobj: Not used in this context. - This class initializes and configures a vertical line plot based on the provided - configuration. The vertical line plot is created using a declarative plotting library from - EMCPy (https://github.com/NOAA-EMC/emcpy). - Example: :: @@ -39,23 +33,22 @@ def __init__(self, config, logger, dataobj): vertical_line_plot = VerticalLine(config, logger, None) """ - # Get the x value to plot - # ----------------------- - xval = config['x'] + self.logger = logger + self.config = config + self.xval = None - # Create declarative plotting HorizontalLine object - # ------------------------------------------- - self.plotobj = emcpy.plots.plots.VerticalLine(xval) +# -------------------------------------------------------------------------------------------------- - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'vertical_line.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['type', 'schema'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) + def data_prep(self): + """ Preparing data for configure_plot """ + # Get the x value to plot + # ----------------------- + self.xval = self.config['x'] # -------------------------------------------------------------------------------------------------- + + @abstractmethod + def configure_plot(self): + """ Virtual method for configuring plot based on selected backend """ + pass diff --git a/src/eva/plotting/batch/base/plot_tools/figure_driver.py b/src/eva/plotting/batch/base/plot_tools/figure_driver.py index 5a7d1bfc..6d0b4e17 100644 --- a/src/eva/plotting/batch/base/plot_tools/figure_driver.py +++ b/src/eva/plotting/batch/base/plot_tools/figure_driver.py @@ -34,7 +34,7 @@ def figure_driver(config, data_collections, timing, logger): This function generates and saves multiple figures based on the provided configuration. It processes each graphic specified in the configuration and creates corresponding figures with - plots. + plots. This function also uses the plotting backend specified in the configuration. """ # Get list of graphics from configuration @@ -192,21 +192,13 @@ def make_figure(handler, figure_conf, plots, dynamic_options, data_collections, layer_list = [] for layer in plot.get("layers"): - # Temporary case to handle different diagnostics - if handler.BACKEND_NAME == 'Emcpy': - eva_class_name = layer.get("type") - eva_module_name = camelcase_to_underscore(eva_class_name) - full_module = "eva.plotting.batch.emcpy.diagnostics."+eva_module_name - layer_class = getattr(im.import_module(full_module), eva_class_name) - layer_list.append(layer_class(layer, logger, data_collections).plotobj) - else: - eva_class_name = handler.BACKEND_NAME + layer.get("type") - eva_module_name = camelcase_to_underscore(eva_class_name) - full_module = handler.MODULE_NAME + eva_module_name - layer_class = getattr(im.import_module(full_module), eva_class_name) - layer = layer_class(layer, logger, data_collections) - layer.data_prep() - layer_list.append(layer.configure_plot()) + eva_class_name = handler.BACKEND_NAME + layer.get("type") + eva_module_name = camelcase_to_underscore(eva_class_name) + full_module = handler.MODULE_NAME + eva_module_name + layer_class = getattr(im.import_module(full_module), eva_class_name) + layer = layer_class(layer, logger, data_collections) + layer.data_prep() + layer_list.append(layer.configure_plot()) # get mapping dictionary proj = None diff --git a/src/eva/plotting/batch/emcpy/diagnostics/density.py b/src/eva/plotting/batch/emcpy/diagnostics/density.py deleted file mode 100644 index e5b12da3..00000000 --- a/src/eva/plotting/batch/emcpy/diagnostics/density.py +++ /dev/null @@ -1,91 +0,0 @@ -from eva.eva_path import return_eva_path -from eva.utilities.config import get -from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.plots -import os -import numpy as np - - -# -------------------------------------------------------------------------------------------------- - - -class Density(): - - """Base class for creating density plots.""" - - def __init__(self, config, logger, dataobj): - - """ - Creates a density plot based on the provided configuration and data. - - Args: - config (dict): A dictionary containing the configuration for the density plot. - logger (Logger): An instance of the logger for logging messages. - dataobj: An instance of the data object containing input data. - - This class initializes and configures a density plot based on the provided configuration and - data. The density plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). - - Example: - - :: - - config = { - "data": { - "variable": "collection::group::variable", - "channel": "channel_name", - "slicing": "slice expression" - }, - "plot_property": "property_value", - "plot_option": "option_value", - "schema": "path_to_schema_file.yaml" - } - logger = Logger() - dataobj = DataObject() - density_plot = Density(config, logger, dataobj) - """ - - # Get the data to plot from the data_collection - # --------------------------------------------- - varstr = config['data']['variable'] - var_cgv = varstr.split('::') - - if len(var_cgv) != 3: - logger.abort('In Density the variable \'var_cgv\' does not appear to ' + - 'be in the required format of collection::group::variable.') - - # Optionally get the channel to plot - channel = None - if 'channel' in config['data']: - channel = config['data'].get('channel') - - data = dataobj.get_variable_data(var_cgv[0], var_cgv[1], var_cgv[2], channel) - - # See if we need to slice data - data = slice_var_from_str(config['data'], data, logger) - - # Density data should be flattened - data = data.flatten() - - # Missing data should also be removed - mask = ~np.isnan(data) - data = data[mask] - - # Create declarative plotting density object - # -------------------------------------------- - self.plotobj = emcpy.plots.plots.Density(data) - - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'batch', 'emcpy', 'defaults', - 'density.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['type', 'schema', 'data'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) - - -# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_density.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_density.py new file mode 100644 index 00000000..7fc3ba68 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_density.py @@ -0,0 +1,50 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object +import emcpy.plots.plots +import os + +from eva.plotting.batch.base.diagnostics.density import Density + +# -------------------------------------------------------------------------------------------------- + + +class EmcpyDensity(Density): + + """ + EmcpyDensity class inherits from the Density class and provides methods + to configure plotting settings for density plots using the emcpy library. + + Attributes: + Inherits attributes from the Density class. + + Methods: + configure_plot(): Configures the plotting settings for the density plot. + """ + + def configure_plot(self): + + """ + Configures the plotting settings for the density plot. + + Returns: + plotobj: Plotting object configured with the specified settings. + """ + + # Create declarative plotting density object + # -------------------------------------------- + self.plotobj = emcpy.plots.plots.Density(self.data) + + # Get defaults from schema + # ------------------------ + layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'batch', 'emcpy', 'defaults', 'density.yaml')) + new_config = get_schema(layer_schema, self.config, self.logger) + delvars = ['type', 'schema', 'data'] + for d in delvars: + new_config.pop(d, None) + self.plotobj = update_object(self.plotobj, new_config, self.logger) + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_histogram.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_histogram.py new file mode 100644 index 00000000..b3efeb07 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_histogram.py @@ -0,0 +1,50 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object +import emcpy.plots.plots +import os + +from eva.plotting.batch.base.diagnostics.histogram import Histogram + +# -------------------------------------------------------------------------------------------------- + + +class EmcpyHistogram(Histogram): + + """ + EmcpyHistogram class is a subclass of the Histogram class, tailored for configuring + and plotting histogram visualizations using the emcpy library. + + Attributes: + Inherits attributes from the Histogram class. + + Methods: + configure_plot(): Configures the plotting settings for the histogram. + """ + + def configure_plot(self): + + """ + Configures the plotting settings for the histogram. + + Returns: + plotobj: The configured plot object for EMCpy histograms. + """ + + # Create declarative plotting histogram object + # -------------------------------------------- + self.plotobj = emcpy.plots.plots.Histogram(self.data) + + # Get defaults from schema + # ------------------------ + layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'batch', 'emcpy', 'defaults', 'histogram.yaml')) + new_config = get_schema(layer_schema, self.config, self.logger) + delvars = ['type', 'schema', 'data'] + for d in delvars: + new_config.pop(d, None) + self.plotobj = update_object(self.plotobj, new_config, self.logger) + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_horizontal_line.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_horizontal_line.py new file mode 100644 index 00000000..61be4afc --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_horizontal_line.py @@ -0,0 +1,50 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object +import emcpy.plots.plots +import os + +from eva.plotting.batch.base.diagnostics.horizontal_line import HorizontalLine + +# -------------------------------------------------------------------------------------------------- + + +class EmcpyHorizontalLine(HorizontalLine): + + """ + EmcpyHorizontalLine class is a subclass of the HorizontalLine class, designed for + configuring and plotting horizontal line visualizations using the emcpy library. + + Attributes: + Inherits attributes from the HorizontalLine class. + + Methods: + configure_plot(): Configures the plotting settings for the horizontal line. + """ + + def configure_plot(self): + + """ + Configures the plotting settings for the horizontal line. + + Returns: + plotobj: The configured plot object for emcpy horizontal lines. + """ + + # Create declarative plotting HorizontalLine object + # ------------------------------------------- + self.plotobj = emcpy.plots.plots.HorizontalLine(self.yval) + + # Get defaults from schema + # ------------------------ + layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'batch', 'emcpy', 'defaults', 'horizontal_line.yaml')) + new_config = get_schema(layer_schema, self.config, self.logger) + delvars = ['type', 'schema'] + for d in delvars: + new_config.pop(d, None) + self.plotobj = update_object(self.plotobj, self.config, self.logger) + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_line_plot.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_line_plot.py new file mode 100644 index 00000000..2781469d --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_line_plot.py @@ -0,0 +1,50 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object +import emcpy.plots.plots +import os + +from eva.plotting.batch.base.diagnostics.line_plot import LinePlot + +# -------------------------------------------------------------------------------------------------- + + +class EmcpyLinePlot(LinePlot): + + """ + EmcpyLinePlot class is a subclass of the LinePlot class, designed for configuring + and plotting line plot visualizations using the emcpy library. + + Attributes: + Inherits attributes from the LinePlot class. + + Methods: + configure_plot(): Configures the plotting settings for the line plot. + """ + + def configure_plot(self): + + """ + Configures the plotting settings for the line plot. + + Returns: + plotobj: The configured plot object for emcpy line plots. + """ + + # Create declarative plotting LinePlot object + # ------------------------------------------- + self.plotobj = emcpy.plots.plots.LinePlot(self.xdata, self.ydata) + + # Get defaults from schema + # ------------------------ + layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'batch', 'emcpy', 'defaults', 'line_plot.yaml')) + new_config = get_schema(layer_schema, self.config, self.logger) + delvars = ['x', 'y', 'type', 'schema', 'channel', 'level', 'datatype'] + for d in delvars: + new_config.pop(d, None) + self.plotobj = update_object(self.plotobj, new_config, self.logger) + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_map_gridded.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_map_gridded.py new file mode 100644 index 00000000..271b2f08 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_map_gridded.py @@ -0,0 +1,44 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object +import emcpy.plots.map_plots +import os + +from eva.plotting.batch.base.diagnostics.map_gridded import MapGridded + +# -------------------------------------------------------------------------------------------------- + + +class EmcpyMapGridded(MapGridded): + """ + EmcpyMapGridded class is a subclass of the MapGridded class, tailored for + configuring and plotting gridded map visualizations using the emcpy library. + + Attributes: + Inherits attributes from the MapGridded class. + + Methods: + configure_plot(): Configures the plotting settings for the gridded map. + """ + + def configure_plot(self): + """ + Configures the plotting settings for the gridded map. + + Returns: + plotobj: The configured plot object for emcpy gridded maps. + """ + + # create declarative plotting MapGridded object + self.plotobj = emcpy.plots.map_plots.MapGridded(self.latvar, self.lonvar, self.datavar) + # get defaults from schema + layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'batch', 'emcpy', 'defaults', 'map_gridded.yaml')) + new_config = get_schema(layer_schema, self.config, self.logger) + delvars = ['longitude', 'latitude', 'data', 'type', 'schema'] + for d in delvars: + new_config.pop(d, None) + self.plotobj = update_object(self.plotobj, new_config, self.logger) + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_map_scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_map_scatter.py new file mode 100644 index 00000000..35f1c0d5 --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_map_scatter.py @@ -0,0 +1,43 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object +import emcpy.plots.map_plots +import os + +from eva.plotting.batch.base.diagnostics.map_scatter import MapScatter + +# -------------------------------------------------------------------------------------------------- + + +class EmcpyMapScatter(MapScatter): + """ + EmcpyMapScatter class is a subclass of the MapScatter class, designed for + configuring and plotting scatter map visualizations using the emcpy library. + + Attributes: + Inherits attributes from the MapScatter class. + + Methods: + configure_plot(): Configures the plotting settings for the scatter map. + """ + + def configure_plot(self): + """ + Configures the plotting settings for the scatter map. + + Returns: + plotobj: The configured plot object for emcpy scatter maps. + """ + + self.plotobj = emcpy.plots.map_plots.MapScatter(self.latvar, self.lonvar, self.datavar) + layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'batch', 'emcpy', 'defaults', 'map_scatter.yaml')) + new_config = get_schema(layer_schema, self.config, self.logger) + delvars = ['longitude', 'latitude', 'data', 'type', 'schema'] + for d in delvars: + new_config.pop(d, None) + self.plotobj = update_object(self.plotobj, new_config, self.logger) + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py index 121d4bff..6d5e8631 100644 --- a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_scatter.py @@ -10,9 +10,23 @@ class EmcpyScatter(Scatter): + """ + EmcpyScatter class is a subclass of the Scatter class, specialized for + configuring and plotting scatter visualizations using the emcpy library. + Attributes: + Inherits attributes from the Scatter class. + + Methods: + configure_plot(): Configures the plotting settings for the scatter plot. + """ def configure_plot(self): + """ + Configures the plotting settings for the scatter plot. + Returns: + plotobj: The configured plot object for emcpy scatter plots. + """ # Create declarative plotting Scatter object # ------------------------------------------ self.plotobj = emcpy.plots.plots.Scatter(self.xdata, self.ydata) @@ -20,7 +34,7 @@ def configure_plot(self): # Get defaults from schema # ------------------------ layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'emcpy', 'defaults', 'scatter.yaml')) + 'batch', 'emcpy', 'defaults', 'scatter.yaml')) new_config = get_schema(layer_schema, self.config, self.logger) delvars = ['x', 'y', 'type', 'schema'] for d in delvars: diff --git a/src/eva/plotting/batch/emcpy/diagnostics/emcpy_vertical_line.py b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_vertical_line.py new file mode 100644 index 00000000..1035f8ed --- /dev/null +++ b/src/eva/plotting/batch/emcpy/diagnostics/emcpy_vertical_line.py @@ -0,0 +1,46 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object +import emcpy.plots.plots +import os + +from eva.plotting.batch.base.diagnostics.vertical_line import VerticalLine + +# -------------------------------------------------------------------------------------------------- + + +class EmcpyVerticalLine(VerticalLine): + """ + EmcpyVerticalLine class is a subclass of the VerticalLine class, designed for + configuring and plotting vertical line visualizations using the emcpy library. + + Attributes: + Inherits attributes from the VerticalLine class. + + Methods: + configure_plot(): Configures the plotting settings for the vertical line. + """ + def configure_plot(self): + """ + Configures the plotting settings for the vertical line. + + Returns: + plotobj: The configured plot object for emcpy vertical lines. + """ + # Create declarative plotting VerticalLine object + # ------------------------------------------- + self.plotobj = emcpy.plots.plots.VerticalLine(self.xval) + + # Get defaults from schema + # ------------------------ + layer_schema = self.config.get('schema', os.path.join(return_eva_path(), 'plotting', + 'batch', 'emcpy', 'defaults', 'vertical_line.yaml')) + new_config = get_schema(layer_schema, self.config, self.logger) + delvars = ['type', 'schema'] + for d in delvars: + new_config.pop(d, None) + self.plotobj = update_object(self.plotobj, new_config, self.logger) + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/histogram.py b/src/eva/plotting/batch/emcpy/diagnostics/histogram.py deleted file mode 100644 index bf9f0905..00000000 --- a/src/eva/plotting/batch/emcpy/diagnostics/histogram.py +++ /dev/null @@ -1,91 +0,0 @@ -from eva.eva_path import return_eva_path -from eva.utilities.config import get -from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.plots -import os -import numpy as np - - -# -------------------------------------------------------------------------------------------------- - - -class Histogram(): - - """Base class for creating histogram plots.""" - - def __init__(self, config, logger, dataobj): - - """ - Creates a histogram plot based on the provided configuration and data. - - Args: - config (dict): A dictionary containing the configuration for the histogram plot. - logger (Logger): An instance of the logger for logging messages. - dataobj: An instance of the data object containing input data. - - This class initializes and configures a histogram plot based on the provided configuration - and data. The histogram plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). - - Example: - - :: - - config = { - "data": { - "variable": "collection::group::variable", - "channel": "channel_name", - "slicing": "slice expression" - }, - "plot_property": "property_value", - "plot_option": "option_value", - "schema": "path_to_schema_file.yaml" - } - logger = Logger() - dataobj = DataObject() - histogram_plot = Histogram(config, logger, dataobj) - """ - - # Get the data to plot from the data_collection - # --------------------------------------------- - varstr = config['data']['variable'] - var_cgv = varstr.split('::') - - if len(var_cgv) != 3: - logger.abort('In Histogram the variable \'var_cgv\' does not appear to ' + - 'be in the required format of collection::group::variable.') - - # Optionally get the channel to plot - channel = None - if 'channel' in config['data']: - channel = config['data'].get('channel') - - data = dataobj.get_variable_data(var_cgv[0], var_cgv[1], var_cgv[2], channel) - - # See if we need to slice data - data = slice_var_from_str(config['data'], data, logger) - - # Histogram data should be flattened - data = data.flatten() - - # Missing data should also be removed - mask = ~np.isnan(data) - data = data[mask] - - # Create declarative plotting histogram object - # -------------------------------------------- - self.plotobj = emcpy.plots.plots.Histogram(data) - - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'batch', 'emcpy', 'defaults', - 'histogram.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['type', 'schema', 'data'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) - - -# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py b/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py deleted file mode 100644 index b89ddc71..00000000 --- a/src/eva/plotting/batch/emcpy/diagnostics/horizontal_line.py +++ /dev/null @@ -1,61 +0,0 @@ -from eva.eva_path import return_eva_path -from eva.utilities.utils import get_schema, update_object -import emcpy.plots.plots -import os - - -# -------------------------------------------------------------------------------------------------- - - -class HorizontalLine(): - - """Base class for creating horizontal line plots.""" - - def __init__(self, config, logger, dataobj): - - """ - Creates a horizontal line plot based on the provided configuration. - - Args: - config (dict): A dictionary containing the configuration for the horizontal line plot. - logger (Logger): An instance of the logger for logging messages. - - This class initializes and configures a horizontal line plot based on the provided - configuration. The horizontal line plot is created using a declarative plotting library from - EMCPy (https://github.com/NOAA-EMC/emcpy). - - Example: - - :: - - config = { - "y": 0.5, - "plot_property": "property_value", - "plot_option": "option_value", - "schema": "path_to_schema_file.yaml" - } - logger = Logger() - horizontal_line_plot = HorizontalLine(config, logger) - """ - - # Get the y value to plot - # ----------------------- - yval = config['y'] - - # Create declarative plotting HorizontalLine object - # ------------------------------------------- - self.plotobj = emcpy.plots.plots.HorizontalLine(yval) - - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'batch', 'emcpy', 'defaults', - 'horizontal_line.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['type', 'schema'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) - - -# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py b/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py deleted file mode 100644 index 8c2f5175..00000000 --- a/src/eva/plotting/batch/emcpy/diagnostics/line_plot.py +++ /dev/null @@ -1,111 +0,0 @@ -from eva.eva_path import return_eva_path -from eva.utilities.config import get -from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.plots -import os -import numpy as np - - -# -------------------------------------------------------------------------------------------------- - - -class LinePlot(): - - """Base class for creating line plots.""" - - def __init__(self, config, logger, dataobj): - - """ - Creates a line plot based on the provided configuration. - - Args: - config (dict): A dictionary containing the configuration for the line plot. - logger (Logger): An instance of the logger for logging messages. - dataobj: An instance of the data object containing input data. - - This class initializes and configures a line plot based on the provided configuration. - The line plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). - - Example: - - :: - - config = { - "x": {"variable": "collection::group::variable"}, - "y": {"variable": "collection::group::variable"}, - "channel": "channel_name", - "plot_property": "property_value", - "plot_option": "option_value", - "schema": "path_to_schema_file.yaml" - } - logger = Logger() - line_plot = LinePlot(config, logger, None) - """ - - # Get the data to plot from the data_collection - # --------------------------------------------- - var0 = config['x']['variable'] - var1 = config['y']['variable'] - - var0_cgv = var0.split('::') - var1_cgv = var1.split('::') - - if len(var0_cgv) != 3: - logger.abort('In Scatter comparison the first variable \'var0\' does not appear to ' + - 'be in the required format of collection::group::variable.') - if len(var1_cgv) != 3: - logger.abort('In Scatter comparison the first variable \'var1\' does not appear to ' + - 'be in the required format of collection::group::variable.') - - # Optionally get the channel|level|datatype to plot - channel = None - if 'channel' in config: - channel = config.get('channel') - level = None - if 'level' in config: - level = config.get('level') - datatype = None - if 'datatype' in config: - datatype = config.get('datatype') - - xdata = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2], - channel, level, datatype) - ydata = dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], - channel, level, datatype) - - # see if we need to slice data - xdata = slice_var_from_str(config['x'], xdata, logger) - ydata = slice_var_from_str(config['y'], ydata, logger) - - # line plot data should be flattened - xdata = xdata.flatten() - ydata = ydata.flatten() - - # Remove NaN values to enable regression - # -------------------------------------- - mask = ~np.isnan(xdata) - xdata = xdata[mask] - ydata = ydata[mask] - - mask = ~np.isnan(ydata) - xdata = xdata[mask] - ydata = ydata[mask] - - # Create declarative plotting LinePlot object - # ------------------------------------------- - self.plotobj = emcpy.plots.plots.LinePlot(xdata, ydata) - - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'batch', 'emcpy', 'defaults', - 'line_plot.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['x', 'y', 'type', 'schema', 'channel', 'level', 'datatype'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) - - -# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py b/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py deleted file mode 100644 index 1e09057c..00000000 --- a/src/eva/plotting/batch/emcpy/diagnostics/map_gridded.py +++ /dev/null @@ -1,70 +0,0 @@ -from eva.eva_path import return_eva_path -from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.map_plots -import os - - -# -------------------------------------------------------------------------------------------------- - - -class MapGridded(): - - """Base class for creating map gridded plots.""" - - def __init__(self, config, logger, dataobj): - - """ - Creates a gridded map plot based on the provided configuration. - - Args: - config (dict): A dictionary containing the configuration for the gridded map plot. - logger (Logger): An instance of the logger for logging messages. - dataobj: An instance of the data object containing input data. - - This class initializes and configures a gridded map plot based on the provided - configuration. The gridded map plot is created using a declarative plotting library from - EMCPy (https://github.com/NOAA-EMC/emcpy). - - Example: - - :: - - config = { - "longitude": {"variable": "collection::group::variable"}, - "latitude": {"variable": "collection::group::variable"}, - "data": {"variable": "collection::group::variable"}, - "plot_property": "property_value", - "plot_option": "option_value", - "schema": "path_to_schema_file.yaml" - } - logger = Logger() - map_plot = MapGridded(config, logger, None) - """ - - # prepare data based on config - lonvar_cgv = config['longitude']['variable'].split('::') - lonvar = dataobj.get_variable_data(lonvar_cgv[0], lonvar_cgv[1], lonvar_cgv[2], None) - lonvar = slice_var_from_str(config['longitude'], lonvar, logger) - latvar_cgv = config['latitude']['variable'].split('::') - latvar = dataobj.get_variable_data(latvar_cgv[0], latvar_cgv[1], latvar_cgv[2], None) - latvar = slice_var_from_str(config['latitude'], latvar, logger) - datavar_cgv = config['data']['variable'].split('::') - datavar = dataobj.get_variable_data(datavar_cgv[0], datavar_cgv[1], datavar_cgv[2], None) - datavar = slice_var_from_str(config['data'], datavar, logger) - - # create declarative plotting MapGridded object - self.plotobj = emcpy.plots.map_plots.MapGridded(latvar, lonvar, datavar) - # get defaults from schema - layer_schema = config.get("schema", - os.path.join(return_eva_path(), - 'plotting', - 'batch', 'emcpy', 'defaults', - 'map_gridded.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['longitude', 'latitude', 'data', 'type', 'schema'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) - - -# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py deleted file mode 100644 index 1c66c7fd..00000000 --- a/src/eva/plotting/batch/emcpy/diagnostics/map_scatter.py +++ /dev/null @@ -1,98 +0,0 @@ -from eva.eva_path import return_eva_path -from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.map_plots -import os -import numpy as np - - -# -------------------------------------------------------------------------------------------------- - - -class MapScatter(): - - """Base class for creating map scatter plots.""" - - def __init__(self, config, logger, dataobj): - - """ - Creates a scatter plot on a map based on the provided configuration. - - Args: - config (dict): A dictionary containing the configuration for the scatter plot on a map. - logger (Logger): An instance of the logger for logging messages. - dataobj: An instance of the data object containing input data. - - This class initializes and configures a scatter plot on a map based on the provided - configuration. The scatter plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). - - Example: - :: - - config = { - "longitude": {"variable": "collection::group::variable"}, - "latitude": {"variable": "collection::group::variable"}, - "data": {"variable": "collection::group::variable", - "channel": "channel_name"}, - "plot_property": "property_value", - "plot_option": "option_value", - "schema": "path_to_schema_file.yaml" - } - logger = Logger() - map_scatter_plot = MapScatter(config, logger, None) - """ - - # prepare data based on config - # Optionally get the channel or level to plot - channel = None - if 'data' in config: - if 'channel' in config['data']: - channel = config['data'].get('channel') - level = None - if 'level' in config: - level = config.get('level') - - lonvar_cgv = config['longitude']['variable'].split('::') - lonvar = dataobj.get_variable_data(lonvar_cgv[0], lonvar_cgv[1], lonvar_cgv[2], None) - lonvar = slice_var_from_str(config['longitude'], lonvar, logger) - latvar_cgv = config['latitude']['variable'].split('::') - latvar = dataobj.get_variable_data(latvar_cgv[0], latvar_cgv[1], latvar_cgv[2], None) - latvar = slice_var_from_str(config['latitude'], latvar, logger) - - # scatter data should be flattened - lonvar = lonvar.flatten() - latvar = latvar.flatten() - - if 'data' in config: - datavar_cgv = config['data']['variable'].split('::') - datavar = dataobj.get_variable_data(datavar_cgv[0], datavar_cgv[1], - datavar_cgv[2], channel, level) - datavar = slice_var_from_str(config['data'], datavar, logger) - - datavar = datavar.flatten() - - # If everything is nan plotting will fail so just plot some large values - if np.isnan(datavar).all(): - datavar[np.isnan(datavar)] = 1.0e38 - else: - datavar = None - - # create declarative plotting MapScatter object - self.plotobj = emcpy.plots.map_plots.MapScatter(latvar, lonvar, datavar) - - # get defaults from schema - layer_schema = config.get("schema", - os.path.join(return_eva_path(), - 'plotting', - 'batch', 'emcpy', 'defaults', - 'map_scatter.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['longitude', 'latitude', 'data', 'type', 'schema', 'level'] - if datavar is None: - delvars.append('cmap') - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) - - -# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/scatter.py b/src/eva/plotting/batch/emcpy/diagnostics/scatter.py deleted file mode 100644 index f5e084ae..00000000 --- a/src/eva/plotting/batch/emcpy/diagnostics/scatter.py +++ /dev/null @@ -1,105 +0,0 @@ -from eva.eva_path import return_eva_path -from eva.utilities.config import get -from eva.utilities.utils import get_schema, update_object, slice_var_from_str -import emcpy.plots.plots -import os -import numpy as np - - -# -------------------------------------------------------------------------------------------------- - - -class Scatter(): - - """Base class for creating scatter plots.""" - - def __init__(self, config, logger, dataobj): - - """ - Creates a scatter plot on a map based on the provided configuration. - - Args: - config (dict): A dictionary containing the configuration for the scatter plot on a map. - logger (Logger): An instance of the logger for logging messages. - dataobj: An instance of the data object containing input data. - - This class initializes and configures a scatter plot on a map based on the provided - configuration. The scatter plot is created using a declarative plotting library from EMCPy - (https://github.com/NOAA-EMC/emcpy). - - Example: - - :: - - config = { - "longitude": {"variable": "collection::group::variable"}, - "latitude": {"variable": "collection::group::variable"}, - "data": {"variable": "collection::group::variable", - "channel": "channel_name"}, - "plot_property": "property_value", - "plot_option": "option_value", - "schema": "path_to_schema_file.yaml" - } - logger = Logger() - map_scatter_plot = MapScatter(config, logger, None) - """ - - # Get the data to plot from the data_collection - # --------------------------------------------- - var0 = config['x']['variable'] - var1 = config['y']['variable'] - - var0_cgv = var0.split('::') - var1_cgv = var1.split('::') - - if len(var0_cgv) != 3: - logger.abort('In Scatter comparison the first variable \'var0\' does not appear to ' + - 'be in the required format of collection::group::variable.') - if len(var1_cgv) != 3: - logger.abort('In Scatter comparison the first variable \'var1\' does not appear to ' + - 'be in the required format of collection::group::variable.') - - # Optionally get the channel to plot - channel = None - if 'channel' in config: - channel = config.get('channel') - - xdata = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2], channel) - xdata1 = dataobj.get_variable_data(var0_cgv[0], var0_cgv[1], var0_cgv[2]) - ydata = dataobj.get_variable_data(var1_cgv[0], var1_cgv[1], var1_cgv[2], channel) - - # see if we need to slice data - xdata = slice_var_from_str(config['x'], xdata, logger) - ydata = slice_var_from_str(config['y'], ydata, logger) - - # scatter data should be flattened - xdata = xdata.flatten() - ydata = ydata.flatten() - - # Remove NaN values to enable regression - # -------------------------------------- - mask = ~np.isnan(xdata) - xdata = xdata[mask] - ydata = ydata[mask] - - mask = ~np.isnan(ydata) - xdata = xdata[mask] - ydata = ydata[mask] - - # Create declarative plotting Scatter object - # ------------------------------------------ - self.plotobj = emcpy.plots.plots.Scatter(xdata, ydata) - - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'batch', 'emcpy', 'defaults', - 'scatter.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['x', 'y', 'type', 'schema'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) - - -# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py b/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py deleted file mode 100644 index a16c67a9..00000000 --- a/src/eva/plotting/batch/emcpy/diagnostics/vertical_line.py +++ /dev/null @@ -1,62 +0,0 @@ -from eva.eva_path import return_eva_path -from eva.utilities.utils import get_schema, update_object -import emcpy.plots.plots -import os - - -# -------------------------------------------------------------------------------------------------- - - -class VerticalLine(): - - """Base class for creating vertical line plots.""" - - def __init__(self, config, logger, dataobj): - - """ - Creates a vertical line plot based on the provided configuration. - - Args: - config (dict): A dictionary containing the configuration for the vertical line plot. - logger (Logger): An instance of the logger for logging messages. - dataobj: Not used in this context. - - This class initializes and configures a vertical line plot based on the provided - configuration. The vertical line plot is created using a declarative plotting library from - EMCPy (https://github.com/NOAA-EMC/emcpy). - - Example: - - :: - - config = { - "x": 10, - "plot_property": "property_value", - "plot_option": "option_value", - "schema": "path_to_schema_file.yaml" - } - logger = Logger() - vertical_line_plot = VerticalLine(config, logger, None) - """ - - # Get the x value to plot - # ----------------------- - xval = config['x'] - - # Create declarative plotting HorizontalLine object - # ------------------------------------------- - self.plotobj = emcpy.plots.plots.VerticalLine(xval) - - # Get defaults from schema - # ------------------------ - layer_schema = config.get('schema', os.path.join(return_eva_path(), 'plotting', - 'batch', 'emcpy', 'defaults', - 'vertical_line.yaml')) - config = get_schema(layer_schema, config, logger) - delvars = ['type', 'schema'] - for d in delvars: - config.pop(d, None) - self.plotobj = update_object(self.plotobj, config, logger) - - -# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_density.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_density.py new file mode 100644 index 00000000..e089efb0 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_density.py @@ -0,0 +1,38 @@ +import os +import pandas as pd +import hvplot.pandas +import holoviews as hv +from bokeh.models import HoverTool + +from eva.plotting.batch.base.diagnostics.density import Density + +# -------------------------------------------------------------------------------------------------- + + +class HvplotDensity(Density): + """ + Subclass of Density for generating density plots using hvplot backend. + + Attributes: + Inherits attributes from the Density class. + """ + + def configure_plot(self): + """ + Configures and generates a density plot using hvplot backend. + + Returns: + plotobj: plot object representing the generated density plot. + """ + df = pd.DataFrame() + df['data'] = self.data + color = self.config['color'] + label = self.config['label'] + + hover = HoverTool(tooltips=[('data', '$data')]) + plotobj = df.hvplot.kde(filled=True, legend='top_left', color=color, + tools=[hover], label=label, height=600, width=600) + + return plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_histogram.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_histogram.py new file mode 100644 index 00000000..83ce6c74 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_histogram.py @@ -0,0 +1,31 @@ +import pandas as pd +import hvplot.pandas + +from eva.plotting.batch.base.diagnostics.histogram import Histogram + +# -------------------------------------------------------------------------------------------------- + + +class HvplotHistogram(Histogram): + """ + Subclass of Histogram for generating histograms using hvplot. + + Attributes: + Inherits attributes from the Histogram class. + """ + def configure_plot(self): + """ + Configures and generates a histogram plot using hvplot. + + Returns: + plotobj: plot object representing the generated histogram plot. + """ + df = pd.DataFrame() + df["var"] = self.data + self.plotobj = df.hvplot.hist(bins=self.config['bins'], color=self.config['color'], + label=self.config['label'], height=600, width=600, + legend='top_left') + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_horizontal_line.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_horizontal_line.py new file mode 100644 index 00000000..65270942 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_horizontal_line.py @@ -0,0 +1,14 @@ +import holoviews as hv +from eva.plotting.batch.base.diagnostics.horizontal_line import HorizontalLine + +# -------------------------------------------------------------------------------------------------- + + +class HvplotHorizontalLine(HorizontalLine): + + def configure_plot(self): + + self.plotobj = hv.HLine(self.yval).opts(color='black', line_width=self.config['linewidth']) + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_line_plot.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_line_plot.py new file mode 100644 index 00000000..b6cb93b8 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_line_plot.py @@ -0,0 +1,32 @@ +import pandas as pd +import hvplot.pandas +from eva.plotting.batch.base.diagnostics.line_plot import LinePlot + +# -------------------------------------------------------------------------------------------------- + + +class HvplotLinePlot(LinePlot): + """ + Subclass of LinePlot for generating line plots using hvplot. + + Attributes: + Inherits attributes from the LinePlot class. + """ + def configure_plot(self): + """ + Configures and generates a line plot using hvplot. + + Returns: + plotobj: plot object representing the generated line plot. + """ + df = pd.DataFrame() + df['x'] = self.xdata + df['y'] = self.ydata + + line = df.hvplot.line(x='x', y='y', c=self.color, clabel=self.label) + scatter = df.hvplot.scatter(x='x', y='y').opts(color=self.color, size=5, marker='o') + self.plotobj = line * scatter + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_map_gridded.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_map_gridded.py new file mode 100644 index 00000000..03e1c860 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_map_gridded.py @@ -0,0 +1,35 @@ +import hvplot.xarray +from eva.plotting.batch.base.diagnostics.map_gridded import MapGridded + +# -------------------------------------------------------------------------------------------------- + + +class HvplotMapGridded(MapGridded): + """ + Subclass of MapGridded for generating gridded map plots using hvplot. + + Attributes: + Inherits attributes from the MapGridded class. + """ + def configure_plot(self): + """ + Configures and generates a gridded map plot using hvplot. + + Returns: + plotobj: plot object representing the generated map plot. + """ + cmap = self.config['cmap'] + x = eval(self.config['data']['slices']) + data_slice = x[0] + dataset = self.dataobj.get_data_collection(self.collection) + var = self.datavar_name + dataset = dataset.assign_coords({'lat_coord': (['lon', 'lat'], self.latvar)}) + dataset = dataset.assign_coords({'lon_coord': (['lon', 'lat'], self.lonvar)}) + ds = dataset.sel(lev=data_slice) + self.plotobj = ds.hvplot.quadmesh('lon_coord', 'lat_coord', z=self.datavar_name, + hover_cols=[self.datavar_name], cmap=cmap, + geo=True, coastline=True, xlabel='Longitude', + ylabel='Latitude', width=800, height=400) + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_map_scatter.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_map_scatter.py new file mode 100644 index 00000000..bf2f783f --- /dev/null +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_map_scatter.py @@ -0,0 +1,49 @@ +from eva.eva_path import return_eva_path +from eva.utilities.config import get +from eva.utilities.utils import get_schema, update_object +import os +import geopandas as gpd +import pandas as pd +import hvplot.pandas +import holoviews as hv + +from eva.plotting.batch.base.diagnostics.map_scatter import MapScatter + +# -------------------------------------------------------------------------------------------------- + + +class HvplotMapScatter(MapScatter): + """ + Subclass of MapScatter for generating scatter map plots using hvplot. + + Attributes: + Inherits attributes from the MapScatter class. + """ + def configure_plot(self): + """ + Configures and generates a scatter map plot using hvplot. + + Returns: + plotobj: plot object representing the generated scatter map plot. + """ + df = pd.DataFrame() + df['Latitude'] = self.latvar + df['Longitude'] = self.lonvar + label = self.config['label'] + cmap = self.config['cmap'] + marker_size = self.config['markersize'] + df[label] = self.datavar + df = df.dropna() + + gdf = gpd.GeoDataFrame( + df[label], geometry=gpd.points_from_xy(x=df.Longitude, y=df.Latitude) + ) + + self.plotobj = gdf.hvplot(global_extent=True, c=label, geo=True, + cmap=cmap, legend=True, coastline=True, + s=int(marker_size)+3, xlabel='Longitude', + ylabel='Latitude', width=800, height=400) + + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py index 71abc662..ec1cf098 100644 --- a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_scatter.py @@ -1,7 +1,3 @@ -from eva.eva_path import return_eva_path -from eva.utilities.config import get -from eva.utilities.utils import get_schema, update_object -import os import pandas as pd import hvplot.pandas import holoviews as hv @@ -13,8 +9,19 @@ class HvplotScatter(Scatter): + """ + Subclass of Scatter for generating scatter plots using hvplot. + Attributes: + Inherits attributes from the Scatter class. + """ def configure_plot(self): + """ + Configures and generates a scatter plot using hvplot. + + Returns: + plotobj: plot object representing the generated scatter plot. + """ # Save and access name of variables df = pd.DataFrame() df['xdata'] = self.xdata @@ -26,18 +33,19 @@ def configure_plot(self): # add line statistics to legend label try: plot_for_slope = df.hvplot.scatter('xdata', 'ydata') - slope = hv.Slope.from_scatter(plot_for_slope) + slope = hv.Slope.from_scatter(plot_for_slope).opts(color=color, line_width=1.5) slope_attrs = linregress(self.xdata, self.ydata) slope_expression = 'y='+f'{slope.slope:.3f}'+"x+"+f'{slope.y_intercept:.3f}' r_sq = ', r^2: ' + f'{slope_attrs.rvalue**2:.3f}' slope_label = "y="+f'{slope.slope:.3f}'+"x+"+f'{slope.y_intercept:.3f}'+r_sq - plot = df.hvplot.scatter('xdata', 'ydata', + plot = df.hvplot.scatter('xdata', 'ydata', width=600, height=600, s=size, c=color, label=label+", "+slope_label) new_plot = hv.Overlay([plot, plot]) plotobj = new_plot * slope plotobj.opts(show_legend=False) except Exception: - plot = df.hvplot.scatter('xdata', 'ydata', s=size, c=color, label=label) + plot = df.hvplot.scatter('xdata', 'ydata', s=size, c=color, label=label, + width=600, height=600) plotobj = hv.Overlay([plot, plot]) plotobj.opts(show_legend=False) diff --git a/src/eva/plotting/batch/hvplot/diagnostics/hvplot_vertical_line.py b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_vertical_line.py new file mode 100644 index 00000000..c65b4200 --- /dev/null +++ b/src/eva/plotting/batch/hvplot/diagnostics/hvplot_vertical_line.py @@ -0,0 +1,24 @@ +import holoviews as hv +from eva.plotting.batch.base.diagnostics.vertical_line import VerticalLine + +# -------------------------------------------------------------------------------------------------- + + +class HvplotVerticalLine(VerticalLine): + """ + Subclass of VerticalLine for generating vertical line plots using hvplot. + + Attributes: + Inherits attributes from the VerticalLine class. + """ + def configure_plot(self): + """ + Configures and generates a vertical line plot using hvplot. + + Returns: + plotobj: plot object representing the generated vertical line plot. + """ + self.plotobj = hv.VLine(self.xval).opts(color='black', line_width=self.config['linewidth']) + return self.plotobj + +# -------------------------------------------------------------------------------------------------- diff --git a/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py b/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py index 0c9971e7..63fb9f95 100644 --- a/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py +++ b/src/eva/plotting/batch/hvplot/plot_tools/create_plots.py @@ -1,4 +1,6 @@ from bokeh.plotting import save, output_file +import panel as pn +import holoviews import hvplot as hv import os @@ -40,6 +42,47 @@ def add_ylabel(self, ylabel, labelpad=None, **kwargs } + def add_text(self, xloc, yloc, text, transform='datacoords', **kwargs): + + if not hasattr(self, 'text'): + self.text = [] + + self.text.append({ + 'xloc': xloc, + 'yloc': yloc, + 'text': text, + 'transform': transform, + 'kwargs': kwargs + }) + + def add_colorbar(self, label=None, fontsize=12, single_cbar=False, + cbar_location=None, **kwargs): + + kwargs.setdefault('orientation', 'horizontal') + + pad = 0.15 if kwargs['orientation'] == 'horizontal' else 0.1 + fraction = 0.065 if kwargs['orientation'] == 'horizontal' else 0.085 + + kwargs.setdefault('pad', pad) + kwargs.setdefault('fraction', fraction) + + if not cbar_location: + h_loc = [0.14, -0.1, 0.8, 0.04] + v_loc = [1.02, 0.12, 0.04, 0.8] + cbar_location = h_loc if kwargs['orientation'] == 'horizontal' else v_loc + + self.colorbar = { + 'label': label, + 'fontsize': fontsize, + 'single_cbar': single_cbar, + 'cbar_loc': cbar_location, + 'kwargs': kwargs + } + + def add_map_features(self, feature_list=['coastline']): + + self.map_features = feature_list + class CreateFigure: @@ -49,16 +92,46 @@ def __init__(self, nrows=1, ncols=1, figsize=(8, 6)): self.figsize = figsize self.plot_list = [] self.fig = None + self.subplot_row = None def create_figure(self): - # Needs work, need to combine all the layers - # and figure out how subplots will work - self.fig = self.plot_list[0].plot_layers[0] - plot_obj = self.plot_list[0] - # Add all features to the figure - for feat in vars(plot_obj).keys(): - self._plot_features(plot_obj, feat) + new_plot_list = [] + item_list = [] + for plot_obj in self.plot_list: + + base_hvplot = holoviews.Scatter([], []) + for layer in plot_obj.plot_layers: + # Combine layers if necessary + base_hvplot = base_hvplot * layer + + self.fig = base_hvplot + for feat in vars(plot_obj).keys(): + self._plot_features(plot_obj, feat) + + new_plot_list.append(self.fig) + + # Keep track of text found in plot object + if hasattr(plot_obj, 'text'): + colors_list = [text_dict['kwargs']['color'] for text_dict in plot_obj.text] + stats_list = [text_dict['text'] for text_dict in plot_obj.text] + hv_text = holoviews.Table({'Color': colors_list, 'Statistics': stats_list}, + ['Color', 'Statistics']) + hv_text.opts(width=1000) + item_list.append(hv_text) + + # Construct subplots if necessary + if len(new_plot_list) == 1: + self.fig = new_plot_list[0] + else: + # use layout to construct subplot + self.fig = holoviews.Layout(new_plot_list).cols(self.ncols) + self.fig.opts(shared_axes=False) + + # Add any text that was found in the plot_list + # combine self.fig and text list into one list + final_list = [self.fig] + item_list + self.fig = holoviews.Layout(final_list).cols(1) def add_suptitle(self, text, **kwargs): self.fig.opts(title=text) @@ -67,22 +140,18 @@ def save_figure(self, pathfile, **kwargs): pathfile_dir = os.path.dirname(pathfile) if not os.path.exists(pathfile_dir): os.makedirs(pathfile_dir) - bokeh_fig = hv.render(self.fig, backend='bokeh') - output_file(pathfile) - save(bokeh_fig) + holoviews.save(self.fig, pathfile, backend="bokeh") def close_figure(self): pass def _plot_features(self, plot_obj, feature): - feature_dict = { 'xlabel': self._plot_xlabel, 'ylabel': self._plot_ylabel, 'legend': self._plot_legend, 'grid': self._plot_grid, } - if feature in feature_dict: feature_dict[feature](vars(plot_obj)[feature])