# PlotRKC.py -- Pamhyr # Copyright (C) 2023-2024 INRAE # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -*- coding: utf-8 -*- import logging from tools import timer from View.Tools.PamhyrPlot import PamhyrPlot from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import ( Qt, QItemSelectionModel, QItemSelection, QItemSelectionRange, ) logger = logging.getLogger() class PlotRKZ(PamhyrPlot): def __init__(self, canvas=None, trad=None, study=None, data=None, toolbar=None, table=None, parent=None): self._study = study super(PlotRKZ, self).__init__( canvas=canvas, trad=trad, data=data, toolbar=toolbar, table=table, parent=parent ) self._isometric_axis = False self.line_rk_zgl = [] self.line_rk_zmin = None self.line_rk_zmin_zmax = None self.line_rk_zmin_zmax_highlight = None self.label_x = self._trad["unit_rk"] self.label_y = self._trad["unit_depth"] self.before_plot_selected = None self.plot_selected = None self.after_plot_selected = None self.parent = parent self._table = table self._colors = [] def onpick(self, event): if event.mouseevent.inaxes != self.canvas.axes: return if event.mouseevent.button.value != 1: return modifiers = QApplication.keyboardModifiers() if modifiers not in [Qt.ControlModifier, Qt.NoModifier, Qt.ShiftModifier]: return ind, point = self._closest_rk(event) if self.parent._table is None: return self.parent._table.blockSignals(True) if modifiers == Qt.ControlModifier: rows = list( set( (i.row() for i in self.parent.tableView.selectedIndexes()) ) ) if ind in rows: rows.remove(ind) else: rows.append(ind) self._select_in_table(rows) elif modifiers == Qt.ShiftModifier: rows = sorted(list( set( (i.row() for i in self.parent.tableView.selectedIndexes()) ) )) if len(rows) > 0: i1 = min(rows[0], rows[-1], ind) i2 = max(rows[0], rows[-1], ind) else: i1 = ind i2 = ind self._select_range_in_table(i1, i2) else: self._select_in_table([ind]) self.parent._table.blockSignals(False) return def _closest_rk(self, event): s = event.artist.get_segments() x = [i[0, 0] for i in s] mx = event.mouseevent.xdata points = enumerate(x) def dist_mouse(point): x = point[1] d = abs(mx - x) return d closest = min( points, key=dist_mouse ) return closest def _select_in_table(self, ind): if self._table is None: return self._table.setFocus() selection = self._table.selectionModel() index = QItemSelection() if len(ind) == 0: return for i in ind: index.append(QItemSelectionRange(self._table.model().index(i, 0))) selection.select( index, QItemSelectionModel.Rows | QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Select ) if len(ind) > 0: self._table.scrollTo(self._table.model().index(ind[-1], 0)) def _select_range_in_table(self, ind1, ind2): if self._table is not None: self._table.setFocus() selection = self._table.selectionModel() index = QItemSelection(self._table.model().index(ind1, 0), self._table.model().index(ind2, 0)) selection.select( index, QItemSelectionModel.Rows | QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Select ) self._table.scrollTo(self._table.model().index(ind2, 0)) @timer def draw(self): self.init_axes() if self.data is None: return if len(self.data.profiles) == 0: return profiles_defined = any( filter( lambda profile: len(profile) > 0, self.data.profiles ) ) if not profiles_defined: self._init = False return self.draw_z_line() self.draw_lr() self.draw_gl() self.draw_bottom() self.draw_profiles_hs(self._data) self.idle() self._init = True def draw_z_line(self): rk = self.data.get_rk_complete_profiles() z_min = self.data.get_z_min() z_max = self.data.get_z_max() self._colors, self._style = self.color_highlight() self.line_rk_zmin_zmax = self.canvas.axes.vlines( x=rk, ymin=z_min, ymax=z_max, color=self._colors, linestyle=self._style, lw=1., picker=10, ) def color_highlight(self): rows = sorted(list( set( (i.row() for i in self.parent.tableView.selectedIndexes()) ) )) colors = [self.color_plot for row in range(len(self._data))] style = ["-" for row in range(len(self._data))] if len(rows) > 0: for row in rows: colors[row] = self.color_plot_current if rows[0] > 0: colors[rows[0]-1] = self.color_plot_previous style[rows[0]-1] = "--" if rows[-1] < len(self._data)-1: colors[rows[-1]+1] = self.color_plot_next style[rows[-1]+1] = "--" return colors, style def draw_lr(self): rk = self.data.get_rk_complete_profiles() lz = [] rz = [] self.line_lr = [] for z in self.data.get_z(): lz.append(z[0]) rz.append(z[-1]) line = self.canvas.axes.plot( rk, lz, color=self.color_plot_river_bottom, linestyle="dotted", lw=1., ) self.line_lr.append(line) line = self.canvas.axes.plot( rk, rz, color=self.color_plot_river_bottom, linestyle="dotted", lw=1., ) self.line_lr.append(line) def draw_gl(self): rk = self.data.get_rk_complete_profiles() ind = 0 self.line_rk_zgl = [] for z in self.data.get_guidelines_z(): # Is incomplete guideline? if len(z) != len(rk): continue self.line_rk_zgl.append( self.canvas.axes.plot( rk, z, lw=1., color=self.colors[ind % len(self.colors)], linestyle=self.linestyle[ind // len(self.colors)] ) ) ind += 1 def draw_bottom(self): rk = self.data.get_rk_complete_profiles() z_min = self.data.get_z_min() self.line_rk_zmin, = self.canvas.axes.plot( rk, z_min, linestyle=":", lw=1.5, color='lightgrey' ) def draw_profiles_hs(self, reach): lhs = filter( lambda hs: hs._input_reach.reach is reach, filter( lambda hs: hs._input_reach is not None, self._study.river.hydraulic_structures.lst ) ) for hs in lhs: if not hs.enabled: continue x = hs.input_rk if x is not None: z_min = reach.get_z_min() z_max = reach.get_z_max() self.canvas.axes.plot( [x, x], [min(z_min), max(z_max)], linestyle="--", lw=1., color=self.color_plot_previous, ) self.canvas.axes.annotate( " > " + hs.name, (x, max(z_max)), horizontalalignment='left', verticalalignment='top', annotation_clip=True, fontsize=9, color=self.color_plot_previous, ) @timer def update(self): if not self._init: self.draw() return self.update_lr() self.update_gl() self.update_current() self.update_idle() def update_current(self): if self._current_data_update: self._colors, self._style = self.color_highlight() self.line_rk_zmin_zmax.set_colors(self._colors) self.line_rk_zmin_zmax.set_linestyle(self._style) def update_gl(self): if self._current_data_update: return rk = self.data.get_rk_complete_profiles() z_min = self.data.get_z_min() z_max = self.data.get_z_max() self.line_rk_zmin.set_data(rk, z_min) # TODO comprendre à quoi sert ce bout de code # ========> # self.line_rk_zmin_zmax.remove() # self._colors, self._style = self.color_highlight() # self.line_rk_zmin_zmax = self.canvas.axes.vlines( # x=rk, # ymin=z_min, # ymax=z_max, # color=self._colors, # linestyle = self._style, # lw=1. # ) # <======== z_complete = self.data.get_guidelines_z() try: for i in range(len(self.line_rk_zgl)): self.line_rk_zgl[i][0].set_data( rk, z_complete[i] ) except Exception as e: logger.warning(f"Failed to update graphic RKZ: {e}") def update_lr(self): for line in self.line_lr: line[0].remove() self.draw_lr()