diff --git a/src/View/Results/PlotSedAdisDt.py b/src/View/Results/PlotSedAdisDt.py new file mode 100644 index 00000000..4b19d3e7 --- /dev/null +++ b/src/View/Results/PlotSedAdisDt.py @@ -0,0 +1,207 @@ +# PlotSedAdisDt.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 +import numpy as np + +from functools import reduce +from datetime import datetime + +from tools import timer, trace +from View.Tools.PamhyrPlot import PamhyrPlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + + +class PlotAdis_dt(PamhyrPlot): + def __init__(self, canvas=None, trad=None, toolbar=None, + results=None, reach_id=0, profile_id=0, + pol_id=0, key="C", parent=None): + super(PlotAdis_dt, self).__init__( + canvas=canvas, + trad=trad, + data=results, + toolbar=toolbar, + parent=parent + ) + + self._current_timestamp = max(results.get("timestamps")) + self._current_reach_id = reach_id + self._current_profile_id = profile_id + self._current_pol_id = pol_id + self._key = key + + self.label_x = self._trad["unit_time_s"] + + self.label = {} + self.label["C"] = _translate("Results", "Concentration") + self.label["M"] = _translate("Results", "Mass") + self.label_max = {} + self.label_max["C"] = _translate("Results", "Max Concentration") + self.label_max["M"] = _translate("Results", "Max Mass") + self.sed_id = {} + self.sed_id["C"] = 0 + self.sed_id["M"] = 1 + + self._isometric_axis = False + + @property + def results(self): + return self.data + + @results.setter + def results(self, results): + self.data = results + self._current_timestamp = max(results.get("timestamps")) + + @timer + def draw(self, highlight=None): + self.init_axes() + + if self.results is None: + return + + reach = self.results.river.reach(self._current_reach_id) + profile = reach.profile(self._current_profile_id) + + if reach.geometry.number_profiles == 0: + self._init = False + return + + self.draw_max(reach) + self.draw_data(reach, profile) + self.draw_current() + + self.set_ticks_time_formater() + + self.enable_legend() + + self.idle() + self.update_current() + self._init = True + + def draw_data(self, reach, profile): + self.ts = list(self.results.get("timestamps")) + self.ts.sort() + + x = self.ts + y = list(map(lambda data_el: data_el[self._current_pol_id][self.sed_id[self._key]], + profile.get_key("pols") + )) + + self._line, = self.canvas.axes.plot( + x, y, + label=self.label[self._key], + color=self.color_plot, + **self.plot_default_kargs + ) + + def draw_current(self): + self._current, = self.canvas.axes.plot( + [self._current_timestamp, self._current_timestamp], + self.canvas.axes.get_ylim(), + color="grey", + linestyle="dashed", + lw=1., + ) + + def draw_max(self, reach): + self.ts = list(self.results.get("timestamps")) + self.ts.sort() + + x = self.ts + y = [] + for ts in x: + ts_y = list(map( + lambda p: p.get_ts_key(ts, "pols")[self._current_pol_id][self.sed_id[self._key]], + reach.profiles)) + y.append(np.max(ts_y)) + + self._line_max, = self.canvas.axes.plot( + x, y, + label=self.label_max[self._key], + color=self.color_plot_highlight, + linestyle='dotted', + lw=1., + markersize=0 + ) + + def set_reach(self, reach_id): + self._current_reach_id = reach_id + self._current_profile_id = 0 + self.draw() + + def set_profile(self, profile_id): + self._current_profile_id = profile_id + self.update() + + def set_timestamp(self, timestamp): + self._current_timestamp = timestamp + self.update_current() + self.update_idle() + + def update(self): + if not self._init: + self.draw() + + self.update_data() + self.update_idle() + + def update_data(self): + reach = self.results.river.reach(self._current_reach_id) + profile = reach.profile(self._current_profile_id) + + x = self.ts + y = list(map(lambda data_el: data_el[self._current_pol_id][self.sed_id[self._key]], + profile.get_key("pols") + )) + + self._line.set_data(x, y) + + self._current.set_data( + [self._current_timestamp, self._current_timestamp], + self.canvas.axes.get_ylim() + ) + + self.hide_current + self.canvas.axes.relim(visible_only=True) + self.canvas.axes.autoscale_view() + self.show_current + + def update_current(self): + self._current.set_data( + [self._current_timestamp, self._current_timestamp], + self.canvas.axes.get_ylim() + ) + + def hide_current(self): + self._current.set_visible(False) + + def show_current(self): + self._current.set_visible(True) + + def set_pollutant(self, pol_id): + if pol_id != self._current_pol_id: + self._current_pol_id = pol_id + self.update() diff --git a/src/View/Results/PlotSedAdisDx.py b/src/View/Results/PlotSedAdisDx.py new file mode 100644 index 00000000..77530ff6 --- /dev/null +++ b/src/View/Results/PlotSedAdisDx.py @@ -0,0 +1,262 @@ +# PlotSedAdisDx.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 +import numpy as np + +from functools import reduce +from datetime import datetime + +from tools import timer, trace +from View.Tools.PamhyrPlot import PamhyrPlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + + +class PlotAdis_dx(PamhyrPlot): + def __init__(self, canvas=None, trad=None, toolbar=None, + results=None, reach_id=0, profile_id=0, + pol_id=0, key="C", parent=None): + super(PlotAdis_dx, self).__init__( + canvas=canvas, + trad=trad, + data=results, + toolbar=toolbar, + parent=parent + ) + + self._current_timestamp = max(results.get("timestamps")) + self._current_reach_id = reach_id + self._current_profile_id = profile_id + self._current_pol_id = pol_id + self._key = key + + self.label_x = self._trad["unit_pk"] + + self.label = {} + self.label["C"] = _translate("Results", "Concentration") + self.label["M"] = _translate("Results", "Mass") + self.label_max = {} + self.label_max["C"] = _translate("Results", "Max Concentration") + self.label_max["M"] = _translate("Results", "Max Mass") + self.sed_id = {} + self.sed_id["C"] = 0 + self.sed_id["M"] = 1 + + self._isometric_axis = False + + @property + def results(self): + return self.data + + @results.setter + def results(self, results): + self.data = results + self._current_timestamp = max(results.get("timestamps")) + + @timer + def draw(self, highlight=None): + self.init_axes() + + if self.results is None: + return + + reach = self.results.river.reach(self._current_reach_id) + profile = reach.profile(self._current_profile_id) + + if reach.geometry.number_profiles == 0: + self._init = False + return + + self.draw_max(reach) + self.draw_data(reach, profile) + self.draw_current(reach) + self.draw_profiles_hs(reach) + + self.enable_legend() + + self.set_y_lim() + + self.idle() + self.update_current() + self._init = True + + def draw_profiles_hs(self, reach): + lhs = filter( + lambda hs: hs._input_reach.reach is reach.geometry, + filter( + lambda hs: hs._input_reach is not None, + self.results.study.river.hydraulic_structures.lst + ) + ) + + for hs in lhs: + x = hs.input_rk + z_min = reach.geometry.get_z_min() + z_max = reach.geometry.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, + ) + + def draw_data(self, reach, profile): + profile = reach.profile(self._current_profile_id) + x = reach.geometry.get_rk() + + y = list(map( + lambda p: p.get_ts_key(self._current_timestamp, "pols")[self._current_pol_id][self.sed_id[self._key]], + reach.profiles + )) + + self.set_y_lim() + + self._line, = self.canvas.axes.plot( + x, y, + label=self.label[self._key], + color=self.color_plot, + **self.plot_default_kargs + ) + + def draw_current(self, reach): + rk = reach.geometry.get_rk() + + self._current, = self.canvas.axes.plot( + [rk[self._current_profile_id], rk[self._current_profile_id]], + [self.y_max, self.y_min], + color="grey", + linestyle="dashed", + lw=1. + ) + + def draw_max(self, reach): + profile = reach.profile(self._current_profile_id) + x = reach.geometry.get_rk() + + y = [] + for p in reach.profiles: + rk_y = list(map(lambda data_el: data_el[self._current_pol_id][self.sed_id[self._key]], + p.get_key("pols") + )) + y.append(np.max(rk_y)) + + self._line_max, = self.canvas.axes.plot( + x, y, + label=self.label_max[self._key], + color=self.color_plot_highlight, + linestyle='dotted', + lw=1., + markersize=0 + ) + + def set_reach(self, reach_id): + self._current_reach_id = reach_id + self._current_profile_id = 0 + self.draw() + + def set_profile(self, profile_id): + self._current_profile_id = profile_id + self.update_current() + self.update_idle() + + def set_timestamp(self, timestamp): + self._current_timestamp = timestamp + self.update() + + def update(self): + if not self._init: + self.draw() + + self.update_data() + self.update_idle() + + def update_data(self): + reach = self.results.river.reach(self._current_reach_id) + profile = reach.profile(self._current_profile_id) + + x = reach.geometry.get_rk() + + y = list(map( + lambda p: p.get_ts_key(self._current_timestamp, "pols")[self._current_pol_id][self.sed_id[self._key]], + reach.profiles + )) + + self._line.set_data(x, y) + + self.update_current() + + self.hide_current + self.canvas.axes.relim(visible_only=True) + self.canvas.axes.autoscale_view() + self.show_current + + def update_current(self): + reach = self.results.river.reach(self._current_reach_id) + rk = reach.geometry.get_rk() + cid = self._current_profile_id + + self._current.set_data( + [rk[cid], rk[cid]], + [self.y_max, self.y_min] + ) + self.canvas.figure.canvas.draw_idle() + + def hide_current(self): + self._current.set_visible(False) + + def show_current(self): + self._current.set_visible(True) + + def set_pollutant(self, pol_id): + if pol_id != self._current_pol_id: + self._current_pol_id = pol_id + self.update() + + def set_y_lim(self): + reach = self.results.river.reach(self._current_reach_id) + + y = list( + map(lambda p: + list(map(lambda data_el: data_el[self._current_pol_id][self.sed_id[self._key]], + p.get_key("pols")) + ), + reach.profiles + ) + ) + + self.y_max = np.max(y) + self.y_min = np.min(y) + self.canvas.axes.set_ylim(self.y_min, self.y_max) diff --git a/src/View/Results/WindowAdisTS.py b/src/View/Results/WindowAdisTS.py index ed55763e..3b003a71 100644 --- a/src/View/Results/WindowAdisTS.py +++ b/src/View/Results/WindowAdisTS.py @@ -48,6 +48,8 @@ from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar from View.Results.PlotCAdisTS import PlotC from View.Results.PlotMAdisTS import PlotM +from View.Results.PlotSedAdisDt import PlotAdis_dt +from View.Results.PlotSedAdisDx import PlotAdis_dx from View.Results.PlotTotSedCAdisTS import PlotTotSedC from View.Results.PlotTotSedEGAdisTS import PlotTotSedEG from View.Results.PlotTotSedEMAdisTS import PlotTotSedEM @@ -109,21 +111,24 @@ class ResultsWindowAdisTS(PamhyrWindow): self._additional_plot = {} try: - #print("timestamps results: ", self._results) + # print("timestamps results: ", self._results) self._timestamps = sorted(list(self._results.get("timestamps"))) print("setup table adists results") + self.setup_slider() + print("///setup slider correct") self.setup_table() print("///setup table correct") self.setup_plots() print("///setup plots correct") - self.setup_slider() - print("///setup slider correct") self.setup_statusbar() print("///setup status bar correct") self.setup_connections() print("///setup connections correct") + self.update_table_selection_reach(0) + self.update_table_selection_profile(0) + self.update_table_selection_pol(0) except Exception as e: logger_exception(e) return @@ -145,10 +150,10 @@ class ResultsWindowAdisTS(PamhyrWindow): undo=self._undo_stack, opt_data=t, ) + self._table[t]._timestamp = self._timestamps[ + self._slider_time.value()] def setup_slider(self): - default_reach = self._results.river.reach(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) @@ -171,29 +176,55 @@ class ResultsWindowAdisTS(PamhyrWindow): self._timer = QTimer(self) def setup_plots(self): - self.canvas_4 = MplCanvas(width=5, height=4, dpi=100) - self.canvas_4.setObjectName("canvas_4") - self.toolbar_4 = PamhyrPlotToolbar( - self.canvas_4, self, items=[ + self.canvas_dt = MplCanvas(width=5, height=4, dpi=100) + self.canvas_dt.setObjectName("canvas_dt") + self.toolbar_dt = PamhyrPlotToolbar( + self.canvas_dt, self, items=[ "home", "move", "zoom", "save", "iso", "back/forward" ] ) - self.plot_layout_4 = self.find( - QVBoxLayout, "verticalLayout_concentration") - self.plot_layout_4.addWidget(self.toolbar_4) - self.plot_layout_4.addWidget(self.canvas_4) + self.plot_layout_dt = self.find( + QVBoxLayout, "verticalLayout_concentration_dt") + self.plot_layout_dt.addWidget(self.toolbar_dt) + self.plot_layout_dt.addWidget(self.canvas_dt) - self.plot_c = PlotC( - canvas=self.canvas_4, + self.plot_c_dt = PlotAdis_dt( + canvas=self.canvas_dt, results=self._results, reach_id=0, profile_id=0, pol_id=0, + key="C", trad=self._trad, - toolbar=self.toolbar_4 + toolbar=self.toolbar_dt ) - self.plot_c.draw() + self.plot_c_dt.draw() + + self.canvas_dx = MplCanvas(width=5, height=4, dpi=100) + self.canvas_dx.setObjectName("canvas_dx") + self.toolbar_dx = PamhyrPlotToolbar( + self.canvas_dt, self, items=[ + "home", "move", "zoom", "save", + "iso", "back/forward" + ] + ) + self.plot_layout_dx = self.find( + QVBoxLayout, "verticalLayout_concentration_dx") + self.plot_layout_dx.addWidget(self.toolbar_dx) + self.plot_layout_dx.addWidget(self.canvas_dx) + + self.plot_c_dx = PlotAdis_dx( + canvas=self.canvas_dx, + results=self._results, + reach_id=0, + profile_id=0, + pol_id=0, + key="C", + trad=self._trad, + toolbar=self.toolbar_dx + ) + self.plot_c_dx.draw() print("///plot c adists correct") @@ -225,7 +256,7 @@ class ResultsWindowAdisTS(PamhyrWindow): print("hear -------------------------------------------------------------------------------") #self.find(QVBoxLayout, "verticalLayout_mass").setDisabled(True) - self.find(QTabWidget, "tabWidget").setTabVisible(2, False) ###setTabEnabled(2, False) + self.find(QTabWidget, "tabWidget").setTabVisible(3, False) ###setTabEnabled(2, False) print("///plot m adists correct") @@ -340,6 +371,16 @@ class ResultsWindowAdisTS(PamhyrWindow): super(ResultsWindowAdisTS, self).closeEvent(event) 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() @@ -409,6 +450,13 @@ class ResultsWindowAdisTS(PamhyrWindow): self._table[t].dataChanged.connect(fun[t]) + self._slider_time.valueChanged.connect(self._set_current_timestamp) + self._button_play.setChecked(False) + self._button_play.clicked.connect(self._pause) + self._button_back.clicked.connect(self._back) + self._button_next.clicked.connect(self._next) + self._button_first.clicked.connect(self._first) + self._button_last.clicked.connect(self._last) self._timer.timeout.connect(self._update_slider) def update_table_selection_reach(self, ind): @@ -457,8 +505,9 @@ class ResultsWindowAdisTS(PamhyrWindow): def update(self, reach_id=None, profile_id=None, pol_id=None, timestamp=None): if reach_id is not None: - self.plot_c.set_reach(reach_id) - self.plot_m.set_reach(reach_id) + self.plot_c_dt.set_reach(reach_id) + self.plot_c_dx.set_reach(reach_id) + #self.plot_m.set_reach(reach_id) self.plot_tot_c.set_reach(reach_id) self.plot_tot_eg.set_reach(reach_id) self.plot_tot_em.set_reach(reach_id) @@ -469,8 +518,9 @@ class ResultsWindowAdisTS(PamhyrWindow): self.update_table_selection_pol(0) if profile_id is not None: - self.plot_c.set_profile(profile_id) - self.plot_m.set_profile(profile_id) + self.plot_c_dt.set_profile(profile_id) + self.plot_c_dx.set_profile(profile_id) + #self.plot_m.set_profile(profile_id) self.plot_tot_c.set_profile(profile_id) self.plot_tot_eg.set_profile(profile_id) self.plot_tot_em.set_profile(profile_id) @@ -482,23 +532,30 @@ class ResultsWindowAdisTS(PamhyrWindow): if pol_id is not None: print("--**//++//**//** update pol_id: ", pol_id) - self.plot_c.set_pollutant(pol_id) - self.plot_m.set_pollutant(pol_id) + self.plot_c_dt.set_pollutant(pol_id) + self.plot_c_dx.set_pollutant(pol_id) + #self.plot_m.set_pollutant(pol_id) self.update_table_selection_pol(pol_id) if timestamp is not None: - self.plot_c.set_timestamp(timestamp) - self.plot_m.set_timestamp(timestamp) - self.plot_tot_c.set_timestamp(timestamp) - self.plot_tot_eg.set_timestamp(timestamp) - self.plot_tot_em.set_timestamp(timestamp) - self.plot_tot_ed.set_timestamp(timestamp) + self.plot_c_dt.set_timestamp(timestamp) + self.plot_c_dx.set_timestamp(timestamp) + #self.plot_m.set_timestamp(timestamp) + #self.plot_tot_c.set_timestamp(timestamp) + #self.plot_tot_eg.set_timestamp(timestamp) + #self.plot_tot_em.set_timestamp(timestamp) + #self.plot_tot_ed.set_timestamp(timestamp) self._table["raw_data"].timestamp = timestamp self.update_statusbar() + 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() @@ -546,15 +603,17 @@ class ResultsWindowAdisTS(PamhyrWindow): self.update(timestamp=timestamp) def _reload_plots(self): - self.plot_c.results = self._results - self.plot_m.results = self._results + self.plot_c_dt.results = self._results + self.plot_c_dx.results = self._results + #self.plot_m.results = self._results self.plot_tot_c.results = self._results self.plot_tot_eg.results = self._results self.plot_tot_em.results = self._results self.plot_tot_ed.results = self._results - self.plot_c.draw() - self.plot_m.draw() + self.plot_c_dt.draw() + self.plot_c_dx.draw() + #self.plot_m.draw() self.plot_tot_c.draw() self.plot_tot_eg.draw() self.plot_tot_em.draw() @@ -572,7 +631,7 @@ class ResultsWindowAdisTS(PamhyrWindow): self._timestamps = sorted(list(self._results.get("timestamps"))) self._reload_plots() - ###self._reload_slider() + self._reload_slider() def _copy(self): logger.info("TODO: copy") diff --git a/src/View/ui/ResultsAdisTS.ui b/src/View/ui/ResultsAdisTS.ui index e4bcf65d..4416961f 100644 --- a/src/View/ui/ResultsAdisTS.ui +++ b/src/View/ui/ResultsAdisTS.ui @@ -149,13 +149,26 @@ - + + + true + - Concentration + Concentration dt - + + + + + + + Concentration dx + + + +