Skip to content

Commit

Permalink
Merge pull request #362 from European-XFEL/feat/rowFiltering
Browse files Browse the repository at this point in the history
Basic row filtering
  • Loading branch information
tmichela authored Feb 11, 2025
2 parents 64c80c2 + 6f1c1e5 commit ca134cc
Show file tree
Hide file tree
Showing 10 changed files with 1,104 additions and 195 deletions.
101 changes: 14 additions & 87 deletions damnit/gui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@
from .plot import (ImagePlotWindow, PlottingControls, ScatterPlotWindow,
Xarray1DPlotWindow)
from .process import ProcessingDialog
from .standalone_comments import TimeComment
from .table import DamnitTableModel, TableView, prettify_notation
from .theme import Theme, ThemeManager, set_lexer_theme
from .user_variables import AddUserVariableDialog
from .web_viewer import PlotlyPlot, UrlSchemeHandler
from .widgets import CollapsibleWidget
from .zulip_messenger import ZulipMessenger

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -595,18 +595,6 @@ def _updates_thread_launcher(self) -> None:
self.update_agent.message.connect(self.handle_update)
QtCore.QTimer.singleShot(0, self._updates_thread.start)

def _set_comment_date(self):
self.comment_time.setText(
time.strftime("%H:%M %d/%m/%Y", time.localtime(time.time()))
)

def _comment_button_clicked(self):
ts = datetime.strptime(self.comment_time.text(), "%H:%M %d/%m/%Y").timestamp()
text = self.comment.text()
comment_id = self.db.add_standalone_comment(ts, text)
self.table.insert_comment_row(comment_id, text, ts)
self.comment.clear()

def get_run_file(self, proposal, run, log=True):
file_name = self.extracted_data_template.format(proposal, run)

Expand Down Expand Up @@ -743,7 +731,6 @@ def show_run_logs(self, proposal, run):
def _create_table_model(self, db, col_settings):
table = DamnitTableModel(db, col_settings, self)
table.value_changed.connect(self.save_value)
table.time_comment_changed.connect(self.save_time_comment)
table.run_visibility_changed.connect(lambda row, state: self.plot.update())
table.rowsInserted.connect(self.on_rows_inserted)
return table
Expand All @@ -756,82 +743,29 @@ def _create_view(self) -> None:
vertical_layout.addWidget(toolbar)

# the table
self.table_view = TableView()
self.table_view = TableView(self)
self.table_view.doubleClicked.connect(self._inspect_data_proxy_idx)
self.table_view.settings_changed.connect(self.save_settings)
self.table_view.zulip_action.triggered.connect(self.export_selection_to_zulip)
self.table_view.process_action.triggered.connect(self.process_runs)
self.table_view.log_view_requested.connect(self.show_run_logs)

# Add table view's toolbar widgets
for widget in self.table_view.get_toolbar_widgets():
toolbar.addWidget(widget)

vertical_layout.addWidget(self.table_view)

# add all other widgets on a collapsible layout
collapsible = CollapsibleWidget()
vertical_layout.addWidget(collapsible)

comment_horizontal_layout = QtWidgets.QHBoxLayout()
self.comment = QtWidgets.QLineEdit(self)
self.comment.setText("Time can be edited in the field on the right.")

self.comment_time = QtWidgets.QLineEdit(self)
self.comment_time.setStyleSheet("width: 25px;")

comment_button = QtWidgets.QPushButton("Additional comment")
comment_button.setEnabled(True)
comment_button.clicked.connect(self._comment_button_clicked)

comment_horizontal_layout.addWidget(comment_button)
comment_horizontal_layout.addWidget(self.comment, stretch=3)
comment_horizontal_layout.addWidget(QtWidgets.QLabel("at"))
comment_horizontal_layout.addWidget(self.comment_time, stretch=1)

collapsible.add_layout(comment_horizontal_layout)

comment_timer = QtCore.QTimer()
self._set_comment_date()
comment_timer.setInterval(30000)
comment_timer.timeout.connect(self._set_comment_date)
comment_timer.start()

# plotting control
# Initialize plot controls
self.plot = PlottingControls(self)
plotting_group = QtWidgets.QGroupBox("Plotting controls")
plot_vertical_layout = QtWidgets.QVBoxLayout()
plot_horizontal_layout = QtWidgets.QHBoxLayout()
plot_parameters_horizontal_layout = QtWidgets.QHBoxLayout()

plot_horizontal_layout.addWidget(self.plot._button_plot)
self.plot._button_plot_runs.setMinimumWidth(200)
plot_horizontal_layout.addStretch()
self.plot_dialog_button = QtWidgets.QPushButton("Plot")
self.plot_dialog_button.clicked.connect(self.plot.show_dialog)
self.comment_button = QtWidgets.QPushButton("Time comment")
self.comment_button.clicked.connect(lambda: TimeComment(self).show())

