handle graph export with empty data (#239); closes #233
All checks were successful
Build AppImage / Explore-Gitea-Actions (push) Successful in 1m45s

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: #239
This commit is contained in:
Dominik Demuth 2024-02-07 18:58:18 +00:00
parent 881eff2770
commit 24640d374e
3 changed files with 44 additions and 31 deletions

View File

@ -3,10 +3,10 @@ from __future__ import annotations
from typing import Any from typing import Any
from numpy import ndarray, iscomplexobj, asarray from numpy import ndarray, iscomplexobj, asarray
from pyqtgraph import PlotDataItem
from ..Qt import QtGui, QtCore, QtWidgets from ..Qt import QtGui, QtCore, QtWidgets
from .._py.valueeditor import Ui_MaskDialog from .._py.valueeditor import Ui_MaskDialog
from ..lib.pg_objects import PlotItem
class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog): class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
@ -35,13 +35,13 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
self.tableView.setModel(self.model) self.tableView.setModel(self.model)
self.tableView.setSelectionModel(self.selection_model) self.tableView.setSelectionModel(self.selection_model)
self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.tableView.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.tableView.customContextMenuRequested.connect(self.ctx) self.tableView.customContextMenuRequested.connect(self.ctx)
self.selection_real = PlotDataItem(x=[], y=[], symbolSize=25, symbol='x', self.selection_real = PlotItem(x=[], y=[], symbolSize=25, symbol='x',
pen=None, symbolPen='#c9308e', symbolBrush='#c9308e') pen=None, symbolPen='#c9308e', symbolBrush='#c9308e')
self.selection_imag = PlotDataItem(x=[], y=[], symbolSize=25, symbol='+', self.selection_imag = PlotItem(x=[], y=[], symbolSize=25, symbol='+',
pen=None, symbolPen='#dcdcdc', symbolBrush='#dcdcdc') pen=None, symbolPen='#dcdcdc', symbolBrush='#dcdcdc')
def __call__(self, items: dict): def __call__(self, items: dict):
self.items = items self.items = items
@ -133,7 +133,7 @@ class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
def keyPressEvent(self, evt): def keyPressEvent(self, evt):
if evt.matches(QtGui.QKeySequence.Copy): if evt.matches(QtGui.QKeySequence.Copy):
self.copy_selection() self.copy_selection()
elif evt.key() == QtCore.Qt.Key_Delete: elif evt.key() == QtCore.Qt.Key.Key_Delete:
self.delete_item() self.delete_item()
else: else:
super().keyPressEvent(evt) super().keyPressEvent(evt)
@ -229,7 +229,7 @@ class ValueModel(QtCore.QAbstractTableModel):
""" """
itemChanged = QtCore.pyqtSignal(int, int, str) itemChanged = QtCore.pyqtSignal(int, int, str)
load_number = 20 load_number = 20
maskRole = QtCore.Qt.UserRole+321 maskRole = QtCore.Qt.ItemDataRole.UserRole+321
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent=parent) super().__init__(parent=parent)
@ -240,7 +240,7 @@ class ValueModel(QtCore.QAbstractTableModel):
self.mask = None self.mask = None
self.headers = ['x', 'y', '\u0394y'] self.headers = ['x', 'y', '\u0394y']
for i, hd in enumerate(self.headers): for i, hd in enumerate(self.headers):
self.setHeaderData(i, QtCore.Qt.Horizontal, hd) self.setHeaderData(i, QtCore.Qt.Orientation.Horizontal, hd)
def rowCount(self, *args, **kwargs) -> int: def rowCount(self, *args, **kwargs) -> int:
return self.total_rows return self.total_rows
@ -258,25 +258,28 @@ class ValueModel(QtCore.QAbstractTableModel):
self.mask = mask.tolist() self.mask = mask.tolist()
self.endResetModel() self.endResetModel()
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [QtCore.Qt.DisplayRole]) self.dataChanged.emit(
self.index(0, 0),
self.index(0, 1), [QtCore.Qt.ItemDataRole.DisplayRole]
)
def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole) -> Any: def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.ItemDataRole.DisplayRole) -> Any:
if not idx.isValid(): if not idx.isValid():
return return
row = idx.row() row = idx.row()
if role in [QtCore.Qt.DisplayRole, QtCore.Qt.EditRole]: if role in [QtCore.Qt.ItemDataRole.DisplayRole, QtCore.Qt.ItemDataRole.EditRole]:
val = self._data[row][idx.column()] val = self._data[row][idx.column()]
return self.as_string(val) return self.as_string(val)
elif role == QtCore.Qt.BackgroundRole: elif role == QtCore.Qt.ItemDataRole.BackgroundRole:
pal = QtGui.QGuiApplication.palette() pal = QtGui.QGuiApplication.palette()
if not self.mask[row]: if not self.mask[row]:
return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Base) return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Base)
else: else:
return pal.color(QtGui.QPalette.Base) return pal.color(QtGui.QPalette.Base)
elif role == QtCore.Qt.ForegroundRole: elif role == QtCore.Qt.ItemDataRole.ForegroundRole:
pal = QtGui.QGuiApplication.palette() pal = QtGui.QGuiApplication.palette()
if not self.mask[row]: if not self.mask[row]:
return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text) return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text)
@ -289,7 +292,7 @@ class ValueModel(QtCore.QAbstractTableModel):
else: else:
return return
def setData(self, idx: QtCore.QModelIndex, value: str | bool, role=QtCore.Qt.DisplayRole) -> Any: def setData(self, idx: QtCore.QModelIndex, value: str | bool, role=QtCore.Qt.ItemDataRole.DisplayRole) -> Any:
col, row = idx.column(), idx.row() col, row = idx.column(), idx.row()
if role == ValueModel.maskRole: if role == ValueModel.maskRole:
@ -299,7 +302,7 @@ class ValueModel(QtCore.QAbstractTableModel):
return True return True
if value: if value:
if role == QtCore.Qt.EditRole: if role == QtCore.Qt.ItemDataRole.EditRole:
if value == self.as_string(self._data[row][col]): if value == self.as_string(self._data[row][col]):
return True return True
@ -322,9 +325,9 @@ class ValueModel(QtCore.QAbstractTableModel):
else: else:
return False return False
def headerData(self, section: int, orientation, role=QtCore.Qt.DisplayRole) -> Any: def headerData(self, section: int, orientation, role=QtCore.Qt.ItemDataRole.DisplayRole) -> Any:
if role == QtCore.Qt.DisplayRole: if role == QtCore.Qt.ItemDataRole.DisplayRole:
if orientation == QtCore.Qt.Horizontal: if orientation == QtCore.Qt.Orientation.Horizontal:
return self.headers[section] return self.headers[section]
else: else:
return str(section+1) return str(section+1)
@ -346,7 +349,7 @@ class ValueModel(QtCore.QAbstractTableModel):
self.endInsertRows() self.endInsertRows()
def flags(self, idx: QtCore.QModelIndex) -> QtCore.Qt.ItemFlag: def flags(self, idx: QtCore.QModelIndex) -> QtCore.Qt.ItemFlag:
return QtCore.QAbstractTableModel.flags(self, idx) | QtCore.Qt.ItemIsEditable return QtCore.QAbstractTableModel.flags(self, idx) | QtCore.Qt.ItemFlag.ItemIsEditable
def removeRows(self, pos: int, rows: int, parent=None, *args, **kwargs) -> bool: def removeRows(self, pos: int, rows: int, parent=None, *args, **kwargs) -> bool:
self.beginRemoveRows(parent, pos, pos+rows-1) self.beginRemoveRows(parent, pos, pos+rows-1)

