From f7e67b672734e9c88641a54322028e4b93d1a8ee Mon Sep 17 00:00:00 2001
From: Lan Dam <ldam@passcal.nmt.edu>
Date: Fri, 25 Aug 2023 10:01:39 -0600
Subject: [PATCH] Change to use matplotlib's lim for zooming

---
 .../multi_threaded_plotting_widget.py         | 26 --------------
 .../view/plotting/plotting_widget/plotting.py | 16 ++++-----
 .../plotting/plotting_widget/plotting_axes.py | 30 +++++++---------
 .../plotting_widget/plotting_widget.py        | 35 +++++++++++--------
 .../view/plotting/state_of_health_widget.py   | 21 +++--------
 .../view/plotting/waveform_dialog.py          | 21 +++--------
 6 files changed, 51 insertions(+), 98 deletions(-)

diff --git a/sohstationviewer/view/plotting/plotting_widget/multi_threaded_plotting_widget.py b/sohstationviewer/view/plotting/plotting_widget/multi_threaded_plotting_widget.py
index 46fecb3a2..0e36e7ab4 100644
--- a/sohstationviewer/view/plotting/plotting_widget/multi_threaded_plotting_widget.py
+++ b/sohstationviewer/view/plotting/plotting_widget/multi_threaded_plotting_widget.py
@@ -334,29 +334,3 @@ class MultiThreadedPlottingWidget(PlottingWidget):
                               f'{self.name} plot stopped', LogType.INFO)
         self.is_working = False
         self.stopped.emit()
-
-    def set_lim(self, first_time=False, is_waveform=False):
-        """
-        The set_lim method of the base class PlottingWidget was not designed
-        with multi-threading in mind, so it made some assumption that is
-        difficult to satisfy in a multi-threaded design. While these
-        assumptions do not affect the initial plotting of the data, they make
-        designing a system for zooming more difficult.
-
-        Rather than trying to comply with the design of PlottingWidget.set_lim,
-        we decide to work around. This set_lim method still keeps the
-        functionality of processing the data based on the zoom range. However,
-        it delegates setting the new limit of the x and y axes to
-        PlottingWidget.set_lim.
-
-        :param first_time: flag that indicate whether set_lim is called the
-            fist time for a data set.
-        """
-        self.data_processors = []
-        if not self.is_working:
-            self.is_working = True
-            start_msg = 'Zooming in...'
-            display_tracking_info(self.tracking_box, start_msg, LogType.INFO)
-            self.create_plotting_channel_processors(self.plotting_data1)
-            self.create_plotting_channel_processors(self.plotting_data2)
-            self.process_channel()
diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting.py b/sohstationviewer/view/plotting/plotting_widget/plotting.py
index faa1f90b0..74ebf7f5d 100644
--- a/sohstationviewer/view/plotting/plotting_widget/plotting.py
+++ b/sohstationviewer/view/plotting/plotting_widget/plotting.py
@@ -108,7 +108,6 @@ class Plotting:
 
         total_samples = len(x)
 
-        x = sorted(x)
         if len(colors) != 1:
             sample_no_colors = [clr['W']]
         else:
@@ -118,7 +117,7 @@ class Plotting:
             ax, [total_samples], sample_no_colors=sample_no_colors,
             chan_db_info=chan_db_info, linked_ax=linked_ax)
         if linked_ax is None:
-            ax.x = x
+            ax.x_list = c_data['times']
         else:
             ax.linkedX = x
         ax.chan_db_info = chan_db_info
@@ -174,18 +173,19 @@ class Plotting:
         ax.plot(points_list[1], len(points_list[1]) * [0.5], linestyle="",
                 marker='s', markersize=2, zorder=constants.Z_ORDER['DOT'],
                 color=clr[colors[1]], picker=True, pickradius=3)
-        x = points_list[0] + points_list[1]
-        x = sorted(x)
+
         ax.set_ylim(-2, 2)
         self.plotting_axes.set_axes_info(
             ax, [len(points_list[0]), len(points_list[1])],
             sample_no_colors=[clr[colors[0]], clr[colors[1]]],
             sample_no_pos=[0.25, 0.75],
             chan_db_info=chan_db_info, linked_ax=linked_ax)