plot_horizontal_layout.addWidget(QtWidgets.QLabel("Y:"))
plot_horizontal_layout.addWidget(self.plot._combo_box_y_axis)
plot_horizontal_layout.addWidget(self.plot.vs_button)
plot_horizontal_layout.addWidget(QtWidgets.QLabel("X:"))
plot_horizontal_layout.addWidget(self.plot._combo_box_x_axis)

plot_vertical_layout.addLayout(plot_horizontal_layout)

plot_parameters_horizontal_layout.addWidget(self.plot._button_plot_runs)
self.plot._button_plot.setMinimumWidth(200)
plot_parameters_horizontal_layout.addStretch()

plot_parameters_horizontal_layout.addWidget(
self.plot._toggle_probability_density
)

plot_vertical_layout.addLayout(plot_parameters_horizontal_layout)

plotting_group.setLayout(plot_vertical_layout)
toolbar.addWidget(self.plot_dialog_button)
toolbar.addWidget(self.comment_button)
for widget in self.table_view.get_toolbar_widgets():
toolbar.addWidget(widget)

collapsible.add_widget(plotting_group)
vertical_layout.addWidget(self.table_view)
vertical_layout.setContentsMargins(0, 7, 0, 0)

vertical_layout.setSpacing(0)
vertical_layout.setContentsMargins(0, 0, 0, 0)
self._view_widget.setLayout(vertical_layout)

def configure_editor(self):
Expand Down Expand Up @@ -983,14 +917,6 @@ def save_value(self, prop, run, name, value):
if self._connect_to_kafka:
self.update_agent.run_values_updated(prop, run, name, value)

def save_time_comment(self, comment_id, value):
if self.db is None:
log.warning("No SQLite database in use, comment not saved")
return

log.debug("Saving time-based comment ID %d", comment_id)
self.db.change_standalone_comment(comment_id, value)

def check_zulip_messenger(self):
if not isinstance(self.zulip_messenger, ZulipMessenger):
self.zulip_messenger = ZulipMessenger(self)
Expand Down Expand Up @@ -1128,6 +1054,7 @@ def styleHint(self, hint, option=None, widget=None, returnData=None):
else:
return super().styleHint(hint, option, widget, returnData)


