From 2833a438036a170a4ceb43d905837aada50c50e7 Mon Sep 17 00:00:00 2001
From: Lan Dam <ldam@passcal.nmt.edu>
Date: Mon, 24 Apr 2023 07:27:01 -0600
Subject: [PATCH] I86 bug click point

---
 sohstationviewer/database/soh.db              | Bin 61440 -> 61440 bytes
 .../view/plotting/plotting_widget/plotting.py |  12 +++--
 .../plotting_widget/plotting_widget.py        |  40 ++++++++++-----
 .../search_message/search_message_dialog.py   |   6 ++-
 sohstationviewer/view/util/functions.py       |  33 ++++++++++++
 tests/test_view/test_util_functions.py        |  48 +++++++++++++++++-
 6 files changed, 118 insertions(+), 21 deletions(-)

diff --git a/sohstationviewer/database/soh.db b/sohstationviewer/database/soh.db
index 0acbb88d0804d0404f79192aa7ea5726900c54da..e522c9a9679a98018fb101a42bf3aa1165335ffe 100755
GIT binary patch
delta 200
zcmZp8z})bFd4e?K%84@0tScGxyeBS9iPtx<)X%9X$~LdE$TK%HjW0~gOUp1SEHX_u
zwlDyK^g{g*{gT{*$`s>@)H1XD&6DHv5_lOH7<`#H8Tc>oZ{(janeV`qjT6%uH*+3p
z=T($v<`ia7XW-=L6!#6V_f0G=Rw&3X&Me8y&npWGF*G*VyzzX102{~zuC0^V9|W@S
wpXFnj9P_|!v!Z}7*XH$A{7joYdb}7n^S#^8$j8efEY1it2&|QP^6mGk0CP@1p8x;=

delta 254
zcmZp8z})bFd4e?KzKJr<tos=B;-wd+#OtS<>Zg@iW*HbKr5I%unwq7X6y;S~8W$L+
zWf%ZKdZB)Zeo1bDX-Z|KL3(c4=E?DS3B3Fh7<?HYGw@&F-^loQGT(tIO#I6>PPAuK
zQ(@+mWl(3}<mVLkO)M@B$S<}B(Fz6m#hE3U`FUkQA%?~Vn>i1)^C~hhFet**2EdhZ
z`f&m!z+m&n^8o^E{AU?_xfV@ke-OyR#{y);JaAjQ-~ktp5R0rhBh-@3E2_kpHhc7V
dF>dC2x1W&@sGX0|H^82ig@J+5Z}RQ;ssN6AOnd+U

diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting.py b/sohstationviewer/view/plotting/plotting_widget/plotting.py
index ad32f12be..13ac3e189 100644
--- a/sohstationviewer/view/plotting/plotting_widget/plotting.py
+++ b/sohstationviewer/view/plotting/plotting_widget/plotting.py
@@ -80,6 +80,7 @@ class Plotting:
             if c == '_':
                 prev_val = val
                 continue
+            points = []
             for times, data in zip(c_data['times'], c_data['data']):
                 if v.startswith('+'):
                     points = [times[i]
@@ -258,7 +259,6 @@ class Plotting:
             for cStr in color_parts:
                 obj, c = cStr.split(':')
                 colors[obj] = c
-
         l_color = 'G'
         has_dot = False
         if 'L' in colors:
@@ -266,12 +266,15 @@ class Plotting:
         if 'D' in colors:
             d_color = colors['D']
             has_dot = True
-
         for x, y in zip(x_list, y_list):
             if not has_dot:
-                ax.myPlot = ax.plot(x, y,
+                # set marker to be able to click point for info
+                # but marker's size is small to not show dot.
+                ax.myPlot = ax.plot(x, y, marker='o', markersize=0.01,
                                     linestyle='-', linewidth=0.7,
-                                    color=clr[l_color])
+                                    zorder=constants.Z_ORDER['LINE'],
+                                    color=clr[l_color],
+                                    picker=True, pickradius=2)
             else:
                 ax.myPlot = ax.plot(x, y, marker='s', markersize=1.5,
                                     linestyle='-', linewidth=0.7,
@@ -280,6 +283,7 @@ class Plotting:
                                     mfc=clr[d_color],
                                     mec=clr[d_color],
                                     picker=True, pickradius=3)
+
         if linked_ax is None:
             ax.x_list = x_list
             ax.y_list = y_list
diff --git a/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py b/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py
index e009e7de9..d7d1519ca 100755
--- a/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py
+++ b/sohstationviewer/view/plotting/plotting_widget/plotting_widget.py
@@ -2,14 +2,15 @@
 Class of which object is used to plot data
 """
 from typing import List
-import numpy as np
 from PySide2.QtCore import QTimer
 from matplotlib import pyplot as pl
 from PySide2 import QtCore, QtWidgets
 
 from sohstationviewer.conf import constants
 from sohstationviewer.view.util.color import set_color_mode
-from sohstationviewer.view.util.functions import get_total_miny_maxy
+from sohstationviewer.view.util.functions import (
+    get_total_miny_maxy, get_index_from_time
+)
 from sohstationviewer.view.plotting.plotting_widget.plotting_axes import (
     PlottingAxes)
 from sohstationviewer.view.plotting.plotting_widget.plotting import Plotting
@@ -279,18 +280,24 @@ class PlottingWidget(QtWidgets.QScrollArea):
             chan_data = self.plotting_data1[chan_id]
             # list of x values of the plot
             x_list = artist.get_xdata()
+            # list of y values of the plot
+            y_list = artist.get_ydata()
+
             # index of the clicked point on the plot
             click_plot_index = event.ind[0]
-            # time value of the clicked point
+
+            # time, val of the clicked point
             clicked_time = x_list[click_plot_index]
-            # indexes of the clicked time in data (one value only)
-            clicked_indexes = np.where(chan_data['times'] == clicked_time)
-            """
-            clicked_indexes and click_plot_index can be different if there
-            are different plots for a channel.
-            """
-            clicked_data = chan_data['data'][clicked_indexes][0]
+            clicked_val = y_list[click_plot_index]
+
+            list_idx, section_idx = get_index_from_time(
+                chan_data, clicked_time, clicked_val)
 
+            if list_idx is None:
+                display_tracking_info(self.tracking_box, "Point not found.")
+                return
+
+            clicked_data = chan_data['data'][list_idx][section_idx]
             if hasattr(ax, 'unit_bw'):
                 clicked_data = ax.unit_bw.format(clicked_data)
             formatted_clicked_time = format_time(
@@ -300,12 +307,17 @@ class PlottingWidget(QtWidgets.QScrollArea):
                         f"Time: {formatted_clicked_time}   "
                         f"Value: {clicked_data}</pre>")
             display_tracking_info(self.tracking_box, info_str)
-
             if 'logIdx' in chan_data.keys():
+                # For Reftek, need to hightlight the corresponding
+                # SOH message line based on the log_idx of the clicked point
                 self.parent.search_message_dialog.show()
-                clicked_log_idx = chan_data['logIdx'][clicked_indexes][0]
-                self.parent.search_message_dialog. \
-                    show_log_entry_from_data_index(clicked_log_idx)
+                clicked_log_idx = chan_data['logIdx'][0][section_idx]
+                try:
+                    self.parent.search_message_dialog. \
+                        show_log_entry_from_data_index(clicked_log_idx)
+                except ValueError as e:
+                    QtWidgets.QMessageBox.warning(self, "Not found",
+                                                  str(e))
 
     def on_button_press_event(self, event):
         """
diff --git a/sohstationviewer/view/search_message/search_message_dialog.py b/sohstationviewer/view/search_message/search_message_dialog.py
index de0eaaa56..56b4b2355 100644
--- a/sohstationviewer/view/search_message/search_message_dialog.py
+++ b/sohstationviewer/view/search_message/search_message_dialog.py
@@ -299,7 +299,9 @@ class SearchMessageDialog(QtWidgets.QWidget):
             count = 0
             for log_line in self.soh_dict[chan_id]:
                 self.add_line(
-                    self.soh_tables_dict[chan_id], text1=count, text2=log_line)
+                    self.soh_tables_dict[chan_id],
+                    text1=str(count),
+                    text2=log_line)
                 count += 1
 
     def add_nav_button(self, nav: QtWidgets.QToolBar,
@@ -510,7 +512,7 @@ class SearchMessageDialog(QtWidgets.QWidget):
         self.search_rowidx = 0
         ret = self.search(col=0, start_search=True)
         if ret is None:
-            raise ValueError(f'Not found line: ({data_index})')
+            raise ValueError(f'Not found line: {data_index}')
         it, r = ret
         self.on_log_entry_clicked(it)
         self._show_log_message(it)
diff --git a/sohstationviewer/view/util/functions.py b/sohstationviewer/view/util/functions.py
index 316fda615..50332b76d 100644
--- a/sohstationviewer/view/util/functions.py
+++ b/sohstationviewer/view/util/functions.py
@@ -292,5 +292,38 @@ def extract_netcodes(data_obj):
     return '\n\t'.join(net_info_list)
 
 
+def get_index_from_time(chan_data: List[np.ndarray], tm: float, val: float) \
+        -> Tuple[int, int]:
+    """
+    Get index of tm in chan_data['time'] which is a list of np.ndarray
+    :param chan_data: dict of data to plot that includes 'times' key
+    :param tm: epoch time of a clicked point
+    :param val: data value of a clicked point
+    :return list_idx: index of the np.ndarray in the list
+    :return section_idx: index of tm inside np.ndarray found
+    """
+    list_idx = None
+    section_idx = None
+    for i in range(len(chan_data['times'])):
+        section_indexes = np.where(chan_data['times'][i] == tm)[0]
+        if len(section_indexes) != 0:
+            if (chan_data['chan_db_info']['plotType'] not in
+                    ['linesDots', 'linesSRate']):
+                # don't check val because the plotting value of some plot
+                # is for displaying only, not actual value. And this type
+                # of data isn't under the effect of overlap.
+                section_idx = section_indexes[0]
+                list_idx = i
+                break
+            else:
+                # to prevent 2 values with the same times in case of overlap
+                for j in section_indexes:
+                    if chan_data['data'][i][j] == val:
+                        list_idx = i
+                        section_idx = j
+                        break
+    return list_idx, section_idx
+
+
 if __name__ == '__main__':
     create_table_of_content_file(Path('../../../documentation'))
diff --git a/tests/test_view/test_util_functions.py b/tests/test_view/test_util_functions.py
index b501a4ef4..bc59a4123 100644
--- a/tests/test_view/test_util_functions.py
+++ b/tests/test_view/test_util_functions.py
@@ -9,7 +9,7 @@ from sohstationviewer.view.util.functions import (
     get_soh_messages_for_view, log_str, is_doc_file,
     create_search_results_file, create_table_of_content_file,
     check_chan_wildcards_format, check_masspos, get_total_miny_maxy,
-    extract_netcodes
+    extract_netcodes, get_index_from_time
 )
 
 from sohstationviewer.view.util.enums import LogType
@@ -455,3 +455,49 @@ class TestExtractNetcodes(TestCase):
         data_obj = MockObj({'3734': {'XX', 'NA'}})
         ret = extract_netcodes(data_obj)
         self.assertEqual(ret, "NA,XX")
+
+
+class TestGetIndexFromTime(TestCase):
+    @classmethod
+    def setUpClass(cls) -> None:
+        cls.plotting_data = {
+            'CH1': {
+                'times': [np.array([1, 2, 3]), np.array([4, 5, 6])],
+                'data': [np.array([6, 9, 7]), np.array([4, 3, 9])],
+                'chan_db_info': {'plotType': 'upDownDots'}
+            },
+            'CH2': {
+                'times': [np.array([1, 2, 3]), np.array([3, 5, 6])],
+                'data': [np.array([6, 9, 7]), np.array([4, 3, 9])],
+                'chan_db_info': {'plotType': 'linesDots'}
+            }
+        }
+
+    def test_time_not_included(self):
+        list_idx, section_idx = get_index_from_time(
+            self.plotting_data['CH1'], 7, 6)
+        self.assertIsNone(list_idx)
+
+    def test_type_not_need_data_info(self):
+        # CH1 has plotType='upDownDots' not in ['linesDots', 'linesSRate']
+        list_idx, section_idx = get_index_from_time(
+            self.plotting_data['CH1'], 4, 4)
+        self.assertEqual(list_idx, 1)
+        self.assertEqual(section_idx, 0)
+
+    def test_type_need_data_info(self):
+        # CH2 has plotType='linesDots in ['linesDots', 'linesSRate']
+        with self.subTest('data not match time'):
+            list_idx, section_idx = get_index_from_time(
+                self.plotting_data['CH2'], 3, 5)
+            self.assertIsNone(list_idx)
+        with self.subTest('data match 1st value'):
+            list_idx, section_idx = get_index_from_time(
+                self.plotting_data['CH2'], 3, 7)
+            self.assertEqual(list_idx, 0)
+            self.assertEqual(section_idx, 2)
+        with self.subTest('data match 2nd value'):
+            list_idx, section_idx = get_index_from_time(
+                self.plotting_data['CH2'], 3, 4)
+            self.assertEqual(list_idx, 1)
+            self.assertEqual(section_idx, 0)
-- 
GitLab