-        if linked_ax is None:
-            ax.x = x
-        else:
-            ax.linkedX = x
+
+        # x_bottom, x_top are the times of data points to be displayed at
+        # bottom or top of the plot
+        ax.x_bottom = np.array(points_list[0])
+        ax.x_top = np.array(points_list[1])
+
         ax.chan_db_info = chan_db_info
         return ax
 
diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting_axes.py b/sohstationviewer/view/plotting/plotting_widget/plotting_axes.py
index 9becc2273..cbe97d144 100644
--- a/sohstationviewer/view/plotting/plotting_widget/plotting_axes.py
+++ b/sohstationviewer/view/plotting/plotting_widget/plotting_axes.py
@@ -184,13 +184,6 @@ class PlottingAxes:
                 axes, label of channel will be displayed with sub title's
                 format - under main title.
         """
-        if linked_ax is None:
-            # clear all texts before recreated.
-            # But not clear when this is a linked_ax because texts are already
-            # cleared with ax, if clear with linked_ax all info of ax won't be
-            # displayed
-            ax.texts.clear()
-
         if label is None:
             label = chan_db_info['label']
 
@@ -229,7 +222,11 @@ class PlottingAxes:
 
         # set samples' total on right side
         if len(sample_no_list) == 1:
-            ax.sampleLbl = ax.text(
+            # center_total_point_lbl: The label to display total number of data
+            # points for plots whose ax has attribute x_list.
+            # The plotTypes that use this label are linesDot, linesSRate,
+            # linesMassPos, dotForTime, multiColorDot
+            ax.center_total_point_lbl = ax.text(
                 1.005, 0.5,
                 sample_no_list[0],
                 horizontalalignment='left',
@@ -240,14 +237,13 @@ class PlottingAxes:
                 size=self.parent.font_size
             )
         else:
-            # Each zoom this infor is created again.
-            # Plots that have data separated in two to have text in top and
-            # bottom, sample rate= 1. These numbers completely depends
-            # on data created in trim_downsample_chan_with_spr_less_or_equal_1
-            # and won't be changed in set_lim, then don't need to assign a
-            # variable for it.
-            # bottom
-            ax.text(
+            # bottom_total_point_lbl, top_total_point_lbl are label to diplay
+            # total number of data points which are splitted into top
+            # and bottom. The ax needs to include attributes x_bottom and x_top
+            # The plotTypes that use these labels are upDownDots (and linesDot
+            # with channel='GPS Lk/Unlk' which will have another MR to add
+            # x_bottom and x_top for this)
+            ax.bottom_total_point_lbl = ax.text(
                 1.005, sample_no_pos[0],
                 sample_no_list[0],
                 horizontalalignment='left',
@@ -258,7 +254,7 @@ class PlottingAxes:
                 size=self.parent.font_size
             )
             # top
-            ax.text(
+            ax.top_total_point_lbl = ax.text(
                 1.005, sample_no_pos[1],
                 sample_no_list[1],
                 horizontalalignment='left',
diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py b/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py
index 9437746df..69956159b 100755
--- a/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py
+++ b/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py
@@ -2,6 +2,7 @@
 Class of which object is used to plot data
 """
 from typing import List, Optional, Union
+import numpy as np
 import matplotlib.text
 from matplotlib import pyplot as pl
 from matplotlib.transforms import Bbox
@@ -557,7 +558,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
                                                                self.max_x)]
 
                 # reset total of samples on the right
-                self.gap_bar.sampleLbl.set_text(len(new_gaps))
+                self.gap_bar.center_total_point_lbl.set_text(len(new_gaps))
 
         for ax in self.axes:
             if hasattr(ax, 'x') and ax.x is None:
@@ -568,10 +569,25 @@ class PlottingWidget(QtWidgets.QScrollArea):
             if not first_time:
                 new_min_y = None
                 new_max_y = None
+                if hasattr(ax, 'x_top'):
+                    # plot_up_down_dots
+                    new_x_bottom_indexes = np.where(
+                        (ax.x_bottom >= self.min_x) &
+                        (ax.x_bottom <= self.max_x))[0]
+                    ax.bottom_total_point_lbl.set_text(
+                        new_x_bottom_indexes.size)
+                    new_x_top_indexes = np.where(
+                        (ax.x_top >= self.min_x) &
+                        (ax.x_top <= self.max_x))[0]
+                    ax.top_total_point_lbl.set_text(
+                        new_x_top_indexes.size)
                 if hasattr(ax, 'x_list'):
                     if not hasattr(ax, 'y_list'):
