From b75bfe5973b926800468b195cc9496d4672b0740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 4 Mar 2021 09:02:34 -0500 Subject: [PATCH 01/29] Implement a matplotlib tool to display models distribution --- gwhat/gwrecharge/models_distplot.py | 488 ++++++++++++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 gwhat/gwrecharge/models_distplot.py diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py new file mode 100644 index 00000000..bbe3e647 --- /dev/null +++ b/gwhat/gwrecharge/models_distplot.py @@ -0,0 +1,488 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright © GWHAT Project Contributors +# https://github.com/jnsebgosselin/gwhat +# +# This file is part of GWHAT (Ground-Water Hydrograph Analysis Toolbox). +# Licensed under the terms of the GNU General Public License. +# ----------------------------------------------------------------------------- + +# ---- Standard library imports +import os +import os.path as osp + +# ---- Third party imports +import numpy as np +import matplotlib as mpl +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg +from matplotlib.figure import Figure +from matplotlib.widgets import AxesWidget +from matplotlib.transforms import ScaledTranslation + +from PyQt5.QtCore import Qt +from PyQt5.QtCore import pyqtSlot as QSlot +from PyQt5.QtCore import pyqtSignal as Signal +from PyQt5.QtWidgets import ( + QGridLayout, QAbstractSpinBox, QApplication, QDoubleSpinBox, + QFileDialog, QGroupBox, QLabel, QMessageBox, QScrollArea, QScrollBar, + QSpinBox, QTabWidget, QWidget, QStyle, QFrame, QMainWindow, + QGroupBox) + + +# ---- Local imports +from gwhat.utils import icons +from gwhat.utils.icons import QToolButtonNormal, QToolButtonSmall +from gwhat.widgets.mplfigureviewer import ImageViewer +from gwhat.widgets.buttons import LangToolButton, ToolBarWidget +from gwhat.widgets.layout import VSep + +mpl.rc('font', **{'family': 'sans-serif', 'sans-serif': ['Arial']}) +LOCS = ['left', 'top', 'right', 'bottom'] + + +COLORS = {'precip': [0/255, 25/255, 51/255], + 'recharge': [0/255, 76/255, 153/255], + 'runoff': [0/255, 128/255, 255/255], + 'evapo': [102/255, 178/255, 255/255]} + + +class ModelsDistplotWidget(QMainWindow): + def __init__(self, parent=None): + super().__init__(parent) + self.setMinimumSize(850, 300) + self.setWindowTitle('Models Distribution') + self.setWindowIcon(icons.get_icon('master')) + + self.setup() + self.set_gluedf(None) + + def setup(self): + """Setup the FigureStackManager withthe provided options.""" + self.figcanvas = ModelsDistplotCanvas() + self.figcanvas.sig_rmse_treshold_selected.connect( + self.set_rmse_treshold) + + # Models Info Group. + self.models_label = QLabel() + self.models_label.setTextInteractionFlags(Qt.TextSelectableByMouse) + + self.models_grpbox = QGroupBox('Models Info') + self.models_layout = QGridLayout(self.models_grpbox) + self.models_layout.addWidget(self.models_label, 0, 0) + + # Selected Models Info Group. + self.selectmodels_label = QLabel() + self.selectmodels_label.setTextInteractionFlags( + Qt.TextSelectableByMouse) + + self.selectmodels_grpbox = QGroupBox('Selected Models Info') + self.selectmodels_layout = QGridLayout(self.selectmodels_grpbox) + self.selectmodels_layout.addWidget(self.selectmodels_label, 0, 0) + + # Setup the central widget. + self.central_widget = QWidget() + self.central_layout = QGridLayout(self.central_widget) + self.central_layout.addWidget(self.figcanvas, 0, 0, 2, 1) + self.central_layout.addWidget(self.models_grpbox, 0, 1) + self.central_layout.addWidget(self.selectmodels_grpbox, 1, 1) + self.central_layout.setColumnStretch(0, 1) + self.central_layout.setRowStretch(2, 1) + self.setCentralWidget(self.central_widget) + + def update_models_info(self): + if self.glue_data is None: + self.models_label.setText('') + return + + rmse = self.glue_data['RMSE'] + cru = self.glue_data['params']['Cru'] + rasmax = self.glue_data['params']['RASmax'] + sy = self.glue_data['params']['Sy'] + text = ( + "Nbr of Models = {}" + "\n\n" + "RMSE min = {:0.1f} mm\n" + "RMSE avg = {:0.1f} mm\n" + "RMSE max = {:0.1f} mm\n" + "\n" + "CRu min = {:0.1f}\n" + "CRu avg = {:0.1f}\n" + "CRu max = {:0.1f}\n" + "\n" + "RASmax min = {:0.2f} mm\n" + "RASmax avg = {:0.2f} mm\n" + "RASmax max = {:0.2f} mm\n" + "\n" + "Sy min = {:0.2f}\n" + "Sy avg = {:0.2f}\n" + "Sy max = {:0.2f}\n\n" + ).format(len(rmse), + np.min(rmse), np.mean(rmse), np.max(rmse), + np.min(cru), np.mean(cru), np.max(cru), + np.min(rasmax), np.mean(rasmax), np.max(rasmax), + np.min(sy), np.mean(sy), np.max(sy)) + self.models_label.setText(text) + + def set_rmse_treshold(self, rmse_treshold=None): + if self.glue_data is None: + return + + rmse_data = self.glue_data['RMSE'] + if rmse_treshold is None: + rmse_treshold = np.max(rmse_data) + + where = np.where(rmse_data <= rmse_treshold)[0] + selectmodels_text = ( + "RMSE Treshold = {:0.1f} mm\n" + "Models % = {:0.1f}\n" + "Nbr of Models = {}" + ).format(rmse_treshold, + len(where) / len(rmse_data) * 100, + len(where)) + + if len(where) > 0: + cru = self.glue_data['params']['Cru'][where] + rasmax = self.glue_data['params']['RASmax'][where] + sy = self.glue_data['params']['Sy'][where] + selectmodels_text += ( + "\n\n" + "CRu min = {:0.1f}\n" + "CRu avg = {:0.1f}\n" + "CRu max = {:0.1f}\n" + "\n" + "RASmax min = {:0.2f} mm\n" + "RASmax avg = {:0.2f} mm\n" + "RASmax max = {:0.2f} mm\n" + "\n" + "Sy min = {:0.2f}\n" + "Sy avg = {:0.2f}\n" + "Sy max = {:0.2f}\n\n" + ).format(np.min(cru), np.mean(cru), np.max(cru), + np.min(rasmax), np.mean(rasmax), np.max(rasmax), + np.min(sy), np.mean(sy), np.max(sy)) + self.selectmodels_label.setText(selectmodels_text) + + def set_gluedf(self, glue_data): + """Set the namespace for the GLUE results dataset.""" + self.glue_data = glue_data + self.update_models_info() + if glue_data is None: + self.figcanvas.clear_figure() + else: + QApplication.setOverrideCursor(Qt.WaitCursor) + self.figcanvas.plot_results(glue_data) + self.set_rmse_treshold(None) + QApplication.restoreOverrideCursor() + + def show(self): + """Qt method override.""" + if self.isMinimized(): + self.setWindowState(self.windowState() & ~Qt.WindowMinimized) + else: + self.resize(self.size()) + super().show() + + +class ModelsDistplotCanvas(FigureCanvasQTAgg): + """ + """ + sig_rmse_treshold_selected = Signal(object) + + colors = {'dark grey': '0.65', + 'light grey': '0.85'} + + FWIDTH, FHEIGHT = 8.5, 5 + xlabel_size = 12 + + def __init__(self, setp={}): + super().__init__(ModelsDistplotFigure()) + + def clear_figure(self): + """Clear the whole figure.""" + pass + + def plot_results(self, glue_data): + self.figure.plot(glue_data) + + def draw(self): + if self.figure.cursor is not None: + self.figure.cursor.clear() + + super().draw() + self.background = self.copy_from_bbox(self.figure.bbox) + + if self.figure.cursor is not None: + self.figure.cursor.restore() + + +class ModelsDistplotCursor(AxesWidget): + + def __init__(self, ax, useblit=True): + super().__init__(ax) + self.infotextpad = 3 + self.infotextheight = 10 + + self._selected_rmse_treshold = None + self.connect_event('motion_notify_event', self.onmove) + self.connect_event('button_press_event', self.onpress) + self.connect_event('button_release_event', self.onrelease) + + self.visible = True + self.useblit = useblit and self.canvas.supports_blit + + self.linev = ax.axvline( + ax.get_ybound()[0], visible=False, color='red', linewidth=1, + ls='--', animated=self.useblit) + + self.treshold_vline = ax.axvline( + ax.get_ybound()[0], visible=False, color='red', linewidth=1, + animated=False) + + scaled_translation = ScaledTranslation( + 0, self.infotextpad/72, self.ax.figure.dpi_scale_trans) + self.infotext = ax.text( + 0, 1, '', va='bottom', ha='left', rotation=0, + fontsize=self.infotextheight, + transform=ax.transAxes + scaled_translation + ) + + def update_infotext(self, xdata): + glue_data = self.ax.figure.glue_data + if glue_data is not None and xdata is not None: + where = np.where(glue_data['RMSE'] <= xdata)[0] + percent = len(where) / len(glue_data['RMSE']) * 100 + text = ("{:0.1f} of the models have a RMSE less " + "than or equal to {:0.1f} mm.").format( + percent, xdata) + else: + text = '' + self.infotext.set_text(text) + + def clear(self): + """Internal event handler to clear the cursor.""" + self.linev.set_visible(False) + self.infotext.set_visible(False) + + def restore(self): + self.linev.set_visible(True) + self.infotext.set_visible(True) + self.ax.draw_artist(self.linev) + self.ax.draw_artist(self.infotext) + + def onpress(self, event): + print('onpress') + if event.button == 1: + self._selected_rmse_treshold = event.xdata + + def onrelease(self, event): + print('onrelease') + if event.button != 1: + return + + if self._selected_rmse_treshold is not None: + self.treshold_vline.set_xdata( + (self._selected_rmse_treshold, self._selected_rmse_treshold)) + self.treshold_vline.set_visible(True) + else: + self.treshold_vline.set_visible(False) + self.canvas.draw_idle() + + self.canvas.sig_rmse_treshold_selected.emit( + self._selected_rmse_treshold) + + def onmove(self, event): + """Internal event handler to draw the cursor when the mouse moves.""" + print('onmove') + if self.ignore(event): + return + if not self.canvas.widgetlock.available(self): + return + if not self.visible: + return + + self.linev.set_xdata((event.xdata, event.xdata)) + self.update_infotext(event.xdata) + + self.linev.set_visible(self.visible) + self.infotext.set_visible(self.visible and event.xdata) + + self._update() + + def _update(self): + if self.useblit: + if self.canvas.background is not None: + self.canvas.restore_region(self.canvas.background) + self.ax.draw_artist(self.linev) + self.ax.draw_artist(self.infotext) + self.canvas.blit() + else: + self.canvas.draw_idle() + return False + + +class ModelsDistplotFigure(Figure): + def __init__(self, *args, **kargs): + super().__init__(*args, **kargs) + self.set_facecolor('white') + self.set_tight_layout(True) + self.setp = { + 'xlabel_size': 14, + 'ylabel_size': 14, + 'left_margin': None, + 'right_margin': None, + 'top_margin': None, + 'bottom_margin': None, + 'figure_border': 15 + } + self.xlabelpad = 10 + self.ylabelpad = 10 + + self.hist_obj = None + self.glue_data = None + self.cursor = None + + self.setup_axes() + + def setup_axes(self): + """Plot the data.""" + self.ax0 = self.add_axes([0, 0, 1, 1]) + self.ax0.patch.set_visible(False) + for axis in ['top', 'bottom', 'left', 'right']: + self.ax0.spines[axis].set_linewidth(0.75) + + # Setup axes labels. + self.ax0.set_xlabel( + 'RMSE', fontsize=self.setp['xlabel_size'], + labelpad=self.xlabelpad) + self.ax0.set_ylabel( + 'Models', fontsize=self.setp['ylabel_size'], + labelpad=self.ylabelpad) + + def setup_margins(self): + if self.ax0 is None: + return + figborderpad = self.setp['figure_border'] + + try: + # This is required when saving the figure in some format like + # pdf and svg. + renderer = self.canvas.get_renderer() + except AttributeError: + self.canvas.draw() + return + + figbbox = self.bbox + ax = self.ax0 + axbbox = self.ax0.bbox + + bbox_xaxis_bottom, bbox_xaxis_top = ( + ax.xaxis.get_ticklabel_extents(renderer)) + bbox_yaxis_left, bbox_yaxis_right = ( + ax.yaxis.get_ticklabel_extents(renderer)) + + bbox_yaxis_label = ax.yaxis.label.get_window_extent(renderer) + bbox_xaxis_label = ax.xaxis.label.get_window_extent(renderer) + + # Calculate left margin width. + left_margin = self.setp['left_margin'] + if left_margin is None: + yaxis_width = axbbox.x0 - bbox_yaxis_left.x0 + ylabel_width = bbox_yaxis_label.width + self.ylabelpad + left_margin = ( + yaxis_width + ylabel_width + figborderpad + ) / figbbox.width + + # Calculate right margin width. + right_margin = self.setp['right_margin'] + if right_margin is None: + xaxis_width = max( + bbox_xaxis_bottom.x1 - axbbox.x1, + bbox_xaxis_top.x1 - axbbox.x1, + 0) + right_margin = (xaxis_width + figborderpad) / figbbox.width + + # Calculate top margin height. + top_margin = self.setp['top_margin'] + if top_margin is None: + cursorinfotext_height = ( + self.cursor.infotextheight + self.cursor.infotextpad if + self.cursor else 0 + ) + yaxis_height = max( + bbox_yaxis_left.y1 - axbbox.y1, + bbox_yaxis_right.y1 - axbbox.y1, + 0) + top_margin = ( + yaxis_height + figborderpad + cursorinfotext_height + ) / figbbox.height + + # Calculate bottom margin height. + bottom_margin = self.setp['bottom_margin'] + if bottom_margin is None: + xaxis_height = axbbox.y0 - bbox_xaxis_bottom.y0 + xlabel_height = bbox_xaxis_label.height + self.xlabelpad + bottom_margin = ( + xaxis_height + xlabel_height + figborderpad + ) / figbbox.height + + # Setup axe position. + for ax in self.axes: + ax.set_position([ + left_margin, bottom_margin, + 1 - left_margin - right_margin, + 1 - top_margin - bottom_margin]) + + def plot(self, glue_data): + self.glue_data = glue_data + glue_rmse = glue_data['RMSE'] + + rmse_min = np.floor(np.min(glue_rmse)) + rmse_max = np.ceil(np.max(glue_rmse)) + bins = np.arange(rmse_min, rmse_max + 1, 0.5) + + self.ax0.set_visible(True) + if self.hist_obj is not None: + self.hist_obj.remove() + self.ax0.hist( + glue_rmse, color='blue', edgecolor='black', bins=bins) + + self.ax0.axis(xmin=bins[0] - 0.5, xmax=bins[-1]) + + if self.cursor is None: + self.cursor = ModelsDistplotCursor(self.ax0) + + def tight_layout(self, force_update=False): + """ + Override matplotlib method to setup the margins of the axes. + """ + self.setup_margins() + + def set_size_inches(self, *args, **kargs): + """ + Override matplotlib method to force a call to tight_layout when + set_size_inches is called. This allow to keep the size of the margins + fixed when the canvas of this figure is resized. + """ + super().set_size_inches(*args, **kargs) + self.tight_layout() + + +if __name__ == '__main__': + from gwhat.projet.reader_projet import ProjetReader + import sys + fname = ("D:/OneDrive/INRS/2017 - Projet INRS PACC/" + "Éval recharge (GWHAT)/evaluate_recharge/evaluate_recharge.gwt") + + project = ProjetReader(fname) + wldset = project.get_wldset('Mercier (03090001)') + glue_data = wldset.get_glue_at(-1) + project.db.close() + + app = QApplication(sys.argv) + + distplotwidget = ModelsDistplotWidget() + distplotwidget.show() + distplotwidget.set_gluedf(glue_data) + distplotwidget.set_gluedf(glue_data) + + sys.exit(app.exec_()) From 884cda92689ec8fc4cff001436b3251af4824cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 4 Mar 2021 09:36:59 -0500 Subject: [PATCH 02/29] ModelsDistplotWidget: improve info results --- gwhat/gwrecharge/models_distplot.py | 112 ++++++++++++++++------------ 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index bbe3e647..b3411183 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -65,24 +65,27 @@ def setup(self): # Models Info Group. self.models_label = QLabel() self.models_label.setTextInteractionFlags(Qt.TextSelectableByMouse) + self.models_label.setTextFormat(Qt.RichText) - self.models_grpbox = QGroupBox('Models Info') + self.models_grpbox = QGroupBox('MODELS INFO') self.models_layout = QGridLayout(self.models_grpbox) self.models_layout.addWidget(self.models_label, 0, 0) + self.models_layout.setRowStretch(1, 1) # Selected Models Info Group. self.selectmodels_label = QLabel() self.selectmodels_label.setTextInteractionFlags( Qt.TextSelectableByMouse) - self.selectmodels_grpbox = QGroupBox('Selected Models Info') + self.selectmodels_grpbox = QGroupBox('SELECTED MODELS INFO') self.selectmodels_layout = QGridLayout(self.selectmodels_grpbox) self.selectmodels_layout.addWidget(self.selectmodels_label, 0, 0) + self.selectmodels_layout.setRowStretch(1, 1) # Setup the central widget. self.central_widget = QWidget() self.central_layout = QGridLayout(self.central_widget) - self.central_layout.addWidget(self.figcanvas, 0, 0, 2, 1) + self.central_layout.addWidget(self.figcanvas, 0, 0, 3, 1) self.central_layout.addWidget(self.models_grpbox, 0, 1) self.central_layout.addWidget(self.selectmodels_grpbox, 1, 1) self.central_layout.setColumnStretch(0, 1) @@ -99,28 +102,32 @@ def update_models_info(self): rasmax = self.glue_data['params']['RASmax'] sy = self.glue_data['params']['Sy'] text = ( - "Nbr of Models = {}" - "\n\n" - "RMSE min = {:0.1f} mm\n" - "RMSE avg = {:0.1f} mm\n" - "RMSE max = {:0.1f} mm\n" - "\n" - "CRu min = {:0.1f}\n" - "CRu avg = {:0.1f}\n" - "CRu max = {:0.1f}\n" - "\n" - "RASmax min = {:0.2f} mm\n" - "RASmax avg = {:0.2f} mm\n" - "RASmax max = {:0.2f} mm\n" - "\n" - "Sy min = {:0.2f}\n" - "Sy avg = {:0.2f}\n" - "Sy max = {:0.2f}\n\n" - ).format(len(rmse), - np.min(rmse), np.mean(rmse), np.max(rmse), - np.min(cru), np.mean(cru), np.max(cru), - np.min(rasmax), np.mean(rasmax), np.max(rasmax), - np.min(sy), np.mean(sy), np.max(sy)) + """ + Nbr of Models = {}

