From e9cc16ea232cc2cfb610da03856a854a79aa4722 Mon Sep 17 00:00:00 2001 From: Adam Theisen Date: Mon, 11 Dec 2023 15:29:06 -0600 Subject: [PATCH 1/2] =?UTF-8?q?ENH:=20Reverting=20back=20the=20secondary?= =?UTF-8?q?=20y-axis=20change=20and=20removing=20the=20seco=E2=80=A6=20(#7?= =?UTF-8?q?69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ENH: Reverting back the secondary y-axis change and removing the secondary y-axis functionality. * ENH: PEP8 --- act/plotting/contourdisplay.py | 2 +- act/plotting/distributiondisplay.py | 125 +++++++++---------- act/plotting/geodisplay.py | 2 +- act/plotting/plot.py | 72 +++-------- act/plotting/skewtdisplay.py | 2 +- act/plotting/timeseriesdisplay.py | 127 +++++--------------- act/plotting/windrosedisplay.py | 19 +-- act/plotting/xsectiondisplay.py | 2 +- act/tests/test_plotting.py | 18 +-- examples/discovery/plot_asos_temp.py | 2 +- examples/discovery/plot_noaa_fmcw_moment.py | 2 +- examples/plotting/plot_scatter.py | 42 +++---- examples/plotting/plot_secondary_y.py | 38 +++--- examples/plotting/plot_violin.py | 6 +- examples/workflows/plot_weighted_average.py | 2 +- 15 files changed, 156 insertions(+), 305 deletions(-) diff --git a/act/plotting/contourdisplay.py b/act/plotting/contourdisplay.py index 75e8bebdd3..cbfe7ef69d 100644 --- a/act/plotting/contourdisplay.py +++ b/act/plotting/contourdisplay.py @@ -19,7 +19,7 @@ class ContourDisplay(Display): """ def __init__(self, ds, subplot_shape=(1,), ds_name=None, **kwargs): - super().__init__(ds, subplot_shape, ds_name, secondary_y_allowed=False, **kwargs) + super().__init__(ds, subplot_shape, ds_name, **kwargs) def create_contour( self, diff --git a/act/plotting/distributiondisplay.py b/act/plotting/distributiondisplay.py index 73d695de8e..a94d0195cf 100644 --- a/act/plotting/distributiondisplay.py +++ b/act/plotting/distributiondisplay.py @@ -33,7 +33,7 @@ class DistributionDisplay(Display): """ def __init__(self, ds, subplot_shape=(1,), ds_name=None, **kwargs): - super().__init__(ds, subplot_shape, ds_name, secondary_y_allowed=True, **kwargs) + super().__init__(ds, subplot_shape, ds_name, **kwargs) def set_xrng(self, xrng, subplot_index=(0,)): """ @@ -55,7 +55,7 @@ def set_xrng(self, xrng, subplot_index=(0,)): elif not hasattr(self, 'xrng') and len(self.axes.shape) == 1: self.xrng = np.zeros((self.axes.shape[0], 2), dtype='datetime64[D]') - self.axes[subplot_index][0].set_xlim(xrng) + self.axes[subplot_index].set_xlim(xrng) self.xrng[subplot_index, :] = np.array(xrng) def set_yrng(self, yrng, subplot_index=(0,)): @@ -81,7 +81,7 @@ def set_yrng(self, yrng, subplot_index=(0,)): if yrng[0] == yrng[1]: yrng[1] = yrng[1] + 1 - self.axes[subplot_index][0].set_ylim(yrng) + self.axes[subplot_index].set_ylim(yrng) self.yrng[subplot_index, :] = yrng def _get_data(self, dsname, fields): @@ -167,9 +167,8 @@ def plot_stacked_bar( if self.fig is None: self.fig = plt.figure() if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) if sortby_field is not None: if 'units' in ydata.attrs: @@ -189,26 +188,26 @@ def plot_stacked_bar( bins=[bins, sortby_bins], **hist_kwargs) x_inds = (x_bins[:-1] + x_bins[1:]) / 2.0 - self.axes[subplot_index][0].bar( + self.axes[subplot_index].bar( x_inds, my_hist[:, 0].flatten(), label=(str(y_bins[0]) + ' to ' + str(y_bins[1])), **kwargs, ) for i in range(1, len(y_bins) - 1): - self.axes[subplot_index][0].bar( + self.axes[subplot_index].bar( x_inds, my_hist[:, i].flatten(), bottom=my_hist[:, i - 1], label=(str(y_bins[i]) + ' to ' + str(y_bins[i + 1])), **kwargs, ) - self.axes[subplot_index][0].legend() + self.axes[subplot_index].legend() else: my_hist, bins = np.histogram(xdata.values.flatten(), bins=bins, density=density, **hist_kwargs) x_inds = (bins[:-1] + bins[1:]) / 2.0 - self.axes[subplot_index][0].bar(x_inds, my_hist) + self.axes[subplot_index].bar(x_inds, my_hist) # Set Title if set_title is None: @@ -220,9 +219,9 @@ def plot_stacked_bar( dt_utils.numpy_to_arm_date(self._ds[dsname].time.values[0]), ] ) - self.axes[subplot_index][0].set_title(set_title) - self.axes[subplot_index][0].set_ylabel('count') - self.axes[subplot_index][0].set_xlabel(xtitle) + self.axes[subplot_index].set_title(set_title) + self.axes[subplot_index].set_ylabel('count') + self.axes[subplot_index].set_xlabel(xtitle) return_dict = {} return_dict['plot_handle'] = self.axes[subplot_index] @@ -310,9 +309,8 @@ def plot_size_distribution( if self.fig is None: self.fig = plt.figure() if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) # Set Title if set_title is None: @@ -327,10 +325,10 @@ def plot_size_distribution( if time is not None: t = pd.Timestamp(time) set_title += ''.join([' at ', ':'.join([str(t.hour), str(t.minute), str(t.second)])]) - self.axes[subplot_index][0].set_title(set_title) - self.axes[subplot_index][0].step(bins.values, xdata.values, **kwargs) - self.axes[subplot_index][0].set_xlabel(xtitle) - self.axes[subplot_index][0].set_ylabel(ytitle) + self.axes[subplot_index].set_title(set_title) + self.axes[subplot_index].step(bins.values, xdata.values, **kwargs) + self.axes[subplot_index].set_xlabel(xtitle) + self.axes[subplot_index].set_ylabel(ytitle) return self.axes[subplot_index] @@ -412,9 +410,8 @@ def plot_stairstep( self.fig = plt.figure() if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) if sortby_field is not None: if 'units' in ydata.attrs: @@ -434,26 +431,26 @@ def plot_stairstep( **hist_kwargs ) x_inds = (x_bins[:-1] + x_bins[1:]) / 2.0 - self.axes[subplot_index][0].step( + self.axes[subplot_index].step( x_inds, my_hist[:, 0].flatten(), label=(str(y_bins[0]) + ' to ' + str(y_bins[1])), **kwargs, ) for i in range(1, len(y_bins) - 1): - self.axes[subplot_index][0].step( + self.axes[subplot_index].step( x_inds, my_hist[:, i].flatten(), label=(str(y_bins[i]) + ' to ' + str(y_bins[i + 1])), **kwargs, ) - self.axes[subplot_index][0].legend() + self.axes[subplot_index].legend() else: my_hist, bins = np.histogram(xdata.values.flatten(), bins=bins, density=density, **hist_kwargs) x_inds = (bins[:-1] + bins[1:]) / 2.0 - self.axes[subplot_index][0].step(x_inds, my_hist, **kwargs) + self.axes[subplot_index].step(x_inds, my_hist, **kwargs) # Set Title if set_title is None: @@ -465,9 +462,9 @@ def plot_stairstep( dt_utils.numpy_to_arm_date(self._ds[dsname].time.values[0]), ] ) - self.axes[subplot_index][0].set_title(set_title) - self.axes[subplot_index][0].set_ylabel('count') - self.axes[subplot_index][0].set_xlabel(xtitle) + self.axes[subplot_index].set_title(set_title) + self.axes[subplot_index].set_ylabel('count') + self.axes[subplot_index].set_xlabel(xtitle) return_dict = {} return_dict['plot_handle'] = self.axes[subplot_index] @@ -569,10 +566,10 @@ def plot_heatmap( # Get the current plotting axis, add day/night background and plot data if self.fig is None: self.fig = plt.figure() + if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) if 'units' in ydata.attrs: ytitle = ''.join(['(', ydata.attrs['units'], ')']) @@ -598,7 +595,7 @@ def plot_heatmap( x_inds = (x_bins[:-1] + x_bins[1:]) / 2.0 y_inds = (y_bins[:-1] + y_bins[1:]) / 2.0 xi, yi = np.meshgrid(x_inds, y_inds, indexing='ij') - mesh = self.axes[subplot_index][0].pcolormesh(xi, yi, my_hist, shading=set_shading, **kwargs) + mesh = self.axes[subplot_index].pcolormesh(xi, yi, my_hist, shading=set_shading, **kwargs) # Set Title if set_title is None: @@ -609,13 +606,13 @@ def plot_heatmap( dt_utils.numpy_to_arm_date(self._ds[dsname].time.values[0]), ] ) - self.axes[subplot_index][0].set_title(set_title) - self.axes[subplot_index][0].set_ylabel(ytitle) - self.axes[subplot_index][0].set_xlabel(xtitle) + self.axes[subplot_index].set_title(set_title) + self.axes[subplot_index].set_ylabel(ytitle) + self.axes[subplot_index].set_xlabel(xtitle) self.add_colorbar(mesh, title='count', subplot_index=subplot_index) return_dict = {} - return_dict['plot_handle'] = self.axes[subplot_index][0] + return_dict['plot_handle'] = self.axes[subplot_index] return_dict['x_bins'] = x_bins return_dict['y_bins'] = y_bins return_dict['histogram'] = my_hist @@ -635,9 +632,9 @@ def set_ratio_line(self, subplot_index=(0, )): if self.axes is None: raise RuntimeError('set_ratio_line requires the plot to be displayed.') # Define the xticks of the figure - xlims = self.axes[subplot_index][0].get_xticks() - ratio = np.linspace(xlims[0], xlims[-1]) - self.axes[subplot_index][0].plot(ratio, ratio, 'k--') + xlims = self.axes[subplot_index].get_xticks() + ratio = np.linspace(xlims, xlims[-1]) + self.axes[subplot_index].plot(ratio, ratio, 'k--') def plot_scatter(self, x_field, @@ -714,12 +711,11 @@ def plot_scatter(self, # Define the axes for the figure if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) # Display the scatter plot, pass keyword args for unspecified attributes - scc = self.axes[subplot_index][0].scatter(xdata, ydata, c=mdata, **kwargs) + scc = self.axes[subplot_index].scatter(xdata, ydata, c=mdata, **kwargs) # Set Title if set_title is None: @@ -746,9 +742,9 @@ def plot_scatter(self, cbar.ax.set_ylabel(ztitle) # Define the axe title, x-axis label, y-axis label - self.axes[subplot_index][0].set_title(set_title) - self.axes[subplot_index][0].set_ylabel(ytitle) - self.axes[subplot_index][0].set_xlabel(xtitle) + self.axes[subplot_index].set_title(set_title) + self.axes[subplot_index].set_ylabel(ytitle) + self.axes[subplot_index].set_xlabel(xtitle) return self.axes[subplot_index] @@ -816,9 +812,8 @@ def plot_violin(self, # Define the axes for the figure if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) # Define the axe label. If units are avaiable, plot. if 'units' in ndata.attrs: @@ -827,14 +822,14 @@ def plot_violin(self, axtitle = field # Display the scatter plot, pass keyword args for unspecified attributes - scc = self.axes[subplot_index][0].violinplot(ndata, - positions=positions, - vert=vert, - showmeans=showmeans, - showmedians=showmedians, - showextrema=showextrema, - **kwargs - ) + scc = self.axes[subplot_index].violinplot(ndata, + positions=positions, + vert=vert, + showmeans=showmeans, + showmedians=showmedians, + showextrema=showextrema, + **kwargs + ) if showmeans is True: scc['cmeans'].set_edgecolor('red') scc['cmeans'].set_label('mean') @@ -852,14 +847,14 @@ def plot_violin(self, ) # Define the axe title, x-axis label, y-axis label - self.axes[subplot_index][0].set_title(set_title) + self.axes[subplot_index].set_title(set_title) if vert is True: - self.axes[subplot_index][0].set_ylabel(axtitle) + self.axes[subplot_index].set_ylabel(axtitle) if positions is None: - self.axes[subplot_index][0].set_xticks([]) + self.axes[subplot_index].set_xticks([]) else: - self.axes[subplot_index][0].set_xlabel(axtitle) + self.axes[subplot_index].set_xlabel(axtitle) if positions is None: - self.axes[subplot_index][0].set_yticks([]) + self.axes[subplot_index].set_yticks([]) - return self.axes[subplot_index][0] + return self.axes[subplot_index] diff --git a/act/plotting/geodisplay.py b/act/plotting/geodisplay.py index 2747aaa1a5..b01d425b43 100644 --- a/act/plotting/geodisplay.py +++ b/act/plotting/geodisplay.py @@ -44,7 +44,7 @@ def __init__(self, ds, ds_name=None, **kwargs): raise ImportError( 'Cartopy needs to be installed on your ' 'system to make geographic display plots.' ) - super().__init__(ds, ds_name, secondary_y_allowed=False, **kwargs) + super().__init__(ds, ds_name, **kwargs) if self.fig is None: self.fig = plt.figure(**kwargs) diff --git a/act/plotting/plot.py b/act/plotting/plot.py index 2dccff414b..0d5a31eca1 100644 --- a/act/plotting/plot.py +++ b/act/plotting/plot.py @@ -66,16 +66,13 @@ class with this set to None will create a new figure handle. See the loaded. subplot_kw : dict, optional The kwargs to pass into :func:`fig.subplots` - secondary_y_allowed : boolean - If the plot type allows a secondary y axis - **kwargs : keywords arguments Keyword arguments passed to :func:`plt.figure`. """ def __init__(self, ds, subplot_shape=(1,), ds_name=None, subplot_kw=None, - secondary_y_allowed=True, **kwargs): + **kwargs): if isinstance(ds, xr.Dataset): if 'datastream' in ds.attrs.keys() is not None: self._ds = {ds.attrs['datastream']: ds} @@ -123,11 +120,10 @@ def __init__(self, ds, subplot_shape=(1,), ds_name=None, subplot_kw=None, self.plot_vars = [] self.cbs = [] if subplot_shape is not None: - self.add_subplots(subplot_shape, subplot_kw=subplot_kw, - secondary_y_allowed=secondary_y_allowed, **kwargs) + self.add_subplots(subplot_shape, subplot_kw=subplot_kw, **kwargs) def add_subplots(self, subplot_shape=(1,), secondary_y=False, subplot_kw=None, - secondary_y_allowed=True, **kwargs): + **kwargs): """ Adds subplots to the Display object. The current figure in the object will be deleted and overwritten. @@ -165,29 +161,11 @@ def add_subplots(self, subplot_shape=(1,), secondary_y=False, subplot_kw=None, self.yrng = np.zeros((subplot_shape[0], 2)) else: raise ValueError('subplot_shape must be a 1 or 2 dimensional' + 'tuple list, or array!') - # Create dummy ax to add secondary y-axes to - if secondary_y_allowed: - dummy_ax = np.empty(list(ax.shape) + [2], dtype=plt.Axes) - for i, axis in enumerate(dummy_ax): - if len(axis.shape) == 1: - dummy_ax[i, 0] = ax[i] - try: - dummy_ax[i, 1] = ax[i].twinx() - except Exception: - dummy_ax[i, 1] = None - else: - for j, axis2 in enumerate(axis): - dummy_ax[i, j, 0] = ax[i, j] - try: - dummy_ax[i, j, 1] = ax[i, j].twinx() - except Exception: - dummy_ax[i, j, 1] = None - else: - dummy_ax = ax + self.fig = fig - self.axes = dummy_ax + self.axes = ax - def put_display_in_subplot(self, display, subplot_index, y_axis_index=0): + def put_display_in_subplot(self, display, subplot_index): """ This will place a Display object into a specific subplot. The display object must only have one subplot. @@ -211,17 +189,12 @@ def put_display_in_subplot(self, display, subplot_index, y_axis_index=0): raise RuntimeError( 'Only single plots can be made as subplots ' + 'of another Display object!' ) - if len(np.shape(display.axes)) == 1: - my_projection = display.axes[0].name - else: - my_projection = display.axes[0][y_axis_index].name + my_projection = display.axes[0].name plt.close(display.fig) display.fig = self.fig - if len(np.shape(self.axes)) == 1: - self.fig.delaxes(self.axes[subplot_index]) - else: - self.fig.delaxes(self.axes[subplot_index][y_axis_index]) + self.fig.delaxes(self.axes[subplot_index]) + the_shape = self.axes.shape if len(the_shape) == 1: second_value = 1 @@ -292,10 +265,7 @@ def add_colorbar(self, mappable, title=None, subplot_index=(0,), pad=None, raise RuntimeError('add_colorbar requires the plot ' 'to be displayed.') fig = self.fig - if np.size(self.axes[subplot_index]) > 1: - ax = self.axes[subplot_index][0] - else: - ax = self.axes[subplot_index] + ax = self.axes[subplot_index] if pad is None: pad = 0.01 @@ -381,8 +351,6 @@ def plot_group(self, func_name, dsname=None, **kwargs): raise RuntimeError("The specified string is not a function of " "the Display object.") subplot_shape = self.display.axes.shape - if len(subplot_shape) > 2: - subplot_shape = subplot_shape[0:-1] i = 0 wrap_around = False @@ -399,7 +367,7 @@ def plot_group(self, func_name, dsname=None, **kwargs): if len(subplot_shape) == 2: subplot_index = (int(i / subplot_shape[1]), i % subplot_shape[1]) else: - subplot_index = (i % subplot_shape[0], 0) + subplot_index = (i % subplot_shape[0],) args, varargs, varkw, _, _, _, _ = inspect.getfullargspec(func) if "subplot_index" in args: kwargs["subplot_index"] = subplot_index @@ -433,21 +401,16 @@ def plot_group(self, func_name, dsname=None, **kwargs): if len(subplot_shape) == 2: subplot_index = (int(i / subplot_shape[1]), i % subplot_shape[1]) else: - subplot_index = (i % subplot_shape[0], 0) - if np.size(self.display.axes) == 1: - self.display.axes[subplot_index].axis('off') - elif np.size(self.display.axes[subplot_index]) > 1: - self.display.axes[subplot_index][0].axis('off') - self.display.axes[subplot_index][1].axis('off') - else: - self.display.axes[subplot_index].axis('off') + subplot_index = (i % subplot_shape[0],) + self.display.axes[subplot_index].axis('off') i = i + 1 for i in range(1, np.prod(subplot_shape)): if len(subplot_shape) == 2: subplot_index = (int(i / subplot_shape[1]), i % subplot_shape[1]) else: - subplot_index = (i % subplot_shape[0], 0) + subplot_index = (i % subplot_shape[0],) + try: self.display.axes[subplot_index].get_legend().remove() except AttributeError: @@ -456,10 +419,7 @@ def plot_group(self, func_name, dsname=None, **kwargs): key_list = list(self.display._ds.keys()) for k in key_list: time_min, time_max = self.xlims[k] - if len(self.mapping[k]) == 1: - subplot_index = self.mapping[k] + (0,) - else: - subplot_index = self.mapping[k] + subplot_index = self.mapping[k] self.display.set_xrng([time_min, time_max], subplot_index) self.display._ds = old_ds diff --git a/act/plotting/skewtdisplay.py b/act/plotting/skewtdisplay.py index 70c8c38604..4be051a4c8 100644 --- a/act/plotting/skewtdisplay.py +++ b/act/plotting/skewtdisplay.py @@ -57,7 +57,7 @@ def __init__(self, ds, subplot_shape=(1,), subplot=None, ds_name=None, set_fig=N # one new_kwargs = kwargs.copy() super().__init__(ds, None, ds_name, subplot_kw=dict(projection='skewx'), - secondary_y_allowed=False, **new_kwargs) + **new_kwargs) # Make a SkewT object for each subplot self.add_subplots(subplot_shape, set_fig=set_fig, subplot=subplot, **kwargs) diff --git a/act/plotting/timeseriesdisplay.py b/act/plotting/timeseriesdisplay.py index b8044fe92c..8586502af0 100644 --- a/act/plotting/timeseriesdisplay.py +++ b/act/plotting/timeseriesdisplay.py @@ -51,8 +51,8 @@ class TimeSeriesDisplay(Display): """ - def __init__(self, ds, subplot_shape=(1,), ds_name=None, secondary_y_allowed=True, **kwargs): - super().__init__(ds, subplot_shape, ds_name, secondary_y_allowed=True, **kwargs) + def __init__(self, ds, subplot_shape=(1,), ds_name=None, **kwargs): + super().__init__(ds, subplot_shape, ds_name, **kwargs) def day_night_background(self, dsname=None, subplot_index=(0,)): """ @@ -94,11 +94,7 @@ def day_night_background(self, dsname=None, subplot_index=(0,)): if self.axes is None: raise RuntimeError('day_night_background requires the plot to ' 'be displayed.') - # Default to the left axis - if np.size(self.axes[subplot_index]) > 1: - ax = self.axes[subplot_index][0] - else: - ax = self.axes[subplot_index] + ax = self.axes[subplot_index] # Find variable names for latitude and longitude variables = list(self._ds[dsname].data_vars) @@ -200,7 +196,7 @@ def day_night_background(self, dsname=None, subplot_index=(0,)): for ii in noon: ax.axvline(x=ii, linestyle='--', color='y', zorder=1) - def set_xrng(self, xrng, subplot_index=(0, 0), y_axis_index=0): + def set_xrng(self, xrng, subplot_index=(0, 0)): """ Sets the x range of the plot. @@ -228,10 +224,7 @@ def set_xrng(self, xrng, subplot_index=(0, 0), y_axis_index=0): 'Expanding range by 2 seconds.\n') xrng[0] -= dt.timedelta(seconds=1) xrng[1] += dt.timedelta(seconds=1) - if np.size(self.axes[subplot_index]) > 1: - self.axes[subplot_index][y_axis_index].set_xlim(xrng) - else: - self.axes[subplot_index].set_xlim(xrng) + self.axes[subplot_index].set_xlim(xrng) # Make sure that the xrng value is a numpy array not pandas if isinstance(xrng[0], pd.Timestamp): @@ -248,7 +241,7 @@ def set_xrng(self, xrng, subplot_index=(0, 0), y_axis_index=0): self.xrng[subplot_index][0] = xrng[0].astype('datetime64[D]').astype(float) self.xrng[subplot_index][1] = xrng[1].astype('datetime64[D]').astype(float) - def set_yrng(self, yrng, subplot_index=(0,), match_axes_ylimits=False, y_axis_index=0): + def set_yrng(self, yrng, subplot_index=(0,), match_axes_ylimits=False): """ Sets the y range of the plot. @@ -263,8 +256,6 @@ def set_yrng(self, yrng, subplot_index=(0,), match_axes_ylimits=False, y_axis_in If True, all axes in the display object will have matching provided ylims. Default is False. This is especially useful when utilizing a groupby display with many axes. - y_axis_index : int - 0 = left y axis, 1 = right y axis """ if self.axes is None: @@ -282,12 +273,9 @@ def set_yrng(self, yrng, subplot_index=(0,), match_axes_ylimits=False, y_axis_in if match_axes_ylimits: for i in range(self.axes.shape[0]): for j in range(self.axes.shape[1]): - self.axes[i, j, y_axis_index].set_ylim(yrng) + self.axes[i, j].set_ylim(yrng) else: - if np.size(self.axes[subplot_index]) > 1: - self.axes[subplot_index][y_axis_index].set_ylim(yrng) - else: - self.axes[subplot_index].set_ylim(yrng) + self.axes[subplot_index].set_ylim(yrng) try: self.yrng[subplot_index, :] = yrng @@ -298,7 +286,7 @@ def plot( self, field, dsname=None, - subplot_index=(0, ), + subplot_index=(0,), cmap=None, set_title=None, add_nan=False, @@ -322,7 +310,6 @@ def plot( labels=False, cbar_label=None, cbar_h_adjust=None, - secondary_y=False, y_axis_flag_meanings=False, colorbar_labels=None, cb_friendly=False, @@ -402,9 +389,6 @@ def plot( cbar_h_adjust : float Option to adjust location of colorbar horizontally. Positive values move to right negative values move to left. - secondary_y : boolean - Option to plot on secondary y axis. - This will automatically change the color of the axis to match the line y_axis_flag_meanings : boolean or int When set to True and plotting state variable with flag_values and flag_meanings attributes will replace y axis numerical values @@ -503,22 +487,10 @@ def plot( self.fig = plt.figure() if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) - - if secondary_y is False: - y_axis_index = 0 - if np.size(self.axes[subplot_index]) > 1: - ax = self.axes[subplot_index][y_axis_index] - self.axes[subplot_index][1].get_yaxis().set_visible(False) - else: - ax = self.axes[subplot_index] - else: - y_axis_index = 1 - ax = self.axes[subplot_index][y_axis_index] - self.axes[subplot_index][1].get_yaxis().set_visible(True) - match_line_label_color = True + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) + + ax = self.axes[subplot_index] if colorbar_labels is not None: flag_values = list(colorbar_labels.keys()) @@ -669,18 +641,12 @@ def plot( else: set_title = ' '.join([dsname, field]) - if secondary_y is False: - ax.set_title(set_title) + ax.set_title(set_title) # Set YTitle if not y_axis_flag_meanings: if match_line_label_color and len(ax.get_lines()) > 0: ax.set_ylabel(ytitle, color=ax.get_lines()[0].get_color()) - ax.tick_params(axis='y', colors=ax.get_lines()[0].get_color()) - if y_axis_index == 0: - ax.spines['left'].set_color(ax.get_lines()[0].get_color()) - if y_axis_index == 1: - ax.spines['right'].set_color(ax.get_lines()[0].get_color()) else: ax.set_ylabel(ytitle) @@ -740,7 +706,7 @@ def plot( if yrng[1] > current_yrng[1]: yrng[1] = current_yrng[1] - self.set_yrng(yrng, subplot_index, y_axis_index=y_axis_index) + self.set_yrng(yrng, subplot_index) # Set X Format if len(subplot_index) == 1: @@ -990,17 +956,10 @@ def plot_barbs_from_u_v( # Set up or get current axes if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) - # Setting up in case there is a use case in the future for a secondary y - y_axis_index = 0 - - if len(np.shape(self.axes)) == 1: - ax = self.axes[subplot_index] - else: - ax = self.axes[subplot_index][y_axis_index] + ax = self.axes[subplot_index] if ydata is None: ydata = np.ones(xdata.shape) @@ -1118,10 +1077,8 @@ def plot_barbs_from_u_v( myFmt = common.get_date_format(days) ax.xaxis.set_major_formatter(myFmt) - if len(np.shape(self.axes)) == 1: - self.axes[subplot_index] = ax - else: - self.axes[subplot_index][y_axis_index] = ax + self.axes[subplot_index] = ax + return self.axes[subplot_index] def plot_time_height_xsection_from_1d_data( @@ -1221,17 +1178,10 @@ def plot_time_height_xsection_from_1d_data( # Set up or get current axes if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) - - # Setting up in case there is a use case in the future for a secondary y - y_axis_index = 0 + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) - if len(np.shape(self.axes)) == 1: - ax = self.axes[subplot_index] - else: - ax = self.axes[subplot_index][y_axis_index] + ax = self.axes[subplot_index] mesh = ax.pcolormesh( x_times, y_levels, np.transpose(data), shading=set_shading, **kwargs @@ -1485,15 +1435,10 @@ def qc_flag_block_plot( # Set up or get current axes if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) - - # Setting y_axis_index in case there is a use case in the future - # to plot the QC on the secondary y-axis - y_axis_index = 0 + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) - ax = self.axes[subplot_index][y_axis_index] + ax = self.axes[subplot_index] # Set X Limit - We want the same time axes for all subplots data = self._ds[dsname][data_field] @@ -1712,7 +1657,6 @@ def fill_between( dsname=None, subplot_index=(0,), set_title=None, - secondary_y=False, **kwargs, ): """ @@ -1731,8 +1675,6 @@ def fill_between( The index of the subplot to set the x range of. set_title : str The title for the plot. - secondary_y : boolean - Option to indicate if the data should be plotted on second y-axis. **kwargs : keyword arguments The keyword arguments for :func:`plt.plot` (1D timeseries) or :func:`plt.pcolormesh` (2D timeseries). @@ -1767,17 +1709,11 @@ def fill_between( self.fig = plt.figure() if self.axes is None: - self.axes = np.array([[plt.axes(), plt.axes().twinx()]]) - for a in self.axes[0]: - self.fig.add_axes(a) + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) # Set ax to appropriate axis - if secondary_y is False: - y_axis_index = 0 - ax = self.axes[subplot_index][y_axis_index] - else: - y_axis_index = 1 - ax = self.axes[subplot_index][y_axis_index] + ax = self.axes[subplot_index] ax.fill_between(xdata.values, data, **kwargs) @@ -1814,7 +1750,6 @@ def fill_between( dt_utils.numpy_to_arm_date(self._ds[dsname].time.values[0]), ] ) - if secondary_y is False: - ax.set_title(set_title) - self.axes[subplot_index][y_axis_index] = ax + ax.set_title(set_title) + self.axes[subplot_index] = ax return self.axes[subplot_index] diff --git a/act/plotting/windrosedisplay.py b/act/plotting/windrosedisplay.py index 105710cae7..ef128c2ccc 100644 --- a/act/plotting/windrosedisplay.py +++ b/act/plotting/windrosedisplay.py @@ -37,7 +37,7 @@ class and has therefore has the same attributes as that class. def __init__(self, ds, subplot_shape=(1,), ds_name=None, **kwargs): super().__init__(ds, subplot_shape, ds_name, subplot_kw=dict(projection='polar'), - secondary_y_allowed=False, **kwargs) + **kwargs) def set_thetarng(self, trng=(0.0, 360.0), subplot_index=(0,)): """ @@ -195,15 +195,7 @@ def plot( our_cmap = matplotlib.colormaps.get_cmap(cmap) our_colors = our_cmap(np.linspace(0, 1, len(spd_bins))) - # Make sure we're dealing with the right axes style - if len(np.shape(self.axes)) == 1: - ax = self.axes[subplot_index] - if np.size(ax) > 1: - ax = ax[0] - elif len(self.axes[subplot_index]) == 2: - ax = self.axes[subplot_index][0] - else: - ax = self.axes[subplot_index] + ax = self.axes[subplot_index] bars = [ ax.bar( @@ -262,12 +254,7 @@ def plot( ) ax.set_title(set_title) - if len(np.shape(self.axes)) == 1: - self.axes[subplot_index] = ax - elif len(self.axes[subplot_index]) == 2: - self.axes[subplot_index][0] = ax - else: - self.axes[subplot_index] = ax + self.axes[subplot_index] = ax return ax diff --git a/act/plotting/xsectiondisplay.py b/act/plotting/xsectiondisplay.py index 86e2a8ef51..d989594c0c 100644 --- a/act/plotting/xsectiondisplay.py +++ b/act/plotting/xsectiondisplay.py @@ -72,7 +72,7 @@ class and has therefore has the same attributes as that class. """ def __init__(self, ds, subplot_shape=(1,), ds_name=None, **kwargs): - super().__init__(ds, subplot_shape, ds_name, secondary_y_allowed=False, **kwargs) + super().__init__(ds, subplot_shape, ds_name, **kwargs) def set_subplot_to_map(self, subplot_index): total_num_plots = self.axes.shape diff --git a/act/tests/test_plotting.py b/act/tests/test_plotting.py index baa8ebd0f9..bc571dfb9c 100644 --- a/act/tests/test_plotting.py +++ b/act/tests/test_plotting.py @@ -53,14 +53,10 @@ def test_plot(): windrose = WindRoseDisplay(met) display.put_display_in_subplot(windrose, subplot_index=(1, 1)) windrose.plot('wdir_vec_mean', 'wspd_vec_mean', spd_bins=np.linspace(0, 10, 4)) - windrose.axes[0, 0].legend(loc='best') + windrose.axes[0].legend(loc='best') met.close() return display.fig - try: - return display.fig - finally: - matplotlib.pyplot.close(display.fig) def test_errors(): @@ -1334,15 +1330,3 @@ def test_scatter(): ds.close() return display.fig - - -@pytest.mark.mpl_image_compare(tolerance=30) -def test_secondary_y(): - ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET1) - display = act.plotting.TimeSeriesDisplay(ds, figsize=(10, 6)) - display.plot('temp_mean', match_line_label_color=True) - display.plot('rh_mean', secondary_y=True, color='orange') - display.day_night_background() - ds.close() - - return display.fig diff --git a/examples/discovery/plot_asos_temp.py b/examples/discovery/plot_asos_temp.py index d145d1212d..8e592ef54e 100644 --- a/examples/discovery/plot_asos_temp.py +++ b/examples/discovery/plot_asos_temp.py @@ -17,5 +17,5 @@ display = act.plotting.TimeSeriesDisplay(my_asoses['ORD'], subplot_shape=(2,), figsize=(15, 10)) display.plot('temp', subplot_index=(0,)) display.plot_barbs_from_u_v(u_field='u', v_field='v', subplot_index=(1,)) -display.axes[1, 0].set_ylim([0, 2]) +display.axes[1].set_ylim([0, 2]) plt.show() diff --git a/examples/discovery/plot_noaa_fmcw_moment.py b/examples/discovery/plot_noaa_fmcw_moment.py index 25f3d1c05b..50bad32ead 100644 --- a/examples/discovery/plot_noaa_fmcw_moment.py +++ b/examples/discovery/plot_noaa_fmcw_moment.py @@ -55,5 +55,5 @@ subplot_index=(1,), ) # Adjust ylims of parsivel plot. -display.axes[1, 0].set_ylim([0, 10]) +display.axes[1].set_ylim([0, 10]) plt.show() diff --git a/examples/plotting/plot_scatter.py b/examples/plotting/plot_scatter.py index 61f8fef229..dd9202f05d 100644 --- a/examples/plotting/plot_scatter.py +++ b/examples/plotting/plot_scatter.py @@ -46,19 +46,19 @@ p = np.poly1d(z) # Plot the best fit line -display.axes[0, 0].plot(ds['true_airspeed'], - p(ds['true_airspeed']), - 'r', - linewidth=2 - ) +display.axes[0].plot(ds['true_airspeed'], + p(ds['true_airspeed']), + 'r', + linewidth=2 + ) # Display the line equation -display.axes[0, 0].text(45, - 135, - "y = %.3fx + (%.3f)" % (z[0], z[1]), - color='r', - fontsize=12 - ) +display.axes[0].text(45, + 135, + "y = %.3fx + (%.3f)" % (z[0], z[1]), + color='r', + fontsize=12 + ) # Calculate Pearson Correlation Coefficient cc_conc = pearsonr(ds['true_airspeed'], @@ -66,18 +66,18 @@ ) # Display the Pearson CC -display.axes[0, 0].text(45, - 130, - "Pearson CC: %.2f" % (cc_conc[0]), - fontsize=12 - ) +display.axes[0].text(45, + 130, + "Pearson CC: %.2f" % (cc_conc[0]), + fontsize=12 + ) # Display the total number of samples -display.axes[0, 0].text(45, - 125, - "N = %.0f" % (ds['true_airspeed'].data.shape[0]), - fontsize=12 - ) +display.axes[0].text(45, + 125, + "N = %.0f" % (ds['true_airspeed'].data.shape[0]), + fontsize=12 + ) # Display the 1:1 ratio line display.set_ratio_line() diff --git a/examples/plotting/plot_secondary_y.py b/examples/plotting/plot_secondary_y.py index a7b2cae3b1..d6062857e9 100644 --- a/examples/plotting/plot_secondary_y.py +++ b/examples/plotting/plot_secondary_y.py @@ -2,12 +2,8 @@ Secondary Y-Axis Plotting ------------------------- -This example shows how to use the new capability -to plot on the secondary y-axis. Previous versions -of ACT only returned one axis object per plot, even -when there was a secondary y-axis. The new functionality -will return two axis objects per plot for the left and -right y axes. +This example shows how to plot on the secondary y-axis +using Matplotlib functionality. The secondary_y functionality has been removed from ACT. """ @@ -25,27 +21,21 @@ display = act.plotting.TimeSeriesDisplay(ds, figsize=(10, 6)) # Plot the data and make the y-axes color match the lines -# Note, you need to specify the color for the secondary-y plot -# if you want it different from the primary y-axis display.plot('temp_mean', match_line_label_color=True) -display.plot('rh_mean', secondary_y=True, color='orange') display.day_night_background() -# In a slight change, the axes returned as part of the display object -# for TimeSeries and DistributionDisplay now return a 2D array instead -# of a 1D array. The second dimension is the axes object for the right -# axis which is automatically created. -# It can still be used like before for modifications after ACT plotting - -# The left axis will have an index of 0 -# \/ -display.axes[0, 0].set_yticks([-5, 0, 5]) -display.axes[0, 0].set_yticklabels(["That's cold", "Freezing", "Above Freezing"]) - -# The right axis will have an index of 1 -# \/ -display.axes[0, 1].set_yticks([65, 75, 85]) -display.axes[0, 1].set_yticklabels(['Not as humid', 'Slightly Humid', 'Humid']) +# Get the secondary y-axes and plot the RH on it +ax2 = display.axes[0].twinx() +ax2.plot(ds['time'], ds['rh_mean'], color='orange') + +# Then the axes can be updated and modified through the normal matplotlib calls. +display.axes[0].set_yticks([-5, 0, 5]) +display.axes[0].set_yticklabels(["That's cold", "Freezing", "Above Freezing"]) + +# Secondary y-axis will use the ax2 axes +ax2.set_yticks([65, 75, 85]) +ax2.set_yticklabels(['Not as humid', 'Slightly Humid', 'Humid']) +ax2.set_ylabel('Relative Humidity (%)', color='orange') plt.tight_layout() plt.show() diff --git a/examples/plotting/plot_violin.py b/examples/plotting/plot_violin.py index d3dbcaf1a8..ce343c8078 100644 --- a/examples/plotting/plot_violin.py +++ b/examples/plotting/plot_violin.py @@ -35,10 +35,10 @@ ) # Update the tick information -display.axes[0, 0].set_xticks([0.5, 1, 2, 2.5]) +display.axes[0].set_xticks([0.5, 1, 2, 2.5]) ticks = ['', 'Ambient Air\nTemp', 'Total\nTemperature', ''] -display.axes[0, 0].set_xticklabels(ticks) +display.axes[0].set_xticklabels(ticks) # Update the y-axis label -display.axes[0, 0].set_ylabel('Temperature Observations [C]') +display.axes[0].set_ylabel('Temperature Observations [C]') plt.show() diff --git a/examples/workflows/plot_weighted_average.py b/examples/workflows/plot_weighted_average.py index 4f8b82fd28..b0645456fc 100644 --- a/examples/workflows/plot_weighted_average.py +++ b/examples/workflows/plot_weighted_average.py @@ -101,5 +101,5 @@ ) display.plot('weighted_mean_accumulated', dsname='weighted', color='k', label='Weighted Avg') display.day_night_background('sgpmetE13.b1') -display.axes[0, 0].legend() +display.axes[0].legend() plt.show() From 363ef69d88dbf859bfa5ee2a057f2b3930012ad0 Mon Sep 17 00:00:00 2001 From: Ken Kehoe Date: Tue, 12 Dec 2023 12:48:51 -0700 Subject: [PATCH 2/2] Correctly inserting two nan values for 2D plots to ensure minimal streaking in plot. (#770) * Correctly inserting two nan values for 2D plots to ensure minimal streaking in plot. * ENH: Adding in simple test * ENH: PEP8 --------- Co-authored-by: AdamTheisen --- act/tests/test_utils.py | 11 +++++++++++ act/utils/data_utils.py | 23 +++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/act/tests/test_utils.py b/act/tests/test_utils.py index 5a5faf5dd0..f62a6a9360 100644 --- a/act/tests/test_utils.py +++ b/act/tests/test_utils.py @@ -82,6 +82,17 @@ def test_add_in_nan(): index = np.where(time_filled == np.datetime64('2019-01-01T01:40'))[0] assert index.size == 0 + # Test for 2D data + time = np.arange('2019-01-01T01:00', '2019-01-01T02:00', dtype='datetime64[m]') + data = np.random.random((len(time), 25)) + + time = np.delete(time, range(3, 8)) + data = np.delete(data, range(3, 8), axis=0) + time_filled, data_filled = act.utils.add_in_nan(time, data) + + assert np.count_nonzero(np.isnan(data_filled[3, :])) == 25 + assert len(time_filled) == len(time) + 2 + def test_get_missing_value(): ds = act.io.arm.read_arm_netcdf(act.tests.sample_files.EXAMPLE_EBBR1) diff --git a/act/utils/data_utils.py b/act/utils/data_utils.py index 0e7e24fa99..5d499338dc 100644 --- a/act/utils/data_utils.py +++ b/act/utils/data_utils.py @@ -427,6 +427,7 @@ def add_in_nan(time, data): # Leaving code in here in case we need to update. # diff = np.diff(time.astype('datetime64[s]'), 1) diff = np.diff(time, 1) + # Wrapping in a try to catch error while switching between numpy 1.10 to 1.11 try: mode = stats.mode(diff, keepdims=True).mode[0] @@ -437,10 +438,24 @@ def add_in_nan(time, data): offset = 0 for i in index[0]: corr_i = i + offset - time_added = time[corr_i] + (time[corr_i + 1] - time[corr_i]) / 2.0 - time = np.insert(time, corr_i + 1, time_added) - data = np.insert(data, corr_i + 1, np.nan, axis=0) - offset += 1 + + if len(data.shape) == 1: + # For line plotting adding a NaN will stop the connection of the line + # between points. So we just need to add a NaN anywhere between the points. + corr_i = i + offset + time_added = time[corr_i] + (time[corr_i + 1] - time[corr_i]) / 2.0 + time = np.insert(time, corr_i + 1, time_added) + data = np.insert(data, corr_i + 1, np.nan, axis=0) + offset += 1 + else: + # For 2D plots need to add a NaN right after and right before the data + # to correctly mitigate streaking with pcolormesh. + time_added_1 = time[corr_i] + 1 # One time step after + time_added_2 = time[corr_i + 1] - 1 # One time step before + time = np.insert(time, corr_i + 1, [time_added_1, time_added_2]) + data = np.insert(data, corr_i + 1, np.nan, axis=0) + data = np.insert(data, corr_i + 2, np.nan, axis=0) + offset += 2 if time_is_DataArray: time = xr.DataArray(time, attrs=time_attributes, dims=time_dims)