from numpy import hstack, vstack, zeros, savetxt
from PySide2.QtWidgets import QDialog, QFileDialog
from pandas import read_csv
from ....GUI.Tools.FloatEdit import FloatEdit
from ....GUI.Tools.WTableData.Ui_DTableData import Ui_DTableData
from os.path import isfile
from PySide2.QtWidgets import QMessageBox
from ..WImport.WImportExcel.WImportExcel import WImportExcel
import matplotlib.pyplot as plt
from ....Functions.Plot.set_plot_gui_icon import set_plot_gui_icon
from PySide2.QtCore import Signal
[docs]class DTableData(Ui_DTableData, QDialog):
saveNeeded = Signal()
def __init__(
self,
parent=None,
data=None,
Vmin=-999999999,
Vmax=999999999,
title=None,
shape_min=(1, 1),
shape_max=(None, None),
col_header=None,
unit_order=None,
button_plot_title="",
):
"""Initialization of the widget
Parameters
----------
data : ndarray
Data to display/edit
Vmin : float
Minimum value of the data
Vmax : float
Maximum value of the data
title : str
Title for the Dialog window
shape_min : list
[Min row number, Min column number], None for "no" constraint
shape_max : list
[Max row number, Max column number], None for "no" constraint
col_header : list
Provide Header to the columns (None to remove header)
unit_order : list
["A(B)", "B(A)"], for excel import, to allow the user to specify the order of his units when two units
"""
QDialog.__init__(self, parent)
self.setupUi(self)
self.data = data if data is not None else zeros((1, 2))
self.Vmin = Vmin
self.Vmax = Vmax
self.def_value = 0 # Value to set in empty cell (get_data)
self.title = title # To change the dialog title
self.button_plot_title = button_plot_title
self.shape_min = shape_min
self.shape_max = shape_max
self.col_header = col_header
self.unit_order = unit_order
self.N_row_txt = "N_row"
self.N_col_txt = "N_column"
self.isEnabled_widget = True # To maintain when updating
# Import/Export Hidden by default, need to be shown in widget definition
self.b_import.hide()
self.b_export.hide()
self.wimport_excel = None
self.update()
self.si_row.valueChanged.connect(self.set_Nrow)
self.si_col.valueChanged.connect(self.set_Ncol)
self.b_export.clicked.connect(self.export_csv)
self.b_import.clicked.connect(self.import_csv)
self.b_plot.clicked.connect(self.s_plot)
[docs] def update(self):
"""Update the Widget according to the current data"""
self.si_row.setValue(self.data.shape[0])
self.si_col.setValue(self.data.shape[1])
self.in_col.setText(self.N_col_txt)
self.in_row.setText(self.N_row_txt)
self.b_plot.setText("Plot " + self.button_plot_title)
# Set row shape limit
Rmin = 1
Rmax = 999999999
if self.shape_min[0] is not None:
Rmin = max(Rmin, self.shape_min[0])
if self.shape_max[0] is not None and self.shape_max[0] >= Rmin:
Rmax = min(Rmax, self.shape_max[0])
self.si_row.setMinimum(Rmin)
self.si_row.setMaximum(Rmax)
if Rmin == Rmax:
self.si_row.setValue(Rmin)
self.set_Nrow()
self.si_row.setEnabled(False)
else:
self.si_row.setEnabled(True)
# Set column shape limit
Cmin = 1
Cmax = 999999999
if self.shape_min[1] is not None:
Cmin = max(Cmin, self.shape_min[1])
if self.shape_max[1] is not None and self.shape_max[1] >= Cmin:
Cmax = min(Cmax, self.shape_max[1])
self.si_col.setMinimum(Cmin)
self.si_col.setMaximum(Cmax)
if Cmin == Cmax:
self.si_col.setValue(Cmin)
self.si_col.setEnabled(False)
else:
self.si_col.setEnabled(True)
# Set title
if self.title is not None:
self.setWindowTitle(self.title)
# Fill the table
self.update_tab()
[docs] def clear(self):
self.w_tab.clear()
self.si_row.setValue(0)
[docs] def update_tab(self):
"""Update the Table to display the current data + setup edit"""
self.w_tab.clear()
nrow = self.data.shape[0]
# Reshape vector to matrix with one column
if len(self.data.shape) == 1:
self.data = self.data.reshape((nrow, 1))
ncol = self.data.shape[1]
self.si_row.setValue(nrow)
self.si_col.setValue(ncol)
self.w_tab.setRowCount(nrow)
self.w_tab.setColumnCount(ncol)
# Set column header
if self.col_header is not None:
self.w_tab.setHorizontalHeaderLabels(self.col_header)
for ii in range(nrow):
for jj in range(ncol):
self.w_tab.setCellWidget(ii, jj, FloatEdit())
# Add the min/max to the widget
self.w_tab.cellWidget(ii, jj).validator().setBottom(self.Vmin)
self.w_tab.cellWidget(ii, jj).validator().setTop(self.Vmax)
# Set the values
self.w_tab.cellWidget(ii, jj).setValue(self.data[ii, jj])
# Enable / Disable
self.w_tab.cellWidget(ii, jj).setEnabled(self.isEnabled_widget)
# Connect the slot
self.w_tab.cellWidget(ii, jj).editingFinished.connect(self.emit_save)
if ncol == 2:
self.b_plot.setHidden(False)
else:
self.b_plot.setHidden(True)
[docs] def set_Nrow(self):
"""Change the number of row"""
Nrow_1 = self.data.shape[0]
Nrow_2 = self.si_row.value()
if Nrow_1 < Nrow_2:
# Add some new lines
data = zeros((Nrow_2, self.data.shape[1]))
data[:Nrow_1, :] = self.data
self.data = data
self.update_tab()
elif Nrow_1 > Nrow_2:
# Remove the last lines
self.data = self.data[:Nrow_2, :]
self.update_tab()
# Else do nothing
[docs] def set_Ncol(self):
"""Change the number of column"""
Ncol_1 = self.data.shape[1]
Ncol_2 = self.si_col.value()
if Ncol_1 < Ncol_2:
# Add some new columns
data = zeros((self.data.shape[0], Ncol_2))
data[:, :Ncol_1] = self.data
self.data = data
self.update_tab()
elif Ncol_1 > Ncol_2:
# Remove the last columns
self.data = self.data[:, :Ncol_2]
self.update_tab()
# Else do nothing
[docs] def get_data(self):
"""Return the current data matrix"""
data = zeros((self.si_row.value(), self.si_col.value()))
for ii in range(self.si_row.value()):
for jj in range(self.si_col.value()):
cellwidget = self.w_tab.cellWidget(ii, jj)
data[ii, jj] = (
cellwidget.value() if cellwidget is not None else self.def_value
)
return data
[docs] def export_csv(self):
"""Write the current table data into a csv file"""
data = self.get_data()
save_file_path = QFileDialog.getSaveFileName(
self, self.tr("Save file"), "", "CSV (*.csv)"
)[0]
if save_file_path not in [None, ""]:
savetxt(save_file_path, data, delimiter=",")
[docs] def import_csv(self):
"""Import the data from a csv file and update the tavle"""
load_path = str(
QFileDialog.getOpenFileName(
self,
self.tr("Load Data file"),
"",
"CSV, Excel (*.csv *.xls *.xlsx);;All files (*.*)",
)[0]
)
if isfile(load_path):
if load_path.endswith(".csv"):
try:
df = read_csv(
load_path,
header=None,
)
if type(df.values[0, 0]) is str:
# Load remove first line
df = read_csv(
load_path,
)
except Exception as e:
QMessageBox().critical(
self,
self.tr("Error"),
self.tr("Error while loading csv file:\n" + str(e)),
)
return
val = df.values
# Check Shape
shape = val.shape
if self.verif_shape_data(shape) is not None:
return
# Try to fill the table
try:
old_data = self.data
self.data = val
self.col_header = None
self.update()
self.emit_save()
except Exception as e:
QMessageBox().critical(
self,
self.tr("Error"),
self.tr("Error while loading csv file:\n" + str(e)),
)
elif load_path.endswith(".xlsx"):
self.wimport_excel = WImportExcel(
load_path=load_path,
nb_cols=self.shape_max[1],
headers=self.unit_order,
)
self.wimport_excel.accepted.connect(self.validate_wimport_excel)
self.wimport_excel.show()
[docs] def validate_wimport_excel(self):
"""UPDATE THE DATA IN THE TAB"""
val = self.wimport_excel.datas
# Check Shape
shape = val.shape
if self.verif_shape_data(shape) is not None:
return
# Try to fill the table
try:
old_data = self.data
self.data = val
self.update()
self.emit_save()
except Exception as e:
QMessageBox().critical(
self,
self.tr("Error"),
self.tr("Error while loading csv file:\n" + str(e)),
)
[docs] def verif_shape_data(self, shape):
"""Verif if the data has the right shape"""
error = ""
if self.shape_min:
if self.shape_min[0] and shape[0] < self.shape_min[0]:
error = "Minimum number of line should be " + str(self.shape_min[0])
if self.shape_min[1] and shape[1] < self.shape_min[1]:
error = "Minimum number of column should be " + str(self.shape_min[1])
if self.shape_max:
if self.shape_max[0] and shape[0] > self.shape_max[0]:
error = "Maximum number of line should be " + str(self.shape_max[0])
if self.shape_max[1] and shape[1] > self.shape_max[1]:
error = "Maximum number of column should be " + str(self.shape_max[1])
if error:
QMessageBox().critical(
self,
self.tr("Error"),
self.tr("Error while loading excel file:\n" + error),
)
return error
[docs] def s_plot(self):
"""display the data in a plot"""
try:
data = self.data
except Exception as e:
QMessageBox.critical(self, self.tr("Error"), str(e))
return
if len(data.shape) == 2 and data.shape[0] == 2:
# Data in line
fig, axes = plt.subplots()
axes.plot(data[0, :], data[1, :])
axes.set_xlabel(self.col_header[0])
axes.set_ylabel(self.col_header[1])
fig.show()
elif len(data.shape) == 2 and data.shape[1] == 2:
# Data in column
fig, axes = plt.subplots()
axes.plot(data[:, 0], data[:, 1])
axes.set_xlabel(self.col_header[0])
axes.set_ylabel(self.col_header[1])
fig.show()
if self.col_header is not None:
fig.canvas.manager.set_window_title(self.col_header[0] + " plot")
set_plot_gui_icon()
[docs] def accept(self):
"""Update the data before closing"""
self.data = self.get_data()
super().accept()
[docs] def emit_save(self):
"""Send a saveNeeded signal to the DMachineSetup"""
self.saveNeeded.emit()