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 new file mode 100644 index 0000000000..bc571dfb9c --- /dev/null +++ b/act/tests/test_plotting.py @@ -0,0 +1,1332 @@ +import glob +from datetime import datetime +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import pytest +import xarray as xr +from numpy.testing import assert_allclose + +import act +import act.tests.sample_files as sample_files +from act.plotting import ( + ContourDisplay, + DistributionDisplay, + GeographicPlotDisplay, + SkewTDisplay, + TimeSeriesDisplay, + WindRoseDisplay, + XSectionDisplay, +) +from act.utils.data_utils import accumulate_precip + +try: + import cartopy + + CARTOPY_AVAILABLE = True +except ImportError: + CARTOPY_AVAILABLE = False + +matplotlib.use('Agg') + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_plot(): + # Process MET data to get simple LCL + files = sample_files.EXAMPLE_MET_WILDCARD + met = act.io.arm.read_arm_netcdf(files) + met_temp = met.temp_mean + met_rh = met.rh_mean + met_lcl = (20.0 + met_temp / 5.0) * (100.0 - met_rh) / 1000.0 + met['met_lcl'] = met_lcl * 1000.0 + met['met_lcl'].attrs['units'] = 'm' + met['met_lcl'].attrs['long_name'] = 'LCL Calculated from SGP MET E13' + + # Plot data + display = TimeSeriesDisplay(met) + display.add_subplots((2, 2), figsize=(15, 10)) + display.plot('wspd_vec_mean', subplot_index=(0, 0)) + display.plot('temp_mean', subplot_index=(1, 0)) + display.plot('rh_mean', subplot_index=(0, 1)) + + 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].legend(loc='best') + met.close() + + return display.fig + + +def test_errors(): + files = sample_files.EXAMPLE_MET_WILDCARD + ds = act.io.arm.read_arm_netcdf(files) + + display = TimeSeriesDisplay(ds) + display.axes = None + with np.testing.assert_raises(RuntimeError): + display.day_night_background() + + display = TimeSeriesDisplay({'met': ds, 'met2': ds}) + with np.testing.assert_raises(ValueError): + display.plot('temp_mean') + with np.testing.assert_raises(ValueError): + display.qc_flag_block_plot('qc_temp_mean') + with np.testing.assert_raises(ValueError): + display.plot_barbs_from_spd_dir('wdir_vec_mean', 'wspd_vec_mean') + with np.testing.assert_raises(ValueError): + display.plot_barbs_from_u_v('wdir_vec_mean', 'wspd_vec_mean') + + del ds.attrs['_file_dates'] + + data = np.empty(len(ds['time'])) * np.nan + lat = ds['lat'].values + lon = ds['lon'].values + ds['lat'].values = data + ds['lon'].values = data + + display = TimeSeriesDisplay(ds) + display.plot('temp_mean') + display.set_yrng([0, 0]) + with np.testing.assert_warns(RuntimeWarning): + display.day_night_background() + ds['lat'].values = lat + with np.testing.assert_warns(RuntimeWarning): + display.day_night_background() + ds['lon'].values = lon * 100.0 + with np.testing.assert_warns(RuntimeWarning): + display.day_night_background() + ds['lat'].values = lat * 100.0 + with np.testing.assert_warns(RuntimeWarning): + display.day_night_background() + + ds.close() + + # Test some of the other errors + ds = act.io.arm.read_arm_netcdf(files) + del ds['temp_mean'].attrs['units'] + display = TimeSeriesDisplay(ds) + display.axes = None + with np.testing.assert_raises(RuntimeError): + display.set_yrng([0, 10]) + with np.testing.assert_raises(RuntimeError): + display.set_xrng([0, 10]) + display.fig = None + display.plot('temp_mean', add_nan=True) + + assert display.fig is not None + assert display.axes is not None + + with np.testing.assert_raises(AttributeError): + display = TimeSeriesDisplay([]) + + fig, ax = matplotlib.pyplot.subplots() + display = TimeSeriesDisplay(ds) + display.add_subplots((2, 2), figsize=(15, 10)) + display.assign_to_figure_axis(fig, ax) + assert display.fig is not None + assert display.axes is not None + + ds = act.io.arm.read_arm_netcdf(files) + display = TimeSeriesDisplay(ds) + ds.clean.cleanup() + display.axes = None + display.fig = None + display.qc_flag_block_plot('atmos_pressure') + assert display.fig is not None + assert display.axes is not None + + matplotlib.pyplot.close(fig=display.fig) + + +def test_histogram_errors(): + files = sample_files.EXAMPLE_MET1 + ds = act.io.arm.read_arm_netcdf(files) + + histdisplay = DistributionDisplay(ds) + histdisplay.axes = None + with np.testing.assert_raises(RuntimeError): + histdisplay.set_yrng([0, 10]) + with np.testing.assert_raises(RuntimeError): + histdisplay.set_xrng([-40, 40]) + histdisplay.fig = None + histdisplay.plot_stacked_bar('temp_mean', bins=np.arange(-40, 40, 5)) + histdisplay.set_yrng([0, 0]) + assert histdisplay.yrng[0][1] == 1.0 + assert histdisplay.fig is not None + assert histdisplay.axes is not None + + with np.testing.assert_raises(AttributeError): + DistributionDisplay([]) + + histdisplay.axes = None + histdisplay.fig = None + histdisplay.plot_stairstep('temp_mean', bins=np.arange(-40, 40, 5)) + assert histdisplay.fig is not None + assert histdisplay.axes is not None + + sigma = 10 + mu = 50 + bins = np.linspace(0, 100, 50) + ydata = 1 / (sigma * np.sqrt(2 * np.pi)) * np.exp(-((bins - mu) ** 2) / (2 * sigma**2)) + y_array = xr.DataArray(ydata, dims={'time': bins}) + bins = xr.DataArray(bins, dims={'time': bins}) + my_fake_ds = xr.Dataset({'time': bins, 'ydata': y_array}) + histdisplay = DistributionDisplay(my_fake_ds) + histdisplay.axes = None + histdisplay.fig = None + histdisplay.plot_size_distribution('ydata', 'time', set_title='Fake distribution.') + assert histdisplay.fig is not None + assert histdisplay.axes is not None + + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + histdisplay = DistributionDisplay({'sgpsondewnpnC1.b1': sonde_ds}) + histdisplay.axes = None + histdisplay.fig = None + histdisplay.plot_heatmap( + 'tdry', + 'alt', + x_bins=np.arange(-60, 10, 1), + y_bins=np.linspace(0, 10000.0, 50), + cmap='coolwarm', + ) + assert histdisplay.fig is not None + assert histdisplay.axes is not None + + matplotlib.pyplot.close(fig=histdisplay.fig) + + +def test_xsection_errors(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_CEIL1) + + display = XSectionDisplay(ds, figsize=(10, 8), subplot_shape=(2,)) + display.axes = None + with np.testing.assert_raises(RuntimeError): + display.set_yrng([0, 10]) + with np.testing.assert_raises(RuntimeError): + display.set_xrng([-40, 40]) + + display = XSectionDisplay(ds, figsize=(10, 8), subplot_shape=(1,)) + with np.testing.assert_raises(RuntimeError): + display.plot_xsection(None, 'backscatter', x='time', cmap='HomeyerRainbow') + + ds.close() + matplotlib.pyplot.close(fig=display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_multidataset_plot_tuple(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET1) + ds2 = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SIRS) + ds = ds.rename({'lat': 'fun_time'}) + ds['fun_time'].attrs['standard_name'] = 'latitude' + ds = ds.rename({'lon': 'not_so_fun_time'}) + ds['not_so_fun_time'].attrs['standard_name'] = 'longitude' + + # You can use tuples if the datasets in the tuple contain a + # datastream attribute. This is required in all ARM datasets. + display = TimeSeriesDisplay((ds, ds2), subplot_shape=(2,), figsize=(15, 10)) + display.plot('short_direct_normal', 'sgpsirsE13.b1', subplot_index=(0,)) + display.day_night_background('sgpsirsE13.b1', subplot_index=(0,)) + display.plot('temp_mean', 'sgpmetE13.b1', subplot_index=(1,)) + display.day_night_background('sgpmetE13.b1', subplot_index=(1,)) + + ax = act.plotting.common.parse_ax(ax=None) + ax, fig = act.plotting.common.parse_ax_fig(ax=None, fig=None) + ds.close() + ds2.close() + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_multidataset_plot_dict(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET1) + ds2 = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SIRS) + + # You can use tuples if the datasets in the tuple contain a + # datastream attribute. This is required in all ARM datasets. + display = TimeSeriesDisplay({'sirs': ds2, 'met': ds}, subplot_shape=(2,), figsize=(15, 10)) + display.plot('short_direct_normal', 'sirs', subplot_index=(0,)) + display.day_night_background('sirs', subplot_index=(0,)) + display.plot('temp_mean', 'met', subplot_index=(1,)) + display.day_night_background('met', subplot_index=(1,)) + ds.close() + ds2.close() + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_wind_rose(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_TWP_SONDE_WILDCARD) + + WindDisplay = WindRoseDisplay(sonde_ds, figsize=(10, 10)) + WindDisplay.plot( + 'deg', + 'wspd', + spd_bins=np.linspace(0, 20, 10), + num_dirs=30, + tick_interval=2, + cmap='viridis', + ) + WindDisplay.set_thetarng(trng=(0.0, 360.0)) + WindDisplay.set_rrng((0.0, 14)) + + sonde_ds.close() + + try: + return WindDisplay.fig + finally: + matplotlib.pyplot.close(WindDisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_barb_sounding_plot(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_TWP_SONDE_WILDCARD) + BarbDisplay = TimeSeriesDisplay({'sonde_darwin': sonde_ds}) + BarbDisplay.plot_time_height_xsection_from_1d_data( + 'rh', 'pres', cmap='coolwarm_r', vmin=0, vmax=100, num_time_periods=25 + ) + BarbDisplay.plot_barbs_from_spd_dir('wspd', 'deg', 'pres', num_barbs_x=20) + sonde_ds.close() + + try: + return BarbDisplay.fig + finally: + matplotlib.pyplot.close(BarbDisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_skewt_plot(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + skewt = SkewTDisplay(sonde_ds) + skewt.plot_from_u_and_v('u_wind', 'v_wind', 'pres', 'tdry', 'dp') + sonde_ds.close() + try: + return skewt.fig + finally: + matplotlib.pyplot.close(skewt.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_skewt_plot_spd_dir(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + skewt = SkewTDisplay(sonde_ds, ds_name='act_datastream') + skewt.plot_from_spd_and_dir('wspd', 'deg', 'pres', 'tdry', 'dp') + sonde_ds.close() + try: + return skewt.fig + finally: + matplotlib.pyplot.close(skewt.fig) + + +@pytest.mark.mpl_image_compare(tolerance=81) +def test_multi_skewt_plot(): + files = sample_files.EXAMPLE_TWP_SONDE_20060121 + test = {} + for f in files: + time = f.split('.')[-3] + sonde_ds = act.io.arm.read_arm_netcdf(f) + sonde_ds = sonde_ds.resample(time='30s').nearest() + test.update({time: sonde_ds}) + + skewt = SkewTDisplay(test, subplot_shape=(2, 2)) + i = 0 + j = 0 + for f in files: + time = f.split('.')[-3] + skewt.plot_from_spd_and_dir( + 'wspd', + 'deg', + 'pres', + 'tdry', + 'dp', + subplot_index=(j, i), + dsname=time, + p_levels_to_plot=np.arange(10.0, 1000.0, 25), + ) + if j == 1: + i += 1 + j = 0 + elif j == 0: + j += 1 + try: + return skewt.fig + finally: + matplotlib.pyplot.close(skewt.fig) + + +@pytest.mark.mpl_image_compare(tolerance=31) +def test_xsection_plot(): + visst_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_CEIL1) + + xsection = XSectionDisplay(visst_ds, figsize=(10, 8)) + xsection.plot_xsection( + None, 'backscatter', x='time', y='range', cmap='coolwarm', vmin=0, vmax=320 + ) + visst_ds.close() + + try: + return xsection.fig + finally: + matplotlib.pyplot.close(xsection.fig) + + +@pytest.mark.skipif(not CARTOPY_AVAILABLE, reason='Cartopy is not installed.') +@pytest.mark.mpl_image_compare(tolerance=30) +def test_xsection_plot_map(): + radar_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_VISST, combine='nested', concat_dim='time') + xsection = XSectionDisplay(radar_ds, figsize=(15, 8)) + xsection.plot_xsection_map( + None, + 'ir_temperature', + vmin=220, + vmax=300, + cmap='Greys', + x='longitude', + y='latitude', + isel_kwargs={'time': 0}, + ) + radar_ds.close() + return xsection.fig + + try: + xsection = XSectionDisplay(radar_ds, figsize=(15, 8)) + xsection.plot_xsection_map( + None, + 'ir_temperature', + vmin=220, + vmax=300, + cmap='Greys', + x='longitude', + y='latitude', + isel_kwargs={'time': 0}, + ) + radar_ds.close() + try: + return xsection.fig + finally: + matplotlib.pyplot.close(xsection.fig) + except Exception: + pass + + +@pytest.mark.skipif(not CARTOPY_AVAILABLE, reason='Cartopy is not installed.') +@pytest.mark.mpl_image_compare(style="default", tolerance=30) +def test_geoplot(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + geodisplay = GeographicPlotDisplay({'sgpsondewnpnC1.b1': sonde_ds}, figsize=(15, 8)) + try: + geodisplay.geoplot( + 'tdry', + cartopy_feature=[ + 'STATES', + 'LAND', + 'OCEAN', + 'COASTLINE', + 'BORDERS', + 'LAKES', + 'RIVERS', + ], + text={'Ponca City': [-97.0725, 36.7125]}, + img_tile=None, + ) + try: + return geodisplay.fig + finally: + matplotlib.pyplot.close(geodisplay.fig) + except Exception: + pass + sonde_ds.close() + + +@pytest.mark.skipif(not CARTOPY_AVAILABLE, reason='Cartopy is not installed.') +@pytest.mark.mpl_image_compare(style="default", tolerance=30) +def test_geoplot_tile(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + geodisplay = GeographicPlotDisplay({'sgpsondewnpnC1.b1': sonde_ds}, figsize=(15, 8)) + try: + geodisplay.geoplot( + 'tdry', + cartopy_feature=[ + 'STATES', + 'LAND', + 'OCEAN', + 'COASTLINE', + 'BORDERS', + 'LAKES', + 'RIVERS', + ], + text={'Ponca City': [-97.0725, 36.7125]}, + img_tile='GoogleTiles', + img_tile_args={'style': 'street'}, + ) + try: + return geodisplay.fig + finally: + matplotlib.pyplot.close(geodisplay.fig) + except Exception: + pass + sonde_ds.close() + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_stair_graph(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + + histdisplay = DistributionDisplay({'sgpsondewnpnC1.b1': sonde_ds}) + histdisplay.plot_stairstep('tdry', bins=np.arange(-60, 10, 1)) + sonde_ds.close() + + try: + return histdisplay.fig + finally: + matplotlib.pyplot.close(histdisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_stair_graph_sorted(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + + histdisplay = DistributionDisplay({'sgpsondewnpnC1.b1': sonde_ds}) + histdisplay.plot_stairstep( + 'tdry', + bins=np.arange(-60, 10, 1), + sortby_field='alt', + sortby_bins=np.linspace(0, 10000.0, 6), + ) + sonde_ds.close() + + try: + return histdisplay.fig + finally: + matplotlib.pyplot.close(histdisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_stacked_bar_graph(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + + histdisplay = DistributionDisplay({'sgpsondewnpnC1.b1': sonde_ds}) + histdisplay.plot_stacked_bar('tdry', bins=np.arange(-60, 10, 1)) + sonde_ds.close() + + try: + return histdisplay.fig + finally: + matplotlib.pyplot.close(histdisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_stacked_bar_graph2(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + + histdisplay = DistributionDisplay({'sgpsondewnpnC1.b1': sonde_ds}) + histdisplay.plot_stacked_bar('tdry') + histdisplay.set_yrng([0, 400]) + histdisplay.set_xrng([-70, 0]) + sonde_ds.close() + + try: + return histdisplay.fig + finally: + matplotlib.pyplot.close(histdisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_stacked_bar_graph_sorted(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + + histdisplay = DistributionDisplay({'sgpsondewnpnC1.b1': sonde_ds}) + histdisplay.plot_stacked_bar( + 'tdry', + bins=np.arange(-60, 10, 1), + sortby_field='alt', + sortby_bins=np.linspace(0, 10000.0, 6), + ) + sonde_ds.close() + + try: + return histdisplay.fig + finally: + matplotlib.pyplot.close(histdisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_heatmap(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + + histdisplay = DistributionDisplay({'sgpsondewnpnC1.b1': sonde_ds}) + histdisplay.plot_heatmap( + 'tdry', + 'alt', + x_bins=np.arange(-60, 10, 1), + y_bins=np.linspace(0, 10000.0, 50), + cmap='coolwarm', + ) + sonde_ds.close() + + try: + return histdisplay.fig + finally: + matplotlib.pyplot.close(histdisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_size_distribution(): + sigma = 10 + mu = 50 + bins = np.linspace(0, 100, 50) + ydata = 1 / (sigma * np.sqrt(2 * np.pi)) * np.exp(-((bins - mu) ** 2) / (2 * sigma**2)) + y_array = xr.DataArray(ydata, dims={'time': bins}) + bins = xr.DataArray(bins, dims={'time': bins}) + my_fake_ds = xr.Dataset({'time': bins, 'ydata': y_array}) + histdisplay = DistributionDisplay(my_fake_ds) + histdisplay.plot_size_distribution('ydata', 'time', set_title='Fake distribution.') + try: + return histdisplay.fig + finally: + matplotlib.pyplot.close(histdisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_contour(): + files = sample_files.EXAMPLE_MET_CONTOUR + time = '2019-05-08T04:00:00.000000000' + data = {} + fields = {} + wind_fields = {} + station_fields = {} + for f in files: + ds = act.io.arm.read_arm_netcdf(f) + data.update({f: ds}) + fields.update({f: ['lon', 'lat', 'temp_mean']}) + wind_fields.update({f: ['lon', 'lat', 'wspd_vec_mean', 'wdir_vec_mean']}) + station_fields.update({f: ['lon', 'lat', 'atmos_pressure']}) + + display = ContourDisplay(data, figsize=(8, 8)) + display.create_contour(fields=fields, time=time, levels=50, contour='contour', cmap='viridis') + display.plot_vectors_from_spd_dir( + fields=wind_fields, time=time, mesh=True, grid_delta=(0.1, 0.1) + ) + display.plot_station(fields=station_fields, time=time, markersize=7, color='red') + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_contour_stamp(): + files = sample_files.EXAMPLE_STAMP_WILDCARD + test = {} + stamp_fields = {} + time = '2020-01-01T00:00:00.000000000' + for f in files: + ds = f.split('/')[-1] + nc_ds = act.io.arm.read_arm_netcdf(f) + test.update({ds: nc_ds}) + stamp_fields.update({ds: ['lon', 'lat', 'plant_water_availability_east']}) + nc_ds.close() + + display = act.plotting.ContourDisplay(test, figsize=(8, 8)) + display.create_contour(fields=stamp_fields, time=time, levels=50, alpha=0.5, twod_dim_value=5) + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_contour2(): + files = sample_files.EXAMPLE_MET_CONTOUR + time = '2019-05-08T04:00:00.000000000' + data = {} + fields = {} + wind_fields = {} + station_fields = {} + for f in files: + ds = act.io.arm.read_arm_netcdf(f) + data.update({f: ds}) + fields.update({f: ['lon', 'lat', 'temp_mean']}) + wind_fields.update({f: ['lon', 'lat', 'wspd_vec_mean', 'wdir_vec_mean']}) + station_fields.update({f: ['lon', 'lat', 'atmos_pressure']}) + + display = ContourDisplay(data, figsize=(8, 8)) + display.create_contour(fields=fields, time=time, levels=50, contour='contour', cmap='viridis') + display.plot_vectors_from_spd_dir( + fields=wind_fields, time=time, mesh=False, grid_delta=(0.1, 0.1) + ) + display.plot_station(fields=station_fields, time=time, markersize=7, color='pink') + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_contourf(): + files = sample_files.EXAMPLE_MET_CONTOUR + time = '2019-05-08T04:00:00.000000000' + data = {} + fields = {} + wind_fields = {} + station_fields = {} + for f in files: + ds = act.io.arm.read_arm_netcdf(f) + data.update({f: ds}) + fields.update({f: ['lon', 'lat', 'temp_mean']}) + wind_fields.update({f: ['lon', 'lat', 'wspd_vec_mean', 'wdir_vec_mean']}) + station_fields.update( + { + f: [ + 'lon', + 'lat', + 'atmos_pressure', + 'temp_mean', + 'rh_mean', + 'vapor_pressure_mean', + 'temp_std', + ] + } + ) + + display = ContourDisplay(data, figsize=(8, 8)) + display.create_contour(fields=fields, time=time, levels=50, contour='contourf', cmap='viridis') + display.plot_vectors_from_spd_dir( + fields=wind_fields, time=time, mesh=True, grid_delta=(0.1, 0.1) + ) + display.plot_station(fields=station_fields, time=time, markersize=7, color='red') + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_contourf2(): + files = sample_files.EXAMPLE_MET_CONTOUR + time = '2019-05-08T04:00:00.000000000' + data = {} + fields = {} + wind_fields = {} + station_fields = {} + for f in files: + ds = act.io.arm.read_arm_netcdf(f) + data.update({f: ds}) + fields.update({f: ['lon', 'lat', 'temp_mean']}) + wind_fields.update({f: ['lon', 'lat', 'wspd_vec_mean', 'wdir_vec_mean']}) + station_fields.update( + { + f: [ + 'lon', + 'lat', + 'atmos_pressure', + 'temp_mean', + 'rh_mean', + 'vapor_pressure_mean', + 'temp_std', + ] + } + ) + + display = ContourDisplay(data, figsize=(8, 8)) + display.create_contour(fields=fields, time=time, levels=50, contour='contourf', cmap='viridis') + display.plot_vectors_from_spd_dir( + fields=wind_fields, time=time, mesh=False, grid_delta=(0.1, 0.1) + ) + display.plot_station(fields=station_fields, time=time, markersize=7, color='pink') + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +# Due to issues with pytest-mpl, for now we just test to see if it runs +def test_time_height_scatter(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + + display = TimeSeriesDisplay({'sgpsondewnpnC1.b1': sonde_ds}, figsize=(7, 3)) + display.time_height_scatter('tdry', day_night_background=False) + + sonde_ds.close() + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_qc_bar_plot(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET1) + ds.clean.cleanup() + var_name = 'temp_mean' + ds.qcfilter.set_test(var_name, index=range(100, 600), test_number=2) + + # Testing out when the assessment is not listed + ds.qcfilter.set_test(var_name, index=range(500, 800), test_number=4) + ds['qc_' + var_name].attrs['flag_assessments'][3] = 'Wonky' + + display = TimeSeriesDisplay({'sgpmetE13.b1': ds}, subplot_shape=(2,), figsize=(7, 4)) + display.plot(var_name, subplot_index=(0,), assessment_overplot=True) + display.day_night_background('sgpmetE13.b1', subplot_index=(0,)) + color_lookup = { + 'Bad': 'red', + 'Incorrect': 'red', + 'Indeterminate': 'orange', + 'Suspect': 'orange', + 'Missing': 'darkgray', + 'Not Failing': 'green', + 'Acceptable': 'green', + } + display.qc_flag_block_plot(var_name, subplot_index=(1,), assessment_color=color_lookup) + + ds.close() + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_2d_as_1d(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_CEIL1) + + display = TimeSeriesDisplay(ds) + display.plot('backscatter', force_line_plot=True, linestyle='None') + + ds.close() + del ds + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_fill_between(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET_WILDCARD) + + accumulate_precip(ds, 'tbrg_precip_total') + + display = TimeSeriesDisplay(ds) + display.fill_between('tbrg_precip_total_accumulated', color='gray', alpha=0.2) + + ds.close() + del ds + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_qc_flag_block_plot(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SURFSPECALB1MLAWER) + + display = TimeSeriesDisplay(ds, subplot_shape=(2,), figsize=(8, 2 * 4)) + + display.plot('surface_albedo_mfr_narrowband_10m', force_line_plot=True, labels=True) + + display.qc_flag_block_plot('surface_albedo_mfr_narrowband_10m', subplot_index=(1,)) + + ds.close() + del ds + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_assessment_overplot(): + var_name = 'temp_mean' + files = sample_files.EXAMPLE_MET1 + ds = act.io.arm.read_arm_netcdf(files) + ds.load() + ds.clean.cleanup() + + ds.qcfilter.set_test(var_name, index=np.arange(100, 300, dtype=int), test_number=2) + ds.qcfilter.set_test(var_name, index=np.arange(420, 422, dtype=int), test_number=3) + ds.qcfilter.set_test(var_name, index=np.arange(500, 800, dtype=int), test_number=4) + ds.qcfilter.set_test(var_name, index=np.arange(900, 901, dtype=int), test_number=4) + + # Plot data + display = TimeSeriesDisplay(ds, subplot_shape=(1,), figsize=(10, 6)) + display.plot(var_name, day_night_background=True, assessment_overplot=True) + + ds.close() + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_assessment_overplot_multi(): + var_name1, var_name2 = 'wspd_arith_mean', 'wspd_vec_mean' + files = sample_files.EXAMPLE_MET1 + ds = act.io.arm.read_arm_netcdf(files) + ds.load() + ds.clean.cleanup() + + ds.qcfilter.set_test(var_name1, index=np.arange(100, 200, dtype=int), test_number=2) + ds.qcfilter.set_test(var_name1, index=np.arange(500, 600, dtype=int), test_number=4) + ds.qcfilter.set_test(var_name2, index=np.arange(300, 400, dtype=int), test_number=4) + + # Plot data + display = TimeSeriesDisplay(ds, subplot_shape=(1,), figsize=(10, 6)) + display.plot( + var_name1, label=var_name1, assessment_overplot=True, overplot_behind=True, linestyle='' + ) + display.plot( + var_name2, + day_night_background=True, + color='green', + label=var_name2, + assessment_overplot=True, + linestyle='', + ) + + ds.close() + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_plot_barbs_from_u_v(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_TWP_SONDE_WILDCARD) + BarbDisplay = TimeSeriesDisplay({'sonde_darwin': sonde_ds}) + BarbDisplay.plot_barbs_from_u_v('u_wind', 'v_wind', 'pres', num_barbs_x=20) + sonde_ds.close() + try: + return BarbDisplay.fig + finally: + matplotlib.pyplot.close(BarbDisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_plot_barbs_from_u_v2(): + bins = list(np.linspace(0, 1, 10)) + xbins = list(pd.date_range(pd.to_datetime('2020-01-01'), pd.to_datetime('2020-01-02'), 12)) + y_data = np.full([len(xbins), len(bins)], 1.0) + x_data = np.full([len(xbins), len(bins)], 2.0) + y_array = xr.DataArray(y_data, dims={'xbins': xbins, 'ybins': bins}, attrs={'units': 'm/s'}) + x_array = xr.DataArray(x_data, dims={'xbins': xbins, 'ybins': bins}, attrs={'units': 'm/s'}) + xbins = xr.DataArray(xbins, dims={'xbins': xbins}) + ybins = xr.DataArray(bins, dims={'ybins': bins}) + fake_ds = xr.Dataset({'xbins': xbins, 'ybins': ybins, 'ydata': y_array, 'xdata': x_array}) + BarbDisplay = TimeSeriesDisplay(fake_ds) + BarbDisplay.plot_barbs_from_u_v( + 'xdata', + 'ydata', + None, + num_barbs_x=20, + num_barbs_y=20, + set_title='test plot', + cmap='jet', + ) + fake_ds.close() + try: + return BarbDisplay.fig + finally: + matplotlib.pyplot.close(BarbDisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_2D_timeseries_plot(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_CEIL1) + display = TimeSeriesDisplay(ds) + display.plot('backscatter', y_rng=[0, 5000], use_var_for_y='range') + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_time_plot(): + files = sample_files.EXAMPLE_MET1 + ds = act.io.arm.read_arm_netcdf(files) + display = TimeSeriesDisplay(ds) + display.plot('time') + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_time_plot_match_color_ylabel(): + files = sample_files.EXAMPLE_MET1 + ds = act.io.arm.read_arm_netcdf(files) + display = TimeSeriesDisplay(ds) + display.plot('time', match_line_label_color=True) + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=40) +def test_time_plot2(): + files = sample_files.EXAMPLE_MET1 + ds = act.io.arm.read_arm_netcdf(files, decode_times=False, use_cftime=False) + display = TimeSeriesDisplay(ds) + display.plot('time') + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_y_axis_flag_meanings(): + variable = 'detection_status' + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_CEIL1, keep_variables=[variable, 'lat', 'lon', 'alt']) + ds.clean.clean_arm_state_variables(variable, override_cf_flag=True) + + display = TimeSeriesDisplay(ds, figsize=(12, 8), subplot_shape=(1,)) + display.plot(variable, subplot_index=(0,), day_night_background=True, y_axis_flag_meanings=18) + display.fig.subplots_adjust(left=0.15, right=0.95, bottom=0.1, top=0.94) + + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=35) +def test_colorbar_labels(): + variable = 'cloud_phase_hsrl' + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_CLOUDPHASE) + ds.clean.clean_arm_state_variables(variable) + + display = TimeSeriesDisplay(ds, figsize=(12, 8), subplot_shape=(1,)) + + y_axis_labels = {} + flag_colors = ['white', 'green', 'blue', 'red', 'cyan', 'orange', 'yellow', 'black', 'gray'] + for value, meaning, color in zip( + ds[variable].attrs['flag_values'], ds[variable].attrs['flag_meanings'], flag_colors + ): + y_axis_labels[value] = {'text': meaning, 'color': color} + + display.plot(variable, subplot_index=(0,), colorbar_labels=y_axis_labels, cbar_h_adjust=0) + display.fig.subplots_adjust(left=0.08, right=0.88, bottom=0.1, top=0.94) + + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_plot_datarose(): + files = sample_files.EXAMPLE_MET_WILDCARD + ds = act.io.arm.read_arm_netcdf(files) + display = act.plotting.WindRoseDisplay(ds, subplot_shape=(2, 3), figsize=(16, 10)) + display.plot_data( + 'wdir_vec_mean', + 'wspd_vec_mean', + 'temp_mean', + num_dirs=12, + plot_type='line', + subplot_index=(0, 0), + ) + display.plot_data( + 'wdir_vec_mean', + 'wspd_vec_mean', + 'temp_mean', + num_dirs=12, + plot_type='line', + subplot_index=(0, 1), + line_plot_calc='median', + ) + display.plot_data( + 'wdir_vec_mean', + 'wspd_vec_mean', + 'temp_mean', + num_dirs=12, + plot_type='line', + subplot_index=(0, 2), + line_plot_calc='stdev', + ) + display.plot_data( + 'wdir_vec_mean', + 'wspd_vec_mean', + 'temp_mean', + num_dirs=12, + plot_type='contour', + subplot_index=(1, 0), + ) + display.plot_data( + 'wdir_vec_mean', + 'wspd_vec_mean', + 'temp_mean', + num_dirs=12, + plot_type='contour', + contour_type='mean', + num_data_bins=10, + clevels=21, + cmap='rainbow', + vmin=-5, + vmax=20, + subplot_index=(1, 1), + ) + display.plot_data( + 'wdir_vec_mean', + 'wspd_vec_mean', + 'temp_mean', + num_dirs=12, + plot_type='boxplot', + subplot_index=(1, 2), + ) + + display2 = act.plotting.WindRoseDisplay( + {'ds1': ds, 'ds2': ds}, subplot_shape=(2, 3), figsize=(16, 10) + ) + with np.testing.assert_raises(ValueError): + display2.plot_data( + 'wdir_vec_mean', + 'wspd_vec_mean', + 'temp_mean', + dsname='ds1', + num_dirs=12, + plot_type='line', + line_plot_calc='T', + subplot_index=(0, 0), + ) + with np.testing.assert_raises(ValueError): + display2.plot_data( + 'wdir_vec_mean', + 'wspd_vec_mean', + 'temp_mean', + num_dirs=12, + plot_type='line', + subplot_index=(0, 0), + ) + with np.testing.assert_raises(ValueError): + display2.plot_data( + 'wdir_vec_mean', + 'wspd_vec_mean', + 'temp_mean', + num_dirs=12, + plot_type='groovy', + subplot_index=(0, 0), + ) + + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_add_nan_line(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET1) + + index = (ds.time.values <= np.datetime64('2019-01-01 04:00:00')) | ( + ds.time.values >= np.datetime64('2019-01-01 06:00:00') + ) + ds = ds.sel({'time': index}) + + index = (ds.time.values <= np.datetime64('2019-01-01 18:34:00')) | ( + ds.time.values >= np.datetime64('2019-01-01 19:06:00') + ) + ds = ds.sel({'time': index}) + + index = (ds.time.values <= np.datetime64('2019-01-01 12:30:00')) | ( + ds.time.values >= np.datetime64('2019-01-01 12:40:00') + ) + ds = ds.sel({'time': index}) + + display = TimeSeriesDisplay(ds, figsize=(15, 10), subplot_shape=(1,)) + display.plot('temp_mean', subplot_index=(0,), add_nan=True, day_night_background=True) + ds.close() + + try: + return display.fig + finally: + matplotlib.pyplot.close(display.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_timeseries_invert(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_IRT25m20s) + display = TimeSeriesDisplay(ds, figsize=(10, 8)) + display.plot('inst_sfc_ir_temp', invert_y_axis=True) + ds.close() + return display.fig + + +def test_plot_time_rng(): + # Test if setting the xrange can be done with pandas or datetime datatype + # eventhough the data is numpy. Check for correctly converting xrange values + # before setting and not causing an exception. + met = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET1) + + # Plot data + xrng = [datetime(2019, 1, 1, 0, 0), datetime(2019, 1, 2, 0, 0)] + display = TimeSeriesDisplay(met) + display.plot('temp_mean', time_rng=xrng) + + xrng = [pd.to_datetime('2019-01-01'), pd.to_datetime('2019-01-02')] + display = TimeSeriesDisplay(met) + display.plot('temp_mean', time_rng=xrng) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_groupby_plot(): + ds = act.io.arm.read_arm_netcdf(act.tests.EXAMPLE_MET_WILDCARD) + + # Create Plot Display + display = WindRoseDisplay(ds, figsize=(15, 15), subplot_shape=(3, 3)) + groupby = display.group_by('day') + groupby.plot_group( + 'plot_data', + None, + dir_field='wdir_vec_mean', + spd_field='wspd_vec_mean', + data_field='temp_mean', + num_dirs=12, + plot_type='line', + ) + + # Set theta tick markers for each axis inside display to be inside the polar axes + for i in range(3): + for j in range(3): + display.axes[i, j].tick_params(pad=-20) + ds.close() + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_match_ylimits_plot(): + files = sample_files.EXAMPLE_MET_WILDCARD + ds = act.io.arm.read_arm_netcdf(files) + display = act.plotting.TimeSeriesDisplay(ds, figsize=(10, 8), subplot_shape=(2, 2)) + groupby = display.group_by('day') + groupby.plot_group('plot', None, field='temp_mean', marker=' ') + groupby.display.set_yrng([-20, 20], match_axes_ylimits=True) + ds.close() + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_enhanced_skewt_plot(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + display = act.plotting.SkewTDisplay(ds) + display.plot_enhanced_skewt(color_field='alt', component_range=85) + ds.close() + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_enhanced_skewt_plot_2(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + display = act.plotting.SkewTDisplay(ds) + overwrite_data = {'Test': 1234.0} + display.plot_enhanced_skewt( + spd_name='u_wind', + dir_name='v_wind', + color_field='alt', + component_range=85, + uv_flag=True, + overwrite_data=overwrite_data, + add_data=overwrite_data, + ) + ds.close() + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_xlim_correction_plot(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET1) + + # Plot data + xrng = [datetime(2019, 1, 1, 0, 0, 0), datetime(2019, 1, 1, 0, 0, 0)] + display = TimeSeriesDisplay(ds) + display.plot('temp_mean', time_rng=xrng) + + ds.close() + + return display.fig + + +def test_histogram_kwargs(): + files = sample_files.EXAMPLE_MET1 + ds = act.io.arm.read_arm_netcdf(files) + hist_kwargs = {'range': (-10, 10)} + histdisplay = DistributionDisplay(ds) + hist_dict = histdisplay.plot_stacked_bar( + 'temp_mean', + bins=np.arange(-40, 40, 5), + sortby_bins=np.arange(-40, 40, 5), + hist_kwargs=hist_kwargs, + ) + hist_array = np.array([0, 0, 0, 0, 0, 0, 493, 883, 64, 0, 0, 0, 0, 0, 0]) + assert_allclose(hist_dict['histogram'], hist_array) + hist_dict = histdisplay.plot_stacked_bar('temp_mean', hist_kwargs=hist_kwargs) + hist_array = np.array([0, 0, 950, 177, 249, 64, 0, 0, 0, 0]) + assert_allclose(hist_dict['histogram'], hist_array) + + hist_dict_stair = histdisplay.plot_stairstep( + 'temp_mean', + bins=np.arange(-40, 40, 5), + sortby_bins=np.arange(-40, 40, 5), + hist_kwargs=hist_kwargs, + ) + hist_array = np.array([0, 0, 0, 0, 0, 0, 493, 883, 64, 0, 0, 0, 0, 0, 0]) + assert_allclose(hist_dict_stair['histogram'], hist_array) + hist_dict_stair = histdisplay.plot_stairstep('temp_mean', hist_kwargs=hist_kwargs) + hist_array = np.array([0, 0, 950, 177, 249, 64, 0, 0, 0, 0]) + assert_allclose(hist_dict_stair['histogram'], hist_array) + + hist_dict_heat = histdisplay.plot_heatmap( + 'temp_mean', + 'rh_mean', + x_bins=np.arange(-60, 10, 1), + y_bins=np.linspace(0, 10000.0, 50), + hist_kwargs=hist_kwargs, + ) + hist_array = [0.0, 0.0, 0.0, 0.0] + assert_allclose(hist_dict_heat['histogram'][0, 0:4], hist_array) + ds.close() + matplotlib.pyplot.close(fig=histdisplay.fig) + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_violin(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET1) + + # Create a DistributionDisplay object to compare fields + display = DistributionDisplay(ds) + + # Create violin display of mean temperature + display.plot_violin('temp_mean', positions=[5.0], set_title='SGP MET E13 2019-01-01') + + ds.close() + + return display.fig + + +@pytest.mark.mpl_image_compare(tolerance=30) +def test_scatter(): + ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_MET1) + # Create a DistributionDisplay object to compare fields + display = DistributionDisplay(ds) + + display.plot_scatter( + 'wspd_arith_mean', 'wspd_vec_mean', m_field='wdir_vec_mean', marker='d', cmap='bwr' + ) + # Set the range of the field on the x-axis + display.set_xrng((0, 14)) + display.set_yrng((0, 14)) + # Display the 1:1 ratio line + display.set_ratio_line() + + ds.close() + + return display.fig diff --git a/act/tests/utils/test_data_utils.py b/act/tests/utils/test_data_utils.py index 7d87fea8cb..03a4bf512d 100644 --- a/act/tests/utils/test_data_utils.py +++ b/act/tests/utils/test_data_utils.py @@ -58,6 +58,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) 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()