+ Ranges:
--- + + + + + + + + + + + + + + + + + +
RMSE ={:0.1f} - {:0.1f} mm
CRu ={:0.1f} - {:0.1f}
RASmax ={:0.2f} - {:0.2f} mm
Sy ={:0.2f} - {:0.2f}
+ """).format(len(rmse), + np.min(rmse), np.max(rmse), + np.min(cru), np.max(cru), + np.min(rasmax), np.max(rasmax), + np.min(sy), np.max(sy)) self.models_label.setText(text) def set_rmse_treshold(self, rmse_treshold=None): @@ -133,33 +140,45 @@ def set_rmse_treshold(self, rmse_treshold=None): where = np.where(rmse_data <= rmse_treshold)[0] selectmodels_text = ( - "RMSE Treshold = {:0.1f} mm\n" - "Models % = {:0.1f}\n" - "Nbr of Models = {}" + "RMSE Cutoff = {:0.1f} mm
" + "Nbr of Models = {}
" + "Models % = {:0.1f}" ).format(rmse_treshold, - len(where) / len(rmse_data) * 100, - len(where)) + len(where), + len(where) / len(rmse_data) * 100) if len(where) > 0: + rmse = self.glue_data['RMSE'][where] cru = self.glue_data['params']['Cru'][where] rasmax = self.glue_data['params']['RASmax'][where] sy = self.glue_data['params']['Sy'][where] selectmodels_text += ( - "\n\n" - "CRu min = {:0.1f}\n" - "CRu avg = {:0.1f}\n" - "CRu max = {:0.1f}\n" - "\n" - "RASmax min = {:0.2f} mm\n" - "RASmax avg = {:0.2f} mm\n" - "RASmax max = {:0.2f} mm\n" - "\n" - "Sy min = {:0.2f}\n" - "Sy avg = {:0.2f}\n" - "Sy max = {:0.2f}\n\n" - ).format(np.min(cru), np.mean(cru), np.max(cru), - np.min(rasmax), np.mean(rasmax), np.max(rasmax), - np.min(sy), np.mean(sy), np.max(sy)) + """ +

