From 7a86983eccf0b96d1923423b723ca91a0436d52c Mon Sep 17 00:00:00 2001 From: Zhang Yunjun Date: Fri, 22 Apr 2022 14:55:55 -0700 Subject: [PATCH] tsview: new "narrow" slider + tsview: switch to a new look of "slider" in tsview.py. The main changes are: 1. use a narrower slider on the Y-axis, for a more compact looking. 2. The slider is solely based on image number, instead of time, to be agnostic against TS files with different time gaps. 3. Plot the reference date as a "gray" dot on the slider, to be more visible. 4. use subplot() with tight_layout() to better scale the subplots + utils.ptime: add get_compact_isoformat() --- mintpy/tsview.py | 76 +++++++++++++++++++------------------------ mintpy/utils/plot.py | 2 +- mintpy/utils/ptime.py | 17 ++++++++-- 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/mintpy/tsview.py b/mintpy/tsview.py index abc0c9493..982e2a8f2 100755 --- a/mintpy/tsview.py +++ b/mintpy/tsview.py @@ -787,17 +787,15 @@ def plot(self): disp_cbar=True, disp_slider=True, print_msg=self.print_msg) - self.fig_img = plt.figure(self.figname_img, figsize=self.figsize_img) + subplot_kw = dict(projection=self.map_proj_obj) if self.map_proj_obj is not None else {} + self.fig_img, self.ax_img = plt.subplots(figsize=self.figsize_img, **subplot_kw) # Figure 1 - Axes 1 - Displacement Map - axes_kw = dict(projection=self.map_proj_obj) if self.map_proj_obj is not None else {} - self.ax_img = self.fig_img.add_axes([0.125, 0.25, 0.75, 0.65], **axes_kw) img_data = np.array(self.ts_data[0][self.idx, :, :]) img_data[self.mask == 0] = np.nan self.plot_init_image(img_data) # Figure 1 - Axes 2 - Time Slider - self.ax_tslider = self.fig_img.add_axes([0.125, 0.05, 0.75, 0.15]) self.plot_init_time_slider(init_idx=self.idx, ref_idx=self.ref_idx) self.tslider.on_changed(self.update_time_slider) @@ -851,7 +849,7 @@ def update_image(self, event): if idx is not None and idx != self.idx: # update title - disp_date = self.dates[idx].strftime('%Y-%m-%d') + disp_date = self.dates[idx].strftime(self.disp_date_format) sub_title = 'N = {n}, Time = {t}'.format(n=idx, t=disp_date) self.ax_img.set_title(sub_title, fontsize=self.font_size) @@ -864,8 +862,8 @@ def update_image(self, event): data_img = ut.wrap(data_img, wrap_range=self.wrap_range) # update - self.tslider.set_val(self.yearList[idx]) # update slider - self.img.set_data(data_img) # update image + self.tslider.set_val(idx) # update slider + self.img.set_data(data_img) # update image self.idx = idx self.fig_img.canvas.draw_idle() self.fig_img.canvas.flush_events() @@ -874,23 +872,23 @@ def update_image(self, event): def update_time_slider(self, val): """Update Displacement Map using Slider""" - idx = np.argmin(np.abs(np.array(self.yearList) - self.tslider.val)) + self.idx = self.tslider.val + # update title - disp_date = self.dates[idx].strftime('%Y-%m-%d') - sub_title = 'N = {n}, Time = {t}'.format(n=idx, t=disp_date) + disp_date = self.dates[self.idx].strftime(self.disp_date_format) + sub_title = 'N = {n}, Time = {t}'.format(n=self.idx, t=disp_date) self.ax_img.set_title(sub_title, fontsize=self.font_size) - # read data - data_img = np.array(self.ts_data[0][idx, :, :]) + # read/update 2D image data + data_img = np.array(self.ts_data[0][self.idx, :, :]) data_img[self.mask == 0] = np.nan if self.wrap: if self.disp_unit_img == 'radian': data_img *= self.range2phase data_img = ut.wrap(data_img, wrap_range=self.wrap_range) - - # update data self.img.set_data(data_img) - self.idx = idx + + # update figure self.fig_img.canvas.draw_idle() self.fig_img.canvas.flush_events() return @@ -898,6 +896,7 @@ def update_time_slider(self, val): ##---------- plot functions def plot_init_image(self, img_data): + """Plot the initial 2D image.""" # prepare data if self.wrap: if self.disp_unit_img == 'radian': @@ -905,7 +904,8 @@ def plot_init_image(self, img_data): img_data = ut.wrap(img_data, wrap_range=self.wrap_range) # Title and Axis Label - disp_date = self.dates[self.idx].strftime('%Y-%m-%d') + self.disp_date_format = ptime.get_compact_isoformat(self.date_list[0]) + disp_date = self.dates[self.idx].strftime(self.disp_date_format) self.fig_title = 'N = {}, Time = {}'.format(self.idx, disp_date) # Initial Pixel of interest @@ -918,39 +918,31 @@ def plot_init_image(self, img_data): # call view.py to plot self.img, self.cbar_img = view.plot_slice(self.ax_img, img_data, self.atr, self)[2:4] + self.fig_img.canvas.set_window_title(self.figname_img) + self.fig_img.tight_layout(rect=(0,0,1,0.97)) + return self.img, self.cbar_img def plot_init_time_slider(self, init_idx=-1, ref_idx=None): - val_step = np.min(np.diff(self.yearList)) - val_min = self.yearList[0] - val_max = self.yearList[-1] + """Plot the initial slider.""" + # initiate axes + self.fig_img.subplots_adjust(bottom=0.16) + self.ax_tslider = self.fig_img.add_axes([0.125, 0.05, 0.75, 0.03]) + # plot slider self.tslider = widgets.Slider( - self.ax_tslider, - label='Time', - valinit=self.yearList[init_idx], - valmin=val_min, - valmax=val_max, - valstep=np.array(self.yearList), - ) - - # plot vertical lines to mark the acquisition times - bar_width = val_step / 6. - num_val = len(self.yearList) - if num_val <= 100: - ymin, ymax = 0.25, 0.5 - kwargs = dict(bottom=ymin, ecolor=None) - xdate = np.array(self.yearList) - self.tslider.ax.bar(xdate, np.ones(num_val)*ymax, width=bar_width, facecolor='black', **kwargs) - if ref_idx is not None: - self.tslider.ax.bar(xdate[ref_idx], ymax, width=bar_width*3, facecolor='crimson', **kwargs) + ax=self.ax_tslider, + label='Image', + valinit=init_idx, + valmin=0, + valmax=self.num_date-1, + valstep=1) + + # plot reference date as a gray dot + if ref_idx is not None: + self.tslider.ax.scatter(ref_idx, 0.5, s=8**2, marker='o', color='gray', edgecolors='w') - # axis format - self.tslider.ax.set_xlim([val_min - bar_width / 2, val_max + bar_width / 2]) - self.tslider.ax.set_ylim([0, 1]) - self.tslider.ax.set_yticks([]) - self.tslider.valtext.set_visible(False) #hide slider values return self.tslider diff --git a/mintpy/utils/plot.py b/mintpy/utils/plot.py index 82805b090..ccc571124 100644 --- a/mintpy/utils/plot.py +++ b/mintpy/utils/plot.py @@ -124,7 +124,7 @@ def add_inner_title(ax, title, loc, prop=None, **kwargs): def auto_figure_size(ds_shape, scale=1.0, disp_cbar=False, disp_slider=False, - cbar_ratio=0.25, slider_ratio=0.30, print_msg=True): + cbar_ratio=0.25, slider_ratio=0.15, print_msg=True): """Get auto figure size based on input data shape Adjust if display colobar on the right and/or slider on the bottom diff --git a/mintpy/utils/ptime.py b/mintpy/utils/ptime.py index e2fe30bff..e7466e090 100644 --- a/mintpy/utils/ptime.py +++ b/mintpy/utils/ptime.py @@ -15,6 +15,20 @@ ################################################################ +def get_compact_isoformat(date_str): + """Get the "compact-looking" isoformat of the input datetime string. + Parameters: date_str - str, an example date string + Returns: iso_format - str, date string in "compact" iso format + """ + date_str = date_str[0] if isinstance(date_str, list) else date_str + iso_format = get_date_str_format(date_str) + iso_format = iso_format.replace('y', 'Y') + iso_format = iso_format.replace('%Y%m%d', '%Y-%m-%d') + iso_format = iso_format.replace('%H%M%S', '%H:%M:%S') + iso_format = iso_format.replace('%H%M', '%H:%M') + return iso_format + + def get_date_str_format(date_str): """Get the datetime string format as defined in: https://docs.python.org/3.7/library/datetime.html#strftime-and-strptime-behavior @@ -25,8 +39,7 @@ def get_date_str_format(date_str): YYMMDD Returns: date_str_format - str, datetime string format """ - if isinstance(date_str, list): - date_str = date_str[0] + date_str = date_str[0] if isinstance(date_str, list) else date_str try: date_str = date_str.decode('utf8')