diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index f7fdcce6..d9b9b45b 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -24,6 +24,8 @@ import subprocess
from queue import Queue
from functools import reduce
from platformdirs import user_cache_dir
+
+from Solver.AdisTS import AdisTS
from tools import logger_exception
from PyQt5 import QtGui
@@ -75,6 +77,7 @@ from View.RunSolver.WindowAdisTS import SelectSolverWindowAdisTS, SolverLogWindo
from View.CheckList.Window import CheckListWindow
from View.CheckList.WindowAdisTS import CheckListWindowAdisTS
from View.Results.Window import ResultsWindow
+from View.Results.WindowAdisTS import ResultsWindowAdisTS
from View.Results.ReadingResultsDialog import ReadingResultsDialog
from View.Debug.Window import ReplWindow
from View.OutputKpAdisTS.Window import OutputKpAdisTSWindow
@@ -1398,6 +1401,63 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
)
res.show()
+ def open_solver_results_adists(self, solver, results=None):
+ def reading_fn():
+ self._tmp_results = results
+
+ # If no specific results, get last results
+ if results is None:
+ def reading_fn():
+ self._tmp_results = self._last_results
+
+ if self._last_results is None:
+ def reading_fn():
+ self._tmp_results = solver.results(
+ self._study,
+ self._solver_workdir(solver),
+ )
+
+ # Open from file
+ if type(results) is str:
+ logger.info(f"Open results from {os.path.dirname(results)}")
+
+ name = os.path.basename(results).replace(".BIN", "")
+
+ def reading_fn():
+ self._tmp_results = solver.results(
+ self._study,
+ os.path.dirname(results),
+ name=name
+ )
+
+ dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
+ dlg.exec_()
+ results = self._tmp_results
+
+ # No results available
+ if results is None:
+ return
+
+ # Windows already opened
+ if self.sub_window_exists(
+ ResultsWindowAdisTS,
+ data=[
+ self._study,
+ None, # No config
+ solver,
+ results
+ ]
+ ):
+ return
+
+ res = ResultsWindowAdisTS(
+ study=self._study,
+ solver=solver,
+ results=results,
+ parent=self
+ )
+ res.show()
+
def _solver_workdir(self, solver):
workdir = os.path.join(
os.path.dirname(self._study.filename),
diff --git a/src/View/Results/Table.py b/src/View/Results/Table.py
index 22be35d5..03e7668b 100644
--- a/src/View/Results/Table.py
+++ b/src/View/Results/Table.py
@@ -45,8 +45,12 @@ _translate = QCoreApplication.translate
class TableModel(PamhyrTableModel):
def _setup_lst(self):
_river = self._data.river
+ print("data: ", self._data)
+ print("river: ", _river)
+ print("reaches: ", _river.reachs)
if self._opt_data == "reach":
self._lst = _river.reachs
+ print("optreach: ", self._lst)
elif self._opt_data == "profile":
self._lst = _river.reach(0).profiles
elif self._opt_data == "raw_data":
diff --git a/src/View/Results/WindowAdisTS.py b/src/View/Results/WindowAdisTS.py
new file mode 100644
index 00000000..62806809
--- /dev/null
+++ b/src/View/Results/WindowAdisTS.py
@@ -0,0 +1,643 @@
+# Window.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 os
+import csv
+import logging
+
+from datetime import datetime
+from tools import trace, timer, logger_exception
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtGui import (
+ QKeySequence, QIcon, QPixmap,
+)
+
+from PyQt5.QtCore import (
+ Qt, QVariant, QAbstractTableModel,
+ QCoreApplication, QModelIndex, pyqtSlot,
+ QItemSelectionModel, QTimer,
+)
+
+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 ResultsWindowAdisTS(PamhyrWindow):
+ _pamhyr_ui = "ResultsAdisTS"
+ _pamhyr_name = "Results"
+
+ def _path_file(self, filename):
+ return os.path.abspath(
+ os.path.join(
+ os.path.dirname(__file__),
+ "..", "ui", "ressources", filename
+ )
+ )
+
+ def __init__(self, study=None, config=None,
+ solver=None, results=None,
+ parent=None):
+ self._solver = solver
+ self._results = results
+
+ trad = ResultsTranslate()
+ name = (
+ trad[self._pamhyr_name] + " - "
+ + study.name + " - "
+ + self._solver.name
+ )
+
+ super(ResultsWindowAdisTS, self).__init__(
+ title=name,
+ study=study,
+ config=config,
+ trad=trad,
+ parent=parent
+ )
+
+ self._hash_data.append(self._solver)
+ self._hash_data.append(self._results)
+
+ self._additional_plot = {}
+
+ try:
+ #print("timestamps results: ", self._results)
+ #self._timestamps = sorted(list(self._results.get("timestamps")))
+
+ print("setup table adists results")
+
+ self.setup_table()
+ #self.setup_plots()
+ #self.setup_slider()
+ #self.setup_statusbar()
+ #self.setup_connections()
+ except Exception as e:
+ logger_exception(e)
+ return
+
+ def setup_table(self):
+ print("setup table adists results")
+ print("results study name: ", self._results.study.name)
+ print("results study river: ", self._results.study.river)
+ self._table = {}
+ for t in ["reach", "profile"]:#, "raw_data"]:
+ print("t: ", t)
+ 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)
+
+ self._icon_start = QIcon()
+ self._icon_start.addPixmap(
+ QPixmap(self._path_file("media-playback-start.png"))
+ )
+
+ self._icon_pause = QIcon()
+ self._icon_pause.addPixmap(
+ QPixmap(self._path_file("media-playback-pause.png"))
+ )
+ self._button_play = self.find(QPushButton, f"playButton")
+ self._button_play.setIcon(self._icon_start)
+ self._button_back = self.find(QPushButton, f"backButton")
+ self._button_next = self.find(QPushButton, f"nextButton")
+ self._button_first = self.find(QPushButton, f"firstButton")
+ self._button_last = self.find(QPushButton, f"lastButton")
+ self._timer = QTimer(self)
+
+ def setup_plots(self):
+ self.canvas = MplCanvas(width=5, height=4, dpi=100)
+ self.canvas.setObjectName("canvas")
+ self.toolbar = PamhyrPlotToolbar(
+ self.canvas, self, items=[
+ "home", "move", "zoom", "save",
+ "iso", "back/forward"
+ ]
+ )
+ 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,
+ trad=self._trad,
+ toolbar=self.toolbar,
+ display_current=True
+ )
+ 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, items=[
+ "home", "move", "zoom", "save",
+ "iso", "back/forward"
+ ]
+ )
+ 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,
+ trad=self._trad,
+ 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, items=[
+ "home", "move", "zoom", "save",
+ "iso", "back/forward"
+ ]
+ )
+ 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,
+ trad=self._trad,
+ 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, items=[
+ "home", "move", "zoom", "save",
+ "iso", "back/forward"
+ ]
+ )
+ 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,
+ trad=self._trad,
+ toolbar=self.toolbar_4
+ )
+ self.plot_h.draw()
+
+ def closeEvent(self, event):
+ try:
+ self._timer.stop()
+ except Exception as e:
+ logger_exception(e)
+
+ 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()
+ 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_reload": self._reload,
+ "action_add": self._add_custom_plot,
+ "action_export": self.export,
+ }
+
+ 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,
+ "raw_data": self._set_current_profile_raw_data,
+ }
+
+ for t in ["reach", "profile", "raw_data"]:
+ 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)
+ 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):
+ 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)
+
+ self._table["profile"].update(ind)
+ self._table["raw_data"].update(ind)
+
+ def update_table_selection_profile(self, ind):
+ for t in ["profile", "raw_data"]:
+ table = self.find(QTableView, f"tableView_{t}")
+ 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)
+
+ for plot in self._additional_plot:
+ self._additional_plot[plot].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)
+
+ for plot in self._additional_plot:
+ self._additional_plot[plot].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)
+
+ for plot in self._additional_plot:
+ self._additional_plot[plot].set_timestamp(timestamp)
+
+ self._table["raw_data"].timestamp = timestamp
+
+ 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_raw_data(self):
+ table = self.find(QTableView, f"tableView_raw_data")
+ 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)
+
+ def _set_current_timestamp(self):
+ timestamp = self._timestamps[self._slider_time.value()]
+ self.update(timestamp=timestamp)
+
+ def _reload_plots(self):
+ self.plot_xy.results = self._results
+ self.plot_ac.results = self._results
+ self.plot_kpc.results = self._results
+ self.plot_h.results = self._results
+
+ self.plot_xy.draw()
+ self.plot_ac.draw()
+ self.plot_kpc.draw()
+ self.plot_h.draw()
+
+ def _reload_slider(self):
+ 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 _reload(self):
+ logger.debug("Reload results...")
+ self._results = self._results.reload()
+
+ self._timestamps = sorted(list(self._results.get("timestamps")))
+
+ self._reload_plots()
+ self._reload_slider()
+
+ 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,
+ 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()
+
+ # play / pause buttons
+ def _update_slider(self):
+ if self._slider_time.value() == self._slider_time.maximum():
+ self._slider_time.setValue(self._slider_time.minimum())
+ else:
+ self._slider_time.setValue(self._slider_time.value()+1)
+
+ def _next(self):
+ self._slider_time.setValue(self._slider_time.value()+1)
+
+ def _back(self):
+ self._slider_time.setValue(self._slider_time.value()-1)
+
+ def _first(self):
+ self._slider_time.setValue(self._slider_time.minimum())
+
+ def _last(self):
+ self._slider_time.setValue(self._slider_time.maximum())
+
+ def _pause(self):
+ if self._button_play.isChecked():
+ self._button_next.setEnabled(False)
+ self._button_back.setEnabled(False)
+ self._button_first.setEnabled(False)
+ self._button_last.setEnabled(False)
+ self._timer.start(100)
+ self._button_play.setIcon(self._icon_pause)
+ else:
+ self._timer.stop()
+ self._button_next.setEnabled(True)
+ self._button_back.setEnabled(True)
+ self._button_first.setEnabled(True)
+ self._button_last.setEnabled(True)
+ self._button_play.setIcon(self._icon_start)
+
+ def export(self):
+ self.file_dialog(
+ select_file=False,
+ callback=lambda d: self.export_to(d[0])
+ )
+
+ def export_to(self, directory):
+ for reach in self._results.river.reachs:
+ self.export_reach(reach, directory)
+
+ def export_reach(self, reach, directory):
+ name = reach.name
+ name = name.replace(" ", "-")
+
+ file_name = os.path.join(
+ directory,
+ f"reach_{name}.csv"
+ )
+
+ with open(file_name, 'w', newline='') as csvfile:
+ writer = csv.writer(csvfile, delimiter=',',
+ quotechar='|', quoting=csv.QUOTE_MINIMAL)
+ writer.writerow(["name", "kp", "data-file"])
+ for profile in reach.profiles:
+ p_file_name = os.path.join(
+ directory,
+ f"cs_{profile.geometry.id}.csv"
+ )
+
+ writer.writerow([
+ profile.name,
+ profile.kp,
+ p_file_name
+ ])
+
+ self.export_profile(reach, profile, p_file_name)
+
+ def export_profile(self, reach, profile, file_name):
+ with open(file_name, 'w', newline='') as csvfile:
+ writer = csv.writer(csvfile, delimiter=',',
+ quotechar='|', quoting=csv.QUOTE_MINIMAL)
+
+ writer.writerow(["timestamp", "z", "q"])
+ timestamps = sorted(self._results.get("timestamps"))
+
+ for ts in timestamps:
+ writer.writerow([
+ ts,
+ profile.get_ts_key(ts, "Z"),
+ profile.get_ts_key(ts, "Q"),
+ ])
diff --git a/src/View/RunSolver/WindowAdisTS.py b/src/View/RunSolver/WindowAdisTS.py
index bdf473c7..a048757a 100644
--- a/src/View/RunSolver/WindowAdisTS.py
+++ b/src/View/RunSolver/WindowAdisTS.py
@@ -412,7 +412,7 @@ class SolverLogWindowAdisTS(PamhyrWindow):
dlg.exec_()
self._parent.set_results(self._solver, self._results)
- self._parent.open_solver_results(self._solver, self._results)
+ self._parent.open_solver_results_adists(self._solver, self._results)
self._solver.has_results_loaded()
diff --git a/src/View/ui/ResultsAdisTS.ui b/src/View/ui/ResultsAdisTS.ui
new file mode 100644
index 00000000..c7c319a0
--- /dev/null
+++ b/src/View/ui/ResultsAdisTS.ui
@@ -0,0 +1,284 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 1280
+ 720
+
+
+
+ MainWindow
+
+
+
+
+
+
+
+ 874
+ 0
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ Qt::Vertical
+
+
+
+
+
-
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ true
+
+
+ true
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+
+
+ ressources/media-skip-backward.pngressources/media-skip-backward.png
+
+
+
+ -
+
+
+
+
+
+
+ ressources/media-seek-backward.pngressources/media-seek-backward.png
+
+
+
+ -
+
+
+ true
+
+
+ true
+
+
+ Qt::DefaultContextMenu
+
+
+ true
+
+
+
+
+
+
+ ressources/media-playback-start.png
+ ressources/media-playback-pause.pngressources/media-playback-start.png
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+
+
+
+
+ ressources/media-seek-forward.pngressources/media-seek-forward.png
+
+
+
+ -
+
+
+
+
+
+
+ ressources/media-skip-forward.pngressources/media-skip-forward.png
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+
+ -
+
+
+ 1
+
+
+
+ Raw data
+
+
+
-
+
+
-
+
+
+ -
+
+
+
+
+
+
+
+
+ Water elevation
+
+
+ -
+
+
-
+
+
+ Qt::Vertical
+
+
+
+ Qt::Horizontal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hydrograph
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ toolBar
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+
+
+
+ ressources/add.pngressources/add.png
+
+
+ Add
+
+
+ Add custom visualization
+
+
+
+
+
+ ressources/reload.pngressources/reload.png
+
+
+ Reload
+
+
+
+
+
+ ressources/export.pngressources/export.png
+
+
+ Export
+
+
+ Export raw data
+
+
+ Ctrl+E
+
+
+
+
+
+