Pamhyr2/src/View/Results/Window.py

494 lines
15 KiB
Python

# Window.py -- Pamhyr
# Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from datetime import datetime
from tools import trace, timer
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence,
)
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QItemSelectionModel,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QSlider, QLabel, QWidget, QGridLayout,
)
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.Results.PlotXY import PlotXY
from View.Results.PlotAC import PlotAC
from View.Results.PlotKPC import PlotKPC
from View.Results.PlotH import PlotH
from View.Results.PlotSedReach import PlotSedReach
from View.Results.PlotSedProfile import PlotSedProfile
from View.Results.CustomPlot.Plot import CustomPlot
from View.Results.CustomPlot.CustomPlotValuesSelectionDialog import (
CustomPlotValuesSelectionDialog,
)
from View.Results.Table import TableModel
from View.Results.translate import ResultsTranslate
from View.Stricklers.Window import StricklersWindow
_translate = QCoreApplication.translate
logger = logging.getLogger()
class ResultsWindow(PamhyrWindow):
_pamhyr_ui = "Results"
_pamhyr_name = "Results"
def __init__(self, study=None, config=None,
solver=None, results=None,
parent=None):
self._solver = solver
self._results = results
self._timestamps = sorted(list(self._results.get("timestamps")))
name = (
self._pamhyr_name + " - "
+ study.name + " - "
+ self._solver.name + " - "
+ self._results.date
)
super(ResultsWindow, self).__init__(
title=name,
study=study,
config=config,
trad=ResultsTranslate(),
parent=parent
)
self._additional_plot = {}
self.setup_table()
self.setup_plot()
self.setup_slider()
self.setup_statusbar()
self.setup_connections()
def setup_table(self):
self._table = {}
for t in ["reach", "profile"]:
table = self.find(QTableView, f"tableView_{t}")
self._table[t] = TableModel(
table_view=table,
table_headers=self._trad.get_dict(f"table_headers_{t}"),
data=self._results,
undo=self._undo_stack,
opt_data=t
)
def setup_slider(self):
self._slider_profile = self.find(QSlider, f"verticalSlider_profile")
default_reach = self._results.river.reach(0)
self._slider_profile.setMaximum(len(default_reach.profiles) - 1)
self._slider_profile.setValue(0)
self._slider_time = self.find(QSlider, f"horizontalSlider_time")
self._slider_time.setMaximum(len(self._timestamps) - 1)
self._slider_time.setValue(len(self._timestamps) - 1)
def setup_plot(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
self.plot_layout.addWidget(self.toolbar)
self.plot_layout.addWidget(self.canvas)
self.plot_xy = PlotXY(
canvas=self.canvas,
results=self._results,
reach_id=0,
profile_id=0,
toolbar=self.toolbar,
display_current=False
)
self.plot_xy.draw()
self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_2.setObjectName("canvas_2")
self.toolbar_2 = PamhyrPlotToolbar(
self.canvas_2, self
)
self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
self.plot_layout_2.addWidget(self.toolbar_2)
self.plot_layout_2.addWidget(self.canvas_2)
self.plot_kpc = PlotKPC(
canvas=self.canvas_2,
results=self._results,
reach_id=0,
profile_id=0,
toolbar=self.toolbar_2
)
self.plot_kpc.draw()
self.canvas_3 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_3.setObjectName("canvas_3")
self.toolbar_3 = PamhyrPlotToolbar(
self.canvas_3, self
)
self.plot_layout_3 = self.find(QVBoxLayout, "verticalLayout_3")
self.plot_layout_3.addWidget(self.toolbar_3)
self.plot_layout_3.addWidget(self.canvas_3)
self.plot_ac = PlotAC(
canvas=self.canvas_3,
results=self._results,
reach_id=0,
profile_id=0,
toolbar=self.toolbar_3
)
self.plot_ac.draw()
self.canvas_4 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_4.setObjectName("canvas_4")
self.toolbar_4 = PamhyrPlotToolbar(
self.canvas_4, self
)
self.plot_layout_4 = self.find(
QVBoxLayout, "verticalLayout_hydrograph")
self.plot_layout_4.addWidget(self.toolbar_4)
self.plot_layout_4.addWidget(self.canvas_4)
self.plot_h = PlotH(
canvas=self.canvas_4,
results=self._results,
reach_id=0,
profile_id=0,
toolbar=self.toolbar_4
)
self.plot_h.draw()
self.canvas_5 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_5.setObjectName("canvas_5")
self.toolbar_5 = PamhyrPlotToolbar(
self.canvas_5, self
)
self.plot_layout_5 = self.find(QVBoxLayout, "verticalLayout_sed_reach")
self.plot_layout_5.addWidget(self.toolbar_5)
self.plot_layout_5.addWidget(self.canvas_5)
if self._study.river.has_sediment():
self.plot_sed_reach = PlotSedReach(
canvas=self.canvas_5,
results=self._results,
reach_id=0,
profile_id=0,
toolbar=self.toolbar_5
)
self.plot_sed_reach.draw()
self.canvas_6 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_6.setObjectName("canvas_6")
self.toolbar_6 = PamhyrPlotToolbar(
self.canvas_6, self
)
self.plot_layout_6 = self.find(
QVBoxLayout, "verticalLayout_sed_profile")
self.plot_layout_6.addWidget(self.toolbar_6)
self.plot_layout_6.addWidget(self.canvas_6)
if self._study.river.has_sediment():
self.plot_sed_profile = PlotSedProfile(
canvas=self.canvas_6,
results=self._results,
reach_id=0,
profile_id=0,
toolbar=self.toolbar_6
)
self.plot_sed_profile.draw()
def _compute_status_label(self):
# Timestamp
ts = self._timestamps[self._slider_time.value()]
t0 = datetime.fromtimestamp(0)
fts = str(
datetime.fromtimestamp(ts) - t0
)
fts.replace("days", _translate("Results", "days"))\
.replace("day", _translate("Results", "day"))
# Reach
table = self.find(QTableView, f"tableView_reach")
indexes = table.selectedIndexes()
if len(indexes) == 0:
reach = self._study.river.edges()[0]
else:
reach = self._study.river.edges()[indexes[0].row()]
# Profile
table = self.find(QTableView, f"tableView_profile")
indexes = table.selectedIndexes()
if len(indexes) == 0:
profile = reach.reach.profile(0)
else:
profile = reach.reach.profile(indexes[0].row())
pname = profile.name if profile.name != "" else profile.kp
return (f"Reach: {reach.name} | " +
f"Profile: {pname} | " +
f"Timestamp : {fts} ({ts} sec)")
def setup_statusbar(self):
txt = self._compute_status_label()
self._status_label = QLabel(txt)
self.statusbar.addPermanentWidget(self._status_label)
def update_statusbar(self):
txt = self._compute_status_label()
self._status_label.setText(txt)
def setup_connections(self):
# Action
actions = {
"action_add": self._add_custom_plot
}
for action in actions:
self.find(QAction, action).triggered.connect(
actions[action]
)
# Table and Plot
fun = {
"reach": self._set_current_reach,
"profile": self._set_current_profile,
}
for t in ["reach", "profile"]:
table = self.find(QTableView, f"tableView_{t}")
table.selectionModel()\
.selectionChanged\
.connect(fun[t])
self._table[t].dataChanged.connect(fun[t])
self._slider_profile.valueChanged.connect(
self._set_current_profile_slider)
self._slider_time.valueChanged.connect(self._set_current_timestamp)
def update_table_selection_reach(self, ind):
table = self.find(QTableView, f"tableView_reach")
selectionModel = table.selectionModel()
index = table.model().index(ind, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def update_table_selection_profile(self, ind):
table = self.find(QTableView, f"tableView_profile")
selectionModel = table.selectionModel()
index = table.model().index(ind, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def update(self, reach_id=None, profile_id=None, timestamp=None):
if reach_id is not None:
self.plot_xy.set_reach(reach_id)
self.plot_ac.set_reach(reach_id)
self.plot_kpc.set_reach(reach_id)
self.plot_h.set_reach(reach_id)
if self._study.river.has_sediment():
self.plot_sed_reach.set_reach(reach_id)
self.plot_sed_profile.set_reach(reach_id)
self.update_table_selection_reach(reach_id)
self.update_table_selection_profile(0)
if profile_id is not None:
self.plot_xy.set_profile(profile_id)
self.plot_ac.set_profile(profile_id)
self.plot_kpc.set_profile(profile_id)
self.plot_h.set_profile(profile_id)
if self._study.river.has_sediment():
self.plot_sed_reach.set_profile(profile_id)
self.plot_sed_profile.set_profile(profile_id)
self.update_table_selection_profile(profile_id)
if timestamp is not None:
self.plot_xy.set_timestamp(timestamp)
self.plot_ac.set_timestamp(timestamp)
self.plot_kpc.set_timestamp(timestamp)
# self.plot_h.set_timestamp(timestamp)
if self._study.river.has_sediment():
self.plot_sed_reach.set_timestamp(timestamp)
self.plot_sed_profile.set_timestamp(timestamp)
self.plot_xy.draw()
self.plot_ac.draw()
self.plot_kpc.draw()
self.plot_h.draw()
if self._study.river.has_sediment():
self.plot_sed_reach.draw()
self.plot_sed_profile.draw()
self.update_statusbar()
def _get_current_reach(self):
table = self.find(QTableView, f"tableView_reach")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return 0
return indexes[0].row()
def _get_current_profile(self):
table = self.find(QTableView, f"tableView_profile")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return 0
return indexes[0].row()
def _get_current_timestamp(self):
return self._timestamps[
self._slider_time.value()
]
def _set_current_reach(self):
table = self.find(QTableView, f"tableView_reach")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return
self.update(reach_id=indexes[0].row())
def _set_current_profile(self):
table = self.find(QTableView, f"tableView_profile")
indexes = table.selectedIndexes()
if len(indexes) == 0:
return
ind = indexes[0].row()
self.update(profile_id=ind)
self._slider_profile.setValue(ind)
def _set_current_profile_slider(self):
pid = self._slider_profile.value()
self.update(profile_id=pid)
return
def _set_current_timestamp(self):
timestamp = self._timestamps[self._slider_time.value()]
self.update(timestamp=timestamp)
def _add_custom_plot(self):
dlg = CustomPlotValuesSelectionDialog(parent=self)
if dlg.exec():
x, y = dlg.value
self.create_new_tab_custom_plot(x, y)
def create_new_tab_custom_plot(self, x:str, y:list):
name = f"{x}: {','.join(y)}"
wname = f"tab_custom_{x}_{y}"
tab_widget = self.find(QTabWidget, f"tabWidget")
# This plot already exists
if name in self._additional_plot:
tab_widget.setCurrentWidget(
tab_widget.findChild(QWidget, wname)
)
return
widget = QWidget()
grid = QGridLayout()
widget.setObjectName(wname)
canvas = MplCanvas(width=5, height=4, dpi=100)
canvas.setObjectName(f"canvas_{x}_{y}")
toolbar = PamhyrPlotToolbar(
canvas, self
)
plot = CustomPlot(
x, y,
self._get_current_reach(),
self._get_current_profile(),
self._get_current_timestamp(),
data=self._results,
canvas=canvas,
toolbar=toolbar,
trad=self._trad,
parent=self,
)
plot.draw()
# Add plot to additional plot
self._additional_plot[name] = plot
grid.addWidget(toolbar, 0, 0)
grid.addWidget(canvas, 1, 0)
widget.setLayout(grid)
tab_widget.addTab(widget, name)
def _copy(self):
logger.info("TODO: copy")
def _paste(self):
logger.info("TODO: paste")
def _undo(self):
self._table.undo()
def _redo(self):
self._table.redo()