class TabBarStyle(QtWidgets.QProxyStyle):
"""
Subclass that enables bold tab text for tab 1 (the editor tab).
Expand Down
58 changes: 43 additions & 15 deletions damnit/gui/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
import numpy as np
import pandas as pd
import xarray as xr

from matplotlib.backends.backend_qtagg import FigureCanvas
from matplotlib.backends.backend_qtagg import \
NavigationToolbar2QT as NavigationToolbar
from matplotlib.colorbar import Colorbar
from matplotlib.figure import Figure
from mpl_pan_zoom import MouseButton, PanManager, zoom_factory
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QObject
from PyQt5.QtGui import QColor, QIcon, QPainter
from PyQt5.QtWidgets import QMessageBox

Expand Down Expand Up @@ -592,37 +593,64 @@ class PlottingControls:
def __init__(self, main_window) -> None:
self._main_window = main_window

self._button_plot = QtWidgets.QPushButton(main_window)
self.dialog = QtWidgets.QDialog(main_window)
self.dialog.setWindowTitle("Plot Controls")

plot_vertical_layout = QtWidgets.QVBoxLayout()
plot_horizontal_layout = QtWidgets.QHBoxLayout()
plot_parameters_horizontal_layout = QtWidgets.QHBoxLayout()

self._button_plot = QtWidgets.QPushButton(self.dialog)
self._button_plot.setEnabled(True)
self._button_plot.setText("Plot summary for all runs")
self._button_plot.clicked.connect(self._plot_summaries_clicked)

self._button_plot_runs = QtWidgets.QPushButton(
"Plot for selected runs", main_window
)
self._button_plot_runs = QtWidgets.QPushButton("Plot for selected runs", self.dialog)
self._button_plot_runs.clicked.connect(self._plot_run_data_clicked)

self._combo_box_x_axis = SearchableComboBox(self._main_window)
self._combo_box_y_axis = SearchableComboBox(self._main_window)
plot_horizontal_layout.addWidget(self._button_plot)
self._button_plot_runs.setMinimumWidth(200)
plot_horizontal_layout.addStretch()

self._toggle_probability_density = QtWidgets.QPushButton(
"Histogram", main_window
)
self._toggle_probability_density.setCheckable(True)
self._toggle_probability_density.setChecked(False)
self._toggle_probability_density.toggled.connect(
self._combo_box_y_axis.setDisabled
)
self._combo_box_x_axis = SearchableComboBox(self.dialog)
self._combo_box_y_axis = SearchableComboBox(self.dialog)

self.vs_button = QtWidgets.QToolButton()
self.vs_button.setText("vs.")
self.vs_button.setToolTip("Click to swap axes")
self.vs_button.clicked.connect(self.swap_plot_axes)

plot_horizontal_layout.addWidget(QtWidgets.QLabel("Y:"))
plot_horizontal_layout.addWidget(self._combo_box_y_axis)
plot_horizontal_layout.addWidget(self.vs_button)
plot_horizontal_layout.addWidget(QtWidgets.QLabel("X:"))
plot_horizontal_layout.addWidget(self._combo_box_x_axis)

self._combo_box_x_axis.setCurrentText("Run")

plot_vertical_layout.addLayout(plot_horizontal_layout)

plot_parameters_horizontal_layout.addWidget(self._button_plot_runs)
self._button_plot.setMinimumWidth(200)
plot_parameters_horizontal_layout.addStretch()

self._toggle_probability_density = QtWidgets.QPushButton("Histogram", self.dialog)
self._toggle_probability_density.setCheckable(True)
self._toggle_probability_density.setChecked(False)
self._toggle_probability_density.toggled.connect(self._combo_box_y_axis.setDisabled)

plot_parameters_horizontal_layout.addWidget(self._toggle_probability_density)

plot_vertical_layout.addLayout(plot_parameters_horizontal_layout)

self.dialog.setLayout(plot_vertical_layout)
self.dialog.setVisible(False)

self._plot_windows = []

def show_dialog(self):
self.dialog.setVisible(True)

def update_columns(self):
keys = self.table.column_titles

Expand Down
132 changes: 132 additions & 0 deletions damnit/gui/standalone_comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import logging

from PyQt5.QtCore import (QAbstractTableModel, QDateTime, QModelIndex, Qt,
QVariant)
from PyQt5.QtWidgets import (QDateTimeEdit, QDialog, QHBoxLayout, QLineEdit,
QPushButton, QTableView, QVBoxLayout, QHeaderView)

log = logging.getLogger(__name__)


class CommentModel(QAbstractTableModel):
def __init__(self, db, parent=None):
super().__init__(parent)
self.db = db
self._data = []
self._headers = ['#', 'Timestamp', 'Comment']
self._sort_column = 1 # Default sort by timestamp
self._sort_order = Qt.DescendingOrder
self.load_comments()

def load_comments(self):
"""Load comments from the database, sorted by timestamp in descending order"""
self._data = self.db.conn.execute("""
SELECT rowid, timestamp, comment FROM time_comments
ORDER BY timestamp DESC
""").fetchall()

self.layoutChanged.emit()

def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
row = index.row()
col = index.column()

if col == 1:
return QDateTime.fromSecsSinceEpoch(
int(self._data[row][col])
).toString("yyyy-MM-dd HH:mm:ss")
else:
return self._data[row][col]
return QVariant()

def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self._headers[section]
return QVariant()

def rowCount(self, parent=QModelIndex()):
return len(self._data)

def columnCount(self, parent=QModelIndex()):
return len(self._headers)

def addComment(self, timestamp, comment):
"""Add a comment to the database"""
if self.db is None:
log.warning("No SQLite database in use, comment not saved")
return

cid = self.db.add_standalone_comment(timestamp, comment)
log.debug("Saving time-based id %d", cid)
# Reload comments to reflect the latest state
self.load_comments()

def sort(self, column, order):
"""Sort table by given column number."""
self._sort_column = column
self._sort_order = order

self.layoutAboutToBeChanged.emit()
self._data = sorted(self._data,
key=lambda x: x[column],
reverse=(order == Qt.DescendingOrder))
self.layoutChanged.emit()


class TimeComment(QDialog):

def __init__(self, parent=None):
super().__init__(parent)

layout = QVBoxLayout()

# Table View
self.tableView = QTableView()
self.tableView.setSortingEnabled(True)
self.model = CommentModel(self.parent().db, self)
self.tableView.setModel(self.model)

# Configure column widths
header = self.tableView.horizontalHeader()
for ix in range(self.model.columnCount() - 1):
header.setSectionResizeMode(ix, header.ResizeToContents)
header.setStretchLastSection(True)
# Set word wrap for the comment column
self.tableView.setWordWrap(True)
# Ensure rows resize properly
self.tableView.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)

# Dialog layout
layout.addWidget(self.tableView)

inputLayout = QHBoxLayout()

self.timestampInput = QDateTimeEdit()
self.timestampInput.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
self.timestampInput.setDateTime(QDateTime.currentDateTime())

self.commentInput = QLineEdit()
self.commentInput.setPlaceholderText('Comment:')

publishButton = QPushButton("Publish")
publishButton.clicked.connect(self.publishComment)

inputLayout.addWidget(self.timestampInput)
inputLayout.addWidget(self.commentInput)
inputLayout.addWidget(publishButton)

layout.addLayout(inputLayout)

self.setLayout(layout)
self.setWindowTitle('Standalone comments')
self.resize(600, 400)

def publishComment(self):
timestamp = self.timestampInput.dateTime().toSecsSinceEpoch()
comment = self.commentInput.text()
if comment:
self.model.addComment(timestamp, comment)
self.commentInput.clear()
self.timestampInput.setDateTime(QDateTime.currentDateTime())
self.tableView.resizeRowsToContents()
Loading

0 comments on commit ca134cc

Please sign in to comment.