From e1e5279366750ec67ce1734264c03cdac3a76d30 Mon Sep 17 00:00:00 2001
From: Pierre-Antoine Rouby <pierre-antoine.rouby@inrae.fr>
Date: Wed, 14 Feb 2024 15:15:08 +0100
Subject: [PATCH] Plot: Start refactoring with Geometry.PlotXY at exemple.

---
 src/View/Geometry/PlotXY.py          | 166 +++++++++-----------
 src/View/Geometry/Window.py          |  10 +-
 src/View/Tools/PamhyrPlot.py         | 145 ++++++++++++++++-
 src/View/Tools/Plot/PamhyrToolbar.py | 226 ++++++++++++++++-----------
 4 files changed, 356 insertions(+), 191 deletions(-)

diff --git a/src/View/Geometry/PlotXY.py b/src/View/Geometry/PlotXY.py
index b7a85280..d95faa23 100644
--- a/src/View/Geometry/PlotXY.py
+++ b/src/View/Geometry/PlotXY.py
@@ -28,7 +28,7 @@ _translate = QCoreApplication.translate
 
 class PlotXY(PamhyrPlot):
     def __init__(self, canvas=None, trad=None, data=None, toolbar=None,
-                 display_current=True, parent=None):
+                 parent=None):
         super(PlotXY, self).__init__(
             canvas=canvas,
             trad=trad,
@@ -37,19 +37,19 @@ class PlotXY(PamhyrPlot):
             parent=parent
         )
 
-        self.display_current = display_current
-
         self.line_xy = []
         self.line_gl = []
 
+        self.label_x = _translate("Geometry", "X (m)")
+        self.label_y = _translate("Geometry", "Y (m)")
+
         self.before_plot_selected = None
         self.plot_selected = None
         self.after_plot_selected = None
 
     @timer
-    def draw(self, highlight=None):
-        self.canvas.axes.cla()
-        self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5)
+    def draw(self):
+        self.init_axes()
 
         if self.data is None:
             return
@@ -58,89 +58,76 @@ class PlotXY(PamhyrPlot):
             self._init = False
             return
 
-        kp_min, kp_max = (-1, -1)
-        if highlight is not None:
-            kp_min, kp_max = highlight
+        self.draw_xy()
+        self.draw_gl()
+        self.draw_current()
 
-        # Axes
-        self.canvas.axes.set_xlabel(
-            _translate("Geometry", "X (m)"),
-            color='black', fontsize=10
-        )
-        self.canvas.axes.set_ylabel(
-            _translate("Geometry", "Y (m)"),
-            color='black', fontsize=10
-        )
-        self.canvas.axes.axis("equal")
+        self.idle()
+        self._init = True
 
+    def draw_xy(self):
         kp = self.data.get_kp_complete_profiles()
-        # self.canvas.axes.set_xlim(
-        #     left=min(kp), right=max(kp)
-        # )
-
-        # Draw line for each profile
-        self.line_xy = [
-            self.canvas.axes.plot(
-                x, y, lw=1.,
-                color='b' if kp_min <= kp <= kp_max else 'r',
-                markersize=3, marker='+'
-            )
-            for x, y, kp in zip(
-                self.data.get_x(), self.data.get_y(),
-                kp
-            )
-        ]
 
-        # Guide lines
-        x_complete = self.data.get_guidelines_x()
-        y_complete = self.data.get_guidelines_y()
+        kp_min, kp_max = (-1, -1)
+        if self._highlight_data is not None:
+            kp_min, kp_max = self._highlight_data
+
+        def color_hightlight(kp):
+            if kp_min <= kp <= kp_max:
+                return self.color_plot_highlight
+            return self.color_plot
 