-                        # dotForTime plots have attribute 'x_list' but not
-                        # 'y_list'
+                        # plot_time_dots and plot_multi_color_dots
+                        x = ax.x_list[0]
+                        new_x_indexes = np.where(
+                            (x >= self.min_x) & (x <= self.max_x))[0]
+                        ax.center_total_point_lbl.set_text(new_x_indexes.size)
                         continue
                     total_points = 0
                     tr_min_ys = []
@@ -590,18 +606,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
                     if tr_min_ys != []:
                         new_min_y = min(tr_min_ys)
                         new_max_y = max(tr_max_ys)
-                else:
-                    total_points = len(ax.x)
-                    if hasattr(ax, 'y') and len(ax.y) > 0:
-                        new_min_y = min(ax.y)
-                        new_max_y = max(ax.y)
-                try:
-                    ax.sampleLbl.set_text(total_points)
-                except AttributeError:
-                    # for case of having top and bottom total points
-                    # which is for RT130's SOH only, trust in total point
-                    # calculated in set_axes_info
-                    pass
+                    ax.center_total_point_lbl.set_text(total_points)
 
                 if new_min_y is not None:
                     self.plotting_axes.set_axes_ylim(
diff --git a/sohstationviewer/view/plotting/state_of_health_widget.py b/sohstationviewer/view/plotting/state_of_health_widget.py
index acb00711d..bc219840a 100644
--- a/sohstationviewer/view/plotting/state_of_health_widget.py
+++ b/sohstationviewer/view/plotting/state_of_health_widget.py
@@ -66,19 +66,8 @@ class SOHWidget(MultiThreadedPlottingWidget):
         linked_ax = None
         if chan_db_info['linkedChan'] not in [None, 'None', '']:
             linked_ax = self.plotting_data1[chan_db_info['linkedChan']]['ax']
-        if 'ax' not in c_data:
-            ax = getattr(self.plotting, plot_functions[plot_type][1])(
-                c_data, chan_db_info, chan_id, None, linked_ax)
-            if ax is None:
-                return
-            c_data['ax'] = ax
-            ax.chan = chan_id
-            self.axes.append(ax)
-        else:
-            for artist in c_data['ax'].lines + c_data['ax'].collections:
-                artist.remove()
-            getattr(self.plotting, plot_functions[plot_type][1])(
-                c_data, chan_db_info, chan_id, c_data['ax'], linked_ax)
-
-    def set_lim(self, first_time=False):
-        super().set_lim(first_time, is_waveform=False)
+        ax = getattr(self.plotting, plot_functions[plot_type][1])(
+            c_data, chan_db_info, chan_id, None, linked_ax)
+        c_data['ax'] = ax
+        ax.chan = chan_id
+        self.axes.append(ax)
diff --git a/sohstationviewer/view/plotting/waveform_dialog.py b/sohstationviewer/view/plotting/waveform_dialog.py
index ffcc0eac5..19ff27ad7 100755
--- a/sohstationviewer/view/plotting/waveform_dialog.py
+++ b/sohstationviewer/view/plotting/waveform_dialog.py
@@ -51,22 +51,11 @@ class WaveformWidget(MultiThreadedPlottingWidget):
         plot_type = chan_db_info['plotType']
 
         # refer to doc string for mass_pos_data to know the reason for 'ax_wf'
-        if 'ax_wf' not in c_data:
-            ax = getattr(self.plotting, plot_functions[plot_type][1])(
-                c_data, chan_db_info, chan_id, None, None)
-            if ax is None:
-                return
-            c_data['ax_wf'] = ax
-            ax.chan = chan_id
-            self.axes.append(ax)
-        else:
-            for artist in c_data['ax_wf'].lines + c_data['ax_wf'].collections:
-                artist.remove()
-            getattr(self.plotting, plot_functions[plot_type][1])(
-                c_data, chan_db_info, chan_id, c_data['ax_wf'], None)
-
-    def set_lim(self, first_time=False):
-        super().set_lim(first_time, is_waveform=True)
+        ax = getattr(self.plotting, plot_functions[plot_type][1])(
+            c_data, chan_db_info, chan_id, None, None)
+        c_data['ax_wf'] = ax
+        ax.chan = chan_id
+        self.axes.append(ax)
 
 
 class WaveformDialog(QtWidgets.QWidget):
-- 
GitLab