diff --git a/act/plotting/timeseriesdisplay.py b/act/plotting/timeseriesdisplay.py index 8586502af0..70aee57dce 100644 --- a/act/plotting/timeseriesdisplay.py +++ b/act/plotting/timeseriesdisplay.py @@ -1256,11 +1256,16 @@ def plot_time_height_xsection_from_1d_data( def time_height_scatter( self, data_field=None, + alt_field='alt', dsname=None, cmap='rainbow', alt_label=None, - alt_field='alt', cb_label=None, + subplot_index=(0,), + plot_alt_field=False, + cb_friendly=False, + day_night_background=False, + set_title=None, **kwargs, ): """ @@ -1273,8 +1278,8 @@ def time_height_scatter( ---------- data_field : str Name of data field in the dataset to plot on second y-axis. - height_field : str - Name of height field in the dataset to plot on first y-axis. + alt_field : str + Variable to use for y-axis. dsname : str or None The name of the datastream to plot. cmap : str @@ -1282,11 +1287,19 @@ def time_height_scatter( alt_label : str Altitude first y-axis label to use. If None, will try to use long_name and units. - alt_field : str - Label for field in the dataset to plot on first y-axis. cb_label : str Colorbar label to use. If not set will try to use long_name and units. + subplot_index : 1 or 2D tuple, list, or array + The index of the subplot to set the x range of. + plot_alt_field : boolean + Set to true to plot the altitude field on the secondary y-axis + cb_friendly : boolean + If set to True will use the Homeyer colormap + day_night_background : boolean + If set to True will plot the day_night_background + set_title : str + Title to set on the plot **kwargs : keyword arguments Any other keyword arguments that will be passed into TimeSeriesDisplay.plot module when the figure @@ -1302,6 +1315,20 @@ def time_height_scatter( elif dsname is None: dsname = list(self._ds.keys())[0] + # Set up or get current plot figure + if self.fig is None: + self.fig = plt.figure() + + # Set up or get current axes + if self.axes is None: + self.axes = np.array([plt.axes()]) + self.fig.add_axes(self.axes[0]) + + if cb_friendly: + cmap = 'HomeyerRainbow' + + ax = self.axes[subplot_index] + # Get data and dimensions data = self._ds[dsname][data_field] altitude = self._ds[dsname][alt_field] @@ -1322,26 +1349,70 @@ def time_height_scatter( except KeyError: cb_label = data_field - colorbar_map = mpl.colormaps.get_cmap(cmap) - self.fig.subplots_adjust(left=0.1, right=0.86, bottom=0.16, top=0.91) - ax1 = self.plot(alt_field, color='black', **kwargs) - ax1.set_ylabel(alt_label) - ax2 = ax1.twinx() - sc = ax2.scatter(xdata.values, data.values, c=data.values, marker='.', cmap=colorbar_map) - cbaxes = self.fig.add_axes( - [ - self.fig.subplotpars.right + 0.02, - self.fig.subplotpars.bottom, - 0.02, - self.fig.subplotpars.top - self.fig.subplotpars.bottom, - ] - ) - cbar = plt.colorbar(sc, cax=cbaxes) - ax2.set_ylim(cbar.mappable.get_clim()) + if 'units' in data.attrs: + ytitle = ''.join(['(', data.attrs['units'], ')']) + else: + ytitle = data_field + + # Set Title + if set_title is None: + if isinstance(self._ds[dsname].time.values[0], np.datetime64): + set_title = ' '.join( + [ + dsname, + data_field, + 'on', + dt_utils.numpy_to_arm_date(self._ds[dsname].time.values[0]), + ] + ) + else: + date_result = search( + r'\d{4}-\d{1,2}-\d{1,2}', self._ds[dsname].time.attrs['units'] + ) + if date_result is not None: + set_title = ' '.join([dsname, data_field, 'on', date_result.group(0)]) + else: + set_title = ' '.join([dsname, data_field]) + + # Plot scatter data + sc = ax.scatter(xdata.values, data.values, c=data.values, cmap=cmap, **kwargs) + + ax.set_title(set_title) + if plot_alt_field: + self.fig.subplots_adjust(left=0.1, right=0.8, bottom=0.15, top=0.925) + pad = 0.02 + (0.02 * len(str(int(np.nanmax(altitude.values))))) + cbar = self.fig.colorbar(sc, pad=pad, cmap=cmap) + + ax2 = ax.twinx() + ax2.set_ylabel(alt_label) + ax2.scatter(xdata.values, altitude.values, color='black') + else: + cbar = self.fig.colorbar(sc, cmap=cmap) + + if day_night_background is True: + self.day_night_background(subplot_index=subplot_index, dsname=dsname) cbar.ax.set_ylabel(cb_label) - ax2.set_yticklabels([]) - return self.axes[0] + # Set X Limit - We want the same time axes for all subplots + self.time_rng = [xdata.min().values, xdata.max().values] + self.set_xrng(self.time_rng, subplot_index) + + # Set X Format + if len(subplot_index) == 1: + days = self.xrng[subplot_index, 1] - self.xrng[subplot_index, 0] + else: + days = ( + self.xrng[subplot_index[0], subplot_index[1], 1] + - self.xrng[subplot_index[0], subplot_index[1], 0] + ) + myFmt = common.get_date_format(days) + ax.xaxis.set_major_formatter(myFmt) + ax.set_xlabel('Time (UTC)') + ax.set_ylabel(ytitle) + + self.axes[subplot_index] = ax + + return self.axes[subplot_index] def qc_flag_block_plot( self, @@ -1401,8 +1472,11 @@ def qc_flag_block_plot( if cb_friendly: color_lookup['Bad'] = (0.9285714285714286, 0.7130901016453677, 0.7130901016453677) color_lookup['Incorrect'] = (0.9285714285714286, 0.7130901016453677, 0.7130901016453677) - color_lookup['Not Failing'] = (0.0, 0.4240129715562796, 0.4240129715562796), - color_lookup['Acceptable'] = (0.0, 0.4240129715562796, 0.4240129715562796), + color_lookup['Not Failing'] = (0.0, 0.4240129715562796, 0.4240129715562796) + color_lookup['Acceptable'] = (0.0, 0.4240129715562796, 0.4240129715562796) + color_lookup['Indeterminate'] = (1.0, 0.6470588235294118, 0.0) + color_lookup['Suspect'] = (1.0, 0.6470588235294118, 0.0) + color_lookup['Missing'] = (0.6627450980392157, 0.6627450980392157, 0.6627450980392157) if assessment_color is not None: for asses, color in assessment_color.items(): @@ -1520,6 +1594,7 @@ def qc_flag_block_plot( yvalues = self._ds[dsname][dims[1]].values cMap = mplcolors.ListedColormap(plot_colors) + print(plot_colors) mesh = ax.pcolormesh( xvalues, yvalues, diff --git a/act/tests/plotting/baseline/test_2D_timeseries_plot.png b/act/tests/plotting/baseline/test_2D_timeseries_plot.png index 9cd2e5c8dd..d8c1e9e6d0 100644 Binary files a/act/tests/plotting/baseline/test_2D_timeseries_plot.png and b/act/tests/plotting/baseline/test_2D_timeseries_plot.png differ diff --git a/act/tests/plotting/baseline/test_2d_as_1d.png b/act/tests/plotting/baseline/test_2d_as_1d.png index 974430a033..ee49a3fa4a 100644 Binary files a/act/tests/plotting/baseline/test_2d_as_1d.png and b/act/tests/plotting/baseline/test_2d_as_1d.png differ diff --git a/act/tests/plotting/baseline/test_add_nan_line.png b/act/tests/plotting/baseline/test_add_nan_line.png index d9c5252fca..4ef242543a 100644 Binary files a/act/tests/plotting/baseline/test_add_nan_line.png and b/act/tests/plotting/baseline/test_add_nan_line.png differ diff --git a/act/tests/plotting/baseline/test_assessment_overplot.png b/act/tests/plotting/baseline/test_assessment_overplot.png index fb67f04ae6..c1108a15b0 100644 Binary files a/act/tests/plotting/baseline/test_assessment_overplot.png and b/act/tests/plotting/baseline/test_assessment_overplot.png differ diff --git a/act/tests/plotting/baseline/test_assessment_overplot_multi.png b/act/tests/plotting/baseline/test_assessment_overplot_multi.png index 1c86202cd7..520c977077 100644 Binary files a/act/tests/plotting/baseline/test_assessment_overplot_multi.png and b/act/tests/plotting/baseline/test_assessment_overplot_multi.png differ diff --git a/act/tests/plotting/baseline/test_barb_sounding_plot.png b/act/tests/plotting/baseline/test_barb_sounding_plot.png index fbdee1ac70..98b8e5b756 100644 Binary files a/act/tests/plotting/baseline/test_barb_sounding_plot.png and b/act/tests/plotting/baseline/test_barb_sounding_plot.png differ diff --git a/act/tests/plotting/baseline/test_colorbar_labels.png b/act/tests/plotting/baseline/test_colorbar_labels.png index 72d51ba88c..ac4ead2ede 100644 Binary files a/act/tests/plotting/baseline/test_colorbar_labels.png and b/act/tests/plotting/baseline/test_colorbar_labels.png differ diff --git a/act/tests/plotting/baseline/test_fill_between.png b/act/tests/plotting/baseline/test_fill_between.png index 5708721531..c595d3ff43 100644 Binary files a/act/tests/plotting/baseline/test_fill_between.png and b/act/tests/plotting/baseline/test_fill_between.png differ diff --git a/act/tests/plotting/baseline/test_match_ylimits_plot.png b/act/tests/plotting/baseline/test_match_ylimits_plot.png index b210f34c50..748b7c8385 100644 Binary files a/act/tests/plotting/baseline/test_match_ylimits_plot.png and b/act/tests/plotting/baseline/test_match_ylimits_plot.png differ diff --git a/act/tests/plotting/baseline/test_multidataset_plot_dict.png b/act/tests/plotting/baseline/test_multidataset_plot_dict.png index f7856a5fa1..a9f6a660f9 100644 Binary files a/act/tests/plotting/baseline/test_multidataset_plot_dict.png and b/act/tests/plotting/baseline/test_multidataset_plot_dict.png differ diff --git a/act/tests/plotting/baseline/test_multidataset_plot_tuple.png b/act/tests/plotting/baseline/test_multidataset_plot_tuple.png index 0be93f1a15..8e74f35dca 100644 Binary files a/act/tests/plotting/baseline/test_multidataset_plot_tuple.png and b/act/tests/plotting/baseline/test_multidataset_plot_tuple.png differ diff --git a/act/tests/plotting/baseline/test_plot.png b/act/tests/plotting/baseline/test_plot.png index 4ab502ff5a..921753d729 100644 Binary files a/act/tests/plotting/baseline/test_plot.png and b/act/tests/plotting/baseline/test_plot.png differ diff --git a/act/tests/plotting/baseline/test_plot_barbs_from_u_v.png b/act/tests/plotting/baseline/test_plot_barbs_from_u_v.png index 0ac3c275a4..a1c58e3410 100644 Binary files a/act/tests/plotting/baseline/test_plot_barbs_from_u_v.png and b/act/tests/plotting/baseline/test_plot_barbs_from_u_v.png differ diff --git a/act/tests/plotting/baseline/test_plot_barbs_from_u_v2.png b/act/tests/plotting/baseline/test_plot_barbs_from_u_v2.png index 35feaa0f4a..0b360d4e6e 100644 Binary files a/act/tests/plotting/baseline/test_plot_barbs_from_u_v2.png and b/act/tests/plotting/baseline/test_plot_barbs_from_u_v2.png differ diff --git a/act/tests/plotting/baseline/test_qc_bar_plot.png b/act/tests/plotting/baseline/test_qc_bar_plot.png index 9aa36b8d0a..d8167eb7d2 100644 Binary files a/act/tests/plotting/baseline/test_qc_bar_plot.png and b/act/tests/plotting/baseline/test_qc_bar_plot.png differ diff --git a/act/tests/plotting/baseline/test_qc_flag_block_plot.png b/act/tests/plotting/baseline/test_qc_flag_block_plot.png index 0f63ad1c84..4ce6106337 100644 Binary files a/act/tests/plotting/baseline/test_qc_flag_block_plot.png and b/act/tests/plotting/baseline/test_qc_flag_block_plot.png differ diff --git a/act/tests/plotting/baseline/test_time_height_scatter.png b/act/tests/plotting/baseline/test_time_height_scatter.png index f115adadb0..08ab88119c 100644 Binary files a/act/tests/plotting/baseline/test_time_height_scatter.png and b/act/tests/plotting/baseline/test_time_height_scatter.png differ diff --git a/act/tests/plotting/baseline/test_time_height_scatter2.png b/act/tests/plotting/baseline/test_time_height_scatter2.png new file mode 100644 index 0000000000..d737ec75d8 Binary files /dev/null and b/act/tests/plotting/baseline/test_time_height_scatter2.png differ diff --git a/act/tests/plotting/baseline/test_time_plot.png b/act/tests/plotting/baseline/test_time_plot.png index ee610dd973..46ab1e433e 100644 Binary files a/act/tests/plotting/baseline/test_time_plot.png and b/act/tests/plotting/baseline/test_time_plot.png differ diff --git a/act/tests/plotting/baseline/test_time_plot2.png b/act/tests/plotting/baseline/test_time_plot2.png index 521c65768f..95b493c342 100644 Binary files a/act/tests/plotting/baseline/test_time_plot2.png and b/act/tests/plotting/baseline/test_time_plot2.png differ diff --git a/act/tests/plotting/baseline/test_time_plot_match_color_ylabel.png b/act/tests/plotting/baseline/test_time_plot_match_color_ylabel.png index 85e5afb5e9..e356448e1c 100644 Binary files a/act/tests/plotting/baseline/test_time_plot_match_color_ylabel.png and b/act/tests/plotting/baseline/test_time_plot_match_color_ylabel.png differ diff --git a/act/tests/plotting/baseline/test_timeseries_invert.png b/act/tests/plotting/baseline/test_timeseries_invert.png index 3546e1c8af..e51dbfd11a 100644 Binary files a/act/tests/plotting/baseline/test_timeseries_invert.png and b/act/tests/plotting/baseline/test_timeseries_invert.png differ diff --git a/act/tests/plotting/baseline/test_xlim_correction_plot.png b/act/tests/plotting/baseline/test_xlim_correction_plot.png index 090b2c0b5c..0d64734bc8 100644 Binary files a/act/tests/plotting/baseline/test_xlim_correction_plot.png and b/act/tests/plotting/baseline/test_xlim_correction_plot.png differ diff --git a/act/tests/plotting/baseline/test_y_axis_flag_meanings.png b/act/tests/plotting/baseline/test_y_axis_flag_meanings.png index 42542dfd90..76de97076d 100644 Binary files a/act/tests/plotting/baseline/test_y_axis_flag_meanings.png and b/act/tests/plotting/baseline/test_y_axis_flag_meanings.png differ diff --git a/act/tests/plotting/test_timeseriesdisplay.py b/act/tests/plotting/test_timeseriesdisplay.py index 0946304f90..e00d5b50ab 100644 --- a/act/tests/plotting/test_timeseriesdisplay.py +++ b/act/tests/plotting/test_timeseriesdisplay.py @@ -63,6 +63,10 @@ def test_errors(): 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') + with np.testing.assert_raises(ValueError): + display.plot_time_height_xsection_from_1d_data('wdir_vec_mean', 'wspd_vec_mean') + with np.testing.assert_raises(ValueError): + display.time_height_scatter('wdir_vec_mean') del ds.attrs['_file_dates'] @@ -192,11 +196,30 @@ def test_barb_sounding_plot(): # Due to issues with pytest-mpl, for now we just test to see if it runs +@pytest.mark.mpl_image_compare(tolerance=30) 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) + display.time_height_scatter('tdry', plot_alt_field=True) + + sonde_ds.close() + + 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 +@pytest.mark.mpl_image_compare(tolerance=30) +def test_time_height_scatter2(): + sonde_ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + + display = TimeSeriesDisplay({'sgpsondewnpnC1.b1': sonde_ds}, figsize=(7, 6), subplot_shape=(2,)) + display.time_height_scatter('tdry', day_night_background=True, subplot_index=(0,), + cb_friendly=True, plot_alt_field=True) + display.time_height_scatter('rh', day_night_background=True, subplot_index=(1,), cb_friendly=True) sonde_ds.close() @@ -281,7 +304,7 @@ def test_qc_flag_block_plot(): 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,)) + display.qc_flag_block_plot('surface_albedo_mfr_narrowband_10m', subplot_index=(1,), cb_friendly=True) ds.close() del ds @@ -389,6 +412,88 @@ def test_plot_barbs_from_u_v2(): matplotlib.pyplot.close(BarbDisplay.fig) +def test_plot_barbs_from_u_v3(): + 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) + pres = np.linspace(1000, 0, len(bins)) + 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}) + pres = xr.DataArray(pres, dims={'ybins': bins}, attrs={'units': 'hPa'}) + fake_ds = xr.Dataset({'xbins': xbins, 'ybins': ybins, 'ydata': y_array, 'xdata': x_array, 'pres': pres}) + BarbDisplay = TimeSeriesDisplay(fake_ds) + BarbDisplay.plot_barbs_from_u_v( + 'xdata', + 'ydata', + None, + set_title='test', + use_var_for_y='pres' + ) + fake_ds.close() + try: + return BarbDisplay.fig + finally: + matplotlib.pyplot.close(BarbDisplay.fig) + + +def test_plot_barbs_from_u_v4(): + bins = list(np.linspace(0, 1, 10)) + xbins = [pd.to_datetime('2020-01-01')] + y_data = np.full([1], 1.0) + x_data = np.full([1], 2.0) + pres = np.linspace(1000, 0, len(bins)) + y_array = xr.DataArray(y_data, dims={'xbins': xbins}, attrs={'units': 'm/s'}) + x_array = xr.DataArray(x_data, dims={'xbins': xbins}, attrs={'units': 'm/s'}) + xbins = xr.DataArray(xbins, dims={'xbins': xbins}) + ybins = xr.DataArray(bins, dims={'ybins': bins}) + pres = xr.DataArray(pres, dims={'ybins': bins}, attrs={'units': 'hPa'}) + fake_ds = xr.Dataset({'xbins': xbins, 'ybins': ybins, 'ydata': y_array, 'xdata': x_array, 'pres': pres}) + BarbDisplay = TimeSeriesDisplay(fake_ds) + BarbDisplay.plot_barbs_from_u_v( + 'xdata', + 'ydata', + None, + set_title='test', + use_var_for_y='pres', + cmap='jet' + ) + fake_ds.close() + try: + return BarbDisplay.fig + finally: + matplotlib.pyplot.close(BarbDisplay.fig) + + +def test_plot_barbs_from_u_v5(): + bins = list(np.linspace(0, 1, 10)) + xbins = [pd.to_datetime('2020-01-01')] + y_data = np.full([1], 1.0) + x_data = np.full([1], 2.0) + pres = np.linspace(1000, 0, len(bins)) + y_array = xr.DataArray(y_data, dims={'xbins': xbins}, attrs={'units': 'm/s'}) + x_array = xr.DataArray(x_data, dims={'xbins': xbins}, attrs={'units': 'm/s'}) + xbins = xr.DataArray(xbins, dims={'xbins': xbins}) + ybins = xr.DataArray(bins, dims={'ybins': bins}) + pres = xr.DataArray(pres, dims={'ybins': bins}, attrs={'units': 'hPa'}) + fake_ds = xr.Dataset({'xbins': xbins, 'ybins': ybins, 'ydata': y_array, 'xdata': x_array, 'pres': pres}) + BarbDisplay = TimeSeriesDisplay(fake_ds) + BarbDisplay.plot_barbs_from_u_v( + 'xdata', + 'ydata', + None, + set_title='test', + use_var_for_y='pres', + ) + 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) diff --git a/examples/plotting/plot_time_height_scatter.py b/examples/plotting/plot_time_height_scatter.py new file mode 100644 index 0000000000..950b36d998 --- /dev/null +++ b/examples/plotting/plot_time_height_scatter.py @@ -0,0 +1,25 @@ +""" +Time-Height Scatter Plot +------------------------ +This will show how to use the time-height scatter +plot function that's part of the TimeSeries Display. + +""" + +import os +from arm_test_data import DATASETS +import matplotlib.pyplot as plt +import act +from act.tests import sample_files + +# Read in radiosonde data +ds = act.io.arm.read_arm_netcdf(sample_files.EXAMPLE_SONDE1) + +# Create scatter plots of the sonde data +display = act.plotting.TimeSeriesDisplay(ds, figsize=(7, 6), subplot_shape=(2,)) +display.time_height_scatter('tdry', plot_alt_field=True, subplot_index=(0,)) +display.time_height_scatter('rh', subplot_index=(1,), cb_friendly=True, day_night_background=True) +plt.tight_layout() +ds.close() + +plt.show()