-        self.line_gl = [
-            self.canvas.axes.plot(
+        self.line_xy = []
+        for x, y, kp in zip(self.data.get_x(),
+                            self.data.get_y(),
+                            kp):
+            line = self.canvas.axes.plot(
                 x, y,
+                color=color_hightlight(kp),
+                **self.plot_default_kargs
             )
-            for x, y in zip(x_complete, y_complete)
-        ]
-
-        if self.display_current:
-            # Previous profile
-            self.before_plot_selected, = self.canvas.axes.plot(
-                self.data.profile(0).x(),
-                self.data.profile(0).y(),
-                lw=1., markersize=3,
-                marker='+', color="k", linestyle="--"
-            )
-            self.before_plot_selected.set_visible(False)
+            self.line_xy.append(line)
 
-            # Current profile
-            self.plot_selected, = self.canvas.axes.plot(
-                self.data.profile(0).x(),
-                self.data.profile(0).y(),
-                lw=1., markersize=3,
-                marker='+', color="b"
-            )
-            self.plot_selected.set_visible(False)
+    def draw_gl(self):
+        x_complete = self.data.get_guidelines_x()
+        y_complete = self.data.get_guidelines_y()
 
-            # Next profile
-            self.after_plot_selected, = self.canvas.axes.plot(
-                self.data.profile(0).x(),
-                self.data.profile(0).y(),
-                lw=1., markersize=3,
-                marker='+', color="m", linestyle='--'
+        self.line_gl = []
+        for x, y in zip(x_complete, y_complete):
+            line = self.canvas.axes.plot(
+                x, y,
             )
-            self.after_plot_selected.set_visible(False)
-
-        self.canvas.axes.autoscale_view(True, True, True)
-        self.canvas.axes.autoscale()
-        self.canvas.figure.tight_layout()
-        self.canvas.figure.canvas.draw_idle()
-        if self.toolbar is not None:
-            self.toolbar.update()
+            self.line_gl.append(line)
+
+    def draw_current(self):
+        # Previous profile
+        self.before_plot_selected, = self.canvas.axes.plot(
+            self.data.profile(0).x(),
+            self.data.profile(0).y(),
+            color=self.color_plot_previous, linestyle="--",
+            **self.plot_default_kargs
+        )
+        self.before_plot_selected.set_visible(False)
 
-        self._init = True
+        # Current profile
+        self.plot_selected, = self.canvas.axes.plot(
+            self.data.profile(0).x(),
+            self.data.profile(0).y(),
+            color=self.color_plot_current, **self.plot_default_kargs
+        )
+        self.plot_selected.set_visible(False)
+
+        # Next profile
+        self.after_plot_selected, = self.canvas.axes.plot(
+            self.data.profile(0).x(),
+            self.data.profile(0).y(),
+            color=self.color_plot_next, linestyle='--',
+            **self.plot_default_kargs
+        )
+        self.after_plot_selected.set_visible(False)
 
     @timer
-    def update(self, ind=None):
+    def update(self):
         if not self._init:
             self.draw()
             return
@@ -148,17 +135,16 @@ class PlotXY(PamhyrPlot):
         if self.data is None:
             return
 
+        self.update_gl()
+        self.update_current()
+
+        self.update_idle()
+
+    def update_gl(self):
         self.data.compute_guidelines()
         x_complete = list(self.data.get_guidelines_x())
         y_complete = list(self.data.get_guidelines_y())
 
-        # self.line_gl = [
-        #     self.canvas.axes.plot(
-        #         x, y,
-        #     )
-        #     for x, y in zip(x_complete, y_complete)
-        # ]
-
         for i in range(self.data.number_profiles):
             if i < len(self.line_xy):
                 self.line_xy[i][0].set_data(
@@ -170,8 +156,8 @@ class PlotXY(PamhyrPlot):
                     self.canvas.axes.plot(
                         self.data.profile(i).x(),
                         self.data.profile(i).y(),
-                        lw=1., color='r',
-                        markersize=3, marker='+'
+                        color='r',
+                        **self.plot_default_kargs
                     )
                 )
 
@@ -189,7 +175,9 @@ class PlotXY(PamhyrPlot):
                     )
                 )
 
