Source code for impy.viewer.widgets.table

from __future__ import annotations
from typing import Any
import warnings
from qtpy.QtWidgets import (QPushButton, QGridLayout, QHBoxLayout, QWidget, QDialog, QComboBox, QLabel, QCheckBox,
                            QMainWindow, QAction, QHeaderView, QTableWidget, QTableWidgetItem, QStyledItemDelegate,
                            QLineEdit, QSpinBox, QFileDialog, QAbstractItemView)
import napari
import os
import numpy as np
import pandas as pd

[docs]def read_csv(viewer:"napari.Viewer", path): df = pd.read_csv(path) name = os.path.splitext(os.path.basename(path))[0] table = TableWidget(viewer, df, name=name) return viewer.window.add_dock_widget(table, area="right", name=table.name)
# TODO: # - block 1,2,3,..., [, ] when table is not editable # https://stackoverflow.com/questions/48299384/disable-keyevent-for-unneeded-qwidget
[docs]class TableWidget(QMainWindow): """ +-------------------------------+ |[Table][Data][Plot] | | | | (table) | +-------------------------------+ | (figure canvas) | +-------------------------------+ """ n_table = 0 def __init__(self, viewer:"napari.Viewer", df: np.ndarray|pd.DataFrame|dict, columns=None, name=None): self.viewer = viewer self.fig = None self.ax = None self.figure_widget = None self.filter_widget = None self._linked_layer = None self.plot_settings = dict(x=None, kind="line", legend=True, subplots=False, sharex=False, sharey=False, logx=False, logy=False, bins=10) self.last_plot = "plot" if df is None or (isinstance(df, dict) and df == {}): if columns is None: df = np.atleast_2d([]) else: df = np.atleast_2d([""]*len(columns)) elif isinstance(df, dict): if np.isscalar(next(iter(df.values()))): df = pd.DataFrame(df, index=[0]) else: df = pd.DataFrame(df) elif not isinstance(df, pd.DataFrame): df = np.atleast_2d(df) self.max_index = df.shape[0] if columns is None: if isinstance(df, pd.DataFrame): columns = list(df.columns) else: columns = list(range(df.shape[1])) if name is None: self.name = f"Table-{self.__class__.n_table}" self.__class__.n_table += 1 else: self.name = name if isinstance(df, pd.DataFrame): data = df.values else: data = df from magicgui.widgets import Table self.table = Table(data, name=self.name, columns=columns) self.table_native: QTableWidget = self.table.native self.table_native.resizeColumnsToContents() @self.table_native.itemChanged.connect def _(item): i = item.column() self.table_native.resizeColumnToContents(i) # When horizontal header is double-clicked, table enters edit mode. header = self.header header.setSectionsClickable(True) header.sectionDoubleClicked.connect(self._edit_header) # When vertical header is double-clicked, move camera/step in viewer. self.table_native.verticalHeader().sectionDoubleClicked.connect(self._linked_callback) super().__init__(viewer.window._qt_window) self.menu_bar = self.menuBar() self._add_table_menu() self._add_edit_menu() self._add_plot_menu() self.setCentralWidget(self.table_native) self.setUnifiedTitleAndToolBarOnMac(True) self.setWindowTitle(self.name) self.table.read_only = True def __repr__(self): return f"TableWidget with data:\n{self.table.to_dataframe().__repr__()}" @property def linked_layer(self): return self._linked_layer @linked_layer.setter def linked_layer(self, layer): from napari.layers import Shapes, Points if self.linked_layer is not None: raise AttributeError("Cannot set linked layer again.") elif not isinstance(layer, (Shapes, Points)): raise TypeError(f"Cannot set {type(layer)}") self._linked_layer = layer # highlight row(s) if object(s) are selected in the viewer. @layer.mouse_drag_callbacks.append def link_selection_to_table(_layer:Shapes|Points, event): while event.type != "mouse_release": yield if self.viewer.dims.ndim == 3: i, _ = _layer.get_value( event.position, view_direction=event.view_direction, dims_displayed=event.dims_displayed, world=True ) if i is None: _layer.selected_data = {} elif "Control" in event.modifiers: _layer.selected_data = _layer.selected_data | {i} else: _layer.selected_data = {i} self._read_selected_data_from_layer(_layer) # delete row(s) if object(s) are deleted in the viewer. def delete_selected_points(_layer:Shapes|Points): selected = sorted(_layer.selected_data) for i in reversed(selected): self.table_native.removeRow(i) _layer.remove_selected() layer.bind_key("Delete", delete_selected_points, overwrite=True) layer.bind_key("Backspace", delete_selected_points, overwrite=True) layer.bind_key("1", delete_selected_points, overwrite=True) # add an empty row if new object is added in the viewer. @layer.events.data.connect def _(event): nrow = self.table_native.rowCount() ncol = self.table_native.columnCount() if len(event.value) > min(nrow, nrow*ncol): self._appendRow() self._read_selected_data_from_layer(event.source) self._connect_item_with_properties() layer.metadata.update({"linked_table": self}) return None def _read_selected_data_from_layer(self, layer): first_item = None self.table_native.clearSelection() for i in layer.selected_data: self.table_native.selectRow(i) if first_item is None: first_item = self.table_native.item(i,0) self.table_native.scrollToItem(first_item, hint=QAbstractItemView.PositionAtTop) return None @property def header_as_tuple(self) -> tuple: return self.table.column_headers @property def header(self) -> QHeaderView: return self.table_native.horizontalHeader() def __array__(self) -> np.ndarray: return self.table.to_dataframe().values
[docs] def to_dataframe(self, selected=False) -> pd.DataFrame: """ Convert table to ``pandas.DataFrame``. Parameters ---------- selected : bool, default is False If True, only selected range will be converted. """ if selected: df = self._get_selected_dataframe() else: df = self.table.to_dataframe() df.index.name = "Index" return df
[docs] def set_header(self, i:int, name:Any): # This method is called when # - a new column is created # - header to row newname = str(name) self.table_native.setHorizontalHeaderItem(i, QTableWidgetItem(newname)) return None
[docs] def set_header_and_properties(self, i:int, name:Any): newname = str(name) oldname = str(self.header_as_tuple[i]) self.table_native.setHorizontalHeaderItem(i, QTableWidgetItem(newname)) if self.linked_layer is not None: prop:dict = self.linked_layer.properties prop[newname] = prop.pop(oldname) self.linked_layer.properties = prop # Due to inconsistency of properties setters between shapes and points, we have to update current # properties only for shapes layer here. from napari.layers import Shapes if isinstance(self.linked_layer, Shapes): from napari.layers.utils.layer_utils import get_current_properties with self.linked_layer.block_update_properties(): self.linked_layer.current_properties = get_current_properties( self.linked_layer._properties, self.linked_layer._property_choices, len(self.linked_layer.data) ) return None
[docs] def store_as_dataframe(self, selected=False): """ Send table contents to Results widget. Parameters ---------- selected : bool, default is False If True, only selected range will be send to results. """ df = self.to_dataframe(selected) self.viewer.window._results.append(df) return None
[docs] def copy_as_dataframe(self, selected=False): """ Send table contents to clipboard. Parameters ---------- selected : bool, default is False If True, only selected range will be send to clipboard. """ self.to_dataframe(selected).to_clipboard() return None
[docs] def save_as_csv(self): dlg = QFileDialog() hist = napari.utils.history.get_save_history() dlg.setHistory(hist) last_hist = hist[0] filename, _ = dlg.getSaveFileName( parent=self.viewer.window.qt_viewer, caption="Save table as csv", directory=last_hist, ) if filename: if not filename.endswith(".csv"): filename += ".csv" self.to_dataframe(False).to_csv(filename) napari.utils.history.update_save_history(filename) return None
[docs] def add_point(self, data="cursor position", size=None, face_color=None, edge_color=None, properties=None, **kwargs): """ Add point in a layer and append its property to the end of the table. They are linked to each other. """ from napari.layers import Points scale = np.array([r[2] for r in self.viewer.dims.range]) if isinstance(data, str) and data == "cursor position": data = np.array(self.viewer.cursor.position) / scale else: data = np.asarray(data) if data.ndim != 1: raise ValueError("1-D array required.") nrow = self.table_native.rowCount() nrow = 0 if nrow*self.table_native.columnCount() == 0 else nrow if properties is None: axes = self.viewer.dims.axis_labels[-data.size:] properties = {axes[k]: data[k] for k in range(data.size)} if self.linked_layer is None: if nrow > 0: raise ValueError("Table already has data. Cannot make a linked layer.") self.linked_layer = \ self.viewer.add_points(data, properties={k:np.atleast_1d(v) for k, v in properties.items()}, scale=scale[-len(data):], size=5 if size is None else size, face_color=[0, 0, 0, 0] if face_color is None else face_color, edge_color=[0, 1, 0, 1] if edge_color is None else edge_color, name=f"Points from {self.name}", n_dimensional=True, **kwargs) elif isinstance(self.linked_layer, Points): with self.linked_layer.events.blocker_all(): self.linked_layer.add(data) if size is not None: self.linked_layer.current_size = size if face_color is not None: self.linked_layer.current_face_color = face_color if edge_color is not None: self.linked_layer.current_edge_color = edge_color if properties is not None: self.linked_layer.current_properties.update(properties) else: type_ = self.linked_layer.__class__.__name__.split(".")[-1] raise TypeError(f"Table is linked to {type_} layer now. Cannot add points.") self._appendRow(data=properties) return None
[docs] def add_shape(self, data, shape_type="rectangle", face_color=None, edge_color=None, properties=None, **kwargs): """ Add point in a layer and append its property to the end of the table. They are linked to each other. """ from napari.layers import Shapes nrow = self.table_native.rowCount() nrow = 0 if nrow*self.table_native.columnCount() == 0 else nrow scale = np.array([r[2] for r in self.viewer.dims.range]) data = np.asarray(data) properties = {"ID": self.max_index} if properties is None else properties if self.linked_layer is None: if nrow > 0: raise ValueError("Table already has data. Cannot make a linked layer.") self.linked_layer = \ self.viewer.add_shapes([data], shape_type=shape_type, properties={k:np.atleast_1d(v) for k, v in properties.items()}, scale=scale[-data.shape[1]:], face_color=[0, 0, 0, 0] if face_color is None else face_color, edge_color=[0, 1, 0, 1] if edge_color is None else edge_color, name=f"Shapes from {self.name}", **kwargs) @self.linked_layer.events.data.connect def _(*args): pass elif isinstance(self.linked_layer, Shapes): with self.linked_layer.events.blocker_all(): self.linked_layer.add(data, shape_type=shape_type, edge_color=edge_color, face_color=face_color) if properties is not None: self.linked_layer.current_properties.update(properties) else: type_ = self.linked_layer.__class__.__name__.split(".")[-1] raise TypeError(f"Table is linked to {type_} layer now. Cannot add points.") self._appendRow(data=properties) return None
def _linked_callback(self, index:int): if self.linked_layer is None: return None data = np.atleast_2d(self.linked_layer.data[index]) # update camera scale = self.linked_layer.scale center = np.mean(data, axis=0) * scale self.viewer.dims.current_step = list(data[0,:].astype(np.int64)) self.viewer.camera.center = center zoom = self.viewer.camera.zoom self.viewer.camera.events.zoom() # Here events are emitted and zoom changes automatically. self.viewer.camera.zoom = zoom self.linked_layer.selected_data = {index} self.linked_layer._set_highlight() return None def _connect_item_with_properties(self): @self.table_native.itemChanged.connect def _(item:QTableWidgetItem): if not item.isSelected(): return None row = item.row() col = item.column() colname = str(self.header_as_tuple[col]) self.linked_layer.properties[colname][row] = item.text() return None
[docs] def plot(self): from .._plt import mpl from napari.utils.notifications import Notification, notification_manager backend = mpl.get_backend() mpl.use("Agg") try: self._add_figuire() df = self._get_selected_dataframe() with warnings.catch_warnings(), mpl.style.context("night"): warnings.simplefilter("ignore", UserWarning) kw = self.plot_settings.copy() kw.pop("bins") if df.shape[1] == 1 and kw["x"] == 0: kw["x"] = None df.plot(ax=self.ax, grid=True, **kw) kw["x"] = 0 else: df.plot(ax=self.ax, grid=True, **kw) self.fig.tight_layout() self.fig.canvas.draw() self.figure_widget.show() except Exception as e: notification_manager.dispatch(Notification.from_exception(e)) finally: mpl.use(backend) self.last_plot = "plot" return None
[docs] def hist(self): from .._plt import mpl from napari.utils.notifications import Notification, notification_manager backend = mpl.get_backend() mpl.use("Agg") try: self._add_figuire() df = self._get_selected_dataframe() with warnings.catch_warnings(), mpl.style.context("night"): warnings.simplefilter("ignore", UserWarning) kw = {k:self.plot_settings[k] for k in ["sharex", "sharey", "bins", "legend"]} df.hist(ax=self.ax, grid=True, **kw) self.fig.tight_layout() self.fig.canvas.draw() self.figure_widget.show() except Exception as e: notification_manager.dispatch(Notification.from_exception(e)) finally: mpl.use(backend) self.last_plot = "hist" return None
[docs] def restore_linked_layer(self): """ Add linked layer to the viewer again, if it has deleted from the layer list. """ if self.linked_layer is None: raise ValueError("No linked layer in this table.") elif self.linked_layer in self.viewer.layers: self.viewer.layers.selection = {self.linked_layer} else: self.viewer.add_layer(self.linked_layer)
[docs] def header_to_row(self): """ Convert table header to the top row. """ if self.linked_layer is not None: raise ValueError("Cannot convert header to row when linked layer exists.") self.table_native.insertRow(0) for i, item in enumerate(self.header_as_tuple): self.table_native.setItem(0, i, QTableWidgetItem(str(item))) self.set_header(i, i) for i in range(self.table_native.rowCount()): self.table_native.setVerticalHeaderItem(i, QTableWidgetItem(str(i))) return None
[docs] def delete_selected_rows(self): """ Delete all the rows that contain selected cells. """ rows, cols = self._get_selected() for i in reversed(rows): self.table_native.removeRow(i) if self.linked_layer is not None: self.linked_layer.selected_data = set(rows) self.linked_layer.remove_selected() return None
[docs] def delete_selected_columns(self): """ Delete all the columns that contain selected cells. """ rows, cols = self._get_selected() for i in reversed(cols): if self.linked_layer is not None: colname = str(self.header_as_tuple[i]) self.linked_layer.properties.pop(colname) self.table_native.removeColumn(i) return None
[docs] def change_plot_setting(self): dlg = PlotSetting(self) dlg.exec_() return None
[docs] def appendRow(self, data=None): """ Append a row on the bottom side. """ if self.linked_layer is not None: raise ValueError("Table has a linked layer. Use 'add_point' instead") return self._appendRow(data=data)
append = appendRow # for compatibility with other widgets def _appendRow(self, data=None): """ Append a row on the bottom side. """ nrow = self.table_native.rowCount() ncol = self.table_native.columnCount() if ncol == 0: return self.newRow(data) self.table_native.insertRow(nrow) self.table_native.setVerticalHeaderItem(nrow, QTableWidgetItem(str(self.max_index))) self.max_index += 1 if not hasattr(data, "__len__"): data = [""] * ncol elif isinstance(data, dict): header = self.table.column_headers data_ = [""] * len(header) for k, v in data.items(): i = header.index(k) data_[i] = v data = data_ elif len(data) > self.table_native.columnCount(): raise ValueError("Input data is longer than the column size.") for i, item in enumerate(data): item = QTableWidgetItem(str(item)) self.table_native.setItem(nrow, i, item) return None
[docs] def appendColumn(self, data=None): """ Append a column on the right side. Also can be used to add 1x1 item to an empty table. """ ncol = self.table_native.columnCount() self.table_native.insertColumn(ncol) # search for an unique name colname = ncol columns = self.header_as_tuple while colname in columns: colname += 1 colname = str(colname) if not hasattr(data, "__len__"): data = [""]*self.table_native.rowCount() elif isinstance(data, dict): raise TypeError("dict input is not been implemented yet.") elif len(data) > self.table_native.rowCount(): raise ValueError("Input data is longer than the row size.") for i, item in enumerate(data): item = QTableWidgetItem(str(item)) self.table_native.setItem(i, ncol, item) if self.linked_layer is not None: prop:dict = self.linked_layer.properties prop[colname] = np.array(data, dtype="<U32") self.linked_layer.properties = prop self.set_header(ncol, colname) self.table_native.resizeColumnsToContents() return None
[docs] def newRow(self, data): """ Add a new row to an empty table. """ if not hasattr(data, "__len__"): # add 1x1 empty item return self.appendColumn() elif isinstance(data, dict): header = list(data.keys()) data = list(data.values()) else: header = np.arange(len(data)) for i, h in enumerate(header): self.table_native.insertColumn(i) self.set_header(i, h) item = QTableWidgetItem(str(data[i])) self.table_native.setItem(0, i, item) self.table_native.resizeColumnsToContents() return None
[docs] def add_filter(self): if self.filter_widget is not None: self.filter_widget.show() return None from napari._qt.widgets.qt_viewer_dock_widget import QtViewerDockWidget filter_central = QWidget(self.filter_widget) filter_central.setLayout(QHBoxLayout()) filter_label = QLabel(filter_central) filter_label.setText("Filter:") filter_central.layout().addWidget(filter_label) self.filter_line = QLineEdit(filter_central) self.filter_line.editingFinished.connect(self._run_filter) filter_central.layout().addWidget(self.filter_line) self.filter_widget = QtViewerDockWidget(self, filter_central, name="Table Filter", area="top", allowed_areas=["top", "bottom"]) self.addDockWidget(self.filter_widget.qt_area, self.filter_widget) return None
[docs] def filterRows(self, column_index:int, value:str): # TODO: float filter such as "a>5" nrow = self.table_native.rowCount() hide = [self.table_native.item(i, column_index).text() != value for i in range(nrow)] if all(hide): for i in range(nrow): self.table_native.setRowHidden(i, False) else: for i in range(nrow): self.table_native.setRowHidden(i, hide[i]) return None
def _run_filter(self): _, selected = self._get_selected() if len(selected) != 1: return None icol = int(selected[0]) value = self.filter_line.text() self.filterRows(icol, value) return None def _change_editability(self): self.table.read_only = not self.table.read_only return None def _get_selected_dataframe(self) -> pd.DataFrame: """ Convert selected cells into pandas.DataFrame if possible. """ sl = self._get_selected() try: df = self.table.to_dataframe().iloc[sl] except TypeError: raise ValueError("Table range is not correctly selected") return df def _get_selected(self) -> tuple[list[int], list[int]]: """ Get fancy indexing slices of the selected region. """ selected:list = self.table_native.selectedRanges() # list of QTableWidgetSelectionRange if len(selected) == 0: return [], [] sl_row = set() sl_column = set() for rng in selected: row_range = set(filter(lambda x: (not self.table_native.isRowHidden(x)), range(rng.topRow(), rng.bottomRow()+1)) ) column_range = set(range(rng.leftColumn(), rng.rightColumn()+1)) sl_row |= row_range sl_column |= column_range return list(sl_row), list(sl_column) def _add_table_menu(self): self.table_menu = self.menu_bar.addMenu("&Table") copy_all = QAction("Copy all", self) copy_all.triggered.connect(self.copy_as_dataframe) copy_all.setShortcut("Ctrl+Shift+C") copy = QAction("Copy selected", self) copy.triggered.connect(lambda: self.copy_as_dataframe(selected=True)) copy.setShortcut("Ctrl+C") store_all = QAction("Store all", self) store_all.triggered.connect(self.store_as_dataframe) store = QAction("Store selected", self) store.triggered.connect(lambda: self.store_as_dataframe(selected=True)) save = QAction("Save as csv", self) save.triggered.connect(self.save_as_csv) resize = QAction("Resize columns", self) resize.triggered.connect(self.table_native.resizeColumnsToContents) filt = QAction("Filter", self) filt.triggered.connect(self.add_filter) restore = QAction("Restore linked layer", self) restore.triggered.connect(self.restore_linked_layer) close = QAction("Delete widget", self) close.triggered.connect(self.delete_self) self.table_menu.addAction(copy_all) self.table_menu.addAction(copy) self.table_menu.addAction(store_all) self.table_menu.addAction(store) self.table_menu.addAction(save) self.table_menu.addSeparator() self.table_menu.addAction(resize) self.table_menu.addAction(restore) self.table_menu.addAction(filt) self.table_menu.addSeparator() self.table_menu.addAction(close) return None def _add_edit_menu(self): self.edit_menu = self.menu_bar.addMenu("&Edit") addrow = QAction("Append row", self) addrow.triggered.connect(self.appendRow) addrow.setShortcut("Alt+R") addcol = QAction("Append column", self) addcol.triggered.connect(self.appendColumn) addcol.setShortcut("Alt+C") head2row = QAction("Header to top row", self) head2row.triggered.connect(self.header_to_row) delrow = QAction("Delete selected rows", self) delrow.triggered.connect(self.delete_selected_rows) delcol = QAction("Delete selected columns", self) delcol.triggered.connect(self.delete_selected_columns) change = QAction("Editable", self, checkable=True, checked=False) change.triggered.connect(self._change_editability) self.edit_menu.addAction(head2row) self.edit_menu.addAction(addrow) self.edit_menu.addAction(addcol) self.edit_menu.addAction(delrow) self.edit_menu.addAction(delcol) self.edit_menu.addAction(change) return None def _add_plot_menu(self): self.plot_menu = self.menu_bar.addMenu("&Plot") plot = QAction("Plot", self.viewer.window._qt_window) plot.triggered.connect(self.plot) hist = QAction("Histogram", self.viewer.window._qt_window) hist.triggered.connect(self.hist) setting = QAction("Setting ...", self.viewer.window._qt_window) setting.triggered.connect(self.change_plot_setting) self.plot_menu.addAction(plot) self.plot_menu.addAction(hist) self.plot_menu.addAction(setting) return None def _add_figuire(self): from .._plt import EventedCanvas, plt_figure if self.fig is None: from napari._qt.widgets.qt_viewer_dock_widget import QtViewerDockWidget self.fig = plt_figure() canvas = EventedCanvas(self.fig) self.figure_widget = QtViewerDockWidget(self, canvas, name="Figure", area="bottom", allowed_areas=["right", "bottom"]) self.figure_widget.setMinimumHeight(120) self.addDockWidget(self.figure_widget.qt_area, self.figure_widget) else: self.fig.clf() self.ax = self.fig.add_subplot(111) return None
[docs] def delete_self(self): """ Remove from the dock widget list of the parent viewer. """ self.removeDockWidget(self.figure_widget) if self.linked_layer is not None: self.linked_layer.metadata.pop("linked_table", None) dock = self.viewer.window._dock_widgets[self.name] self.viewer.window.remove_dock_widget(dock) return None
def _edit_header(self, i:int): """ Enter edit header mode when a header item is double-clicked. Parameters ---------- i : int The index of header item that is double-clicked. References ---------- - https://www.qtcentre.org/threads/42388-Make-QHeaderView-Editable """ if self.table.read_only: return None line = QLineEdit(parent=self.header) # set geometry edit_geometry = line.geometry() edit_geometry.setWidth(self.header.sectionSize(i)) edit_geometry.moveLeft(self.header.sectionViewportPosition(i)) line.setGeometry(edit_geometry) line.setText(str(self.header_as_tuple[i])) line.setHidden(False) line.setFocus() line.selectAll() self._line = line # we have to retain the pointer, otherwise got error sometimes @self._line.editingFinished.connect def _(): self._line.setHidden(True) self.set_header_and_properties(i, self._line.text()) self.table_native.resizeColumnToContents(i) return None
[docs]class PlotSetting(QDialog): def __init__(self, table:TableWidget): self.table = table super().__init__(table.viewer.window._qt_window) self.resize(180, 120) self.setLayout(QGridLayout()) self.add_widgets()
[docs] def add_widgets(self): label = QLabel(self) label.setText("Set the plotting style.") self.layout().addWidget(label) self.usex = self._add_checkbox(text="Left-most column as X-axis", checked=(self.table.plot_settings["x"] == 0)) combo = QWidget(self) combo.setLayout(QHBoxLayout()) self.kind = QComboBox(self) self.kind.addItems(["line", "bar", "box", "kde"]) self.kind.setCurrentText(self.table.plot_settings["kind"]) combo.layout().addWidget(self.kind) label = QLabel(self) label.setText("The kind of plot") combo.layout().addWidget(label) self.layout().addWidget(combo) self.bins = QSpinBox(self) self.bins.setRange(2, 100) self.bins.setValue(self.table.plot_settings["bins"]) self.layout().addWidget(self.bins) self.legend = self._add_checkbox(text="Show legend", checked=self.table.plot_settings["legend"]) self.subplots = self._add_checkbox(text="Subplots", checked=self.table.plot_settings["subplots"]) self.sharex = self._add_checkbox(text="Share X-axis", checked=self.table.plot_settings["sharex"]) self.sharey = self._add_checkbox(text="Share Y-axis", checked=self.table.plot_settings["sharey"]) self.logx = self._add_checkbox(text="log-X", checked=self.table.plot_settings["logx"]) self.logy = self._add_checkbox(text="log-Y", checked=self.table.plot_settings["logy"]) self._add_buttons()
[docs] def ok(self): self.change_setting() self.close() return None
[docs] def apply(self): self.change_setting() getattr(self.table, self.table.last_plot)() return None
[docs] def change_setting(self): out = dict() out["x"] = 0 if self.usex.isChecked() else None out["kind"] = str(self.kind.currentText()) out["bins"] = self.bins.value() for attr in ["legend", "subplots", "sharex", "sharey", "logx", "logy"]: out[attr] = getattr(self, attr).isChecked() self.table.plot_settings.update(out) return None
def _add_checkbox(self, text:str, checked:bool): checkbox = QCheckBox(self) checkbox.setText(text) checkbox.setChecked(checked) self.layout().addWidget(checkbox) return checkbox def _add_buttons(self): buttons = QWidget(self) buttons.setLayout(QHBoxLayout()) ok_button = QPushButton("Save and close", self) ok_button.clicked.connect(self.ok) buttons.layout().addWidget(ok_button) apply_button = QPushButton("Apply", self) apply_button.clicked.connect(self.apply) buttons.layout().addWidget(apply_button) cancel_button = QPushButton("Cancel", self) cancel_button.clicked.connect(self.close) buttons.layout().addWidget(cancel_button) self.layout().addWidget(buttons)
[docs]class FilterWidget(QWidget): def __init__(self, parent): super().__init__(parent=parent) self.setLayout(QHBoxLayout()) def _add_widgets(self): self.line = QLineEdit(self) self.line.chan