View File

@ -670,11 +670,14 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
else: else:
if os.path.exists(outfile): if os.path.exists(outfile):
if QtWidgets.QMessageBox.warning(self, 'Export graphic', if QtWidgets.QMessageBox.warning(
f'{os.path.split(outfile)[1]} already exists.\n' self,
f'Do you REALLY want to replace it?', 'Export graphic',
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, f'{os.path.split(outfile)[1]} already exists.\n'
QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No: f'Do you REALLY want to replace it?',
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.No
) == QtWidgets.QMessageBox.No:
return return
bg_color = self._bgcolor bg_color = self._bgcolor
@ -716,16 +719,20 @@ class QGraphWindow(QtWidgets.QGraphicsView, Ui_GraphWindow):
logger.exception(f'{item} could not exported because {e.args}') logger.exception(f'{item} could not exported because {e.args}')
continue continue
if len(item) == 2:
# plot can show errorbars
item_dic['yerr'] = item[1].opts['topData']
if item_dic: if item_dic:
if len(item) == 2:
# plot can show errorbars
if len(item_dic['x']):
item_dic['yerr'] = item[1].opts['topData']
else:
item_dic['yerr'] = []
dic['items'].append(item_dic) dic['items'].append(item_dic)
for item in self._external_items: for item in self._external_items:
try: try:
dic['items'].append(item.get_data_opts()) item_dic = item.get_data_opts()
if item_dic:
dic['items'].append(item_dic)
except Exception as e: except Exception as e:
logger.exception(f'{item} could not be exported because {e.args}') logger.exception(f'{item} could not be exported because {e.args}')
continue continue

View File

@ -183,6 +183,8 @@ class PlotItem(PlotDataItem):
brush = self.opts['symbolBrush'] brush = self.opts['symbolBrush']
if isinstance(brush, tuple): if isinstance(brush, tuple):
self.opts['symbolcolor'] = brush self.opts['symbolcolor'] = brush
elif isinstance(brush, str):
self.opts['symbolcolor'] = int(f'0x{brush[1:3]}', 16), int(f'0x{brush[3:5]}', 16), int(f'0x{brush[5:7]}', 16)
else: else:
c = brush.color() c = brush.color()
self.opts['symbolcolor'] = c.red(), c.green(), c.blue() self.opts['symbolcolor'] = c.red(), c.green(), c.blue()
@ -340,7 +342,8 @@ class PlotItem(PlotDataItem):
opts = self.opts opts = self.opts
item_dic = { item_dic = {
'x': x, 'y': y, 'x': x,
'y': y,
'name': opts.get('name', ''), 'name': opts.get('name', ''),
'symbolsize': opts['symbolSize'], 'symbolsize': opts['symbolSize'],
} }