-        if ind is not None and self.display_current:
+    def update_current(self):
+        if self._current_data_update:
+            ind = self._current_data
             before = ind - 1
             after = ind + 1
 
@@ -215,9 +203,3 @@ class PlotXY(PamhyrPlot):
                     self.data.profile(after).y()
                 )
                 self.after_plot_selected.set_visible(True)
-
-        self.canvas.axes.relim()
-        self.canvas.axes.autoscale()
-        self.canvas.axes.autoscale_view()
-        self.canvas.figure.tight_layout()
-        self.canvas.figure.canvas.draw_idle()
diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py
index b5e5181c..458374ce 100644
--- a/src/View/Geometry/Window.py
+++ b/src/View/Geometry/Window.py
@@ -113,6 +113,11 @@ class GeometryWindow(PamhyrWindow):
         table.setAlternatingRowColors(True)
 
     def setup_plots(self):
+        self.setup_plots_xy()
+        self.setup_plots_kpc()
+        self.setup_plots_ac()
+
+    def setup_plots_xy(self):
         self._canvas_xy = MplCanvas(width=3, height=4, dpi=100)
         self._canvas_xy.setObjectName("canvas_xy")
         self._toolbar_xy = PamhyrPlotToolbar(
@@ -124,6 +129,7 @@ class GeometryWindow(PamhyrWindow):
         self._plot_layout_xy.addWidget(self._canvas_xy)
         self.plot_xy()
 
+    def setup_plots_kpc(self):
         self._canvas_kpc = MplCanvas(width=6, height=4, dpi=100)
         self._canvas_kpc.setObjectName("canvas_kpc")
         self._toolbar_kpc = PamhyrPlotToolbar(
@@ -135,6 +141,7 @@ class GeometryWindow(PamhyrWindow):
         self._plot_layout_kpc.addWidget(self._canvas_kpc)
         self.plot_kpc()
 
+    def setup_plots_ac(self):
         self._canvas_ac = MplCanvas(width=9, height=4, dpi=100)
         self._canvas_ac.setObjectName("canvas_ac")
         self._toolbar_ac = PamhyrPlotToolbar(
@@ -375,7 +382,8 @@ class GeometryWindow(PamhyrWindow):
 
     def select_plot_xy(self, ind: int):
         self.tableView.model().blockSignals(True)
-        self._plot_xy.update(ind=ind)
+        self._plot_xy.current = ind
+        self._plot_xy.update()
         self.tableView.model().blockSignals(False)
 
     def select_plot_kpc(self, ind: int):
diff --git a/src/View/Tools/PamhyrPlot.py b/src/View/Tools/PamhyrPlot.py
index c49003d2..0789c233 100644
--- a/src/View/Tools/PamhyrPlot.py
+++ b/src/View/Tools/PamhyrPlot.py
@@ -22,17 +22,57 @@ from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
 
 
 class PamhyrPlot(APlot):
-    def __init__(self, data=None, trad=None,
-                 canvas=None, toolbar=None,
+    color_axes = "black"
+    color_axes_grid = "grey"
+    color_axes_labels = "black"
+    color_plot = "red"
+    color_plot_highlight = "blue"
+    color_plot_previous = "black"
+    color_plot_current = "blue"
+    color_plot_next = "purple"
+
+    plot_default_kargs = {
+        "lw" : 1.,
+        "markersize" : 3,
+        "marker" : "+",
+    }
+
+    def __init__(self, data=None,
+                 trad=None,     # Translate object
+                 canvas=None,   # Use existing canvas
+                 canvas_height=4, canvas_width=5,
+                 canvas_dpi=100,
+                 toolbar=None,
+                 table=None,
                  parent=None):
         if canvas is None:
-            canvas = MplCanvas()
+            canvas = MplCanvas(
+                height=canvas_height, width=canvas_width,
+                dpi=canvas_dpi
+            )
 
         self._trad = trad
         self._canvas = canvas
         self._toolbar = toolbar
+        self._table = table
         self._parent = parent
 
+        self._label_x = "X"
+        self._label_y = "Y"
+
+        self._isometric_axis = True
+
+        self._auto_relim = True
+        self._autoscale = True
+
+        self._auto_relim_update = False
+        self._autoscale_update = False
+
+        self._highlight_data = None
+        self._highlight_data_update = False
+        self._current_data = None
+        self._current_data_update = False
+
         super(PamhyrPlot, self).__init__(data=data)
 
     @property
@@ -42,3 +82,102 @@ class PamhyrPlot(APlot):
     @property
     def toolbar(self):
         return self._toolbar
+
+    @property
+    def table(self):
+        return self._table
+
+    @property
+    def label_x(self):
+        return self._label_x
+
+    @label_x.setter
+    def label_x(self, name):
+        self._label_x = name
+
+    @property
+    def label_y(self):
+        return self._label_x
+
+    @label_y.setter
+    def label_y(self, name):
+        self._label_y = name
+
+    @property
+    def highlight(self):
+        return self._highlight_data
+
+    @highlight.setter
+    def highlight(self, data):
+        self._highlight_data = data
+        self._highlight_data_update = True
+
+    @property
+    def current(self):
+        return self._current_data
+
+    @current.setter
+    def current(self, data):
+        self._current_data = data
+        self._current_data_update = True
+
+    def init_axes(self):
+        self.canvas.axes.cla()
+        self.canvas.axes.grid(
+            color=self.color_axes_grid,
+            linestyle='--',
+            linewidth=0.5
+        )
+
+        self.init_axes_labels()
+        self.init_axes_axis()
+
+    def init_axes_labels(self):
+        self.canvas.axes.set_xlabel(
+            self._label_x,
+            color=self.color_axes_labels,
+            fontsize=10
+        )
+
+        self.canvas.axes.set_ylabel(
+            self._label_y,
+            color=self.color_axes_labels,
+            fontsize=10
+        )
+
+    def init_axes_axis(self):
+        if self._isometric_axis:
+            self.canvas.axes.axis("equal")
+        else:
+            self.canvas.axes.axis("tight")
+
+    def idle(self):
+        if self._auto_relim:
+            self.canvas.axes.relim()
+
+        if self._autoscale:
+            self.canvas.axes.autoscale_view(True, True, True)
+            self.canvas.axes.autoscale()
+
+        self.canvas.figure.tight_layout()
+        self.canvas.figure.canvas.draw_idle()
+
+        self.toolbar_update()
+
+    def update_idle(self):
+        if self._auto_relim_update:
+            self.canvas.axes.relim()
+
+        if self._autoscale_update:
+            self.canvas.axes.autoscale_view(True, True, True)
+            self.canvas.axes.autoscale()
+
+        self.canvas.figure.tight_layout()
+        self.canvas.figure.canvas.draw_idle()
+
+        self.toolbar_update()
+
+
+    def toolbar_update(self):
+        if self._toolbar is not None:
+            self._toolbar.update()
diff --git a/src/View/Tools/Plot/PamhyrToolbar.py b/src/View/Tools/Plot/PamhyrToolbar.py
index cf334127..90afad12 100644
--- a/src/View/Tools/Plot/PamhyrToolbar.py
+++ b/src/View/Tools/Plot/PamhyrToolbar.py
@@ -54,137 +54,173 @@ class PamhyrPlotToolbar(NavigationToolbar2QT):
             (None, None, None, None),
         ]
 
-        icons = []
+        self.icons = []
 
         if "home" in items:
-            self.toolitems.append(
-                (
-                    'Home',
-                    _translate("Toolbar", 'Default view'),
-                    'home', 'home'
-                )
-            )
-            self.toolitems.append((None, None, None, None))
+            self.init_tool_home()
+            self.add_tool_separator()
 
         if "back/forward" in items:
-            self.toolitems.append(
-                (
-                    'Back',
-                    _translate("Toolbar", 'Back to previous view'),
-                    'back', 'back'
-                )
-            )
-            self.toolitems.append(('Forward', _translate(
-                "Toolbar", 'Return to next view'), 'forward', 'forward'))
-            self.toolitems.append((None, None, None, None))
+            self.init_tool_back_forward()
+            self.add_tool_separator()
 
         if "move" in items:
-            self.toolitems.append(
-                ('Pan', _translate(
-                    "Toolbar",
-                    'Axes panoramic'
-                ), 'move', 'pan'))
-            self.toolitems.append((None, None, None, None))
+            self.init_tool_move()
+            self.add_tool_separator()
 
         if "zoom" in items:
-            self.toolitems.append(
-                (
-                    'Zoom',
-                    _translate("Toolbar", 'Zoom'),
-                    'zoom_to_rect', 'zoom'
-                )
+            self.init_tool_zoom()
+            self.add_tool_separator()
+
+        if "iso" in items:
+            self.init_tool_iso()
+            self.add_tool_separator()
+
+        if "save" in items:
+            self.init_tool_save()
+
+        NavigationToolbar2QT.__init__(self, canvas, parent)
+        btn_size = QSize(40, 28)
+        actions = self.findChildren(QAction)
+
+        for a, i in self.icons:
+            self._actions[a].setIcon(i)
+
+        self.addSeparator()
+
+    def add_tool_separator(self):
+        self.toolitems.append((None, None, None, None))
+
+
+    def init_tool_home(self):
+        self.toolitems.append(
+            (
+                'Home',
+                _translate("Toolbar", 'Default view'),
+                'home', 'home'
             )
-            self.toolitems.append((None, None, None, None))
+        )
+
+    def init_tool_back_forward(self):
+        self.toolitems.append(
+            (
+                'Back',
+                _translate("Toolbar", 'Back to previous view'),
+                'back', 'back'
+            )
+        )
+        self.toolitems.append(
+            (
+                'Forward',
+                _translate("Toolbar", 'Return to next view'),
+                'forward', 'forward'
+            )
+        )
+
+    def init_tool_move(self):
+        self.toolitems.append(
+            (
+                'Pan',
+                _translate("Toolbar", 'Axes panoramic'),
+                'move', 'pan'
+            )
+        )
+
+    def init_tool_zoom(self):
+        self.toolitems.append(
+            (
+                'Zoom',
+                _translate("Toolbar", 'Zoom'),
+                'zoom_to_rect', 'zoom'
+            )
+        )
 
-            icon_zoom = QtGui.QIcon()
-            icon_zoom.addPixmap(QtGui.QPixmap(
-                os.path.abspath(f"{file_path}/../../ui/ressources/zoom.png")
-            ))
+        icon_zoom = QtGui.QIcon()
+        icon_zoom.addPixmap(QtGui.QPixmap(
+            os.path.abspath(f"{file_path}/../../ui/ressources/zoom.png")
+        ))
 
-            icons.append(("zoom", icon_zoom))
+        self.icons.append(("zoom", icon_zoom))
 
-        if "iso" in items:
-            self.toolitems.append(
-                ('Isometric_view', _translate(
-                    "Toolbar", 'Isometric view (Shift+W)'
-                ), '', 'isometric_view')
+    def init_tool_iso(self):
+        self.toolitems.append(
+            (
+                'Isometric_view',
+                _translate("Toolbar", 'Isometric view (Shift+W)'),
+                '', 'isometric_view'
             )
-            self.toolitems.append((None, None, None, None))
+        )
 
-            self.toolitems.append(
-                ('GlobalView', _translate(
-                    "Toolbar", 'Auto scale view (Shift+X)'
-                ), '', 'non_isometric_view')
+        self.toolitems.append(
+            (
+                'GlobalView',
+                _translate("Toolbar", 'Auto scale view (Shift+X)'),
+                '', 'non_isometric_view'
             )
-            self.toolitems.append((None, None, None, None)),
+        )
 
-            icon_btn_isometric_view = QtGui.QIcon()
-            icon_btn_isometric_view.addPixmap(
-                QtGui.QPixmap(
-                    os.path.abspath(
-                        f"{file_path}/../../ui/ressources/zoom_fit_11.png"
-                    )
+        icon_btn_isometric_view = QtGui.QIcon()
+        icon_btn_isometric_view.addPixmap(
+            QtGui.QPixmap(
+                os.path.abspath(
+                    f"{file_path}/../../ui/ressources/zoom_fit_11.png"
                 )
             )
+        )
 
-            icon_btn_global_view = QtGui.QIcon()
-            icon_btn_global_view.addPixmap(
-                QtGui.QPixmap(
+        icon_btn_global_view = QtGui.QIcon()
+        icon_btn_global_view.addPixmap(
+            QtGui.QPixmap(
                     os.path.abspath(
-                        f"{file_path}/../../ui/ressources/zoom_fit.png"
+                    f"{file_path}/../../ui/ressources/zoom_fit.png"
                     )
-                )
             )
+        )
 
-            icons.append(("isometric_view", icon_btn_isometric_view))
-            icons.append(("non_isometric_view", icon_btn_global_view))
+        self.icons.append(("isometric_view", icon_btn_isometric_view))
+        self.icons.append(("non_isometric_view", icon_btn_global_view))
 
-        if "save" in items:
-            self.toolitems.append(
-                ('Save', _translate(
-                    "Toolbar", 'Save the figure'
-                ), 'filesave', 'save_figure')
+    def init_tool_save(self):
+        self.toolitems.append(
+            (
+                'Save',
+                _translate("Toolbar", 'Save the figure'),
+                'filesave', 'save_figure'
             )
-            self.toolitems.append((None, None, None, None))
+        )
 
-        NavigationToolbar2QT.__init__(self, canvas, parent)
-        btn_size = QSize(40, 28)
-        actions = self.findChildren(QAction)
+    def save_figure(self, *args):
+        file_types = self.canvas.get_supported_filetypes_grouped()
+        default_file_type = self.canvas.get_default_filetype()
 
-        for a, i in icons:
-            self._actions[a].setIcon(i)
+        start = os.path.join(
+            os.path.expanduser(mpl.rcParams['savefig.directory']),
+            self.canvas.get_default_filename()
+        )
 
-        self.addSeparator()
+        filters = []
+        selected_filter = None
+        for name in file_types:
+            exts = file_types[name]
+            exts_list = " ".join([f"*.{ext}" for ext in exts])
+            new = f"{name} ({exts_list})"
 
-    def save_figure(self, *args):
-        filetypes = self.canvas.get_supported_filetypes_grouped()
-        sorted_filetypes = sorted(filetypes.items())
-        default_filetype = self.canvas.get_default_filetype()
+            if default_file_type in exts:
+                selected_filter = new
 
-        startpath = os.path.expanduser(mpl.rcParams['savefig.directory'])
-        start = os.path.join(startpath, self.canvas.get_default_filename())
-        filters = []
-        selectedFilter = None
-        for name, exts in sorted_filetypes:
-            exts_list = " ".join(['*.%s' % ext for ext in exts])
-            filter = '%s (%s)' % (name, exts_list)
-            if default_filetype in exts:
-                selectedFilter = filter
-            filters.append(filter)
+            filters.append(new)
         filters = ';;'.join(filters)
 
-        fname, filter = qt_compat._getSaveFileName(
+        file_name, _ = qt_compat._getSaveFileName(
             self.canvas.parent(),
             _translate("MainWindow_reach", "Select destination file"),
-            start,
-            filters, selectedFilter)
-
-        if fname:
-            if startpath != "":
-                mpl.rcParams['savefig.directory'] = os.path.dirname(fname)
+            start, filters,
+            selected_filter
+        )
 
+        if file_name:
             try:
-                self.canvas.figure.savefig(fname)
+                self.canvas.figure.savefig(file_name)
             except Exception as e:
                 QtWidgets.QMessageBox.critical(
                     self, "Error saving file", str(e),
-- 
GitLab