+ Ranges:
--- + + + + + + + + + + + + + + + + + +
RMSE ={:0.1f} - {:0.1f} mm
CRu ={:0.1f} - {:0.1f}
RASmax ={:0.2f} - {:0.2f} mm
Sy ={:0.2f} - {:0.2f}
+ """ + ).format(np.min(rmse), np.max(rmse), + np.min(cru), np.max(cru), + np.min(rasmax), np.max(rasmax), + np.min(sy), np.max(sy)) self.selectmodels_label.setText(selectmodels_text) def set_gluedf(self, glue_data): @@ -270,12 +289,10 @@ def restore(self): self.ax.draw_artist(self.infotext) def onpress(self, event): - print('onpress') if event.button == 1: self._selected_rmse_treshold = event.xdata def onrelease(self, event): - print('onrelease') if event.button != 1: return @@ -292,7 +309,6 @@ def onrelease(self, event): def onmove(self, event): """Internal event handler to draw the cursor when the mouse moves.""" - print('onmove') if self.ignore(event): return if not self.canvas.widgetlock.available(self): From fc4246773718b9baddbbb710c6d46262c5c792a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 4 Mar 2021 09:54:28 -0500 Subject: [PATCH 03/29] Update file header --- gwhat/gwrecharge/gwrecharge_gui.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gwhat/gwrecharge/gwrecharge_gui.py b/gwhat/gwrecharge/gwrecharge_gui.py index 12711516..a53ff850 100644 --- a/gwhat/gwrecharge/gwrecharge_gui.py +++ b/gwhat/gwrecharge/gwrecharge_gui.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- - -# Copyright © 2014-2018 GWHAT Project Contributors +# ----------------------------------------------------------------------------- +# Copyright © GWHAT Project Contributors # https://github.com/jnsebgosselin/gwhat # # This file is part of GWHAT (Ground-Water Hydrograph Analysis Toolbox). # Licensed under the terms of the GNU General Public License. +# ----------------------------------------------------------------------------- # ---- Stantard imports import time From 907860a83138a733aef3ef0a361408cfda5f3bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 4 Mar 2021 10:41:31 -0500 Subject: [PATCH 04/29] ModelsDistplotWidget: improve results display --- gwhat/gwrecharge/models_distplot.py | 77 ++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index b3411183..36cc9bef 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -30,11 +30,7 @@ # ---- Local imports -from gwhat.utils import icons -from gwhat.utils.icons import QToolButtonNormal, QToolButtonSmall -from gwhat.widgets.mplfigureviewer import ImageViewer -from gwhat.widgets.buttons import LangToolButton, ToolBarWidget -from gwhat.widgets.layout import VSep +from gwhat.utils.icons import get_icon mpl.rc('font', **{'family': 'sans-serif', 'sans-serif': ['Arial']}) LOCS = ['left', 'top', 'right', 'bottom'] @@ -49,9 +45,9 @@ class ModelsDistplotWidget(QMainWindow): def __init__(self, parent=None): super().__init__(parent) - self.setMinimumSize(850, 300) + self.setMinimumSize(850, 450) self.setWindowTitle('Models Distribution') - self.setWindowIcon(icons.get_icon('master')) + self.setWindowIcon(get_icon('models_dist')) self.setup() self.set_gluedf(None) @@ -62,12 +58,16 @@ def setup(self): self.figcanvas.sig_rmse_treshold_selected.connect( self.set_rmse_treshold) + ft = QApplication.instance().font() + ft.setPointSize(ft.pointSize() - 1) + # Models Info Group. self.models_label = QLabel() self.models_label.setTextInteractionFlags(Qt.TextSelectableByMouse) self.models_label.setTextFormat(Qt.RichText) + self.models_label.setFont(ft) - self.models_grpbox = QGroupBox('MODELS INFO') + self.models_grpbox = QGroupBox("Models info") self.models_layout = QGridLayout(self.models_grpbox) self.models_layout.addWidget(self.models_label, 0, 0) self.models_layout.setRowStretch(1, 1) @@ -76,8 +76,10 @@ def setup(self): self.selectmodels_label = QLabel() self.selectmodels_label.setTextInteractionFlags( Qt.TextSelectableByMouse) + self.selectmodels_label.setTextFormat(Qt.RichText) + self.selectmodels_label.setFont(ft) - self.selectmodels_grpbox = QGroupBox('SELECTED MODELS INFO') + self.selectmodels_grpbox = QGroupBox("Cutoff models info") self.selectmodels_layout = QGridLayout(self.selectmodels_grpbox) self.selectmodels_layout.addWidget(self.selectmodels_label, 0, 0) self.selectmodels_layout.setRowStretch(1, 1) @@ -104,22 +106,26 @@ def update_models_info(self): text = ( """ Nbr of Models = {}

