diff --git a/metplotpy/plots/bar/bar_config.py b/metplotpy/plots/bar/bar_config.py index 844564f5..00c4e979 100644 --- a/metplotpy/plots/bar/bar_config.py +++ b/metplotpy/plots/bar/bar_config.py @@ -111,30 +111,6 @@ def __init__(self, parameters: dict) -> None: self.legend_orientation = 'h' self.show_legend = self._get_show_legend() - - def _get_plot_disp(self) -> list: - """ - Retrieve the values that determine whether to display a particular series - and convert them to bool if needed - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_config_vals = self.get_config_value('plot_disp') - plot_display_bools = [] - for val in plot_display_config_vals: - if isinstance(val, bool): - plot_display_bools.append(val) - - if isinstance(val, str): - plot_display_bools.append(val.upper() == 'TRUE') - - return self.create_list_by_series_ordering(plot_display_bools) - def _get_fcst_vars(self, index): """ Retrieve a list of the inner keys (fcst_vars) to the fcst_var_val dictionary. diff --git a/metplotpy/plots/base_plot.py b/metplotpy/plots/base_plot.py index 2253e321..e41e2e47 100644 --- a/metplotpy/plots/base_plot.py +++ b/metplotpy/plots/base_plot.py @@ -28,6 +28,17 @@ from . import constants +### +# Global matplotlib default setting overrides +### + +# set default for dashed lines to be longer and wider spaced +plt.rcParams['lines.dashed_pattern'] = [10, 10] + +# Turn off spines globally +plt.rcParams['axes.spines.top'] = False +plt.rcParams['axes.spines.right'] = False + turn_on_logging = strtobool('LOG_BASE_PLOT') # Log when Chrome is downloaded at runtime if turn_on_logging: @@ -401,10 +412,11 @@ def _add_legend(self, ax: plt.Axes, handles_and_labels=None) -> None: def _add_xaxis(self, ax: plt.Axes, fontproperties: FontProperties, label=None, grid_on=None) -> None: """ - Configures and adds x-axis to the plot + Configures and adds x-axis to the plot. Handles vertical plot by switching x and y axis. """ + is_vert = getattr(self.config_obj, 'vert_plot', False) if label is None: - label = self.config_obj.xaxis + label = self.config_obj.xaxis if not is_vert else self.config_obj.yaxis_1 if grid_on is None: grid_on = self.config_obj.grid_on @@ -412,17 +424,8 @@ def _add_xaxis(self, ax: plt.Axes, fontproperties: FontProperties, label=None, g ax.set_xlabel(label, fontproperties=fontproperties, labelpad=abs(self.config_obj.parameters['xlab_offset']) * constants.PIXELS_TO_POINTS) - if self.config_obj.indy_label: - # use the indices as tick locations - xtick_locs = np.arange(len(self.config_obj.indy_label)) - if self.config_obj.indy_vals: - # Use the actual numeric values from indy_vals as tick locations - try: - xtick_locs = [float(i) for i in self.config_obj.indy_vals] - # if they are not numeric, revert to using the indices - except ValueError: - pass - + if self.config_obj.indy_label and not is_vert: + xtick_locs = self._get_xtick_locs() ax.set_xticks(xtick_locs, self.config_obj.indy_label) ax.tick_params(axis="x", direction="in", which="both", labelrotation=self.config_obj.x_tickangle) @@ -431,23 +434,38 @@ def _add_xaxis(self, ax: plt.Axes, fontproperties: FontProperties, label=None, g linestyle='-', linewidth=self.config_obj.parameters['grid_lwd']) ax.set_axisbelow(True) - if self.config_obj.xaxis_reverse: - ax.invert_xaxis() + if not is_vert: + if len(self.config_obj.parameters['xlim']) > 0: + # TODO: support xlim_step? only used for line plots + ax.set_xlim(self.config_obj.parameters['xlim']) + elif getattr(self.config_obj, 'start_from_zero', False): + xtick_locs = self._get_xtick_locs() + if len(xtick_locs) > 0: + ax.set_xlim(min(xtick_locs), max(xtick_locs)) + + if self.config_obj.xaxis_reverse: + ax.invert_xaxis() + else: + if len(self.config_obj.parameters['ylim']) > 0: + ax.set_xlim(self.config_obj.parameters['ylim']) def _add_yaxis(self, ax: plt.Axes, fontproperties: FontProperties, label=None, grid_on=None) -> None: """ - Configures and adds y-axis to the plot + Configures and adds y-axis to the plot. Handles vertical plot by switching x and y axis. """ + is_vert = getattr(self.config_obj, 'vert_plot', False) if label is None: - label = self.config_obj.yaxis_1 + label = self.config_obj.yaxis_1 if not is_vert else self.config_obj.xaxis + if grid_on is None: grid_on = self.config_obj.grid_on + ax.set_ylabel(label, fontproperties=fontproperties, labelpad=abs(self.config_obj.parameters['ylab_offset']) * constants.PIXELS_TO_POINTS) ax.tick_params(axis="y", direction="in", which="both", labelrotation=self.config_obj.y_tickangle) # set y limits if defined in config or if min/max are provided - if len(self.config_obj.parameters['ylim']) > 0: + if not is_vert and len(self.config_obj.parameters['ylim']) > 0: ax.set_ylim(self.config_obj.parameters['ylim']) # add grid lines if requested @@ -455,6 +473,34 @@ def _add_yaxis(self, ax: plt.Axes, fontproperties: FontProperties, label=None, g ax.grid(True, which='major', axis='y', color=self.config_obj.blended_grid_col, linestyle='-', linewidth=self.config_obj.parameters['grid_lwd']) ax.set_axisbelow(True) + if not is_vert: + if len(self.config_obj.parameters['ylim']) > 0: + ax.set_ylim(self.config_obj.parameters['ylim']) + else: + if self.config_obj.indy_label: + xtick_locs = self._get_xtick_locs() + ax.set_yticks(xtick_locs, self.config_obj.indy_label) + + if getattr(self.config_obj, 'start_from_zero', False): + if len(xtick_locs) > 0: + ax.set_ylim(min(xtick_locs), max(xtick_locs)) + + if self.config_obj.xaxis_reverse: + ax.invert_yaxis() + + def _get_xtick_locs(self): + # use the indices as tick locations + xtick_locs = np.arange(len(self.config_obj.indy_label)) + if self.config_obj.indy_vals: + # Use the actual numeric values from indy_vals as tick locations + try: + xtick_locs = [float(i) for i in self.config_obj.indy_vals] + # if they are not numeric, revert to using the indices + except ValueError: + pass + + return xtick_locs + def _add_x2axis(self, ax, n_stats, fontproperties: FontProperties) -> None: """ Creates x2axis based on the properties from the config file. @@ -472,14 +518,24 @@ def _add_x2axis(self, ax, n_stats, fontproperties: FontProperties) -> None: if not self.config_obj.show_nstats: return - ax_top = ax.secondary_xaxis('top') - ax_top.set_xlabel('NStats', fontproperties=fontproperties, - labelpad=abs(self.config_obj.parameters['x2lab_offset']) * constants.PIXELS_TO_POINTS) - current_locs = ax.get_xticks() - ax_top.set_xticks(current_locs, n_stats, size=self.config_obj.x2_tickfont_size) + label_args = { + 'fontproperties': fontproperties, + 'labelpad': abs(self.config_obj.parameters['x2lab_offset']) * constants.PIXELS_TO_POINTS, + } + + if not self.config_obj.vert_plot: + ax_top = ax.secondary_xaxis('top') + ax_top.set_xlabel('NStats', **label_args) + current_locs = ax.get_xticks() + ax_top.set_xticks(current_locs, n_stats, size=self.config_obj.x2_tickfont_size) - # this doesn't appear to be working to add ticks at the top - ax_top.tick_params(axis="x", direction="in", labelrotation=self.config_obj.x2_tickangle) + # this doesn't appear to be working to add ticks at the top + ax_top.tick_params(axis="x", direction="in", labelrotation=self.config_obj.x2_tickangle) + else: + ax_right = ax.secondary_yaxis('right') + ax_right.set_ylabel('NStats', **label_args) + current_locs = ax.get_yticks() + ax_right.set_yticks(current_locs, n_stats, size=self.config_obj.x2_tickfont_size) def _add_y2axis(self, ax: plt.Axes, fontproperties: Union[FontProperties, None]): """ @@ -495,6 +551,19 @@ def _add_y2axis(self, ax: plt.Axes, fontproperties: Union[FontProperties, None]) return ax_right + def _sync_yaxes(self, ax, ax2, yaxis_min: Union[float, None], yaxis_max: Union[float, None]): + if not self.config_obj.sync_yaxes or self.config_obj.vert_plot: + return + + # set y limits if defined in config or if min/max are provided + if len(self.config_obj.parameters['ylim']) > 0: + yaxis_min = self.config_obj.parameters['ylim'][0] + yaxis_max = self.config_obj.parameters['ylim'][1] + + if yaxis_min is not None and yaxis_max is not None: + ax.set_ylim(yaxis_min, yaxis_max) + ax2.set_ylim(yaxis_min, yaxis_max) + def _add_lines(self, ax: plt.Axes, config_obj: Config, x_points_index: Union[list, None] = None) -> None: """Adds custom horizontal and/or vertical line to the plot. All line's metadata is in the config_obj.lines @@ -539,7 +608,10 @@ def _add_lines(self, ax: plt.Axes, config_obj: Config, x_points_index: Union[lis self.logger.warning(msg) print(f"WARNING: {msg}") - def _get_x_locs_and_width(self, x_points, index): + def _get_x_locs_and_width(self, x_points, index, stagger_scale=None): + if stagger_scale is None: + stagger_scale = constants.MPL_DEFAULT_BAR_WIDTH + try: # Attempt to convert x_points to floats (handles numeric indy_vals) # Threshold values (e.g., ">5.0") will raise a ValueError/TypeError @@ -565,7 +637,7 @@ def _get_x_locs_and_width(self, x_points, index): n = max(n_visible_series, 1) # Scale width and offset by min_spacing to ensure bars fit within the numeric gaps - width = (min_spacing * constants.MPL_DEFAULT_BAR_WIDTH) / n + width = (min_spacing * stagger_scale) / n offset = (index - (n - 1) / 2.0) * width x_locs = base + offset return x_locs, width diff --git a/metplotpy/plots/box/box.py b/metplotpy/plots/box/box.py index 0968938e..4575486f 100644 --- a/metplotpy/plots/box/box.py +++ b/metplotpy/plots/box/box.py @@ -200,19 +200,6 @@ def _add_custom_lines(self, ax): if len(self.series_list) > 0: self._add_lines(ax, self.config_obj, self.config_obj.indy_vals) - def _sync_yaxes(self, ax, ax2, yaxis_min: Union[float, None], yaxis_max: Union[float, None]): - if not self.config_obj.sync_yaxes: - return - - # set y limits if defined in config or if min/max are provided - if len(self.config_obj.parameters['ylim']) > 0: - yaxis_min = self.config_obj.parameters['ylim'][0] - yaxis_max = self.config_obj.parameters['ylim'][1] - - if yaxis_min is not None and yaxis_max is not None: - ax.set_ylim(yaxis_min, yaxis_max) - ax2.set_ylim(yaxis_min, yaxis_max) - def _draw_series(self, ax: plt.Axes, ax2, series: BoxSeries, idx: int): """ Draws the boxes on the plot diff --git a/metplotpy/plots/box/box_config.py b/metplotpy/plots/box/box_config.py index 6591de0c..63a2cb36 100644 --- a/metplotpy/plots/box/box_config.py +++ b/metplotpy/plots/box/box_config.py @@ -155,29 +155,6 @@ def __init__(self, parameters: dict) -> None: self.showfliers = False self.boxpoints = False - def _get_plot_disp(self) -> list: - """ - Retrieve the values that determine whether to display a particular series - and convert them to bool if needed - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_config_vals = self.get_config_value('plot_disp') - plot_display_bools = [] - for val in plot_display_config_vals: - if isinstance(val, bool): - plot_display_bools.append(val) - - if isinstance(val, str): - plot_display_bools.append(val.upper() == 'TRUE') - - return self.create_list_by_series_ordering(plot_display_bools) - def _get_fcst_vars(self, index): """ Retrieve a list of the inner keys (fcst_vars) to the fcst_var_val dictionary. diff --git a/metplotpy/plots/config.py b/metplotpy/plots/config.py index 67c84816..e6a8f9c5 100644 --- a/metplotpy/plots/config.py +++ b/metplotpy/plots/config.py @@ -47,6 +47,7 @@ def __init__(self, parameters): self.title_color = constants.DEFAULT_TITLE_COLOR self.xaxis = self.get_config_value('xaxis') self.xaxis_reverse = False + self.vert_plot = False self.yaxis_1 = self.get_config_value('yaxis_1') self.yaxis_2 = self.get_config_value('yaxis_2') self.sync_yaxes = False @@ -744,6 +745,29 @@ def _get_plot_resolution(self) -> int: # dpi used by matplotlib return dpi + def _get_plot_disp(self) -> list: + """ + Retrieve the values that determine whether to display a particular series + and convert them to bool if needed + + Args: + + Returns: + A list of boolean values indicating whether or not to + display the corresponding series + """ + + plot_display_config_vals = self.get_config_value('plot_disp') + plot_display_bools = [] + for val in plot_display_config_vals: + if isinstance(val, bool): + plot_display_bools.append(val) + + if isinstance(val, str): + plot_display_bools.append(val.upper() == 'TRUE') + + return self.create_list_by_series_ordering(plot_display_bools) + def _convert_units_to_inches(self, value, units): units_lower = units.lower() if units_lower == 'mm': diff --git a/metplotpy/plots/contour/contour.py b/metplotpy/plots/contour/contour.py index 0d400eba..9c6e2d00 100644 --- a/metplotpy/plots/contour/contour.py +++ b/metplotpy/plots/contour/contour.py @@ -18,15 +18,15 @@ import re import csv +from typing import Union + import pandas as pd -import plotly.graph_objects as go -from plotly.subplots import make_subplots -from plotly.graph_objects import Figure +from matplotlib import pyplot as plt +from matplotlib.colors import ListedColormap -from metplotpy.plots.constants_plotly import PLOTLY_PAPER_BGCOOR -from metplotpy.plots.base_plot_plotly import BasePlot -from metplotpy.plots import util_plotly as util +from metplotpy.plots.base_plot import BasePlot +from metplotpy.plots import util from metplotpy.plots.contour.contour_config import ContourConfig from metplotpy.plots.contour.contour_series import ContourSeries from metplotpy.plots.series import Series @@ -35,8 +35,7 @@ class Contour(BasePlot): - """ Generates a Plotly contour plot - """ + """Generates a contour plot""" defaults_name = 'contour_defaults.yaml' @@ -62,21 +61,7 @@ def __init__(self, parameters: dict) -> None: self.logger.info(f"Start contour plot: {datetime.now()}") # Check that we have all the necessary settings for each series - self.logger.info("Consistency checking of config settings for colors,legends, etc.") - is_config_consistent = self.config_obj._config_consistency_check() - if not is_config_consistent: - self.logger.error("ValueError: The number of series defined by " - "series_val_1 is inconsistent with the number of " - "settings required for describing each series. " - "Please check the number of your configuration" - " file's plot_disp, series_order, user_legend," - " colors settings. ") - raise ValueError("The number of series defined by series_val_1 is" - " inconsistent with the number of settings" - " required for describing each series. Please check" - " the number of your configuration file's " - " plot_disp, series_order, user_legend," - " colors settings.") + self.config_obj.config_consistency_check() # Read in input data, location specified in config file self.logger.info(f"Begin reading input data: {datetime.now()}") @@ -89,25 +74,14 @@ def __init__(self, parameters: dict) -> None: self.input_df = calc_util.perform_event_equalization(self.parameters, self.input_df) self.logger.info(f"Event equalization complete: {datetime.now()}") - # Create a list of series objects. - # Each series object contains all the necessary information for plotting, - # such as - # line width, and criteria needed to subset the input dataframe. self.series_list = self._create_series(self.input_df) - # create figure - # pylint:disable=assignment-from-no-return - # Need to have a self.figure that we can pass along to - # the methods in base_plot.py (BasePlot class methods) to - # create binary versions of the plot. self._create_figure() def __repr__(self): - """ Implement repr which can be useful for debugging this - class. - """ + """Implement repr which can be useful for debugging this class.""" - return f'Counture({self.parameters!r})' + return f'Countur({self.parameters!r})' def _read_input_data(self): """ @@ -140,7 +114,6 @@ def _create_series(self, input_data): self.logger.info(f"Generating series objects: {datetime.now()}") # add series for y1 axis - num_series_y1 = len(self.config_obj.get_series_y()) for i, name in enumerate(self.config_obj.get_series_y()): series_obj = ContourSeries(self.config_obj, i, input_data, series_list, name) series_list.append(series_obj) @@ -158,50 +131,60 @@ def _create_figure(self): self.logger.info(f"Creating the figure: {datetime.now()}") # create and draw the plot - self.figure = self._create_layout() - self._add_xaxis() - self._add_yaxis() - self._add_legend() + _, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) - for series in self.series_list: + wts_size_styles = self.get_weights_size_styles() - # Don't generate the plot for this series if - # it isn't requested (as set in the config file) - if series.plot_disp: - self._draw_series(series) + self._add_title(ax, wts_size_styles['title']) + self._add_caption(plt, wts_size_styles['caption']) - x_points_index = list(range(0, len(self.config_obj.indy_vals))) - ordered_indy_label = self.config_obj.create_list_by_plot_val_ordering(self.config_obj.indy_label) + self._add_series(ax) + + xlab_style = wts_size_styles['xlab'] if not self.config_obj.vert_plot else wts_size_styles['ylab'] + ylab_style = wts_size_styles['ylab'] if not self.config_obj.vert_plot else wts_size_styles['xlab'] + self._add_xaxis(ax, xlab_style, grid_on=False) + self._add_yaxis(ax, ylab_style, grid_on=False) + + plt.tight_layout() + + self.logger.info(f"Figure creating complete: {datetime.now()}") + + def _add_series(self, ax): - # display only 5 tick labels on teh x-axis if + # display only 5 tick labels on the x-axis if # - it is a date and # - the size of labels is more than 5 and - # - user did not provide custom labels (the x values and labels array are the same) + # - user did not provide custom labels (the x values and labels array are the same) - if self.config_obj.reverse_x is True: + x_points_index = list(range(0, len(self.config_obj.indy_vals))) + ordered_indy_label = self.config_obj.create_list_by_plot_val_ordering(self.config_obj.indy_label) + + if self.config_obj.xaxis_reverse: ordered_indy_label.reverse() - if self.config_obj.indy_var in ['fcst_init_beg', 'fcst_valid_beg'] \ - and len(self.config_obj.indy_vals) > 5 \ - and ordered_indy_label == self.series_list[0].series_points['x']: + if (self.config_obj.indy_var in ['fcst_init_beg', 'fcst_valid_beg'] + and len(self.config_obj.indy_vals) > 5 + and ordered_indy_label == self.series_list[0].series_points['x']): step = int(len(self.config_obj.indy_vals) / 5) ordered_indy_label_new = [''] * len(self.config_obj.indy_vals) for i in range(0, len(ordered_indy_label), step): ordered_indy_label_new[i] = ordered_indy_label[i] ordered_indy_label = ordered_indy_label_new - self.figure.update_layout( - xaxis={ - 'tickmode': 'array', - 'tickvals': x_points_index, - 'ticktext': ordered_indy_label, - 'type': 'category', - 'ticks': "outside" - } - ) - self.logger.info(f"Figure creating complete: {datetime.now()}") + self.config_obj.indy_label = ordered_indy_label + self.config_obj.indy_vals = x_points_index + + # add series points + for series in self.series_list: + + # Don't generate the plot for this series if + # it isn't requested (as set in the config file) + if not series.plot_disp: + continue + + self._draw_series(ax, series) - def _draw_series(self, series: Series) -> None: + def _draw_series(self, ax, series: Series) -> None: """ Draws the data @@ -209,180 +192,49 @@ def _draw_series(self, series: Series) -> None: """ self.logger.info(f"Drawing the data: {datetime.now()}") line_width = self.config_obj.linewidth_list[series.idx] - if self.config_obj.add_contour_overlay is False: + if not self.config_obj.add_contour_overlay: line_width = 0 - # apply y axis limits - if len(self.config_obj.parameters['ylim']) > 0: - zmin = self.config_obj.parameters['ylim'][0] - zmax = self.config_obj.parameters['ylim'][1] - zauto = False - else: - zmin = None - zmax = None - zauto = True - - self.figure.add_trace( - go.Contour( - z=series.series_points['z'], - x=series.series_points['x'], - y=series.series_points['y'], - showscale=self.config_obj.add_color_bar, - ncontours=self.config_obj.contour_intervals, - line={'color': self.config_obj.colors_list[series.idx], - 'width': line_width, - 'dash': self.config_obj.linestyles_list[series.idx], - 'smoothing': 0}, - contours={ - # 'size': 10, - 'showlabels': True, - 'labelfont': { # label font properties - 'size': 10, - 'color': self.config_obj.colors_list[series.idx] - } - }, - colorscale=self.config_obj.color_palette, - zmin=zmin, - zmax=zmax, - zauto=zauto - ) - ) - self.logger.info(f"Finished drawing data: {datetime.now()}") - - def _create_layout(self) -> Figure: - """ - Creates a new layout based on the properties from the config file - including plots size, annotation and title - - :return: Figure object - """ - # create annotation - annotation = [ - {'text': util.apply_weight_style(self.config_obj.parameters['plot_caption'], - self.config_obj.parameters['caption_weight']), - 'align': 'left', - 'showarrow': False, - 'xref': 'paper', - 'yref': 'paper', - 'x': self.config_obj.parameters['caption_align'], - 'y': self.config_obj.caption_offset, - 'font': { - 'size': self.config_obj.caption_size, - 'color': self.config_obj.parameters['caption_col'] - } - }] - # create title - title = {'text': util.apply_weight_style(self.config_obj.title, - self.config_obj.parameters['title_weight']), - 'font': { - 'size': self.config_obj.title_font_size, - }, - 'y': self.config_obj.title_offset, - 'x': self.config_obj.parameters['title_align'], - 'xanchor': 'center', - 'xref': 'paper' - } - - # create a layout and allow y2 axis - fig = make_subplots(specs=[[{"secondary_y": self.allow_secondary_y}]]) - - # add size, annotation, title - fig.update_layout( - width=self.config_obj.plot_width, - height=self.config_obj.plot_height, - margin=self.config_obj.plot_margins, - paper_bgcolor=PLOTLY_PAPER_BGCOOR, - annotations=annotation, - title=title, - plot_bgcolor=PLOTLY_PAPER_BGCOOR + ylim = self.config_obj.parameters.get('ylim', []) + z_range = {'vmin': ylim[0], 'vmax': ylim[1]} if len(ylim) > 0 else {'vmin': None, + 'vmax': None} + + # add filled contours + contour_filled = ax.contourf( + series.series_points['x'], + series.series_points['y'], + series.series_points['z'], + levels=self.config_obj.contour_intervals, + cmap=ListedColormap(self.config_obj.color_palette), + **z_range ) - return fig - def _add_xaxis(self) -> None: - """ - Configures and adds x-axis to the plot - """ - self.figure.update_xaxes(title_text=self.config_obj.xaxis, - showgrid=False, - zeroline=False, - automargin=True, - title_font={ - 'size': self.config_obj.x_title_font_size - }, - title_standoff=abs(self.config_obj.parameters['xlab_offset']), - tickangle=self.config_obj.x_tickangle, - tickfont={'size': self.config_obj.x_tickfont_size} - ) - - def _add_yaxis(self) -> None: - """ - Configures and adds y-axis to the plot - """ - self.figure.update_yaxes(title_text= - util.apply_weight_style(self.config_obj.yaxis_1, - self.config_obj.parameters['ylab_weight']), - secondary_y=False, - showgrid=False, - zeroline=False, - automargin=True, - title_font={ - 'size': self.config_obj.y_title_font_size - }, - title_standoff=abs(self.config_obj.parameters['ylab_offset']), - tickangle=self.config_obj.y_tickangle, - tickfont={'size': self.config_obj.y_tickfont_size} - ) - - def _add_legend(self) -> None: - """ - Creates a plot legend based on the properties from the config file - and attaches it to the initial Figure - """ - self.figure.update_layout(legend={'x': self.config_obj.bbox_x, - 'y': self.config_obj.bbox_y, - 'xanchor': 'center', - 'yanchor': 'top', - 'bordercolor': self.config_obj.legend_border_color, - 'borderwidth': self.config_obj.legend_border_width, - 'orientation': self.config_obj.legend_orientation, - 'font': { - 'size': self.config_obj.legend_size, - 'color': "black" - } - }) - - def remove_file(self): - """ - Removes previously made image file . Invoked by the parent class before self.output_file - attribute can be created, but overridden here. - """ - - super().remove_file() - self._remove_html() - - def _remove_html(self) -> None: - """ - Removes previously made HTML file. - """ - - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" + # add lines + if line_width > 0: + contour_lines = ax.contour( + series.series_points['x'], + series.series_points['y'], + series.series_points['z'], + levels=self.config_obj.contour_intervals, + colors=self.config_obj.colors_list[series.idx], + linewidths=line_width, + linestyles=self.config_obj.linestyles_list[series.idx], + **z_range + ) - # remove the old file if it exist - if os.path.exists(html_name): - os.remove(html_name) + # add line labels + ax.clabel( + contour_lines, + inline=True, + fontsize=10, + colors=self.config_obj.colors_list[series.idx] + ) - def write_html(self) -> None: - """ - Is needed - creates and saves the html representation of the plot WITHOUT Plotly.js - """ - if self.config_obj.create_html is True: - # construct the file name from plot_filename - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" + # add color bar + if self.config_obj.add_color_bar: + plt.colorbar(contour_filled, ax=ax) - # save html - self.figure.write_html(html_name, include_plotlyjs=False) + self.logger.info(f"Finished drawing data: {datetime.now()}") def write_output_file(self) -> None: """ @@ -423,7 +275,7 @@ def write_output_file(self) -> None: writer.writerows(series.series_points['z']) file.writelines('\n') file.writelines('\n') - file.close() + self.logger.info(f"Finished writing output file: {datetime.now()}") diff --git a/metplotpy/plots/contour/contour_config.py b/metplotpy/plots/contour/contour_config.py index 4336c1e3..a34361e5 100644 --- a/metplotpy/plots/contour/contour_config.py +++ b/metplotpy/plots/contour/contour_config.py @@ -15,9 +15,9 @@ """ __author__ = 'Tatiana Burek' -from ..config_plotly import Config -from .. import constants_plotly as constants -from .. import util_plotly as util +from ..config import Config +from .. import constants +from .. import util import metcalcpy.util.utils as utils @@ -45,16 +45,16 @@ def __init__(self, parameters: dict) -> None: self.log_filename = self.get_config_value('log_filename') # plot parameters - self.plot_width = self.calculate_plot_dimension('plot_width', 'pixels') - self.plot_height = self.calculate_plot_dimension('plot_height', 'pixels') - self.plot_margins = dict(l=0, - r=self.parameters['mar'][3] + 20, - t=self.parameters['mar'][2] + 80, - b=self.parameters['mar'][0] + 80, - pad=5 - ) + self.plot_width = self.calculate_plot_dimension('plot_width') + self.plot_height = self.calculate_plot_dimension('plot_height') + self.plot_margins = { + 'l': 0, + 'r': self.parameters['mar'][3] + 20, + 't': self.parameters['mar'][2] + 80, + 'b': self.parameters['mar'][0] + 80, + 'pad': 5, + } self.dump_points_1 = self._get_bool('dump_points_1') - self.create_html = self._get_bool('create_html') self.plot_stat = self._get_plot_stat() ############################################## @@ -66,7 +66,7 @@ def __init__(self, parameters: dict) -> None: ############################################## # title parameters self.title_font_size = self.parameters['title_size'] * constants.DEFAULT_TITLE_FONT_SIZE - self.title_offset = self.parameters['title_offset'] * constants.DEFAULT_TITLE_OFFSET + self.title_offset = 1.0 + abs(self.parameters['title_offset']) * constants.DEFAULT_TITLE_OFFSET self.y_title_font_size = self.parameters['ylab_size'] + constants.DEFAULT_TITLE_FONTSIZE ############################################## @@ -84,7 +84,6 @@ def __init__(self, parameters: dict) -> None: if self.x_tickangle in constants.XAXIS_ORIENTATION.keys(): self.x_tickangle = constants.XAXIS_ORIENTATION[self.x_tickangle] self.x_tickfont_size = self.parameters['xtlab_size'] + constants.DEFAULT_TITLE_FONTSIZE - self.xaxis = util.apply_weight_style(self.xaxis, self.parameters['xlab_weight']) ############################################## @@ -121,8 +120,8 @@ def __init__(self, parameters: dict) -> None: self.contour_intervals = self.get_config_value('contour_intervals') self.color_palette = self._get_colorscale() self.add_color_bar = self._get_bool('add_color_bar') - self.reverse_x = self._get_bool('reverse_x') - self.reverse_y = self._get_bool('reverse_y') + self.xaxis_reverse = self._get_bool('reverse_x') or self._get_bool('xaxis_reverse') + self.yaxis_reverse = self._get_bool('reverse_y') or self._get_bool('yaxis_reverse') self.add_contour_overlay = self._get_bool('add_contour_overlay') def _get_colorscale(self): @@ -135,33 +134,10 @@ def _get_colorscale(self): """ color_palette = self.get_config_value('color_palette') if color_palette not in util.COLORSCALES.keys(): - print(f'WARNING: Color pallet {color_palette} doesn\'t supported. Using default pallet') + print(f"WARNING: Color pallet {color_palette} doesn't supported. Using default pallet") color_palette = 'green_red' return util.COLORSCALES[color_palette] - def _get_plot_disp(self) -> list: - """ - Retrieve the values that determine whether to display a particular series - and convert them to bool if needed - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_config_vals = self.get_config_value('plot_disp') - plot_display_bools = [] - for val in plot_display_config_vals: - if isinstance(val, bool): - plot_display_bools.append(val) - - if isinstance(val, str): - plot_display_bools.append(val.upper() == 'TRUE') - - return self.create_list_by_series_ordering(plot_display_bools) - def _get_fcst_vars(self, index): """ Retrieve a list of the inner keys (fcst_vars) to the fcst_var_val dictionary. @@ -188,56 +164,22 @@ def _get_fcst_vars(self, index): return fcst_var_val_dict - def _get_linestyles(self) -> list: - """ - Retrieve all the line styles. Convert line style names from - the config file into plotly python's line style names. - - Args: - - Returns: - line_styles: a list of the plotly line styles - """ - line_styles = self.get_config_value('series_line_style') - line_style_list = [] - for line_style in line_styles: - if line_style in constants.LINE_STYLE_TO_PLOTLY_DASH.keys(): - line_style_list.append(constants.LINE_STYLE_TO_PLOTLY_DASH[line_style]) - else: - line_style_list.append(None) - return self.create_list_by_series_ordering(line_style_list) - - def _config_consistency_check(self) -> bool: - """ - Checks that the number of settings defined for plot_ci, - plot_disp, series_order, user_legend colors, and series_symbols - are consistent. - - Args: - - Returns: - True if the number of settings for each of the above - settings is consistent with the number of - series (as defined by the cross product of the model - and vx_mask defined in the series_val_1 setting) + def config_consistency_check(self) -> None: + """Checks that the number of settings are consistent with number of series. + @raises ValueError if any of settings are inconsistent with the + number of series (as defined by the cross product of the model + and vx_mask defined in the series_val_1 setting) """ - # Determine the number of series based on the number of - # permutations from the series_var setting in the - # config file - - # Numbers of values for other settings for series - num_plot_disp = len(self.plot_disp) - num_series_ord = len(self.series_ordering) - num_legends = len(self.user_legends) - num_line_widths = len(self.linewidth_list) - num_linestyles = len(self.linestyles_list) - status = False - - if 1 == num_plot_disp == num_series_ord \ - == num_legends == num_line_widths == num_linestyles: - status = True - return status + lists_to_check = { + "plot_disp": self.plot_disp, + "series_ordering": self.series_ordering, + "colors_list": self.colors_list, + "user_legends": self.user_legends, + "linewidth_list": self.linewidth_list, + "linestyles_list": self.linestyles_list, + } + self._config_compare_lists_to_num_series(lists_to_check) def _get_user_legends(self, legend_label_type: str = '') -> list: """ @@ -275,8 +217,8 @@ def get_series_y(self) -> list: """ all_fields_values = {} - if self._get_fcst_vars(1): - all_fields_values['fcst_var'] = list(self._get_fcst_vars(1).keys()) + if self.get_fcst_vars_keys(1): + all_fields_values['fcst_var'] = self.get_fcst_vars_keys(1) stat_name = self.get_config_value('list_stat_1') if stat_name is not None: diff --git a/metplotpy/plots/contour/contour_series.py b/metplotpy/plots/contour/contour_series.py index 5f55acc6..a86651e0 100644 --- a/metplotpy/plots/contour/contour_series.py +++ b/metplotpy/plots/contour/contour_series.py @@ -18,7 +18,7 @@ import numpy as np import warnings -import metplotpy.plots.util_plotly as util +import metplotpy.plots.util as util from ..series import Series @@ -100,11 +100,11 @@ def _create_series_points(self) -> dict: self.logger.info(f"Creating the series points: {datetime.now()}") y_real = self.config.indy_vals.copy() - if self.config.reverse_x is True: + if self.config.xaxis_reverse: y_real.reverse() x_real = self.config.series_vals_1[0].copy() - if self.config.reverse_y is True: + if self.config.yaxis_reverse: x_real.reverse() z = [[None for i in range(len(y_real))] for j in range(len(x_real))] diff --git a/metplotpy/plots/eclv/eclv.py b/metplotpy/plots/eclv/eclv.py index 7e789dec..0c1e604a 100644 --- a/metplotpy/plots/eclv/eclv.py +++ b/metplotpy/plots/eclv/eclv.py @@ -18,18 +18,17 @@ from operator import add from typing import Union import itertools +from datetime import datetime -import plotly.graph_objects as go +from matplotlib import pyplot as plt -from datetime import datetime from metcalcpy.event_equalize import event_equalize -from metplotpy.plots.base_plot_plotly import BasePlot -from metplotpy.plots.constants_plotly import PLOTLY_AXIS_LINE_COLOR, PLOTLY_AXIS_LINE_WIDTH +from metplotpy.plots.base_plot import BasePlot from metplotpy.plots.eclv.eclv_config import EclvConfig from metplotpy.plots.eclv.eclv_series import EclvSeries from metplotpy.plots.line.line import Line -from metplotpy.plots import util_plotly as util +from metplotpy.plots import util from metplotpy.plots.series import Series @@ -65,24 +64,7 @@ def __init__(self, parameters: dict) -> None: self.logger.info(f"Start eclv plot: {datetime.now()}") # Check that we have all the necessary settings for each series - is_config_consistent = self.config_obj._config_consistency_check() - if not is_config_consistent: - self.logger.error("ValueError: The number of series defined by " - "series_val_1 is " - "inconsistent with the number of settings " - "required for" - " describing each series. Please check the number " - "of" - " your configuration file's plot_i, plot_disp, " - "series_order, user_legend, colors and " - f"series_symbols settings. {datetime.now()}") - - raise ValueError("The number of series defined by series_val_1 is" - " inconsistent with the number of settings" - " required for describing each series. Please check" - " the number of your configuration file's plot_i," - " plot_disp, series_order, user_legend," - " colors, and series_symbols settings.") + self.config_obj.config_consistency_check() # Read in input data, location specified in config file self.logger.info(f"Begin reading input data: {datetime.now()}") @@ -114,11 +96,6 @@ def __init__(self, parameters: dict) -> None: # line width, and criteria needed to subset the input dataframe. self.series_list = self._create_series(self.input_df) - # create figure - # pylint:disable=assignment-from-no-return - # Need to have a self.figure that we can pass along to - # the methods in base_plot.py (BasePlot class methods) to - # create binary versions of the plot. self.logger.info(f"Begin creating the figure: {datetime.now()}") self._create_figure() self.logger.info(f"End creating the figure: {datetime.now()}") @@ -172,11 +149,45 @@ def _create_figure(self): Create a eclv plot from defaults and custom parameters """ self.logger.info(f"Begin creating the figure: {datetime.now()}") + + # some x points could be very close to each other and the x-axis ticktext is + # bunched up do not print the ticktext for the first points by creating the + # custom array of x values + for ind, val in enumerate(self.series_list[0].series_points[0]['x_pnt']): + var_round = round(val, 2) + if ind != 0 and var_round < 0.06: + self.x_axis_ticktext.append('') + else: + self.x_axis_ticktext.append(var_round) + self.config_obj.indy_label = self.x_axis_ticktext + self.config_obj.indy_vals = self.series_list[0].series_points[0]['x_pnt'] + # create and draw the plot - self.figure = self._create_layout() - self._add_xaxis() - self._add_yaxis() - self._add_legend() + _, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) + + wts_size_styles = self.get_weights_size_styles() + + self._add_title(ax, wts_size_styles['title']) + self._add_caption(plt, wts_size_styles['caption']) + n_stats = self._add_series(ax) + + self._add_xaxis(ax, wts_size_styles['xlab']) + self._add_yaxis(ax, wts_size_styles['ylab']) + + # add x2 axis + if wts_size_styles.get('x2lab'): + self._add_x2axis(ax, n_stats, wts_size_styles['x2lab']) + + self._add_legend(ax) + + # add custom lines + self._add_lines(ax, self.config_obj, self.config_obj.indy_vals) + + plt.tight_layout() + + self.logger.info(f"Finished creating the figure: {datetime.now()}") + + def _add_series(self, ax, ax2=None): # placeholder for the number of stats n_stats = [0] * len(self.series_list[0].series_points[0]['x_pnt']) @@ -187,99 +198,24 @@ def _create_figure(self): # Don't generate the plot for this series if # it isn't requested (as set in the config file) if series.plot_disp: - self._draw_series(series) + self._draw_series(ax, ax2, series) # aggregate number of stats for series_points in series.series_points: n_stats = list(map(add, n_stats, series_points['nstat'])) - # add custom lines - if len(self.series_list) > 0: - self._add_lines(self.config_obj) - - # apply y axis limits - self._yaxis_limits() + x_points = [] - # some x points could be very close to each other and the x-axis ticktext is - # bunched up do not print the ticktext for the first points by creating the - # custom array of x values - for ind, val in enumerate(self.series_list[0].series_points[0]['x_pnt']): - var_round = round(val, 2) - if ind != 0 and var_round < 0.06: - self.x_axis_ticktext.append('') + # create ticktext array similar to x-axis ticktext + for idx, val in enumerate(self.x_axis_ticktext): + if val != '': + x_points.append(n_stats[idx]) else: - self.x_axis_ticktext.append(var_round) - - self.figure.update_layout( - xaxis=dict( - tickmode='array', - tickvals=self.series_list[0].series_points[0]['x_pnt'], - ticktext=self.x_axis_ticktext, - tickangle=self.config_obj.x_tickangle - ), - yaxis=dict( - zeroline=True, - zerolinecolor=PLOTLY_AXIS_LINE_COLOR, - zerolinewidth=PLOTLY_AXIS_LINE_WIDTH - ) - ) - - # add x2 axis - self._add_x2axis(n_stats) + x_points.append('') - self.logger.info(f"Finished creating the figure: {datetime.now()}") + return x_points - def _add_x2axis(self, n_stats) -> None: - """ - Creates x2axis based on the properties from the config file - and attaches it to the initial Figure - - :param n_stats: - labels for the axis - """ - - if self.config_obj.show_nstats: - x_points = [] - - # create ticktext array simolar to x-axis ticktext - for idx, val in enumerate(self.x_axis_ticktext): - if val != '': - x_points.append(n_stats[idx]) - else: - x_points.append('') - - self.figure.update_layout(xaxis2={ - 'linecolor': PLOTLY_AXIS_LINE_COLOR, - 'linewidth': PLOTLY_AXIS_LINE_WIDTH, - 'overlaying': 'x', - 'side': 'top', - 'showgrid': False, - 'zeroline': False, - 'ticks': "inside", - 'title_font': { - 'size': self.config_obj.x2_title_font_size - }, - 'tickmode': 'array', - 'tickvals': self.series_list[0].series_points[0]['x_pnt'], - 'ticktext': x_points, - 'tickangle': self.config_obj.x2_tickangle, - 'tickfont': { - 'size': self.config_obj.x2_tickfont_size - }, - 'scaleanchor': 'x', - 'automargin': False, - 'matches': 'x', - } - ) - - # need to add an invisible line with all values = None - self.figure.add_trace( - go.Scatter( - y=[None] * len(self.series_list[0].series_points[0]['x_pnt']), - x=self.series_list[0].series_points[0]['x_pnt'], - xaxis='x2', showlegend=False) - ) - - def _draw_series(self, series: Series, + def _draw_series(self, ax: plt.Axes, ax2, series: Series, x_points_index_adj: Union[list, None] = None) -> None: """ Draws the formatted line with CIs if needed on the plot @@ -288,59 +224,22 @@ def _draw_series(self, series: Series, :param x_points_index_adj: values for adjusting x-values position """ self.logger.info(f"Begin drawing the series : {datetime.now()}") - # pct series can have mote than one line + + # pct series can have more than one line for ind, series_points in enumerate(series.series_points): y_points = series_points['dbl_med'] x_points = series_points['x_pnt'] - # show or not ci - # see if any ci values in not 0 - no_ci_up = all(v == 0 for v in series_points['dbl_up_ci']) - no_ci_lo = all(v == 0 for v in series_points['dbl_lo_ci']) - error_y_visible = True - if (no_ci_up is True and no_ci_lo is True) or self.config_obj.plot_ci[ - series.idx] == 'NONE': - error_y_visible = False - - # add the plot - self.figure.add_trace( - go.Scatter(x=x_points, - y=y_points, - showlegend=ind == 0, - mode=self.config_obj.mode[series.idx], - textposition="top right", - name=self.config_obj.user_legends[series.idx], - connectgaps=self.config_obj.con_series[series.idx] == 1, - line={'color': self.config_obj.colors_list[series.idx], - 'width': self.config_obj.linewidth_list[series.idx], - 'dash': self.config_obj.linestyles_list[series.idx]}, - marker_symbol=self.config_obj.marker_list[series.idx], - marker_color=self.config_obj.colors_list[series.idx], - marker_line_color=self.config_obj.colors_list[series.idx], - marker_size=self.config_obj.marker_size[series.idx], - error_y={'type': 'data', - 'symmetric': False, - 'array': series_points['dbl_up_ci'], - 'arrayminus': series_points['dbl_lo_ci'], - 'visible': error_y_visible, - 'thickness': self.config_obj.linewidth_list[ - series.idx]}, - hovertemplate="
".join([ - "Cost/Lost Ratio: %{customdata}", - "Economic Value: %{y}" - ]), - customdata=x_points - ), - secondary_y=False - ) - - self.logger.info(f"Finished drawing the series :" - f" {datetime.now()}") + self._draw_series_item(series, series_points, ax, ax2, x_points, y_points) + + self.logger.info(f"Finished drawing the series : {datetime.now()}") def write_output_file(self) -> None: """ saves series points to the files """ + if not self.config_obj.dump_points_1: + return self.logger.info(f"Begin writing output file: {datetime.now()}") @@ -348,40 +247,35 @@ def write_output_file(self) -> None: # (the input data file) except replace the .data # extension with .points1 extension match = re.match(r'(.*)(.data)', self.config_obj.parameters['stat_input']) + if not match: + return + + filename = match.group(1) + # replace the default path with the custom + if self.config_obj.points_path is not None: + filename = os.path.join(self.config_obj.points_path, os.path.basename(filename)) + + filename = filename + '.points1' + os.makedirs(os.path.dirname(filename), exist_ok=True) + + with open(filename, 'w') as file_handle: + writer = csv.writer(file_handle, delimiter='\t') + for series in self.series_list: + for vals_ind, vals in enumerate(series.series_points): + keys = sorted(vals.keys()) + if vals_ind == 0: + writer.writerow(keys) + else: + file_handle.writelines('\n') + for ind, dbl_med in enumerate(vals['dbl_med']): + vals['dbl_lo_ci'][ind] = dbl_med - vals['dbl_lo_ci'][ind] + vals['dbl_up_ci'][ind] = dbl_med + vals['dbl_up_ci'][ind] + writer.writerows( + zip(*[[round(num, 6) for num in vals[key]] for key in + keys])) + file_handle.writelines('\n') + file_handle.writelines('\n') - if self.config_obj.dump_points_1 is True and match: - filename = match.group(1) - # replace the default path with the custom - if self.config_obj.points_path is not None: - # get the file name - path = filename.split(os.path.sep) - if len(path) > 0: - filename = path[-1] - else: - filename = '.' + os.path.sep - filename = self.config_obj.points_path + os.path.sep + filename - - filename = filename + '.points1' - os.makedirs(os.path.dirname(filename), exist_ok=True) - - with open(filename, 'w') as file: - writer = csv.writer(file, delimiter='\t') - for series in self.series_list: - for vals_ind, vals in enumerate(series.series_points): - keys = sorted(vals.keys()) - if vals_ind == 0: - writer.writerow(keys) - else: - file.writelines('\n') - for ind, dbl_med in enumerate(vals['dbl_med']): - vals['dbl_lo_ci'][ind] = dbl_med - vals['dbl_lo_ci'][ind] - vals['dbl_up_ci'][ind] = dbl_med + vals['dbl_up_ci'][ind] - writer.writerows( - zip(*[[round(num, 6) for num in vals[key]] for key in - keys])) - file.writelines('\n') - file.writelines('\n') - file.close() self.logger.info(f"Finished writing output file: {datetime.now()}") diff --git a/metplotpy/plots/eclv/eclv_config.py b/metplotpy/plots/eclv/eclv_config.py index 82b17e43..ac806f7f 100644 --- a/metplotpy/plots/eclv/eclv_config.py +++ b/metplotpy/plots/eclv/eclv_config.py @@ -60,41 +60,26 @@ def _get_user_legends(self, legend_label_type: str = '') -> list: return self.create_list_by_series_ordering(legend_list) - def _config_consistency_check(self) -> bool: - """ - Checks that the number of settings defined for plot_ci, - plot_disp, series_order, user_legend colors, and series_symbols - are consistent. - - Args: - - Returns: - True if the number of settings for each of the above - settings is consistent with the number of - series (as defined by the cross product of the model - and vx_mask defined in the series_val_1 setting) + def config_consistency_check(self): + """Checks that the number of settings are consistent with number of series. + @raises ValueError if any of settings are inconsistent with the + number of series (as defined by the cross product of the model + and vx_mask defined in the series_val_1 setting) """ - # Determine the number of series based on the number of - # permutations from the series_var setting in the - # config file - - # Numbers of values for other settings for series - num_ci_settings = len(self.plot_ci) - num_plot_disp = len(self.plot_disp) - num_markers = len(self.marker_list) - num_series_ord = len(self.series_ordering) - num_colors = len(self.colors_list) - num_legends = len(self.user_legends) - num_line_widths = len(self.linewidth_list) - num_linestyles = len(self.linestyles_list) - status = False - - if self.num_series == num_plot_disp == \ - num_markers == num_series_ord == num_colors \ - == num_legends == num_line_widths == num_linestyles == num_ci_settings: - status = True - return status + + lists_to_check = { + "plot_ci": self.plot_ci, + "plot_disp": self.plot_disp, + "marker_list": self.marker_list, + "series_ordering": self.series_ordering, + "colors_list": self.colors_list, + "user_legends": self.user_legends, + "linewidth_list": self.linewidth_list, + "linestyles_list": self.linestyles_list, + "show_legend": self.show_legend, + } + self._config_compare_lists_to_num_series(lists_to_check) def calculate_number_of_series(self) -> int: """ diff --git a/metplotpy/plots/eclv/eclv_series.py b/metplotpy/plots/eclv/eclv_series.py index 0c34becf..585205bf 100644 --- a/metplotpy/plots/eclv/eclv_series.py +++ b/metplotpy/plots/eclv/eclv_series.py @@ -18,7 +18,7 @@ from scipy.stats import norm import metcalcpy.util.utils as utils -import metplotpy.plots.util_plotly as util +import metplotpy.plots.util as util from ..line.line_series import LineSeries diff --git a/metplotpy/plots/ens_ss/ens_ss.py b/metplotpy/plots/ens_ss/ens_ss.py index 8ae2e960..1f456114 100644 --- a/metplotpy/plots/ens_ss/ens_ss.py +++ b/metplotpy/plots/ens_ss/ens_ss.py @@ -207,7 +207,7 @@ def _create_figure(self): self.logger.info(f"Begin creating the figure: {datetime.now()}") # create and draw the plot - fig, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) + _, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) wts_size_styles = self.get_weights_size_styles() diff --git a/metplotpy/plots/ens_ss/ens_ss_config.py b/metplotpy/plots/ens_ss/ens_ss_config.py index ce776c8b..b28b8e7f 100644 --- a/metplotpy/plots/ens_ss/ens_ss_config.py +++ b/metplotpy/plots/ens_ss/ens_ss_config.py @@ -154,29 +154,6 @@ def _get_fcst_vars(self, index): return fcst_var_val_dict - def _get_plot_disp(self) -> list: - """ - Retrieve the values that determine whether to display a particular series - and convert them to bool if needed - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_config_vals = self.get_config_value('plot_disp') - plot_display_bools = [] - for val in plot_display_config_vals: - if isinstance(val, bool): - plot_display_bools.append(val) - - if isinstance(val, str): - plot_display_bools.append(val.upper() == 'TRUE') - - return self.create_list_by_series_ordering(plot_display_bools) - def config_consistency_check(self) -> None: """Checks that the number of settings defined for plot_disp, series_ordering, colors_list, user_legends, and show_legend diff --git a/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py b/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py index 60e9aaa7..5636eca4 100644 --- a/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py +++ b/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds.py @@ -18,19 +18,16 @@ import csv import pandas as pd +import numpy as np -import plotly.graph_objects as go -from plotly.subplots import make_subplots -from plotly.graph_objects import Figure +from matplotlib import pyplot as plt -from metplotpy.plots.constants_plotly import PLOTLY_AXIS_LINE_COLOR, PLOTLY_AXIS_LINE_WIDTH, \ - PLOTLY_PAPER_BGCOOR from metplotpy.plots.equivalence_testing_bounds.equivalence_testing_bounds_series \ import EquivalenceTestingBoundsSeries from metplotpy.plots.line.line_config import LineConfig from metplotpy.plots.line.line_series import LineSeries -from metplotpy.plots.base_plot_plotly import BasePlot -from metplotpy.plots import util_plotly as util +from metplotpy.plots.base_plot import BasePlot +from metplotpy.plots import util import metcalcpy.util.utils as calc_util @@ -61,16 +58,7 @@ def __init__(self, parameters: dict) -> None: self.logger.info(f"Start equivalence testing bounds: {datetime.now()}") # Check that we have all the necessary settings for each series - is_config_consistent = self.config_obj._config_consistency_check() - if not is_config_consistent: - error_msg = ("The number of series defined by series_val_1/2 and derived" - " curves is inconsistent with the number of settings" - " required for describing each series. Please check" - " the number of your configuration file's plot_i," - " plot_disp, series_order, user_legend," - " colors, show_legend and series_symbols settings.") - self.logger.error(f"ValueError: {error_msg}: {datetime.now()}") - raise ValueError(error_msg) + self.config_obj.config_consistency_check() # Read in input data, location specified in config file self.input_df = self._read_input_data() @@ -87,11 +75,6 @@ def __init__(self, parameters: dict) -> None: # line width, and criteria needed to subset the input dataframe. self.series_list = self._create_series(self.input_df) - # create figure - # pylint:disable=assignment-from-no-return - # Need to have a self.figure that we can pass along to - # the methods in met_plot.py (BasePlot class methods) to - # create binary versions of the plot. self._create_figure() def __repr__(self): @@ -184,8 +167,7 @@ def _create_series(self, input_data): # reorder series series_list = self.config_obj.create_list_by_series_ordering(series_list) - self.logger.info(f"Finished creating series object:" - f" {datetime.now()}") + self.logger.info(f"Finished creating series object: {datetime.now()}") return series_list def _create_figure(self): @@ -195,322 +177,122 @@ def _create_figure(self): self.logger.info(f"Creating the figure: {datetime.now()}") # create and draw the plot - self.figure = self._create_layout() - self._add_xaxis() - self._add_yaxis() - self._add_y2axis() - self._add_legend() + _, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) - # add series lines + wts_size_styles = self.get_weights_size_styles() + + self._add_title(ax, wts_size_styles['title']) + self._add_caption(plt, wts_size_styles['caption']) + + ax_y2 = None + if self.config_obj.parameters['list_stat_2']: + ax_y2 = self._add_y2axis(ax, wts_size_styles['y2lab']) + + handles_and_labels = self._add_series(ax, ax_y2) + + xlab_style = wts_size_styles['xlab'] if not self.config_obj.vert_plot else wts_size_styles['ylab'] + ylab_style = wts_size_styles['ylab'] if not self.config_obj.vert_plot else wts_size_styles['xlab'] + self._add_xaxis(ax, xlab_style) + self._add_yaxis(ax, ylab_style, grid_on=False) + # if y limits are not set, use -1 to 1 + if not getattr(self.config_obj, 'vert_plot', False) and not len(self.config_obj.parameters['ylim']): + ax.set_ylim(-1, 1) + + self._add_legend(ax, handles_and_labels) + + # add custom lines + self._add_lines(ax, self.config_obj, self.config_obj.indy_vals) + + plt.tight_layout() + + self.logger.info(f"Finished creating the figure: {datetime.now()}") + + def _add_series(self, ax, ax2): + handles_and_labels = [] ind = 0 for series in self.series_list: # Don't generate the plot for this series if # it isn't requested (as set in the config file) if series.plot_disp: - self._draw_series(series, ind) + handle = self._draw_series(ax, ax2, series, ind) + handles_and_labels.append((handle, handle.get_label())) ind = ind + 1 - self.logger.info(f"Finished creating the figure: {datetime.now()}") + return handles_and_labels - def _draw_series(self, series: LineSeries, ind: int) -> None: + def _draw_series(self, ax, ax2, series: LineSeries, ind: int): """ Draws the formatted ETB line on the plot :param series: EquivalenceTestingBounds series object with data and parameters - :param x_points_index_adj: values for adjusting x-values position + :param ind: index of the series """ self.logger.info(f"Start drawing the lines on the plot: {datetime.now()}") ci_tost_up = series.series_points['ci_tost'][1] ci_tost_lo = series.series_points['ci_tost'][0] dif = series.series_points['dif'] - # add the plot - self.figure.add_trace( - go.Scatter(x=[dif], - y=[ind], - showlegend=self.config_obj.show_legend[series.idx] == 1, - mode=self.config_obj.mode[series.idx], - textposition="top right", - name=self.config_obj.user_legends[series.idx], - connectgaps=self.config_obj.con_series[series.idx] == 1, - line={'color': self.config_obj.colors_list[series.idx], - 'width': self.config_obj.linewidth_list[series.idx], - 'dash': self.config_obj.linestyles_list[series.idx]}, - marker_symbol=self.config_obj.marker_list[series.idx], - marker_color=self.config_obj.colors_list[series.idx], - marker_line_color=self.config_obj.colors_list[series.idx], - marker_size=self.config_obj.marker_size[series.idx], - error_x={'type': 'data', - 'symmetric': False, - 'array': [ci_tost_up - dif], - 'arrayminus': [dif - ci_tost_lo], - 'visible': True, - 'thickness': self.config_obj.linewidth_list[series.idx], - 'width': 0 - } - ), - secondary_y=series.y_axis != 1 - ) - # add bounds lines - self.figure.add_shape(type="line", - x0=series.series_points['eqbound'][0], - y0=0, - x1=series.series_points['eqbound'][0], - y1=1, - yref='paper', - xref='x', - line={'color': self.config_obj.colors_list[series.idx], - 'width': 1, - 'dash': 'dash' - } - ) - - self.figure.add_shape(type="line", - x0=series.series_points['eqbound'][1], - y0=0, - x1=series.series_points['eqbound'][1], - y1=1, - yref='paper', - xref='x', - line={'color': self.config_obj.colors_list[series.idx], - 'width': 1, - 'dash': 'dash' - } - ) - self.logger.info(f"Finished drawing the lines on the plot: {datetime.now()}") - - def _create_layout(self) -> Figure: - """ - Creates a new layout based on the properties from the config file - including plots size, annotation and title + x_points = [dif] + y_points = [ind] - :return: Figure object - """ - # create annotation - annotation = [ - {'text': util.apply_weight_style(self.config_obj.parameters['plot_caption'], - self.config_obj.parameters[ - 'caption_weight']), - 'align': 'left', - 'showarrow': False, - 'xref': 'paper', - 'yref': 'paper', - 'x': self.config_obj.parameters['caption_align'], - 'y': self.config_obj.caption_offset, - 'font': { - 'size': self.config_obj.caption_size, - 'color': self.config_obj.parameters['caption_col'] - } - }] - # create title - title = {'text': util.apply_weight_style(self.config_obj.title, - self.config_obj.parameters[ - 'title_weight']), - 'font': { - 'size': self.config_obj.title_font_size, - }, - 'y': self.config_obj.title_offset, - 'x': self.config_obj.parameters['title_align'], - 'xanchor': 'center', - 'xref': 'paper' - } - - # create a layout and allow y2 axis - fig = make_subplots(specs=[[{"secondary_y": True}]]) - - # add size, annotation, title - fig.update_layout( - width=self.config_obj.plot_width, - height=self.config_obj.plot_height, - margin=self.config_obj.plot_margins, - paper_bgcolor=PLOTLY_PAPER_BGCOOR, - annotations=annotation, - title=title, - plot_bgcolor=PLOTLY_PAPER_BGCOOR + # add the plot + # convert to a numpy array to change None values to NaN + asymmetric_error = np.array([ + ci_tost_up - dif, + dif - ci_tost_lo + ], dtype=float).reshape(2, 1) + + # determine which y-axis to use for the plot + plot_ax = ax if series.y_axis == 1 else ax2 + + # plot error bar + plot_mode = self.config_obj.mode[series.idx] + marker = self.config_obj.marker_list[series.idx] if 'markers' in plot_mode else None + line_style = self.config_obj.linestyles_list[series.idx] if 'lines' in plot_mode else 'None' + + # Swap x and y data if vertical plot + plot_x = y_points if self.config_obj.vert_plot else x_points + plot_y = x_points if self.config_obj.vert_plot else y_points + + # Swap error bars (yerr becomes xerr) if vertical plot + x_err_val = asymmetric_error if not self.config_obj.vert_plot else None + y_err_val = asymmetric_error if self.config_obj.vert_plot else None + + plot_obj = plot_ax.errorbar( + x=plot_x, + y=plot_y, + label=self.config_obj.user_legends[series.idx], + # line style + color=self.config_obj.colors_list[series.idx], + linestyle=line_style, + linewidth=self.config_obj.linewidth_list[series.idx], + # marker style + marker=marker, + markersize=self.config_obj.marker_size[series.idx], + markeredgecolor=self.config_obj.colors_list[series.idx], + markerfacecolor=self.config_obj.colors_list[series.idx], + # error bar + xerr=x_err_val, + yerr=y_err_val, + elinewidth=self.config_obj.linewidth_list[series.idx], + capsize=5, ) - return fig - - def _add_xaxis(self) -> None: - """ - Configures and adds x-axis to the plot - """ - self.figure.update_xaxes(title_text=self.config_obj.xaxis, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=self.config_obj.grid_on, - ticks="inside", - zeroline=False, - gridwidth=self.config_obj.parameters['grid_lwd'], - gridcolor=self.config_obj.blended_grid_col, - automargin=True, - title_font={ - 'size': self.config_obj.x_title_font_size - }, - title_standoff=abs( - self.config_obj.parameters['xlab_offset']), - tickangle=self.config_obj.x_tickangle, - tickfont={'size': self.config_obj.x_tickfont_size} - ) - - def _add_yaxis(self) -> None: - """ - Configures and adds y-axis to the plot - """ - self.figure.update_yaxes(title_text= - util.apply_weight_style(self.config_obj.yaxis_1, - self.config_obj.parameters[ - 'ylab_weight']), - secondary_y=False, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=False, - zeroline=False, - ticks="", - gridwidth=self.config_obj.parameters['grid_lwd'], - gridcolor=self.config_obj.blended_grid_col, - automargin=True, - title_font={ - 'size': self.config_obj.y_title_font_size - }, - title_standoff=abs( - self.config_obj.parameters['ylab_offset']) + 15, - tickangle=self.config_obj.y_tickangle, - tickfont={'size': self.config_obj.y_tickfont_size}, - showticklabels=False - ) - - def _add_y2axis(self) -> None: - """ - Adds y2-axis if needed - """ - if self.config_obj.parameters['list_stat_2']: - self.figure.update_yaxes(title_text= - util.apply_weight_style(self.config_obj.yaxis_2, - self.config_obj.parameters[ - 'y2lab_weight']), - secondary_y=True, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=False, - zeroline=False, - ticks="inside", - title_font={ - 'size': self.config_obj.y2_title_font_size - }, - title_standoff=abs( - self.config_obj.parameters['y2lab_offset']), - tickangle=self.config_obj.y2_tickangle, - tickfont={'size': self.config_obj.y2_tickfont_size} - ) - - def _add_legend(self) -> None: - """ - Creates a plot legend based on the properties from the config file - and attaches it to the initial Figure - """ - self.figure.update_layout(legend={'x': self.config_obj.bbox_x, - 'y': self.config_obj.bbox_y, - 'xanchor': 'center', - 'yanchor': 'top', - 'bordercolor': - self.config_obj.legend_border_color, - 'borderwidth': - self.config_obj.legend_border_width, - 'orientation': - self.config_obj.legend_orientation, - 'font': { - 'size': self.config_obj.legend_size, - 'color': "black" - } - }) - - def _add_x2axis(self, n_stats) -> None: - """ - Creates x2axis based on the properties from the config file - and attaches it to the initial Figure - - :param n_stats: - labels for the axis - """ - if self.config_obj.show_nstats: - x_points_index = list(range(0, len(n_stats))) - self.figure.update_layout(xaxis2={'title_text': - util.apply_weight_style('NStats', - self.config_obj.parameters[ - 'x2lab_weight'] - ), - 'linecolor': PLOTLY_AXIS_LINE_COLOR, - 'linewidth': PLOTLY_AXIS_LINE_WIDTH, - 'overlaying': 'x', - 'side': 'top', - 'showgrid': False, - 'zeroline': False, - 'ticks': "inside", - 'title_font': { - 'size': - self.config_obj.x2_title_font_size - }, - 'title_standoff': abs( - self.config_obj.parameters[ - 'x2lab_offset'] - ), - 'tickmode': 'array', - 'tickvals': x_points_index, - 'ticktext': n_stats, - 'tickangle': self.config_obj.x2_tickangle, - 'tickfont': { - 'size': - self.config_obj.x2_tickfont_size - }, - 'scaleanchor': 'x' - } - ) - - # need to add an invisible line with all values = None - self.figure.add_trace( - go.Scatter(y=[None] * len(x_points_index), x=x_points_index, - xaxis='x2', showlegend=False) - ) - - def remove_file(self): - """ - Removes previously made image file . Invoked by the parent class before - self.output_file - attribute can be created, but overridden here. - """ - super().remove_file() - self._remove_html() - - def _remove_html(self) -> None: - """ - Removes previously made HTML file. - """ - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" - - # remove the old file if it exist - if os.path.exists(html_name): - os.remove(html_name) - - def write_html(self) -> None: - """ - Is needed - creates and saves the html representation of the plot WITHOUT - Plotly.js - """ - - self.logger.info(f"Write html file: {datetime.now()}") - - if self.config_obj.create_html is True: - # construct the file name from plot_filename - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" + # add bounds lines + x = [series.series_points['eqbound'][0], series.series_points['eqbound'][0]] + if len(self.config_obj.parameters['ylim']) > 0: + y = [self.config_obj.parameters['ylim'][0], self.config_obj.parameters['ylim'][1]] + else: + y = [-1, 1] + ax.plot(x, y, color=self.config_obj.colors_list[series.idx], linewidth=1, linestyle='--') - # save html - self.figure.write_html(html_name, include_plotlyjs=False) + x = [series.series_points['eqbound'][1], series.series_points['eqbound'][1]] + ax.plot(x, y, color=self.config_obj.colors_list[series.idx], linewidth=1, linestyle='--') - self.logger.info(f"Finished writing html file: {datetime.now()}") + self.logger.info(f"Finished drawing the lines on the plot: {datetime.now()}") + return plot_obj def write_output_file(self) -> None: """ @@ -558,7 +340,7 @@ def write_output_file(self) -> None: os.makedirs(os.path.dirname(filename), exist_ok=True) # save points - self._save_points(ci_tost_df.values.tolist(), filename) + self._save_points(ci_tost_df.to_numpy().tolist(), filename) self.logger.info(f"Finished writing the output file: {datetime.now()}") diff --git a/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds_series.py b/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds_series.py index f91aa8be..bcec550a 100644 --- a/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds_series.py +++ b/metplotpy/plots/equivalence_testing_bounds/equivalence_testing_bounds_series.py @@ -24,7 +24,7 @@ import metcalcpy.util.correlation as pg import metcalcpy.util.utils as utils -import metplotpy.plots.util_plotly as util +import metplotpy.plots.util as util from metcalcpy.sum_stat import calculate_statistic from .. import GROUP_SEPARATOR from ..line.line_series import LineSeries diff --git a/metplotpy/plots/histogram/hist_config.py b/metplotpy/plots/histogram/hist_config.py index fd1fecdb..a47042f8 100644 --- a/metplotpy/plots/histogram/hist_config.py +++ b/metplotpy/plots/histogram/hist_config.py @@ -103,29 +103,6 @@ def __init__(self, parameters: dict) -> None: self.points_path = self.get_config_value('points_path') - def _get_plot_disp(self) -> list: - """ - Retrieve the values that determine whether to display a particular ser - and convert them to bool if needed - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding ser - """ - - plot_display_config_vals = self.get_config_value('plot_disp') - plot_display_bools = [] - for val in plot_display_config_vals: - if isinstance(val, bool): - plot_display_bools.append(val) - - if isinstance(val, str): - plot_display_bools.append(val.upper() == 'TRUE') - - return self.create_list_by_series_ordering(plot_display_bools) - def get_series_y(self) -> list: """ Creates an array of ser components (excluding derived) tuples for the specified y-axis diff --git a/metplotpy/plots/line/line.py b/metplotpy/plots/line/line.py index 0cbe7fcf..25840b71 100644 --- a/metplotpy/plots/line/line.py +++ b/metplotpy/plots/line/line.py @@ -10,7 +10,7 @@ """ Class Name: line.py """ -__author__ = 'Tatiana Burek' +__author__ = 'Tatiana Burek, George McCabe' import os from datetime import datetime @@ -23,16 +23,12 @@ import numpy as np import pandas as pd -import plotly.graph_objects as go -from plotly.subplots import make_subplots -from plotly.graph_objects import Figure +from matplotlib import pyplot as plt -from metplotpy.plots.constants_plotly import PLOTLY_AXIS_LINE_COLOR, PLOTLY_AXIS_LINE_WIDTH, \ - PLOTLY_PAPER_BGCOOR from metplotpy.plots.line.line_config import LineConfig from metplotpy.plots.line.line_series import LineSeries -from metplotpy.plots.base_plot_plotly import BasePlot -from metplotpy.plots import util_plotly as util +from metplotpy.plots.base_plot import BasePlot +from metplotpy.plots import util from metplotpy.plots.series import Series import metcalcpy.util.utils as calc_util @@ -57,8 +53,6 @@ def __init__(self, parameters: dict) -> None: # init common layout super().__init__(parameters, self.defaults_name) - self.allow_secondary_y = True - # instantiate a LineConfig object, which holds all the necessary settings # from the # config file that represents the BasePlot object (Line). @@ -69,16 +63,7 @@ def __init__(self, parameters: dict) -> None: self.logger.info(f"Begin creating the line plot: {datetime.now()}") # Check that we have all the necessary settings for each series - is_config_consistent = self.config_obj._config_consistency_check() - if not is_config_consistent: - error_msg = ("The number of series defined by series_val_1/2 and derived " - "curves is inconsistent with the number of settings " - "required for describing each series. Please check " - "the number of your configuration file's plot_ci, " - "plot_disp, series_order, user_legend, " - "colors, series_symbols, and show_legend settings.") - self.logger.error(f"ValueError: {error_msg}: {datetime.now()}") - raise ValueError(error_msg) + self.config_obj.config_consistency_check() # Read in input data, location specified in config file self.input_df = self._read_input_data() @@ -96,11 +81,6 @@ def __init__(self, parameters: dict) -> None: # line width, and criteria needed to subset the input dataframe. self.series_list = self._create_series(self.input_df) - # create figure - # pylint:disable=assignment-from-no-return - # Need to have a self.figure that we can pass along to - # the methods in base_plot.py (BasePlot class methods) to - # create binary versions of the plot. self._create_figure() def __repr__(self): @@ -155,9 +135,6 @@ def _create_series(self, input_data): series_obj = LineSeries(self.config_obj, i, input_data, series_list, name) series_list.append(series_obj) - - - # add series for y2 axis num_series_y2 = len(self.config_obj.get_series_y(2)) for i, name in enumerate(self.config_obj.get_series_y(2)): @@ -218,529 +195,149 @@ def _create_figure(self) -> None: self.logger.info(f"Begin create the figure: {datetime.now()}") # create and draw the plot - self.figure = self._create_layout() - self._add_xaxis() - self._add_yaxis() - self._add_y2axis() - self._add_legend() - - # calculate stag adjustments - stag_adjustments = self._calc_stag_adjustments() - - x_points_index = list(range(0, len(self.config_obj.indy_vals))) - - # create a vertical plot if needed - self._adjust_for_vertical(x_points_index) - - # reverse xaxis if needed - if self.config_obj.xaxis_reverse is True: - if self.config_obj.vert_plot is True: - self.figure.update_yaxes(autorange="reversed") - else: - self.figure.update_xaxes(autorange="reversed") - - # placeholder for the number of stats - n_stats = [0] * len(self.config_obj.indy_vals) + _, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) - # placeholder for the min and max values for y-axis - yaxis_min = None - yaxis_max = None + wts_size_styles = self.get_weights_size_styles() - # add series lines - for series in self.series_list: + self._add_title(ax, wts_size_styles['title']) + self._add_caption(plt, wts_size_styles['caption']) - # Don't generate the plot for this series if - # it isn't requested (as set in the config file) - if series.plot_disp: + ax_y2 = None + if self.config_obj.parameters['list_stat_2']: + ax_y2 = self._add_y2axis(ax, wts_size_styles['y2lab']) - # collect min-max if we need to sync axis - if self.config_obj.sync_yaxes is True: - yaxis_min, yaxis_max = self._find_min_max(series, yaxis_min, - yaxis_max) + n_stats, yaxis_min, yaxis_max, handles_and_labels = self._add_series(ax, ax_y2) - # apply staggering offset if applicable - if stag_adjustments[series.idx] == 0: - x_points_index_adj = x_points_index - else: - x_points_index_adj = x_points_index + stag_adjustments[series.idx] + xlab_style = wts_size_styles['xlab'] if not self.config_obj.vert_plot else wts_size_styles['ylab'] + ylab_style = wts_size_styles['ylab'] if not self.config_obj.vert_plot else wts_size_styles['xlab'] + self._add_xaxis(ax, xlab_style) + self._add_yaxis(ax, ylab_style) - self._draw_series(series, x_points_index_adj) + # add x2 axis + if wts_size_styles.get('x2lab'): + self._add_x2axis(ax, n_stats, wts_size_styles['x2lab']) - # aggregate number of stats - n_stats = list(map(add, n_stats, series.series_points['nstat'])) + self._add_legend(ax, handles_and_labels) # add custom lines - self._add_lines(self.config_obj, x_points_index) + self._add_lines(ax, self.config_obj, self.config_obj.indy_vals) - # apply y axis limits - self._yaxis_limits() - self._y2axis_limits() + plt.tight_layout() # sync axis - self._sync_yaxis(yaxis_min, yaxis_max) - - # add x2 axis - self._add_x2axis(n_stats) - - # Allow plots to start from the y=0 line if set in the config file - if self.config_obj.start_from_zero is True: - self.figure.update_xaxes(range=[0, len(x_points_index) - 1]) + self._sync_yaxes(ax, ax_y2, yaxis_min, yaxis_max) self.logger.info(f"Finished creating the figure: {datetime.now()}") - def _draw_series(self, series: Series, x_points_index_adj: Union[list, None] = - None) \ - -> None: - """ - Draws the formatted line with CIs if needed on the plot + def _add_series(self,ax, ax2): + handles_and_labels = [] - :param series: Line series object with data and parameters - :param x_points_index_adj: values for adjusting x-values position - """ - self.logger.info(f"Begin drawing the lines on the plot: {datetime.now()}") - y_points = series.series_points['dbl_med'] - - # show or not ci - # see if any ci values in not 0 - no_ci_up = all(v == 0 for v in series.series_points['dbl_up_ci']) - no_ci_lo = all(v == 0 for v in series.series_points['dbl_lo_ci']) - error_y_visible = True - if ((no_ci_up is True and no_ci_lo is True) or self.config_obj.plot_ci[ - series.idx] == 'NONE'): - error_y_visible = False - - - # switch x and y values for the vertical plot - error_x = {} - error_y = {} - if self.config_obj.vert_plot is True: - y_points, x_points_index_adj = x_points_index_adj, y_points - self._xaxis_limits() - self.figure.update_xaxes(autorange=False) - - # Error bars for vertical plot - error_x = {'type': 'data', - 'symmetric': False, - 'array': series.series_points['dbl_up_ci'], - 'arrayminus': series.series_points['dbl_lo_ci'], - 'visible': error_y_visible, - 'thickness': self.config_obj.linewidth_list[ - series.idx]} - else: - # Error bars - error_y = { - 'type': 'data', - 'symmetric': False, - 'array': series.series_points['dbl_up_ci'], - 'arrayminus': series.series_points['dbl_lo_ci'], - 'visible': error_y_visible, - 'thickness': self.config_obj.linewidth_list[series.idx] - } - - # add the plot - # orient the confidence interval bars based on the vert_plot setting in - # the yaml configuration file. - self.figure.add_trace( - go.Scatter(x=x_points_index_adj, - y=y_points, - showlegend=self.config_obj.show_legend[series.idx] == 1, - mode=self.config_obj.mode[series.idx], - textposition="top right", - name=self.config_obj.user_legends[series.idx], - connectgaps=self.config_obj.con_series[series.idx] == 1, - line={'color': self.config_obj.colors_list[series.idx], - 'width': self.config_obj.linewidth_list[series.idx], - 'dash': self.config_obj.linestyles_list[series.idx]}, - marker_symbol=self.config_obj.marker_list[series.idx], - marker_color=self.config_obj.colors_list[series.idx], - marker_line_color=self.config_obj.colors_list[series.idx], - marker_size=self.config_obj.marker_size[series.idx], - error_x=error_x, - error_y=error_y - ), - secondary_y=series.y_axis != 1 - ) - - - self.logger.info(f"Finished drawing the lines on the plot:" - f" {datetime.now()}") - - def _create_layout(self) -> Figure: - """ - Creates a new layout based on the properties from the config file - including plots size, annotation and title - - :return: Figure object - """ - # create annotation - annotation = [ - {'text': util.apply_weight_style(self.config_obj.parameters['plot_caption'], - self.config_obj.parameters[ - 'caption_weight']), - 'align': 'left', - 'showarrow': False, - 'xref': 'paper', - 'yref': 'paper', - 'x': self.config_obj.parameters['caption_align'], - 'y': self.config_obj.caption_offset, - 'font': { - 'size': self.config_obj.caption_size, - 'color': self.config_obj.parameters['caption_col'] - } - }] - # create title - title = {'text': util.apply_weight_style(self.config_obj.title, - self.config_obj.parameters[ - 'title_weight']), - 'font': { - 'size': self.config_obj.title_font_size, - }, - 'y': self.config_obj.title_offset, - 'x': self.config_obj.parameters['title_align'], - 'xanchor': 'center', - 'xref': 'paper' - } - - # create a layout and allow y2 axis - fig = make_subplots(specs=[[{"secondary_y": self.allow_secondary_y}]]) - - # add size, annotation, title - fig.update_layout( - width=self.config_obj.plot_width, - height=self.config_obj.plot_height, - margin=self.config_obj.plot_margins, - paper_bgcolor=PLOTLY_PAPER_BGCOOR, - annotations=annotation, - title=title, - plot_bgcolor=PLOTLY_PAPER_BGCOOR - ) - return fig - - def _calc_stag_adjustments(self) -> list: - """ - Calculates the x-axis adjustment for each point if requested. - It needed so hte points and CIs for each x-axis values don't be placed on top - of each other - - :return: the list of the adjustment values - """ - - # get the total number of series - num_stag = len(self.config_obj.all_series_y1) + len( - self.config_obj.all_series_y2) - - # init the result with 0 - stag_vals = [0] * num_stag + # placeholder for the number of stats + n_stats = [0] * len(self.config_obj.indy_vals) - # calculate staggering values - if self.config_obj.indy_stagger is True: - dbl_adj_scale = (len(self.config_obj.indy_vals) - 1) / 150 - stag_vals = np.linspace(-(num_stag / 2) * dbl_adj_scale, - (num_stag / 2) * dbl_adj_scale, - num_stag, - True) - stag_vals = stag_vals + dbl_adj_scale / 2 - return stag_vals + # placeholder for the min and max values for y-axis + yaxis_min = None + yaxis_max = None - def _adjust_for_vertical(self, x_points_index: list) -> None: - """ - Switches x and y axis (creates a vertical plot) if needed + # add series lines + for series in self.series_list: - :param x_points_index: list of indexes for the original x -axis - """ - self.logger.info(f"Begin switching x and y axis: {datetime.now()}") - ordered_indy_label = self.config_obj.create_list_by_plot_val_ordering( - self.config_obj.indy_label) - if self.config_obj.vert_plot is True: - self.figure.update_layout( - yaxis={ - 'tickmode': 'array', - 'tickvals': x_points_index, - 'ticktext': ordered_indy_label - } - ) - else: - self.figure.update_layout( - xaxis={ - 'tickmode': 'array', - 'tickvals': x_points_index, - 'ticktext': ordered_indy_label - } - ) + # Don't generate the plot for this series if it isn't requested + if not series.plot_disp: + continue - self.logger.info(f"Finished switching x and y axis: {datetime.now()}") + # collect min-max if we need to sync axis + if self.config_obj.sync_yaxes: + yaxis_min, yaxis_max = self._find_min_max(series, yaxis_min, yaxis_max) - def _add_xaxis(self) -> None: - """ - Configures and adds x-axis to the plot - """ - self.figure.update_xaxes(title_text=self.config_obj.xaxis, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=self.config_obj.grid_on, - ticks="inside", - zeroline=False, - gridwidth=self.config_obj.parameters['grid_lwd'], - gridcolor=self.config_obj.blended_grid_col, - automargin=True, - title_font={ - 'size': self.config_obj.x_title_font_size - }, - title_standoff=abs( - self.config_obj.parameters['xlab_offset']), - tickangle=self.config_obj.x_tickangle, - tickfont={'size': self.config_obj.x_tickfont_size}, - tickformat='d' - ) - - def _add_yaxis(self) -> None: - """ - Configures and adds y-axis to the plot - """ - self.figure.update_yaxes(title_text= - util.apply_weight_style(self.config_obj.yaxis_1, - self.config_obj.parameters[ - 'ylab_weight']), - secondary_y=False, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=self.config_obj.grid_on, - zeroline=False, - ticks="inside", - gridwidth=self.config_obj.parameters['grid_lwd'], - gridcolor=self.config_obj.blended_grid_col, - automargin=True, - title_font={ - 'size': self.config_obj.y_title_font_size - }, - title_standoff=abs( - self.config_obj.parameters['ylab_offset']), - tickangle=self.config_obj.y_tickangle, - tickfont={'size': self.config_obj.y_tickfont_size} - ) - - def _add_y2axis(self) -> None: - """ - Adds y2-axis if needed - """ - if self.config_obj.parameters['list_stat_2']: - self.figure.update_yaxes(title_text= - util.apply_weight_style(self.config_obj.yaxis_2, - self.config_obj.parameters[ - 'y2lab_weight']), - secondary_y=True, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=False, - zeroline=False, - ticks="inside", - title_font={ - 'size': self.config_obj.y2_title_font_size - }, - title_standoff=abs( - self.config_obj.parameters['y2lab_offset']), - tickangle=self.config_obj.y2_tickangle, - tickfont={'size': self.config_obj.y2_tickfont_size} - ) - - def _add_legend(self) -> None: - """ - Creates a plot legend based on the properties from the config file - and attaches it to the initial Figure - """ - self.figure.update_layout(legend={'x': self.config_obj.bbox_x, - 'y': self.config_obj.bbox_y - 0.1, - 'xanchor': 'center', - 'yanchor': 'top', - 'bordercolor': - self.config_obj.legend_border_color, - 'borderwidth': - self.config_obj.legend_border_width, - 'orientation': - self.config_obj.legend_orientation, - 'font': { - 'size': self.config_obj.legend_size, - 'color': "black" - } - }) - - def _xaxis_limits(self) -> None: - """ - Apply limits on x axis if needed - especially when a vertical plot is requested + handle = self._draw_series(ax, ax2, series) + handles_and_labels.append((handle, handle.get_label())) - step size by default is 1 if undefined /non-existent + # aggregate number of stats + n_stats = list(map(add, n_stats, series.series_points['nstat'])) + return n_stats, yaxis_min, yaxis_max, handles_and_labels - step size must be integer value - """ - if len(self.config_obj.parameters['xlim']) > 0: - step = round(float(self.config_obj.parameters['xlim_step'])) - if step is None: - step = 1 - - # Convert string values to float, use numpy arange to - # generate a list of labels based on the min, max, and step values - # Round the min and max values to nearest integer - min_x= round(float(self.config_obj.parameters['xlim'][0])) - max_x= round(float(self.config_obj.parameters['xlim'][1])) - tick_labels = list(np.arange(min_x , max_x + step, step)) - - self.figure.update_layout( - xaxis={ - 'range': [min_x, max_x], - 'autorange':False, - 'tickvals':tick_labels} - ) - - def _yaxis_limits(self) -> None: + def _draw_series(self, ax: plt.Axes, ax2, series: Series): """ - Apply limits on y axis if needed - """ - if len(self.config_obj.parameters['ylim']) > 0: - self.figure.update_layout( - yaxis={'range': [self.config_obj.parameters['ylim'][0], - self.config_obj.parameters['ylim'][1]], - 'autorange': False}) - - + Draws the formatted line with CIs if needed on the plot - def _y2axis_limits(self) -> None: - """ - Apply limits on y2 axis if needed - """ - if len(self.config_obj.parameters['y2lim']) > 0: - self.figure.update_layout( - yaxis2={'range': [self.config_obj.parameters['y2lim'][0], - self.config_obj.parameters['y2lim'][1]], - 'autorange': False}) - - def _sync_yaxis(self, yaxis_min: Union[float, None], - yaxis_max: Union[float, None]) -> None: + :param series: Line series object with data and parameters """ - Forces y1 and y2 axes sync if needed by specifying the same limits on both axis. - Use ylim property to determine the limits. If this value is not provided - - use method parameters + self.logger.info(f"Begin drawing the lines on the plot: {datetime.now()}") - :param yaxis_min: min value or None - :param yaxis_max: max value or None - """ - if self.config_obj.sync_yaxes is True: - if len(self.config_obj.parameters['ylim']) > 0: - # use plot config parameter - range_min = self.config_obj.parameters['ylim'][0] - range_max = self.config_obj.parameters['ylim'][1] - else: - # use method parameter - range_min = yaxis_min - range_max = yaxis_max - - if range_min is not None and range_max is not None: - # update y axis - self.figure.update_layout(yaxis={'range': [range_min, - range_max], - 'autorange': False}) - - # update y2 axis - self.figure.update_layout(yaxis2={'range': [range_min, - range_max], - 'autorange': False}) - - def _add_x2axis(self, n_stats) -> None: - """ - Creates x2axis based on the properties from the config file - and attaches it to the initial Figure + # adjust the x points to stagger them to prevent points from overlapping + x_points_index_adj, _ = self._get_x_locs_and_width(self.config_obj.indy_vals, series.idx, + stagger_scale=0.1) - :param n_stats: - labels for the axis - """ - if self.config_obj.show_nstats: - x_points_index = list(range(0, len(n_stats))) - self.figure.update_layout(xaxis2={'title_text': - util.apply_weight_style('NStats', - self.config_obj.parameters[ - 'x2lab_weight'] - ), - 'linecolor': PLOTLY_AXIS_LINE_COLOR, - 'linewidth': PLOTLY_AXIS_LINE_WIDTH, - 'overlaying': 'x', - 'side': 'top', - 'showgrid': False, - 'zeroline': False, - 'ticks': "inside", - 'title_font': { - 'size': - self.config_obj.x2_title_font_size - }, - 'title_standoff': abs( - self.config_obj.parameters[ - 'x2lab_offset'] - ), - 'tickmode': 'array', - 'tickvals': x_points_index, - 'ticktext': n_stats, - 'tickangle': self.config_obj.x2_tickangle, - 'tickfont': { - 'size': - self.config_obj.x2_tickfont_size - }, - 'scaleanchor': 'x' - } - ) - # reverse x2axis if needed - if self.config_obj.xaxis_reverse is True: - self.figure.update_layout(xaxis2={'autorange': "reversed"}) - - # need to add an invisible line with all values = None - self.figure.add_trace( - go.Scatter(y=[None] * len(x_points_index), x=x_points_index, - xaxis='x2', showlegend=False) - ) - - def remove_file(self): - """ - Removes previously made image file . Invoked by the parent class before - self.output_file - attribute can be created, but overridden here. - """ + # convert to a numpy array to change None values to NaN + y_points = np.array(series.series_points['dbl_med'], dtype=float) - super().remove_file() - self._remove_html() + plot_obj = self._draw_series_item(series, series.series_points, ax, ax2, x_points_index_adj, y_points) - def _remove_html(self) -> None: - """ - Removes previously made HTML file. - """ + self.logger.info(f"Finished drawing the lines on the plot: {datetime.now()}") + return plot_obj - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" + def _draw_series_item(self, series, series_points, ax, ax2, x_points, y_points): + # show or not ci - see if any ci values in not 0 + no_ci_up = all(v == 0 for v in series_points['dbl_up_ci']) + no_ci_lo = all(v == 0 for v in series_points['dbl_lo_ci']) - # remove the old file if it exist - if os.path.exists(html_name): - os.remove(html_name) + # convert to a numpy array to change None values to NaN + asymmetric_error = np.array([ + series_points['dbl_up_ci'], + series_points['dbl_lo_ci'] + ], dtype=float) - def write_html(self) -> None: - """ - Is needed - creates and saves the html representation of the plot WITHOUT - Plotly.js - """ - logger = util.get_common_logger(self.config_obj.log_level, - self.config_obj.log_filename) - logger.info(f"Begin writing to html file: {datetime.now()}") - if self.config_obj.create_html is True: - # construct the file name from plot_filename - base_name, _ = os.path.splitext(self.get_config_value('plot_filename')) - html_name = f"{base_name}.html" + error_y_visible = True + if (no_ci_up and no_ci_lo) or self.config_obj.plot_ci[series.idx] == 'NONE': + error_y_visible = False - # save html - self.figure.write_html(html_name, include_plotlyjs=False) + # determine which y-axis to use for the plot + plot_ax = ax if series.y_axis == 1 else ax2 + + # plot error bar + plot_mode = self.config_obj.mode[series.idx] + marker = self.config_obj.marker_list[series.idx] if 'markers' in plot_mode else None + line_style = self.config_obj.linestyles_list[series.idx] if 'lines' in plot_mode else 'None' + + # Swap x and y data if vertical plot + plot_x = y_points if self.config_obj.vert_plot else x_points + plot_y = x_points if self.config_obj.vert_plot else y_points + + # Swap error bars (yerr becomes xerr) if vertical plot + x_err_val = asymmetric_error if (self.config_obj.vert_plot and error_y_visible) else None + y_err_val = asymmetric_error if (not self.config_obj.vert_plot and error_y_visible) else None + + plot_obj = plot_ax.errorbar( + x=plot_x, + y=plot_y, + label=self.config_obj.user_legends[series.idx], + # line style + color=self.config_obj.colors_list[series.idx], + linestyle=line_style, + linewidth=self.config_obj.linewidth_list[series.idx], + # marker style + marker=marker, + markersize=self.config_obj.marker_size[series.idx], + markeredgecolor=self.config_obj.colors_list[series.idx], + markerfacecolor=self.config_obj.colors_list[series.idx], + # error bar + xerr=x_err_val, + yerr=y_err_val, + elinewidth=self.config_obj.linewidth_list[series.idx], + capsize=5, + ) + return plot_obj - logger.info(f"Finished writing to html file: {datetime.now()}") def write_output_file(self) -> None: """ Formats y1 and y2 series point data to the 2-dim arrays and saves them to the files """ + if not self.config_obj.dump_points_1 and not self.config_obj.dump_points_2: + return self.logger.info(f"Begin writing to output file: {datetime.now()}") # if points_path parameter doesn't exist, @@ -749,51 +346,51 @@ def write_output_file(self) -> None: # extension with .points1 extension # otherwise use points_path path match = re.match(r'(.*)(.data)', self.config_obj.parameters['stat_input']) - if (self.config_obj.dump_points_1 is True or self.config_obj.dump_points_2 is - True and match): - - # create 2-dim array for y1 points and fill it with 0 - all_points_1 = [[0 for x in range(len(self.config_obj.all_series_y1) * 3)] - for y in - range(len(self.config_obj.indy_vals))] - if self.config_obj.series_vals_2: - # create 2-dim array for y1 points and feel it with 0 - all_points_2 = [ - [0 for x in range(len(self.config_obj.all_series_y2) * 3)] for y in - range(len(self.config_obj.indy_vals))] + if not match: + return + + # create 2-dim array for y1 points and fill it with 0 + all_points_1 = [[0 for _ in range(len(self.config_obj.all_series_y1) * 3)] + for _ in range(len(self.config_obj.indy_vals))] + if self.config_obj.series_vals_2: + # create 2-dim array for y1 points and feel it with 0 + all_points_2 = [ + [0 for _ in range(len(self.config_obj.all_series_y2) * 3)] + for _ in range(len(self.config_obj.indy_vals)) + ] + else: + all_points_2 = [] + + # separate indexes for y1 and y2 series + series_idx_y1 = 0 + series_idx_y2 = 0 + + # get points from each series + for series in self.series_list: + if series.y_axis == 1: + self._record_points(all_points_1, series_idx_y1, series) + series_idx_y1 = series_idx_y1 + 1 + else: + self._record_points(all_points_2, series_idx_y2, series) + series_idx_y2 = series_idx_y2 + 1 + + # replace the default path with the custom + filename = match.group(1) + if self.config_obj.points_path is not None: + # get the file name + path = filename.split(os.path.sep) + if len(path) > 0: + filename = path[-1] else: - all_points_2 = [] - - # separate indexes for y1 and y2 series - series_idx_y1 = 0 - series_idx_y2 = 0 - - # get points from each series - for series in self.series_list: - if series.y_axis == 1: - self._record_points(all_points_1, series_idx_y1, series) - series_idx_y1 = series_idx_y1 + 1 - else: - self._record_points(all_points_2, series_idx_y2, series) - series_idx_y2 = series_idx_y2 + 1 - - # replace the default path with the custom - filename = match.group(1) - if self.config_obj.points_path is not None: - # get the file name - path = filename.split(os.path.sep) - if len(path) > 0: - filename = path[-1] - else: - filename = '.' + os.path.sep - filename = self.config_obj.points_path + os.path.sep + filename - os.makedirs(filename, exist_ok=True) - - # save points - self._save_points(all_points_1, filename + ".points1") - self._save_points(all_points_2, filename + ".points2") - - self.logger.info(f"Finished writing to output file: {datetime.now()}") + filename = '.' + os.path.sep + filename = self.config_obj.points_path + os.path.sep + filename + os.makedirs(filename, exist_ok=True) + + # save points + self._save_points(all_points_1, filename + ".points1") + self._save_points(all_points_2, filename + ".points2") + + self.logger.info(f"Finished writing to output file: {datetime.now()}") @staticmethod def _find_min_max(series: LineSeries, yaxis_min: Union[float, None], @@ -849,17 +446,14 @@ def _record_points(self, all_points: list, series_idx: int, all_points[indy_val_idx][series_idx * 3] = y_points[indy_val_idx] # place CI-low value or None - if not y_points[indy_val_idx] is None \ - and not dbl_lo_ci[indy_val_idx] is None: - + if y_points[indy_val_idx] is not None and dbl_lo_ci[indy_val_idx] is not None: all_points[indy_val_idx][series_idx * 3 + 1] = \ y_points[indy_val_idx] - dbl_lo_ci[indy_val_idx] else: all_points[indy_val_idx][series_idx * 3 + 1] = None # place CI-up value or None - if not y_points[indy_val_idx] is None \ - and not dbl_up_ci[indy_val_idx] is None: + if y_points[indy_val_idx] is not None and dbl_up_ci[indy_val_idx] is not None: all_points[indy_val_idx][series_idx * 3 + 2] = \ y_points[indy_val_idx] + dbl_up_ci[indy_val_idx] else: @@ -890,7 +484,6 @@ def _save_points(points: list, output_file: str) -> None: with open(output_file, "w+") as my_csv: csv_writer = csv.writer(my_csv, delimiter=' ') csv_writer.writerows(all_points_formatted) - my_csv.close() except TypeError: print('Can\'t save points to a file') diff --git a/metplotpy/plots/line/line_config.py b/metplotpy/plots/line/line_config.py index ffb38334..9ac1edda 100644 --- a/metplotpy/plots/line/line_config.py +++ b/metplotpy/plots/line/line_config.py @@ -17,9 +17,9 @@ import itertools -from ..config_plotly import Config -from .. import constants_plotly as constants -from .. import util_plotly as util +from ..config import Config +from .. import constants +from .. import util import metcalcpy.util.utils as utils @@ -70,7 +70,7 @@ def __init__(self, parameters: dict) -> None: ############################################## # title parameters self.title_font_size = self.parameters['title_size'] * constants.DEFAULT_TITLE_FONT_SIZE - self.title_offset = self.parameters['title_offset'] * constants.DEFAULT_TITLE_OFFSET + self.title_offset = 1.0 + abs(self.parameters['title_offset']) * constants.DEFAULT_TITLE_OFFSET self.y_title_font_size = self.parameters['ylab_size'] + constants.DEFAULT_TITLE_FONTSIZE ############################################## @@ -100,7 +100,6 @@ def __init__(self, parameters: dict) -> None: if self.x_tickangle in constants.XAXIS_ORIENTATION.keys(): self.x_tickangle = constants.XAXIS_ORIENTATION[self.x_tickangle] self.x_tickfont_size = self.parameters['xtlab_size'] + constants.DEFAULT_TITLE_FONTSIZE - self.xaxis = util.apply_weight_style(self.xaxis, self.parameters['xlab_weight']) ############################################## # x2-axis parameters @@ -147,29 +146,6 @@ def __init__(self, parameters: dict) -> None: self.legend_border_color = "black" self.points_path = self.get_config_value('points_path') - def _get_plot_disp(self) -> list: - """ - Retrieve the values that determine whether to display a particular series - and convert them to bool if needed - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_config_vals = self.get_config_value('plot_disp') - plot_display_bools = [] - for val in plot_display_config_vals: - if isinstance(val, bool): - plot_display_bools.append(val) - - if isinstance(val, str): - plot_display_bools.append(val.upper() == 'TRUE') - - return self.create_list_by_series_ordering(plot_display_bools) - def _get_fcst_vars(self, index): """ Retrieve a list of the inner keys (fcst_vars) to the fcst_var_val dictionary. @@ -272,90 +248,6 @@ def _get_fixed_vars_vals(self) -> dict: return updated_fixed_vars_vals_dict - - def _get_mode(self) -> list: - """ - Retrieve all the modes. Convert mode names from - the config file into plotly python's mode names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - modes = self.get_config_value('series_type') - mode_list = [] - for mode in modes: - if mode in constants.TYPE_TO_PLOTLY_MODE.keys(): - # the recognized plotly marker names: - # circle-open (for small circle), circle, triangle-up, - # square, diamond, or hexagon - mode_list.append(constants.TYPE_TO_PLOTLY_MODE[mode]) - else: - mode_list.append('lines+markers') - return self.create_list_by_series_ordering(mode_list) - - def _get_linestyles(self) -> list: - """ - Retrieve all the line styles. Convert line style names from - the config file into plotly python's line style names. - - Args: - - Returns: - line_styles: a list of the plotly line styles - """ - line_styles = self.get_config_value('series_line_style') - line_style_list = [] - for line_style in line_styles: - if line_style in constants.LINE_STYLE_TO_PLOTLY_DASH.keys(): - line_style_list.append(constants.LINE_STYLE_TO_PLOTLY_DASH[line_style]) - else: - line_style_list.append(None) - return self.create_list_by_series_ordering(line_style_list) - - def _get_markers(self) -> list: - """ - Retrieve all the markers. Convert marker names from - the config file into plotly python's marker names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - markers = self.get_config_value('series_symbols') - markers_list = [] - for marker in markers: - if marker in constants.AVAILABLE_PLOTLY_MARKERS_LIST: - # the recognized plotly marker names: - # circle-open (for small circle), circle, triangle-up, - # square, diamond, or hexagon - markers_list.append(marker) - else: - markers_list.append(constants.PCH_TO_PLOTLY_MARKER[marker]) - return self.create_list_by_series_ordering(markers_list) - - def _get_markers_size(self) -> list: - """ - Retrieve all the markers. Convert marker names from - the config file into plotly python's marker names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - markers = self.get_config_value('series_symbols') - markers_size = [] - for marker in markers: - if marker in constants.AVAILABLE_PLOTLY_MARKERS_LIST: - markers_size.append(marker) - else: - markers_size.append(constants.PCH_TO_PLOTLY_MARKER_SIZE[marker]) - - return self.create_list_by_series_ordering(markers_size) - def _get_plot_stat(self) -> str: """ Retrieves the plot_stat setting from the config file. @@ -380,24 +272,13 @@ def _get_plot_stat(self) -> str: " Supported values are sum, mean, and median.") return stat_to_plot - def _config_consistency_check(self) -> bool: - """ - Checks that the number of settings defined for plot_ci, - plot_disp, series_order, user_legend, colors, and series_symbols - are consistent. - - Args: - - Returns: - True if the number of settings for each of the above - settings is consistent with the number of - series (as defined by the cross product of the model - and vx_mask defined in the series_val_1 setting) + def config_consistency_check(self) -> bool: + """Checks that the number of settings are consistent with number of series. + @raises ValueError if any of settings are inconsistent with the + number of series (as defined by the cross product of the model + and vx_mask defined in the series_val_1 setting) """ - # Determine the number of series based on the number of - # permutations from the series_var setting in the - # config file lists_to_check = { "plot_ci": self.plot_ci, @@ -411,18 +292,7 @@ def _config_consistency_check(self) -> bool: "show_legend": self.show_legend, "con_series": self.con_series, } - status = True - for name, list_to_check in lists_to_check.items(): - - if len(list_to_check) == self.num_series: - continue - - self.logger.error( - f"number of series ({self.num_series}) does not match {name} ({len(list_to_check)})" - ) - status = False - - return status + self._config_compare_lists_to_num_series(lists_to_check) def _get_plot_ci(self) -> list: """ diff --git a/metplotpy/plots/line/line_series.py b/metplotpy/plots/line/line_series.py index cea0dd7a..6b8194f1 100644 --- a/metplotpy/plots/line/line_series.py +++ b/metplotpy/plots/line/line_series.py @@ -26,7 +26,7 @@ from scipy.stats import norm import metcalcpy.util.utils as utils -import metplotpy.plots.util_plotly as util +import metplotpy.plots.util as util from ..series import Series from .. import GROUP_SEPARATOR diff --git a/metplotpy/plots/performance_diagram/performance_diagram_config.py b/metplotpy/plots/performance_diagram/performance_diagram_config.py index 1863b985..4ccf8b4b 100644 --- a/metplotpy/plots/performance_diagram/performance_diagram_config.py +++ b/metplotpy/plots/performance_diagram/performance_diagram_config.py @@ -5,11 +5,7 @@ # ** Research Applications Lab (RAL) # ** P.O.Box 3000, Boulder, Colorado, 80307-3000, USA # ============================* - - - -#!/usr/bin/env conda run -n blenny_363 python """ Class Name: performance_diagram_config.py @@ -123,24 +119,6 @@ def _get_series_order(self): series_order_list = [ord for ord in ordinals] return series_order_list - - def _get_plot_disp(self): - """ - Retrieve the boolean values that determine whether to display a particular series - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_vals = self.get_config_value('plot_disp') - plot_display_bools = [pd for pd in plot_display_vals] - plot_display_bools_ordered = self.create_list_by_series_ordering(plot_display_bools) - return plot_display_bools_ordered - - def _get_plot_stat(self): """ Retrieves the plot_stat setting from the config file. diff --git a/metplotpy/plots/reliability_diagram/reliability.py b/metplotpy/plots/reliability_diagram/reliability.py index 022cf420..bf95bb63 100644 --- a/metplotpy/plots/reliability_diagram/reliability.py +++ b/metplotpy/plots/reliability_diagram/reliability.py @@ -138,7 +138,7 @@ def _create_figure(self): self.logger.info(f"Begin creating the lines on the reliability plot: {datetime.now()}") - fig, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) + _, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) wts_size_styles = self.get_weights_size_styles() @@ -160,7 +160,7 @@ def _create_figure(self): # format large numbers like 3 million as 3M ax2.yaxis.set_major_formatter(ticker.EngFormatter()) - self._add_series(ax, ax2) + handles_and_labels = self._add_series(ax, ax2) self._add_xaxis(ax, wts_size_styles['xlab']) ax.set_xlim(0, 1) @@ -168,7 +168,7 @@ def _create_figure(self): ax.set_ylim(0, 1) ax.set_yticks(np.linspace(0, 1, 11)) - self._add_legend(ax) + self._add_legend(ax, handles_and_labels) self._add_custom_lines(ax) @@ -183,6 +183,7 @@ def _add_custom_lines(self, ax): self._add_lines(ax, self.config_obj, self.config_obj.indy_vals) def _add_series(self, ax, ax2): + handles_and_labels = [] # calculate stag adjustments stag_adjustments = self._calc_stag_adjustments() @@ -199,7 +200,10 @@ def _add_series(self, ax, ax2): # Don't generate the plot for this series if # it isn't requested (as set in the config file) if series.plot_disp: - self._draw_series(ax, ax2, series, x_points_index_adj, index) + handle = self._draw_series(ax, ax2, series, x_points_index_adj, index) + handles_and_labels.append((handle, handle.get_label())) + + return handles_and_labels def _draw_series(self, ax, ax2, series: ReliabilitySeries, x_points_index_adj: list, idx) -> None: """ @@ -250,7 +254,7 @@ def _draw_series(self, ax, ax2, series: ReliabilitySeries, x_points_index_adj: l marker = self.config_obj.marker_list[series.idx] if 'markers' in plot_mode else None line_style = self.config_obj.linestyles_list[series.idx] if 'lines' in plot_mode else 'None' - ax.errorbar( + plot_obj = ax.errorbar( x=x_points_index_adj, y=y_points, label=self.config_obj.user_legends[series.idx], @@ -269,6 +273,7 @@ def _draw_series(self, ax, ax2, series: ReliabilitySeries, x_points_index_adj: l ) self.logger.info(f"Finished with bar plot and skill lines :{datetime.now()}") + return plot_obj def _add_noskill_polygon(self, ax, o_bar: Union[float, None]) -> None: """ diff --git a/metplotpy/plots/reliability_diagram/reliability_config.py b/metplotpy/plots/reliability_diagram/reliability_config.py index ad5dafdd..a416dd53 100644 --- a/metplotpy/plots/reliability_diagram/reliability_config.py +++ b/metplotpy/plots/reliability_diagram/reliability_config.py @@ -131,29 +131,6 @@ def __init__(self, parameters: dict) -> None: self.legend_orientation = 'h' self.legend_border_color = "black" - def _get_plot_disp(self) -> list: - """ - Retrieve the values that determine whether to display a particular series - and convert them to bool if needed - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_config_vals = self.get_config_value('plot_disp') - plot_display_bools = [] - for val in plot_display_config_vals: - if isinstance(val, bool): - plot_display_bools.append(val) - - if isinstance(val, str): - plot_display_bools.append(val.upper() == 'TRUE') - - return self.create_list_by_series_ordering(plot_display_bools) - def _get_fcst_vars(self, index): """ Retrieve a list of the inner keys (fcst_vars) to the fcst_var_val dictionary. diff --git a/metplotpy/plots/revision_series/revision_series.py b/metplotpy/plots/revision_series/revision_series.py index e03ea8ff..983a321f 100644 --- a/metplotpy/plots/revision_series/revision_series.py +++ b/metplotpy/plots/revision_series/revision_series.py @@ -17,15 +17,12 @@ from typing import Union -import numpy as np +from matplotlib import pyplot as plt -import plotly.graph_objects as go - -from metplotpy.plots.constants_plotly import PLOTLY_AXIS_LINE_COLOR, PLOTLY_AXIS_LINE_WIDTH -from metplotpy.plots.base_plot_plotly import BasePlot +from metplotpy.plots.base_plot import BasePlot from metplotpy.plots.line.line import Line -from metplotpy.plots import util_plotly as util +from metplotpy.plots import util as util from metplotpy.plots.series import Series import metcalcpy.util.utils as calc_util @@ -62,16 +59,7 @@ def __init__(self, parameters: dict) -> None: self.logger.info('Begin revision series plotting.') # Check that we have all the necessary settings for each series - is_config_consistent = self.config_obj._config_consistency_check() - if not is_config_consistent: - value_error_msg = ("The number of series defined by series_val_1 is" - " inconsistent with the number of settings" - " required for describing each series. Please check " - " the number of your configuration file's plot_i," - " plot_disp, series_order, user_legend," - " colors, show_legend and series_symbols settings.") - self.logger.error(f"ValueError: {value_error_msg}") - raise ValueError(value_error_msg) + self.config_obj.config_consistency_check() # Read in input data, location specified in config file self.input_df = self._read_input_data() @@ -80,17 +68,8 @@ def __init__(self, parameters: dict) -> None: if self.config_obj.use_ee is True: self.input_df = calc_util.perform_event_equalization(self.parameters, self.input_df) - # Create a list of series objects. - # Each series object contains all the necessary information for plotting, - # such as color, marker symbol, - # line width, and criteria needed to subset the input dataframe. self.series_list = self._create_series(self.input_df) - # create figure - # pylint:disable=assignment-from-no-return - # Need to have a self.figure that we can pass along to - # the methods in base_plot.py (BasePlot class methods) to - # create binary versions of the plot. self._create_figure() def __repr__(self): @@ -135,125 +114,77 @@ def _create_figure(self): self.logger.info(f"Begin creating the {self.LONG_NAME} figure: {datetime.now()}") # create and draw the plot - self.figure = self._create_layout() - self._add_xaxis() - self._add_yaxis() - self._add_legend() + _, ax = plt.subplots(figsize=(self.config_obj.plot_width, self.config_obj.plot_height)) + + wts_size_styles = self.get_weights_size_styles() + + self._add_title(ax, wts_size_styles['title']) + self._add_caption(plt, wts_size_styles['caption']) + + x_points_index = self._add_series(ax) - # calculate stag adjustments - stag_adjustments = self._calc_stag_adjustments() + xlab_style = wts_size_styles['xlab'] if not self.config_obj.vert_plot else wts_size_styles['ylab'] + ylab_style = wts_size_styles['ylab'] if not self.config_obj.vert_plot else wts_size_styles['xlab'] + self._add_xaxis(ax, xlab_style) + self._add_yaxis(ax, ylab_style) + + self._add_legend(ax) + + # add custom lines + self._add_lines(ax, self.config_obj, x_points_index) + + plt.tight_layout() + + self.logger.info(f"Finish creating {self.LONG_NAME} figure: {datetime.now()}") + + def _add_series(self, ax, ax2=None): + x_points_index = [] + ordered_indy_label = [] if len(self.series_list) > 0: x_points_index = list(range(0, len(self.series_list[0].series_points['points']))) ordered_indy_label = self.series_list[0].series_points['points']['fcst_lead'].tolist() - self.figure.update_layout( - xaxis={ - 'tickmode': 'array', - 'tickvals': x_points_index, - 'ticktext': ordered_indy_label, - 'tickangle': -90 - } - ) - - else: - x_points_index = [] + + self.config_obj.indy_label = ordered_indy_label + self.config_obj.indy_vals = x_points_index # add series points for series in self.series_list: # Don't generate the plot for this series if # it isn't requested (as set in the config file) - if series.plot_disp: + if not series.plot_disp: + continue - # apply staggering offset if applicable - if stag_adjustments[series.idx] == 0: - x_points_index_adj = x_points_index - else: - x_points_index_adj = x_points_index + stag_adjustments[series.idx] + x_points_index_adj = x_points_index + if self.config_obj.indy_stagger: + x_points_index_adj, _ = self._get_x_locs_and_width(x_points_index, series.idx, + stagger_scale=0.1) + self._draw_series(ax, None, series, x_points_index_adj) - self._draw_series(series, x_points_index_adj) + return x_points_index - # add custom lines - self._add_lines(self.config_obj, x_points_index) - - # apply y axis limits - self._yaxis_limits() - - self.logger.info(f"Finish creating {self.LONG_NAME} figure: {datetime.now()}") - - def _draw_series(self, series: Series, x_points_index_adj: Union[list, None] = None) -> None: + def _draw_series(self, ax, ax2, series: Series, x_points_adj: Union[list, None] = None) -> None: """ Draws the formatted series points on the plot :param series: RevisionSeries object with data and parameters - :param x_points_index_adj: values for adjusting x-values position + :param x_points_adj: values for adjusting x-values position """ self.logger.info(f"Draw the formatted series: {datetime.now()}") - y_points = series.series_points['points']['stat_value'].tolist() - - # add the plot - self.figure.add_trace( - go.Scatter(x=x_points_index_adj, - y=y_points, - showlegend=self.config_obj.show_legend[series.idx] == 1, - mode='markers', - textposition="top right", - name=series.user_legends, - marker_symbol=self.config_obj.marker_list[series.idx], - marker_color=self.config_obj.colors_list[series.idx], - marker_line_color=self.config_obj.colors_list[series.idx], - marker_size=self.config_obj.marker_size[series.idx], - ), - secondary_y=False + ax.plot( + x_points_adj, series.series_points['points']['stat_value'].tolist(), + label=series.user_legends, + # marker style + marker=self.config_obj.marker_list[series.idx], + markersize=self.config_obj.marker_size[series.idx], + markeredgecolor=self.config_obj.colors_list[series.idx], + markerfacecolor=self.config_obj.colors_list[series.idx], + # no lines + linestyle='None', ) self.logger.info(f"Finished drawing series: {datetime.now()}") - def _add_xaxis(self) -> None: - """ - Configures and adds x-axis to the plot - """ - self.figure.update_xaxes(title_text=self.config_obj.xaxis, - linecolor=PLOTLY_AXIS_LINE_COLOR, - linewidth=PLOTLY_AXIS_LINE_WIDTH, - showgrid=self.config_obj.grid_on, - ticks="inside", - zeroline=False, - gridwidth=self.config_obj.parameters['grid_lwd'], - gridcolor=self.config_obj.blended_grid_col, - automargin=True, - title_font={ - 'size': self.config_obj.x_title_font_size - }, - title_standoff=abs(self.config_obj.parameters['xlab_offset']), - tickangle=self.config_obj.x_tickangle, - tickfont={'size': self.config_obj.x_tickfont_size} - ) - - def _calc_stag_adjustments(self) -> list: - """ - Calculates the x-axis adjustment for each point if requested. - It needed so the points for each x-axis values don't be placed on top of each other - - :return: the list of the adjustment values - """ - - self.logger.info("Calculating the x-axis adjustment.") - # get the total number of series - num_stag = len(self.config_obj.all_series_y1) - - # init the result with 0 - stag_vals = [0] * num_stag - - # calculate staggering values - if self.config_obj.indy_stagger is True: - dbl_adj_scale = (len(self.config_obj.indy_vals) - 1) / 150 - stag_vals = np.linspace(-(num_stag / 2) * dbl_adj_scale, - (num_stag / 2) * dbl_adj_scale, - num_stag, - True) - stag_vals = stag_vals + dbl_adj_scale / 2 - return stag_vals - def write_output_file(self) -> None: """ Formats y1 series point data and saves them to the files diff --git a/metplotpy/plots/revision_series/revision_series_config.py b/metplotpy/plots/revision_series/revision_series_config.py index f7e5a0d8..875c2679 100644 --- a/metplotpy/plots/revision_series/revision_series_config.py +++ b/metplotpy/plots/revision_series/revision_series_config.py @@ -14,9 +14,9 @@ """ import itertools from datetime import datetime -from ..config_plotly import Config -from .. import constants_plotly as constants -from .. import util_plotly as util +from ..config import Config +from .. import constants +from .. import util import metcalcpy.util.utils as utils @@ -49,7 +49,7 @@ def __init__(self, parameters: dict) -> None: ############################################## # title parameters self.title_font_size = self.parameters['title_size'] * constants.DEFAULT_TITLE_FONT_SIZE - self.title_offset = self.parameters['title_offset'] * constants.DEFAULT_TITLE_OFFSET + self.title_offset = 1.0 + abs(self.parameters['title_offset']) * constants.DEFAULT_TITLE_OFFSET self.y_title_font_size = self.parameters['ylab_size'] + constants.DEFAULT_TITLE_FONTSIZE ############################################## @@ -62,11 +62,11 @@ def __init__(self, parameters: dict) -> None: ############################################## # x-axis parameters self.x_title_font_size = self.parameters['xlab_size'] + constants.DEFAULT_TITLE_FONTSIZE - self.x_tickangle = self.parameters['xtlab_orient'] - if self.x_tickangle in constants.XAXIS_ORIENTATION.keys(): - self.x_tickangle = constants.XAXIS_ORIENTATION[self.x_tickangle] + # self.x_tickangle = self.parameters['xtlab_orient'] + # if self.x_tickangle in constants.XAXIS_ORIENTATION.keys(): + # self.x_tickangle = constants.XAXIS_ORIENTATION[self.x_tickangle] + self.x_tickangle = 90 self.x_tickfont_size = self.parameters['xtlab_size'] + constants.DEFAULT_TITLE_FONTSIZE - self.xaxis = util.apply_weight_style(self.xaxis, self.parameters['xlab_weight']) ############################################## # series parameters @@ -103,106 +103,6 @@ def __init__(self, parameters: dict) -> None: self.revision_run = self._get_bool('revision_run') self.indy_stagger = self._get_bool('indy_stagger_1') - def _get_plot_disp(self) -> list: - """ - Retrieve the values that determine whether to display a particular series - and convert them to bool if needed - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_config_vals = self.get_config_value('plot_disp') - plot_display_bools = [] - for val in plot_display_config_vals: - if isinstance(val, bool): - plot_display_bools.append(val) - - if isinstance(val, str): - plot_display_bools.append(val.upper() == 'TRUE') - - return self.create_list_by_series_ordering(plot_display_bools) - - def _get_markers(self) -> list: - """ - Retrieve all the markers. Convert marker names from - the config file into plotly python's marker names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - markers = self.get_config_value('series_symbols') - markers_list = [] - for marker in markers: - if marker in constants.AVAILABLE_PLOTLY_MARKERS_LIST: - # the recognized plotly marker names: - # circle-open (for small circle), circle, triangle-up, - # square, diamond, or hexagon - markers_list.append(marker) - else: - markers_list.append(constants.PCH_TO_PLOTLY_MARKER[marker]) - return self.create_list_by_series_ordering(markers_list) - - def _get_markers_size(self) -> list: - """ - Retrieve all the markers. Convert marker names from - the config file into plotly python's marker names. - - Args: - - Returns: - markers: a list of the plotly markers - """ - markers = self.get_config_value('series_symbols') - markers_size = [] - for marker in markers: - if marker in constants.AVAILABLE_PLOTLY_MARKERS_LIST: - markers_size.append(marker) - else: - markers_size.append(constants.PCH_TO_PLOTLY_MARKER_SIZE[marker]) - - return self.create_list_by_series_ordering(markers_size) - - def _config_consistency_check(self) -> bool: - """ - Checks that the number of settings defined for plot_ci, - plot_disp, series_order, user_legend colors, and series_symbols - are consistent. - - Args: - - Returns: - True if the number of settings for each of the above - settings is consistent with the number of - series (as defined by the cross product of the model - and vx_mask defined in the series_val_1 setting) - - """ - - self.logger.info(f"Begin consistency checK: {datetime.now()}") - # Determine the number of series based on the number of - # permutations from the series_var setting in the - # config file - - # Numbers of values for other settings for series - num_plot_disp = len(self.plot_disp) - num_markers = len(self.marker_list) - num_series_ord = len(self.series_ordering) - num_colors = len(self.colors_list) - num_legends = len(self.user_legends) - status = False - - if self.num_series == num_plot_disp == \ - num_markers == num_series_ord == num_colors \ - == num_legends: - status = True - return status - def _get_user_legends(self, legend_label_type: str = '') -> list: """ Retrieve the text that is to be displayed in the legend at the bottom of the plot. @@ -247,8 +147,8 @@ def get_series_y(self) -> list: for x in reversed(list(all_fields_values_orig.keys())): all_fields_values[x] = all_fields_values_orig.get(x) - if self._get_fcst_vars(1): - all_fields_values['fcst_var'] = list(self._get_fcst_vars(1).keys()) + if self.get_fcst_vars_keys(1): + all_fields_values['fcst_var'] = self.get_fcst_vars_keys(1) stat_name = self.get_config_value('list_stat_1') if stat_name is not None: @@ -279,10 +179,7 @@ def calculate_number_of_series(self) -> int: """ # Retrieve the lists from the series_val_1 dictionary series_vals_list = self.series_vals_1.copy() - if isinstance(self.fcst_var_val_1, list) is True: - fcst_vals = self.fcst_var_val_1 - elif isinstance(self.fcst_var_val_1, dict) is True: - fcst_vals = list(self.fcst_var_val_1.values()) + fcst_vals = list(self.fcst_var_val_1.values()) fcst_vals_flat = [item for sublist in fcst_vals for item in sublist] series_vals_list.append(fcst_vals_flat) diff --git a/metplotpy/plots/roc_diagram/roc_diagram_config.py b/metplotpy/plots/roc_diagram/roc_diagram_config.py index 06855e27..fe13c39f 100644 --- a/metplotpy/plots/roc_diagram/roc_diagram_config.py +++ b/metplotpy/plots/roc_diagram/roc_diagram_config.py @@ -234,30 +234,6 @@ def _get_series_order(self): series_order_list = list(ordinals) return series_order_list - - def _get_plot_disp(self): - """ - Retrieve the boolean values that determine whether to display a particular series - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_config_vals = self.get_config_value('plot_disp') - plot_display_bools = [] - for p in plot_display_config_vals: - if str(p).upper() == "TRUE": - plot_display_bools.append(True) - else: - plot_display_bools.append(False) - - plot_display_bools_ordered = self.create_list_by_series_ordering(plot_display_bools) - return plot_display_bools_ordered - - def _get_point_thresh(self): """ Retrieve the value (true/false) of the add_point_threshold diff --git a/metplotpy/plots/taylor_diagram/taylor_diagram_config.py b/metplotpy/plots/taylor_diagram/taylor_diagram_config.py index dc182b6c..9e94efe6 100644 --- a/metplotpy/plots/taylor_diagram/taylor_diagram_config.py +++ b/metplotpy/plots/taylor_diagram/taylor_diagram_config.py @@ -233,32 +233,6 @@ def _get_series_order(self): series_order_list = [ord for ord in ordinals] return series_order_list - def _get_plot_disp(self) -> list: - """ - Retrieve the boolean values that determine whether to display a particular series - - Args: - - Returns: - A list of boolean values indicating whether or not to - display the corresponding series - """ - - plot_display_vals = self.get_config_value('plot_disp') - plot_display_strings = [pd for pd in plot_display_vals] - plot_display_strings_ordered = self.create_list_by_series_ordering(plot_display_strings) - - # Convert each string to the boolean representation - plot_display_bools_ordered = [] - for cur in plot_display_strings_ordered: - bool_str = str(cur).lower() - if bool_str == 'true': - plot_display_bools_ordered.append(True) - else: - plot_display_bools_ordered.append(False) - - return plot_display_bools_ordered - def _config_consistency_check(self) -> bool: """ Checks that the number of settings defined for diff --git a/test/eclv/custom_eclv.yaml b/test/eclv/custom_eclv.yaml index c79150f3..dd0a5505 100644 --- a/test/eclv/custom_eclv.yaml +++ b/test/eclv/custom_eclv.yaml @@ -123,5 +123,5 @@ plot_filename: !ENV '${TEST_OUTPUT}/eclv.png' # Debug and info log level will produce more log output. #log_level: WARNING show_legend: - -True - -True \ No newline at end of file +- True +- True \ No newline at end of file diff --git a/test/eclv/custom_eclv_ctc.yaml b/test/eclv/custom_eclv_ctc.yaml index dfae7fea..0311bf7a 100644 --- a/test/eclv/custom_eclv_ctc.yaml +++ b/test/eclv/custom_eclv_ctc.yaml @@ -115,5 +115,5 @@ stat_input: !ENV '${TEST_DIR}/eclv_ctc.data' plot_filename: !ENV '${TEST_OUTPUT}/eclv_ctc.png' show_legend: - -True - -True \ No newline at end of file +- True +- True \ No newline at end of file diff --git a/test/eclv/custom_eclv_pct.yaml b/test/eclv/custom_eclv_pct.yaml index bb8a3dc6..c65dbbdd 100644 --- a/test/eclv/custom_eclv_pct.yaml +++ b/test/eclv/custom_eclv_pct.yaml @@ -105,4 +105,4 @@ ytlab_size: 1 stat_input: !ENV '${TEST_DIR}/eclv_pct.data' plot_filename: !ENV '${TEST_OUTPUT}/eclv_pct.png' show_legend: - -True \ No newline at end of file +- True \ No newline at end of file diff --git a/test/line/test_line_plot.py b/test/line/test_line_plot.py index 648b6dae..e3f1f2ef 100644 --- a/test/line/test_line_plot.py +++ b/test/line/test_line_plot.py @@ -37,7 +37,6 @@ "line_groups.png", "line_groups.points1", "line_groups.points2", - "line_groups.html", ]), ("custom_line_groups2.yaml", [ "line_groups2.png", diff --git a/test/revision_series/custom_revision_series.yaml b/test/revision_series/custom_revision_series.yaml index 66b38deb..6b304ea8 100644 --- a/test/revision_series/custom_revision_series.yaml +++ b/test/revision_series/custom_revision_series.yaml @@ -32,13 +32,13 @@ grid_lwd: 1 grid_on: 'True' grid_x: listX indy_label: -- '2011-07-02 03:00:00' -- '2011-07-02 06:00:00' -- '2011-07-02 09:00:00' -- '2011-07-02 12:00:00' -- '2011-07-02 15:00:00' -- '2011-07-02 18:00:00' -- '2011-07-02 21:00:00' +- '07-02 03' +- '07-02 06' +- '07-02 09' +- '07-02 12' +- '07-02 15' +- '07-02 18' +- '07-02 21' indy_plot_val: [] indy_vals: - '2011-07-02 03:00:00' @@ -53,7 +53,7 @@ legend_box: o legend_inset: x: 0.0 y: -0.25 -legend_ncol: 3 +legend_ncol: 1 legend_size: 0.8 line_type: None list_stat_1: