From 7c012f4e21b42dda91ab3d058139104f13420ed3 Mon Sep 17 00:00:00 2001 From: bch0w Date: Thu, 23 Feb 2023 10:37:53 -0900 Subject: [PATCH 1/6] reverse sort on absolute sorting to get smallest values on top --- pysep/recsec.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pysep/recsec.py b/pysep/recsec.py index 54bd74c..aa75429 100755 --- a/pysep/recsec.py +++ b/pysep/recsec.py @@ -198,9 +198,11 @@ def __init__(self, pysep_path=None, syn_path=None, stations=None, - 'backazimuth': sort by source-receiver backazimuth (deg) with constant vertical spacing. Requires `azimuth_start_deg` - 'distance': sort by source-receiver distance (km) with constant - vertical spacing. Requires `distance_units` + vertical spacing. Smallest distances at the top of the figure. + Requires `distance_units` - 'abs_distance': absolute vertical spacing of waveforms defined by - source-receiver distance. Requires `distance_units` + source-receiver distance. Smallest distance at the top of the + figure. Requires `distance_units` - 'abs_azimuth': absolute vertical spacing of waveforms defined by source-receiver azimuth (deg). - 'abs_backazimuth': absolute vertical spacing of waveforms by @@ -1730,6 +1732,10 @@ def _set_y_axis_absolute(self): ytick_major = self.kwargs.get("ytick_major", 90) ylabel = f"Azimuth [{DEG}]" + # Reverse the y-axis to get small values on top when sorting by + # distance or azimuth, which is counter to how a y-axis plotted + self.ax.invert_yaxis() + # Set ytick label major and minor which is either dist or az self.ax.yaxis.set_major_locator(MultipleLocator(ytick_major)) self.ax.yaxis.set_minor_locator(MultipleLocator(ytick_minor)) @@ -1982,10 +1988,11 @@ def parse_args(): - 'backazimuth': sort by source-receiver backazimuth (deg) with constant vertical spacing. Requires `azimuth_start_deg` - 'distance': sort by source-receiver distance (km) with constant - vertical spacing. Requires `azimuth_start_deg` AND - `distance_units` + vertical spacing. Smallest distances at top of figure. + Requires `distance_units` - 'abs_distance': absolute vertical spacing of waveforms defined by - source-receiver distance (km). Requires `distance_units` + source-receiver distance (km). Smallest distances at top of + figure. Requires `distance_units` - 'abs_azimuth': absolute vertical spacing of waveforms defined by source-receiver azimuth (deg). Requires `azimuth_start_deg` From 2a037e52972cc04f66731342374427c6389b252a Mon Sep 17 00:00:00 2001 From: bch0w Date: Thu, 23 Feb 2023 11:11:37 -0900 Subject: [PATCH 2/6] absolute sorting default y-axis label now plots on the y-axis rather than inside the figure, co-existing with distance labels --- pysep/recsec.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/pysep/recsec.py b/pysep/recsec.py index aa75429..fdb4013 100755 --- a/pysep/recsec.py +++ b/pysep/recsec.py @@ -348,6 +348,8 @@ def __init__(self, pysep_path=None, syn_path=None, stations=None, - 'y_axis': Replace tick labels on the y-axis (left side of figure), This won't work if using absolute sorting and will be over- written by 'default' + - 'y_axis_abs': Waveform labels overlapping with y-axis labels + (showing distance or azimuth) - 'y_axis_right': Replace tick labels on the right side of the y-axis. This option won't work with absolute sorting - 'x_min': Place labels on top of the waveforms at the global min @@ -662,7 +664,7 @@ def check_parameters(self): err.xlim_s = f"start time must be less than stop time" acceptable_y_label_loc = ["default", "y_axis", "y_axis_right", "x_min", - "x_max", None] + "x_max", "y_axis_abs", None] if self.y_label_loc not in acceptable_y_label_loc: err.y_label_loc = f"must be in {acceptable_y_label_loc}" if "abs" in self.sort_by and "y_axis" in self.sort_by: @@ -1663,9 +1665,9 @@ def _plot_axes(self, start=0, stop=None): if "abs_" in self.sort_by: self._set_y_axis_absolute() if self.y_label_loc is not None: - # By default, place absolute sorted labels at xmin + # By default, place absolute sorted labels on y-axis if self.y_label_loc == "default": - loc = "x_min" + loc = "y_axis_abs" else: loc = self.y_label_loc self._set_y_axis_text_labels(start=start, stop=stop, loc=loc) @@ -1804,9 +1806,10 @@ def _set_y_axis_text_labels(self, start=0, stop=-1, loc="y_axis"): if self.time_shift_s.sum() != 0: y_fmt += "|TSHIFT" + # Option 1: Replacing y-axis tick labels with waveform labels # Set the y-axis labels to the left side of the left figure border if loc == "y_axis": - # For relative plotting (not abs_), replace y_tick labels with + # For relative plotting (not abs_), replace y_tyick labels with # station information self.ax.set_yticks(self.y_axis[start:stop]) self.ax.set_yticklabels(y_tick_labels) @@ -1820,33 +1823,47 @@ def _set_y_axis_text_labels(self, start=0, stop=-1, loc="y_axis"): self.ax.yaxis.set_label_position("right") plt.text(1.01, .99, y_fmt, ha="left", va="top", transform=self.ax.transAxes, fontsize=fontsize) - # Set the y-axis labels inside the figure border, at xmin or xmax + # Option 2: Plotting labels as text objects, separate from y-axis labels else: + # 2a: Set the y-axis labels inside the figure border (xmin or xmax) # Trying to figure out where the min or max X value is on the plot if loc == "x_min": ha = "left" + va = "top" func = min x_val = func(self.stats.xmin) - plt.text(.01, .99, y_fmt, ha="left", va="top", + plt.text(.01, .99, y_fmt, ha=ha, va=va, transform=self.ax.transAxes, fontsize=fontsize) elif loc == "x_max": ha = "right" + va = "top" func = max x_val = func(self.stats.xmax) - plt.text(.99, .99, y_fmt, ha="right", va="top", + plt.text(.99, .99, y_fmt, ha=ha, va=va, transform=self.ax.transAxes, fontsize=fontsize) + # 2b: Set the y-axis labels outside figure border, co-existing with + # y-labels showing distance or azimuth + elif loc == "y_axis_abs": + ha = "right" + va = "center" + func = min + x_val = func(self.stats.xmin) + plt.text(.01, .99, y_fmt, ha=ha, va=va, + transform=self.ax.transAxes, fontsize=fontsize) + if self.xlim_s is not None: x_val = func([func(self.xlim_s), x_val]) # Plotting y-axis labels for absolute scales if len(self.y_axis) == len(self.st): for idx, s in zip(self.sorted_idx[start:stop], y_tick_labels): - plt.text(x=x_val, y=self.y_axis[idx], s=s, ha=ha, c=c, - fontsize=fontsize) + plt.text(x=x_val, y=self.y_axis[idx], s=s, ha=ha, va=va, + c=c, fontsize=fontsize) # Plotting y-axis labels for relative scales elif len(self.y_axis) == len(y_tick_labels): for y, s in zip(self.y_axis, y_tick_labels): - plt.text(x=x_val, y=y, s=s, ha=ha, c=c, fontsize=fontsize) + plt.text(x=x_val, y=y, s=s, ha=ha, va=va, c=c, + fontsize=fontsize) def _plot_title(self, nwav=None): """ From a186ce6301b3cee247a59a8f9ad83d8be3222eb1 Mon Sep 17 00:00:00 2001 From: bch0w Date: Thu, 23 Feb 2023 11:30:51 -0900 Subject: [PATCH 3/6] fixing docstrings with new parameters --- pysep/recsec.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pysep/recsec.py b/pysep/recsec.py index fdb4013..97287e5 100755 --- a/pysep/recsec.py +++ b/pysep/recsec.py @@ -1763,6 +1763,9 @@ def _set_y_axis_text_labels(self, start=0, stop=-1, loc="y_axis"): - y_axis: Place labels along the y-axis (left side of the figure) Will replace the actual y-tick labels so not applicable for absolute sorting which requries the y-axis labels + - y_axis_abs: for absolute plotting, place waveform labels to the + left side of the figure (outside border), co-existing with + y-axis labels - y_axis_right: same as `y_axis` but set on the right side of figure - x_min: Place labels on the waveforms at the minimum x value - x_max: Place labels on the waveforms at the maximum x value @@ -1848,7 +1851,7 @@ def _set_y_axis_text_labels(self, start=0, stop=-1, loc="y_axis"): va = "center" func = min x_val = func(self.stats.xmin) - plt.text(.01, .99, y_fmt, ha=ha, va=va, + plt.text(0, .99, y_fmt, ha=ha, va=va, transform=self.ax.transAxes, fontsize=fontsize) if self.xlim_s is not None: From 7021f6a8944a0e595774fb6f8bb98ddf7c87a853 Mon Sep 17 00:00:00 2001 From: bch0w Date: Fri, 24 Feb 2023 11:23:18 -0900 Subject: [PATCH 4/6] added option y_axis_abs_right for absolute scale plotting which places waveform text labels outside the right border of the figure, with the normal y axis showing distance or azimuth values. this is the default option for absolute sorting --- pysep/recsec.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pysep/recsec.py b/pysep/recsec.py index 97287e5..3f67538 100755 --- a/pysep/recsec.py +++ b/pysep/recsec.py @@ -348,8 +348,12 @@ def __init__(self, pysep_path=None, syn_path=None, stations=None, - 'y_axis': Replace tick labels on the y-axis (left side of figure), This won't work if using absolute sorting and will be over- written by 'default' - - 'y_axis_abs': Waveform labels overlapping with y-axis labels + - 'y_axis_abs': For absolute y-axis only. waveform labels plotted + on the left side outside border, with y-axis labels overlapping (showing distance or azimuth) + - 'y_axis_abs_right': For absolute y-axis only. waveform labels + plotted on the right side outside border, with y-axis labels + on the left side of the figure (showing distance or azimuth) - 'y_axis_right': Replace tick labels on the right side of the y-axis. This option won't work with absolute sorting - 'x_min': Place labels on top of the waveforms at the global min @@ -664,7 +668,8 @@ def check_parameters(self): err.xlim_s = f"start time must be less than stop time" acceptable_y_label_loc = ["default", "y_axis", "y_axis_right", "x_min", - "x_max", "y_axis_abs", None] + "x_max", "y_axis_abs", "y_axis_abs_right", + None] if self.y_label_loc not in acceptable_y_label_loc: err.y_label_loc = f"must be in {acceptable_y_label_loc}" if "abs" in self.sort_by and "y_axis" in self.sort_by: @@ -1667,7 +1672,7 @@ def _plot_axes(self, start=0, stop=None): if self.y_label_loc is not None: # By default, place absolute sorted labels on y-axis if self.y_label_loc == "default": - loc = "y_axis_abs" + loc = "y_axis_abs_right" else: loc = self.y_label_loc self._set_y_axis_text_labels(start=start, stop=stop, loc=loc) @@ -1853,6 +1858,13 @@ def _set_y_axis_text_labels(self, start=0, stop=-1, loc="y_axis"): x_val = func(self.stats.xmin) plt.text(0, .99, y_fmt, ha=ha, va=va, transform=self.ax.transAxes, fontsize=fontsize) + elif loc == "y_axis_abs_right": + ha = "left" + va = "center" + func = max + x_val = func(self.stats.xmax) + plt.text(1., .99, y_fmt, ha=ha, va=va, + transform=self.ax.transAxes, fontsize=fontsize) if self.xlim_s is not None: x_val = func([func(self.xlim_s), x_val]) From 37d424b490d1b4d6462a071f56d62d805a29a2b8 Mon Sep 17 00:00:00 2001 From: bch0w Date: Fri, 24 Feb 2023 12:27:42 -0900 Subject: [PATCH 5/6] bugfix azimuth bins now recognize multi-page azimuth sorting and do not show azimuth bins if no data are available at those azimiuths on a given page' --- pysep/recsec.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/pysep/recsec.py b/pysep/recsec.py index 3f67538..61dae07 100755 --- a/pysep/recsec.py +++ b/pysep/recsec.py @@ -1470,9 +1470,9 @@ def plot(self, subset=None, page_num=None, **kwargs): # set functions as they may overwrite what is done here self.ax = set_plot_aesthetic(ax=self.ax, **self.kwargs) - # Partition the figure by user-specified azimuth bins + # Partition the figure by user-specified azimuth bins if self.sort_by and "azimuth" in self.sort_by: - self._plot_azimuth_bins() + self._plot_azimuth_bins(start=start, stop=stop) # Finalize the figure accoutrements self._plot_title(nwav=nwav) @@ -1581,10 +1581,17 @@ def _plot_tmarks(self): plt.axvline(x=tmark, c=c, linewidth=lw, linestyle=ls, zorder=z, alpha=alpha) - def _plot_azimuth_bins(self): + def _plot_azimuth_bins(self, start=None, stop=None): """ If plotting by azimuth, create visual bin separators so the user has a visual understanding of radiation patterns etc. + + :type start: int + :param start: optional starting index to determine the bounds of + the azimuth bins + :type stop: int + :param stop: optional stop index to determine the bounds of + the azimuth bins """ azimuth_binsize = self.kwargs.get("azimuth_binsize", 45) @@ -1600,6 +1607,16 @@ def _plot_azimuth_bins(self): # Make sure that the bins go from 0 <= azimuth_bins <= 360 azimuth_bins = azimuth_bins % 360 + # Cut down the azimuth bins to the closest values if we are doing + # multi-page record sections so that we don't end up plotting too many + if start is not None and stop is not None: + min_az = self.azimuths[self.sorted_idx[start:stop]].min() + max_az = self.azimuths[self.sorted_idx[start:stop]].max() + # Find the closest azimiuth bins + idx_start = np.argmin(np.abs(azimuth_bins - min_az)) + idx_end = np.argmax(np.abs(azimuth_bins - max_az)) + azimuth_bins = azimuth_bins[idx_start:idx_end] + # In an absolute plot, the y values are simply the azimuth bins if "abs" in self.sort_by: y_vals = azimuth_bins @@ -2142,14 +2159,16 @@ def parse_args(): def plotw_rs(*args, **kwargs): """ - Main call function for command line use of RecordSection. + Plot Waveform Record Section (main call function for RecordSection) - Runs the record section plotting functions in order. Contains the logic for - breaking up figure into multiple pages. + Instantiates the RecordSection class, runs processing and parameter getting, + and then plots record section. Contains additional logic for breaking up + figures into multiple pages if requested by the user, while keeping sort + order and waveform spacing consistent across multiple reord sections. .. note:: - All arguments should be parsed into the argparser, *args and **kwargs - are just there to keep the IDE happy + + See RecordSection.__init__() for acceptable args and kwargs """ _start = datetime.now() logger.info(f"starting record section plotter") From 468fcb79a270c13fd8295bccb4d8fbdb28d6f326 Mon Sep 17 00:00:00 2001 From: bch0w Date: Mon, 27 Feb 2023 15:33:42 -0900 Subject: [PATCH 6/6] bugfix azimuth bin divider was not searching properly causing no bins to be plotted --- pysep/recsec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pysep/recsec.py b/pysep/recsec.py index 2b39724..3578683 100755 --- a/pysep/recsec.py +++ b/pysep/recsec.py @@ -1638,9 +1638,9 @@ def _plot_azimuth_bins(self, start=None, stop=None): if start is not None and stop is not None: min_az = self.azimuths[self.sorted_idx[start:stop]].min() max_az = self.azimuths[self.sorted_idx[start:stop]].max() - # Find the closest azimiuth bins + # Find the closest azimuth bins idx_start = np.argmin(np.abs(azimuth_bins - min_az)) - idx_end = np.argmax(np.abs(azimuth_bins - max_az)) + idx_end = np.argmin(np.abs(azimuth_bins - max_az)) azimuth_bins = azimuth_bins[idx_start:idx_end] # In an absolute plot, the y values are simply the azimuth bins