- Ranges:
--- - + Ranges
+
- + + - + + - + + - + +
RMSE =RMSE: {:0.1f} - {:0.1f} mm
CRu =CRu: {:0.1f} - {:0.1f}
RASmax =RASmax: {:0.2f} - {:0.2f} mm
Sy =Sy: {:0.2f} - {:0.2f}
@@ -140,9 +146,25 @@ def set_rmse_treshold(self, rmse_treshold=None): where = np.where(rmse_data <= rmse_treshold)[0] selectmodels_text = ( - "RMSE Cutoff = {:0.1f} mm
" - "Nbr of Models = {}
" - "Models % = {:0.1f}" + """ + + + + + + + + + + + + + + + + +
RMSE Cutoff: {:0.1f} mm
Nbr of Models: {}
Models %: {:0.1f}
+ """ ).format(rmse_treshold, len(where), len(where) / len(rmse_data) * 100) @@ -155,22 +177,26 @@ def set_rmse_treshold(self, rmse_treshold=None): selectmodels_text += ( """

- Ranges:
--- + Ranges
- + + - + + - + + - + +
RMSE =RMSE: {:0.1f} - {:0.1f} mm
CRu =CRu: {:0.1f} - {:0.1f}
RASmax =RASmax: {:0.2f} - {:0.2f} mm
Sy =Sy: {:0.2f} - {:0.2f}
@@ -496,6 +522,11 @@ def set_size_inches(self, *args, **kargs): app = QApplication(sys.argv) + ft = app.font() + ft.setFamily('Segoe UI') + ft.setPointSize(10) + app.setFont(ft) + distplotwidget = ModelsDistplotWidget() distplotwidget.show() distplotwidget.set_gluedf(glue_data) From bd79020b8dd0828d5702f99415b3ce65a4d0e781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 4 Mar 2021 10:45:30 -0500 Subject: [PATCH 05/29] ModelsDistplotWidget: change range sub-title style --- gwhat/gwrecharge/models_distplot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 36cc9bef..21cc497c 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -106,7 +106,7 @@ def update_models_info(self): text = ( """ Nbr of Models = {}

- Ranges
+ Ranges
--- @@ -177,7 +177,7 @@ def set_rmse_treshold(self, rmse_treshold=None): selectmodels_text += ( """

- Ranges
+ Ranges
---
RMSE
From 7362c3e83890f1116a0ae1f7a0fbcdf9761f3df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 4 Mar 2021 10:45:52 -0500 Subject: [PATCH 06/29] Add ModelsDistplotWidget to RechgEvalWidget --- gwhat/gwrecharge/gwrecharge_gui.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/gwhat/gwrecharge/gwrecharge_gui.py b/gwhat/gwrecharge/gwrecharge_gui.py index a53ff850..09ac069f 100644 --- a/gwhat/gwrecharge/gwrecharge_gui.py +++ b/gwhat/gwrecharge/gwrecharge_gui.py @@ -26,8 +26,9 @@ from gwhat.gwrecharge.gwrecharge_calc2 import RechgEvalWorker from gwhat.gwrecharge.gwrecharge_plot_results import FigureStackManager from gwhat.gwrecharge.glue import GLUEDataFrameBase -from gwhat.utils.icons import QToolButtonSmall, get_iconsize -from gwhat.utils import icons +from gwhat.gwrecharge.models_distplot import ModelsDistplotWidget +from gwhat.utils.icons import QToolButtonSmall, get_iconsize, get_icon +from gwhat.utils.qthelpers import create_toolbutton class RechgEvalWidget(QFrame): @@ -42,6 +43,7 @@ def __init__(self, parent): self.wxdset = None self.wldset = None self.figstack = FigureStackManager(parent=self) + self.modelsdistplot = ModelsDistplotWidget(parent=self) self.progressbar = QProgressBar() self.progressbar.setValue(0) @@ -49,7 +51,6 @@ def __init__(self, parent): self.__initUI__() # Set the worker and thread mechanics - self.rechg_worker = RechgEvalWorker() self.rechg_worker.sig_glue_finished.connect(self.receive_glue_calcul) self.rechg_worker.sig_glue_progress.connect(self.progressbar.setValue) @@ -194,17 +195,26 @@ def setup_toolbar(self): btn_calib = QPushButton('Compute Recharge') btn_calib.clicked.connect(self.btn_calibrate_isClicked) - self.btn_show_result = QToolButtonSmall(icons.get_icon('search')) + self.btn_show_result = QToolButtonSmall(get_icon('search')) self.btn_show_result.clicked.connect(self.figstack.show) self.btn_show_result.setToolTip("Show GLUE results.") self.btn_save_glue = ExportGLUEButton(self.wxdset) + self.btn_modelsdistplot = create_toolbutton( + parent=self, + icon='models_dist', + tip='Display the model distribution plot.', + triggered=self.modelsdistplot.show, + iconsize=get_iconsize('normal') + ) + layout = QGridLayout(toolbar) layout.addWidget(btn_calib, 0, 0) layout.addWidget(self.btn_show_result, 0, 1) - layout.addWidget(self.btn_save_glue, 0, 2) - layout.setContentsMargins(10, 0, 10, 0) # (L, T, R, B) + layout.addWidget(self.btn_modelsdistplot, 0, 2) + layout.addWidget(self.btn_save_glue, 0, 3) + layout.setContentsMargins(10, 0, 10, 0) return toolbar @@ -215,6 +225,7 @@ def set_wldset(self, wldset): gluedf = None if wldset is None else wldset.get_glue_at(-1) self._setup_ranges_from_wldset(gluedf) self.figstack.set_gluedf(gluedf) + self.modelsdistplot.set_gluedf(gluedf) self.btn_save_glue.set_model(gluedf) def set_wxdset(self, wxdset): @@ -327,6 +338,7 @@ def receive_glue_calcul(self, glue_dataframe): self.btn_save_glue.set_model(glue_dataframe) self.figstack.set_gluedf(glue_dataframe) + self.modelsdistplot.set_gluedf(glue_dataframe) class ExportGLUEButton(ExportDataButton): @@ -418,8 +430,6 @@ def save_water_levels_tofile(self, savefilename=None): QApplication.restoreOverrideCursor() -# %% ---- if __name__ == '__main__' - if __name__ == '__main__': from gwhat.gwrecharge.gwrecharge_calc2 import load_glue_from_npy from gwhat.gwrecharge.glue import GLUEDataFrame From 20736d4d76c89e85ed1d25d2c514807542da0591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 4 Mar 2021 10:54:24 -0500 Subject: [PATCH 07/29] RechgEvalWidget: close child windows when closing --- gwhat/HydroCalc2.py | 1 + gwhat/gwrecharge/gwrecharge_gui.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index 225e5c4b..a90fedaf 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -549,6 +549,7 @@ def close(self): CONF.set('hydrocalc', 'current_tool_index', self.tools_tabwidget.currentIndex()) self.brf_eval_widget.close() + self.rechg_eval_widget.close() super().close() def showEvent(self, event): diff --git a/gwhat/gwrecharge/gwrecharge_gui.py b/gwhat/gwrecharge/gwrecharge_gui.py index 09ac069f..09103588 100644 --- a/gwhat/gwrecharge/gwrecharge_gui.py +++ b/gwhat/gwrecharge/gwrecharge_gui.py @@ -340,6 +340,12 @@ def receive_glue_calcul(self, glue_dataframe): self.figstack.set_gluedf(glue_dataframe) self.modelsdistplot.set_gluedf(glue_dataframe) + def close(self): + """Extend Qt method to close child windows.""" + self.figstack.close() + self.modelsdistplot.close() + super().close() + class ExportGLUEButton(ExportDataButton): """ From 58679cebcfbe41001a2ce5ce3d9b1d3c6fb11972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 4 Mar 2021 10:54:41 -0500 Subject: [PATCH 08/29] Add models_dist icon --- gwhat/utils/icons.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gwhat/utils/icons.py b/gwhat/utils/icons.py index f0d0e59d..b41da95d 100644 --- a/gwhat/utils/icons.py +++ b/gwhat/utils/icons.py @@ -139,6 +139,9 @@ 'link_off': [ ('mdi.link-off',), {'color': COLOR, 'scale_factor': 1, 'rotated': 90}], + 'models_dist': [ + ('mdi.chart-bell-curve',), + {'color': COLOR, 'scale_factor': 1.2}], 'pan': [ ('mdi.pan',), {'color': COLOR, 'scale_factor': 1.3}], From 438164ce655e52f55259359317cb6c36f6704d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 12 Mar 2021 12:20:45 -0500 Subject: [PATCH 09/29] TEMP COMMIT --- gwhat/HydroCalc2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gwhat/HydroCalc2.py b/gwhat/HydroCalc2.py index a90fedaf..225e5c4b 100644 --- a/gwhat/HydroCalc2.py +++ b/gwhat/HydroCalc2.py @@ -549,7 +549,6 @@ def close(self): CONF.set('hydrocalc', 'current_tool_index', self.tools_tabwidget.currentIndex()) self.brf_eval_widget.close() - self.rechg_eval_widget.close() super().close() def showEvent(self, event): From cae4b9f8bf650bade05c29b327b1f836d57702ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 17 Mar 2021 09:06:34 -0400 Subject: [PATCH 10/29] ModelsDistplotWidget: tweak some the precision --- gwhat/gwrecharge/gwrecharge_gui.py | 4 ++-- gwhat/gwrecharge/models_distplot.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gwhat/gwrecharge/gwrecharge_gui.py b/gwhat/gwrecharge/gwrecharge_gui.py index 09103588..4abeb502 100644 --- a/gwhat/gwrecharge/gwrecharge_gui.py +++ b/gwhat/gwrecharge/gwrecharge_gui.py @@ -94,10 +94,10 @@ def __init__(self, items, parent=None): # Runoff coefficient (Cro) : - self.CRO_min = QDoubleSpinBox(0.1, 3) + self.CRO_min = QDoubleSpinBox(0.1, 2) self.CRO_min.setRange(0, 1) - self.CRO_max = QDoubleSpinBox(0.3, 3) + self.CRO_max = QDoubleSpinBox(0.3, 2) self.CRO_max.setRange(0, 1) # Snowmelt parameters : diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 21cc497c..aeedc60e 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -116,17 +116,17 @@ def update_models_info(self): - + - + - +
RMSE
CRu : {:0.1f} - {:0.1f}{:0.2f} - {:0.2f}
RASmax : {:0.2f} - {:0.2f} mm{:0.0f} - {:0.0f} mm
Sy : {:0.2f} - {:0.2f}{:0.3f} - {:0.3f}
""").format(len(rmse), @@ -187,17 +187,17 @@ def set_rmse_treshold(self, rmse_treshold=None): CRu : - {:0.1f} - {:0.1f} + {:0.2f} - {:0.2f} RASmax : - {:0.2f} - {:0.2f} mm + {:0.0f} - {:0.0f} mm Sy : - {:0.2f} - {:0.2f} + {:0.3f} - {:0.3f} """ From a87cc38e038d16fb86e0f90899c56636253601ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 17 Mar 2021 09:08:44 -0400 Subject: [PATCH 11/29] ModelsDistplotWidget: improve info text for the cutoff rmse --- gwhat/gwrecharge/models_distplot.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index aeedc60e..5d989871 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -284,7 +284,8 @@ def __init__(self, ax, useblit=True): animated=False) scaled_translation = ScaledTranslation( - 0, self.infotextpad/72, self.ax.figure.dpi_scale_trans) + self.infotextpad/72, self.infotextpad/72, + self.ax.figure.dpi_scale_trans) self.infotext = ax.text( 0, 1, '', va='bottom', ha='left', rotation=0, fontsize=self.infotextheight, @@ -296,9 +297,9 @@ def update_infotext(self, xdata): if glue_data is not None and xdata is not None: where = np.where(glue_data['RMSE'] <= xdata)[0] percent = len(where) / len(glue_data['RMSE']) * 100 - text = ("{:0.1f} of the models have a RMSE less " + text = ("{} models ({:0.1f}%) have a RMSE less " "than or equal to {:0.1f} mm.").format( - percent, xdata) + len(where), percent, xdata) else: text = '' self.infotext.set_text(text) From b611f71ee8d6f6ece6306cb368e018ac80e61889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 17 Mar 2021 09:11:41 -0400 Subject: [PATCH 12/29] ModelsDistplotWidget: add the option to change the bins number --- gwhat/gwrecharge/models_distplot.py | 82 ++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 5d989871..4f8b1d30 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -26,7 +26,7 @@ QGridLayout, QAbstractSpinBox, QApplication, QDoubleSpinBox, QFileDialog, QGroupBox, QLabel, QMessageBox, QScrollArea, QScrollBar, QSpinBox, QTabWidget, QWidget, QStyle, QFrame, QMainWindow, - QGroupBox) + QGroupBox, QToolBar, QDoubleSpinBox) # ---- Local imports @@ -94,6 +94,41 @@ def setup(self): self.central_layout.setRowStretch(2, 1) self.setCentralWidget(self.central_widget) + self.setup_toolbar() + + def setup_toolbar(self): + """ + Setup the toolbar of this mainwindow. + """ + self.setContextMenuPolicy(Qt.NoContextMenu) + toolbar = QToolBar() + toolbar.setFloatable(False) + toolbar.setMovable(False) + toolbar.setStyleSheet( + "QToolBar {spacing:1px; padding: 5px;}") + self.addToolBar(Qt.TopToolBarArea, toolbar) + + # Setup the bins widget. + bins_tooltip = "Number of equal-width bins in the range." + bins_label = QLabel('Bins nbr:') + bins_label.setToolTip(bins_tooltip) + + self.bins_sbox = QDoubleSpinBox() + self.bins_sbox.setDecimals(0) + self.bins_sbox.setMaximum(999) + self.bins_sbox.setMinimum(1) + self.bins_sbox.setValue(30) + self.bins_sbox.setToolTip(bins_tooltip) + self.bins_sbox.valueChanged.connect(self.figcanvas.figure.set_bins_nbr) + + bins_widget = QWidget() + bins_layout = QGridLayout(bins_widget) + bins_layout.setContentsMargins(0, 0, 0, 0) + bins_layout.addWidget(bins_label, 0, 0) + bins_layout.addWidget(self.bins_sbox, 0, 1) + + toolbar.addWidget(bins_widget) + def update_models_info(self): if self.glue_data is None: self.models_label.setText('') @@ -364,8 +399,8 @@ def _update(self): class ModelsDistplotFigure(Figure): - def __init__(self, *args, **kargs): - super().__init__(*args, **kargs) + def __init__(self, bins_nbr=30): + super().__init__() self.set_facecolor('white') self.set_tight_layout(True) self.setp = { @@ -383,6 +418,7 @@ def __init__(self, *args, **kargs): self.hist_obj = None self.glue_data = None self.cursor = None + self.bins_nbr = bins_nbr self.setup_axes() @@ -476,24 +512,37 @@ def setup_margins(self): 1 - top_margin - bottom_margin]) def plot(self, glue_data): + """ + Generate the histogram plot using the RMSE values of the models stored + in glue_data. + """ self.glue_data = glue_data - glue_rmse = glue_data['RMSE'] + self._draw_hist() - rmse_min = np.floor(np.min(glue_rmse)) - rmse_max = np.ceil(np.max(glue_rmse)) - bins = np.arange(rmse_min, rmse_max + 1, 0.5) + def _draw_hist(self): + """ + Draw the histogram of the models RMSE. + """ + glue_rmse = self.glue_data['RMSE'] self.ax0.set_visible(True) if self.hist_obj is not None: - self.hist_obj.remove() - self.ax0.hist( - glue_rmse, color='blue', edgecolor='black', bins=bins) - - self.ax0.axis(xmin=bins[0] - 0.5, xmax=bins[-1]) + for patch in self.hist_obj: + patch.remove() + n, bins, self.hist_obj = self.ax0.hist( + glue_rmse, color='blue', edgecolor='black', bins=self.bins_nbr) + + bins_width = bins[1] - bins[0] + self.ax0.axis( + ymin=0, ymax=np.max(n) + 1, + xmin=bins[0] - bins_width / 2, xmax=bins[-1] + bins_width / 2, + ) if self.cursor is None: self.cursor = ModelsDistplotCursor(self.ax0) + self.canvas.draw() + def tight_layout(self, force_update=False): """ Override matplotlib method to setup the margins of the axes. @@ -509,6 +558,13 @@ def set_size_inches(self, *args, **kargs): super().set_size_inches(*args, **kargs) self.tight_layout() + def set_bins_nbr(self, bins_nbr): + """ + Set the number of equal-width bins to plot in the histogram. + """ + self.bins_nbr = int(bins_nbr) + self._draw_hist() + if __name__ == '__main__': from gwhat.projet.reader_projet import ProjetReader @@ -517,7 +573,7 @@ def set_size_inches(self, *args, **kargs): "Éval recharge (GWHAT)/evaluate_recharge/evaluate_recharge.gwt") project = ProjetReader(fname) - wldset = project.get_wldset('Mercier (03090001)') + wldset = project.get_wldset('Mercier_v2 (03090001)') glue_data = wldset.get_glue_at(-1) project.db.close() From 978a311fa519b01eb4ed78aaaeb9ce82ef827492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 17 Mar 2021 09:11:55 -0400 Subject: [PATCH 13/29] Complete some missing docstrings --- gwhat/gwrecharge/models_distplot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 4f8b1d30..d4f80d92 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -423,7 +423,7 @@ def __init__(self, bins_nbr=30): self.setup_axes() def setup_axes(self): - """Plot the data.""" + """Setup the axes of the figure.""" self.ax0 = self.add_axes([0, 0, 1, 1]) self.ax0.patch.set_visible(False) for axis in ['top', 'bottom', 'left', 'right']: @@ -438,6 +438,7 @@ def setup_axes(self): labelpad=self.ylabelpad) def setup_margins(self): + """Setup the margins of the figure.""" if self.ax0 is None: return figborderpad = self.setp['figure_border'] From 342227539fd5dbdc60f4bc079d8e2a3dbf461ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 17 Mar 2021 09:12:24 -0400 Subject: [PATCH 14/29] ModelsDistplotFigure: change how the top margin is calculated --- gwhat/gwrecharge/models_distplot.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index d4f80d92..aa370292 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -488,12 +488,8 @@ def setup_margins(self): self.cursor.infotextheight + self.cursor.infotextpad if self.cursor else 0 ) - yaxis_height = max( - bbox_yaxis_left.y1 - axbbox.y1, - bbox_yaxis_right.y1 - axbbox.y1, - 0) top_margin = ( - yaxis_height + figborderpad + cursorinfotext_height + figborderpad + cursorinfotext_height ) / figbbox.height # Calculate bottom margin height. From 44c82af97e168d8e78c4ed2feb45549d650cf509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 17 Mar 2021 11:46:33 -0400 Subject: [PATCH 15/29] ModelsDistplotCursor: save state when clearing/restoring --- gwhat/gwrecharge/models_distplot.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index aa370292..6555f5cf 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -340,13 +340,26 @@ def update_infotext(self, xdata): self.infotext.set_text(text) def clear(self): - """Internal event handler to clear the cursor.""" + """ + Clear the cursor. + + This method must be called by the canvas BEFORE making a copy of + the canvas background. + """ + self.__linev_visible = self.linev.get_visible() + self.__infotext_visible = self.infotext.get_visible() self.linev.set_visible(False) self.infotext.set_visible(False) def restore(self): - self.linev.set_visible(True) - self.infotext.set_visible(True) + """ + Restore the cursor. + + This method must be called by the canvas AFTER a copy has been made + of the canvas background. + """ + self.linev.set_visible(self.__linev_visible) + self.infotext.set_visible(self.__infotext_visible) self.ax.draw_artist(self.linev) self.ax.draw_artist(self.infotext) From 9b14da7a511f07b2c477764a6098a1b623e447da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 17 Mar 2021 11:48:16 -0400 Subject: [PATCH 16/29] Update treshold info after a new glue_dataset is set --- gwhat/gwrecharge/models_distplot.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 6555f5cf..cbce5dd2 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -173,8 +173,10 @@ def update_models_info(self): def set_rmse_treshold(self, rmse_treshold=None): if self.glue_data is None: + self.selectmodels_label.setText('') return + # Update the left panel info. rmse_data = self.glue_data['RMSE'] if rmse_treshold is None: rmse_treshold = np.max(rmse_data) @@ -251,7 +253,7 @@ def set_gluedf(self, glue_data): else: QApplication.setOverrideCursor(Qt.WaitCursor) self.figcanvas.plot_results(glue_data) - self.set_rmse_treshold(None) + self.set_rmse_treshold(None) QApplication.restoreOverrideCursor() def show(self): @@ -311,11 +313,11 @@ def __init__(self, ax, useblit=True): self.useblit = useblit and self.canvas.supports_blit self.linev = ax.axvline( - ax.get_ybound()[0], visible=False, color='red', linewidth=1, + ax.get_xbound()[1], visible=False, color='red', linewidth=1, ls='--', animated=self.useblit) self.treshold_vline = ax.axvline( - ax.get_ybound()[0], visible=False, color='red', linewidth=1, + ax.get_xbound()[1], visible=False, color='red', linewidth=1, animated=False) scaled_translation = ScaledTranslation( @@ -528,6 +530,13 @@ def plot(self, glue_data): """ self.glue_data = glue_data self._draw_hist() + if self.cursor is None: + self.cursor = ModelsDistplotCursor(self.ax0) + else: + self.cursor.clear() + self.canvas.sig_rmse_treshold_selected.emit( + self.cursor._selected_rmse_treshold) + self.canvas.draw_idle() def _draw_hist(self): """ @@ -548,9 +557,6 @@ def _draw_hist(self): xmin=bins[0] - bins_width / 2, xmax=bins[-1] + bins_width / 2, ) - if self.cursor is None: - self.cursor = ModelsDistplotCursor(self.ax0) - self.canvas.draw() def tight_layout(self, force_update=False): From 2bbc7bbe0b00b47294f21c29a9f573bf2b2527e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 17 Mar 2021 11:59:23 -0400 Subject: [PATCH 17/29] Correct git commit mistake --- gwhat/gwrecharge/models_distplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index cbce5dd2..9c8d3d27 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -248,12 +248,12 @@ def set_gluedf(self, glue_data): """Set the namespace for the GLUE results dataset.""" self.glue_data = glue_data self.update_models_info() + self.set_rmse_treshold(None) if glue_data is None: self.figcanvas.clear_figure() else: QApplication.setOverrideCursor(Qt.WaitCursor) self.figcanvas.plot_results(glue_data) - self.set_rmse_treshold(None) QApplication.restoreOverrideCursor() def show(self): From 63f7f83f9f548ece06f5e623915cb6e25d85e13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 17 Mar 2021 23:22:30 -0400 Subject: [PATCH 18/29] ModelsDistplotWidget: rework bins layout --- gwhat/gwrecharge/models_distplot.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 9c8d3d27..45a4bf84 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -109,22 +109,19 @@ def setup_toolbar(self): self.addToolBar(Qt.TopToolBarArea, toolbar) # Setup the bins widget. - bins_tooltip = "Number of equal-width bins in the range." - bins_label = QLabel('Bins nbr:') - bins_label.setToolTip(bins_tooltip) - self.bins_sbox = QDoubleSpinBox() self.bins_sbox.setDecimals(0) self.bins_sbox.setMaximum(999) self.bins_sbox.setMinimum(1) self.bins_sbox.setValue(30) - self.bins_sbox.setToolTip(bins_tooltip) self.bins_sbox.valueChanged.connect(self.figcanvas.figure.set_bins_nbr) bins_widget = QWidget() + bins_widget.setToolTip("Number of equal-width bins in the range.") + bins_layout = QGridLayout(bins_widget) - bins_layout.setContentsMargins(0, 0, 0, 0) - bins_layout.addWidget(bins_label, 0, 0) + bins_layout.setContentsMargins(5, 0, 0, 0) + bins_layout.addWidget(QLabel('Bins nbr:'), 0, 0) bins_layout.addWidget(self.bins_sbox, 0, 1) toolbar.addWidget(bins_widget) From 6035acd9ae04a91fab0df82d738972ff65df7ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 18 Mar 2021 13:36:12 -0400 Subject: [PATCH 19/29] ModelsDistplotFigure: improve how ymax is calculated --- gwhat/gwrecharge/models_distplot.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 45a4bf84..c5155a11 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -548,9 +548,13 @@ def _draw_hist(self): n, bins, self.hist_obj = self.ax0.hist( glue_rmse, color='blue', edgecolor='black', bins=self.bins_nbr) + renderer = self.canvas.get_renderer() + ax_bbox = self.ax0.yaxis.label.get_window_extent(renderer) + bins_width = bins[1] - bins[0] self.ax0.axis( - ymin=0, ymax=np.max(n) + 1, + ymin=0, + ymax=(ax_bbox.height * np.max(n)) / (ax_bbox.height - 3), xmin=bins[0] - bins_width / 2, xmax=bins[-1] + bins_width / 2, ) From 647515d6c03d7b04549495cce3c15bbed1b52bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 31 Mar 2021 09:47:08 -0400 Subject: [PATCH 20/29] update if __name__ == '__main__': --- gwhat/gwrecharge/models_distplot.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index c5155a11..c3b74190 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -585,6 +585,7 @@ def set_bins_nbr(self, bins_nbr): if __name__ == '__main__': from gwhat.projet.reader_projet import ProjetReader + from gwhat.utils.qthelpers import create_qapplication import sys fname = ("D:/OneDrive/INRS/2017 - Projet INRS PACC/" "Éval recharge (GWHAT)/evaluate_recharge/evaluate_recharge.gwt") @@ -594,12 +595,7 @@ def set_bins_nbr(self, bins_nbr): glue_data = wldset.get_glue_at(-1) project.db.close() - app = QApplication(sys.argv) - - ft = app.font() - ft.setFamily('Segoe UI') - ft.setPointSize(10) - app.setFont(ft) + app = create_qapplication() distplotwidget = ModelsDistplotWidget() distplotwidget.show() From 4c1475aa7c47633f2f2434b5d516f8dd77a7b6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 31 Mar 2021 09:51:18 -0400 Subject: [PATCH 21/29] ModelsDistplotWidget: add structure to set rmse treshold --- gwhat/gwrecharge/models_distplot.py | 30 ++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index c3b74190..3a3d5195 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -293,6 +293,11 @@ def draw(self): if self.figure.cursor is not None: self.figure.cursor.restore() + def set_rmse_treshold(self, rmse_treshold): + """Set the value of the RMSE treshold in the figure cursor.""" + if self.figure.cursor: + self.figure.cursor.set_rmse_treshold(rmse_treshold) + class ModelsDistplotCursor(AxesWidget): @@ -338,6 +343,21 @@ def update_infotext(self, xdata): text = '' self.infotext.set_text(text) + def set_rmse_treshold(self, value): + """Set änd plot a new value for the RMSE treshold vertical line.""" + self._selected_rmse_treshold = value + self.draw_rmse_treshold() + + def draw_rmse_treshold(self): + """Draw the RMSE treshold vertical line.""" + if self._selected_rmse_treshold is not None: + self.treshold_vline.set_xdata( + (self._selected_rmse_treshold, self._selected_rmse_treshold)) + self.treshold_vline.set_visible(True) + else: + self.treshold_vline.set_visible(False) + self.canvas.draw_idle() + def clear(self): """ Clear the cursor. @@ -369,15 +389,7 @@ def onpress(self, event): def onrelease(self, event): if event.button != 1: return - - if self._selected_rmse_treshold is not None: - self.treshold_vline.set_xdata( - (self._selected_rmse_treshold, self._selected_rmse_treshold)) - self.treshold_vline.set_visible(True) - else: - self.treshold_vline.set_visible(False) - self.canvas.draw_idle() - + self.set_rmse_treshold(event.xdata) self.canvas.sig_rmse_treshold_selected.emit( self._selected_rmse_treshold) From 19fe3a915721960b22ca18c54c6a79a09d4c7077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 31 Mar 2021 10:15:42 -0400 Subject: [PATCH 22/29] ModelsDistplotWidget: add the possibility to clear treshold with right click --- gwhat/gwrecharge/models_distplot.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 3a3d5195..457771bc 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -307,6 +307,7 @@ def __init__(self, ax, useblit=True): self.infotextheight = 10 self._selected_rmse_treshold = None + self._cleared_rmse_treshold = False self.connect_event('motion_notify_event', self.onmove) self.connect_event('button_press_event', self.onpress) self.connect_event('button_release_event', self.onrelease) @@ -343,6 +344,11 @@ def update_infotext(self, xdata): text = '' self.infotext.set_text(text) + def clear_rmse_treshold(self): + """Clear the RMSE treshold vertical line.""" + self._selected_rmse_treshold = None + self.draw_rmse_treshold() + def set_rmse_treshold(self, value): """Set änd plot a new value for the RMSE treshold vertical line.""" self._selected_rmse_treshold = value @@ -385,13 +391,18 @@ def restore(self): def onpress(self, event): if event.button == 1: self._selected_rmse_treshold = event.xdata + elif event.button == 3: + self._cleared_rmse_treshold = True def onrelease(self, event): - if event.button != 1: - return - self.set_rmse_treshold(event.xdata) - self.canvas.sig_rmse_treshold_selected.emit( - self._selected_rmse_treshold) + if event.button == 1: + self.set_rmse_treshold(event.xdata) + self.canvas.sig_rmse_treshold_selected.emit( + self._selected_rmse_treshold) + elif event.button == 3 and self._cleared_rmse_treshold is True: + self._cleared_rmse_treshold = False + self.clear_rmse_treshold() + self.canvas.sig_rmse_treshold_selected.emit(None) def onmove(self, event): """Internal event handler to draw the cursor when the mouse moves.""" From 9ed9e85b084d164c4dd39967721e1bec861c7b66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 31 Mar 2021 10:17:22 -0400 Subject: [PATCH 23/29] ModelsDistplotWidget: add a combobox to set treshold --- gwhat/gwrecharge/models_distplot.py | 63 ++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 457771bc..e302f30e 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -73,6 +73,41 @@ def setup(self): self.models_layout.setRowStretch(1, 1) # Selected Models Info Group. + rmse_cutoff_tooltip = ( + "RMSE cutoff value" + "

" + "The RMSE cutoff value is reprensented by a plain red vertical " + "line on the graph. Cutoff models info are calculated from the " + "family of models whose RMSE falls below that cutoff value." + "

" + "You can also select a new RMSE cutoff value by clicking " + "with the left button of the mouse on the graph and you " + "can clear the treshold by clicking with the right button." + ) + self.rmse_cutoff_sbox = QDoubleSpinBox() + self.rmse_cutoff_sbox.setDecimals(1) + self.rmse_cutoff_sbox.setSingleStep(0.1) + self.rmse_cutoff_sbox.setRange(0, 9999) + self.rmse_cutoff_sbox.setValue(0) + self.rmse_cutoff_sbox.setSpecialValueText('None') + self.rmse_cutoff_sbox.setKeyboardTracking(False) + self.rmse_cutoff_sbox.setButtonSymbols(QDoubleSpinBox.NoButtons) + self.rmse_cutoff_sbox.valueChanged.connect( + self._handle_rmse_treshold_changed) + + rmse_cutoff_widget = QWidget() + rmse_cutoff_widget.setToolTip(rmse_cutoff_tooltip) + + ft = rmse_cutoff_widget.font() + ft.setPointSize(ft.pointSize() - 1) + rmse_cutoff_widget.setFont(ft) + + rmse_cutoff_layout = QGridLayout(rmse_cutoff_widget) + rmse_cutoff_layout.setContentsMargins(2, 0, 0, 0) + rmse_cutoff_layout.addWidget(QLabel('RMSE cutoff:'), 0, 0) + rmse_cutoff_layout.addWidget(self.rmse_cutoff_sbox, 0, 1) + rmse_cutoff_layout.addWidget(QLabel('mm'), 0, 2) + self.selectmodels_label = QLabel() self.selectmodels_label.setTextInteractionFlags( Qt.TextSelectableByMouse) @@ -81,8 +116,10 @@ def setup(self): self.selectmodels_grpbox = QGroupBox("Cutoff models info") self.selectmodels_layout = QGridLayout(self.selectmodels_grpbox) - self.selectmodels_layout.addWidget(self.selectmodels_label, 0, 0) - self.selectmodels_layout.setRowStretch(1, 1) + self.selectmodels_layout.addWidget(rmse_cutoff_widget, 0, 0) + self.selectmodels_layout.addWidget(self.selectmodels_label, 1, 0) + self.selectmodels_layout.setRowStretch(2, 1) + self.selectmodels_layout.setSpacing(2) # Setup the central widget. self.central_widget = QWidget() @@ -168,10 +205,23 @@ def update_models_info(self): np.min(sy), np.max(sy)) self.models_label.setText(text) + def _handle_rmse_treshold_changed(self, value): + value = None if value == 0 else value + self.figcanvas.set_rmse_treshold(value) + self.set_rmse_treshold(value) + def set_rmse_treshold(self, rmse_treshold=None): if self.glue_data is None: self.selectmodels_label.setText('') + self.rmse_cutoff_sbox.blockSignals(True) + self.rmse_cutoff_sbox.setValue(0) + self.rmse_cutoff_sbox.blockSignals(False) return + else: + self.rmse_cutoff_sbox.blockSignals(True) + self.rmse_cutoff_sbox.setValue( + 0 if rmse_treshold is None else rmse_treshold) + self.rmse_cutoff_sbox.blockSignals(False) # Update the left panel info. rmse_data = self.glue_data['RMSE'] @@ -182,11 +232,6 @@ def set_rmse_treshold(self, rmse_treshold=None): selectmodels_text = ( """ - - - - - @@ -199,9 +244,7 @@ def set_rmse_treshold(self, rmse_treshold=None):
RMSE Cutoff: {:0.1f} mm
Nbr of Models :
""" - ).format(rmse_treshold, - len(where), - len(where) / len(rmse_data) * 100) + ).format(len(where), len(where) / len(rmse_data) * 100) if len(where) > 0: rmse = self.glue_data['RMSE'][where] From 18dedcd52887d3a076ca825536dda4123ba7c9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 21 Apr 2021 16:50:54 -0400 Subject: [PATCH 24/29] Make raise window if button is clicked again --- gwhat/gwrecharge/models_distplot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index e302f30e..8a81b18b 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -298,11 +298,11 @@ def set_gluedf(self, glue_data): def show(self): """Qt method override.""" - if self.isMinimized(): - self.setWindowState(self.windowState() & ~Qt.WindowMinimized) - else: - self.resize(self.size()) - super().show() + if self.windowState() == Qt.WindowMinimized: + self.setWindowState(Qt.WindowNoState) + super().show() + self.activateWindow() + self.raise_() class ModelsDistplotCanvas(FigureCanvasQTAgg): From 6651cd63194c13661c76ca5163cbe5b10f2621a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Thu, 22 Apr 2021 10:37:22 -0400 Subject: [PATCH 25/29] Improve size of text in the figure --- gwhat/gwrecharge/models_distplot.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 8a81b18b..62e59f3c 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -347,7 +347,7 @@ class ModelsDistplotCursor(AxesWidget): def __init__(self, ax, useblit=True): super().__init__(ax) self.infotextpad = 3 - self.infotextheight = 10 + self.infotextheight = 11 self._selected_rmse_treshold = None self._cleared_rmse_treshold = False @@ -482,8 +482,10 @@ def __init__(self, bins_nbr=30): self.set_facecolor('white') self.set_tight_layout(True) self.setp = { - 'xlabel_size': 14, - 'ylabel_size': 14, + 'xlabel_size': 16, + 'ylabel_size': 16, + 'xtickslabel_size': 12, + 'ytickslabel_size': 12, 'left_margin': None, 'right_margin': None, 'top_margin': None, @@ -507,6 +509,12 @@ def setup_axes(self): for axis in ['top', 'bottom', 'left', 'right']: self.ax0.spines[axis].set_linewidth(0.75) + # Setup axe tick parameters. + self.ax0.tick_params( + axis='x', which='major', labelsize=self.setp['xtickslabel_size']) + self.ax0.tick_params( + axis='y', which='major', labelsize=self.setp['ytickslabel_size']) + # Setup axes labels. self.ax0.set_xlabel( 'RMSE', fontsize=self.setp['xlabel_size'], From 871075a8d8d33ab6f425c8cf14ff8aa890b28ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 23 Apr 2021 11:19:31 -0400 Subject: [PATCH 26/29] ModelsDistplotWidget: add copy to clipboard functionality --- gwhat/gwrecharge/models_distplot.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 62e59f3c..efbc133a 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -8,6 +8,7 @@ # ----------------------------------------------------------------------------- # ---- Standard library imports +import io import os import os.path as osp @@ -19,18 +20,19 @@ from matplotlib.widgets import AxesWidget from matplotlib.transforms import ScaledTranslation -from PyQt5.QtCore import Qt -from PyQt5.QtCore import pyqtSlot as QSlot -from PyQt5.QtCore import pyqtSignal as Signal -from PyQt5.QtWidgets import ( +from qtpy.QtGui import QImage +from qtpy.QtCore import Qt, Signal +from qtpy.QtWidgets import ( QGridLayout, QAbstractSpinBox, QApplication, QDoubleSpinBox, - QFileDialog, QGroupBox, QLabel, QMessageBox, QScrollArea, QScrollBar, + QFileDialog, QLabel, QMessageBox, QScrollArea, QScrollBar, QSpinBox, QTabWidget, QWidget, QStyle, QFrame, QMainWindow, QGroupBox, QToolBar, QDoubleSpinBox) # ---- Local imports from gwhat.utils.icons import get_icon +from gwhat.utils.qthelpers import create_toolbutton + mpl.rc('font', **{'family': 'sans-serif', 'sans-serif': ['Arial']}) LOCS = ['left', 'top', 'right', 'bottom'] @@ -145,6 +147,14 @@ def setup_toolbar(self): "QToolBar {spacing:1px; padding: 5px;}") self.addToolBar(Qt.TopToolBarArea, toolbar) + self.btn_copy_to_clipboard = create_toolbutton( + self, icon='copy_clipboard', + text="Copy", + tip="Put a copy of the figure on the Clipboard.", + triggered=self.figcanvas.copy_to_clipboard, + shortcut='Ctrl+C') + toolbar.addWidget(self.btn_copy_to_clipboard) + # Setup the bins widget. self.bins_sbox = QDoubleSpinBox() self.bins_sbox.setDecimals(0) @@ -341,6 +351,13 @@ def set_rmse_treshold(self, rmse_treshold): if self.figure.cursor: self.figure.cursor.set_rmse_treshold(rmse_treshold) + def copy_to_clipboard(self): + """Put a copy of the figure on the clipboard.""" + buf = io.BytesIO() + self.figure.savefig(buf, dpi=300) + QApplication.clipboard().setImage(QImage.fromData(buf.getvalue())) + buf.close() + class ModelsDistplotCursor(AxesWidget): From b8c2d7a72f706de70fc567a93a7a3aa4917d1784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 23 Apr 2021 11:20:05 -0400 Subject: [PATCH 27/29] ModelsDistplotFigure: force integers on the y-axis --- gwhat/gwrecharge/models_distplot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index efbc133a..462d161b 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -18,6 +18,7 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure from matplotlib.widgets import AxesWidget +from matplotlib.ticker import MaxNLocator from matplotlib.transforms import ScaledTranslation from qtpy.QtGui import QImage @@ -531,6 +532,7 @@ def setup_axes(self): axis='x', which='major', labelsize=self.setp['xtickslabel_size']) self.ax0.tick_params( axis='y', which='major', labelsize=self.setp['ytickslabel_size']) + self.ax0.yaxis.set_major_locator(MaxNLocator(integer=True)) # Setup axes labels. self.ax0.set_xlabel( From 014f5e25201309c15967700d0c418a65e3a1b0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 23 Apr 2021 11:20:25 -0400 Subject: [PATCH 28/29] ModelsDistplotFigure: take into account dpi when setting borderpad --- gwhat/gwrecharge/models_distplot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index 462d161b..a5f09eb1 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -546,7 +546,7 @@ def setup_margins(self): """Setup the margins of the figure.""" if self.ax0 is None: return - figborderpad = self.setp['figure_border'] + figborderpad = self.setp['figure_border'] / 72 * self.dpi try: # This is required when saving the figure in some format like @@ -574,7 +574,7 @@ def setup_margins(self): yaxis_width = axbbox.x0 - bbox_yaxis_left.x0 ylabel_width = bbox_yaxis_label.width + self.ylabelpad left_margin = ( - yaxis_width + ylabel_width + figborderpad + yaxis_width + ylabel_width + (figborderpad) ) / figbbox.width # Calculate right margin width. From b4e9caa6b8c07e30dcddb460fa4c8e10e4c6ac25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Fri, 23 Apr 2021 11:20:35 -0400 Subject: [PATCH 29/29] Complete a missing docstring --- gwhat/gwrecharge/models_distplot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gwhat/gwrecharge/models_distplot.py b/gwhat/gwrecharge/models_distplot.py index a5f09eb1..12819032 100644 --- a/gwhat/gwrecharge/models_distplot.py +++ b/gwhat/gwrecharge/models_distplot.py @@ -338,6 +338,9 @@ def plot_results(self, glue_data): self.figure.plot(glue_data) def draw(self): + """ + Override matplotlib method to handle the blitting of the cursors. + """ if self.figure.cursor is not None: self.figure.cursor.clear()