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