diff --git a/nixview/ui/central_widget.py b/nixview/ui/central_widget.py index ad30d0f..8bec77f 100644 --- a/nixview/ui/central_widget.py +++ b/nixview/ui/central_widget.py @@ -1,12 +1,8 @@ -import os -from PyQt5.QtWidgets import QComboBox, QFrame, QGroupBox, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QSplitter, QStackedLayout, QAbstractItemView, QTextEdit, QVBoxLayout, QWidget, QGridLayout -from PyQt5.QtGui import QPixmap -from PyQt5.QtCore import QItemSelectionModel, Qt, QSettings, pyqtSignal +from PyQt5.QtWidgets import QStackedLayout, QWidget -from nixview.file_handler import FileHandler, ItemDescriptor -import nixview.communicator as comm -import nixview.constants as cnst -from nixview.data_models.tree_model import NixTreeView, TreeModel, TreeType +from nixview.ui.plotscreen import PlotScreen +from nixview.ui.splashscreen import SplashScreen +from nixview.ui.filescreen import FileScreen class CentralWidget(QWidget): @@ -14,7 +10,7 @@ class CentralWidget(QWidget): super().__init__(parent=parent) self._splash = SplashScreen() - self._file_view = FileView(self) + self._file_view = FileScreen(self) self._plot_screen = PlotScreen(self) self._plot_screen.close_signal.connect(self.on_plot_close) self._stack = QStackedLayout(self) @@ -38,221 +34,3 @@ class CentralWidget(QWidget): self._file_view.reset() self._splash.reset() self._stack.setCurrentIndex(0) - - -class FileView(QWidget): - def __init__(self, parent=None) -> None: - super().__init__(parent=parent) - self._file_handler = FileHandler() - - vbox = QVBoxLayout() - self.setLayout(vbox) - - main_splitter = QSplitter(Qt.Vertical) - self.layout().addWidget(main_splitter) - - self._info = EntityInfo(self) - main_splitter.addWidget(self._info) - - self._data_tree = NixTreeView(self) - - self._tree_type_combo = QComboBox() - self._tree_type_combo.adjustSize() - self._tree_type_combo.addItems([TreeType.Data.value, TreeType.Full.value, TreeType.Metadata.value]) - self._tree_type_combo.currentTextChanged.connect(self.update) - - hbox = QHBoxLayout() - hbox.addWidget(QLabel("Tree type:")) - hbox.addWidget(self._tree_type_combo) - hbox.addStretch() - data_group = QGroupBox("Data") - data_vbox = QVBoxLayout() - data_vbox.setContentsMargins(1, 10, 1, 1) - - data_vbox.addLayout(hbox) - data_vbox.addWidget(self._data_tree) - data_group.setLayout(data_vbox) - - main_splitter.addWidget(data_group) - main_splitter.setSizes([200, 600]) - vbox.addWidget(main_splitter) - - def dataTreeSelection(self, current_index, last_index): - if not current_index.isValid(): - return - item = current_index.internalPointer() - comm.communicator.item_selected.emit(item) - self._info.setEntityInfo(item.node_descriptor) - - def update(self): - tt = TreeType.Data - if self._tree_type_combo.currentText() == TreeType.Data.value: - tt = TreeType.Data - elif self._tree_type_combo.currentText() == TreeType.Full.value: - tt = TreeType.Full - elif self._tree_type_combo.currentText() == TreeType.Metadata.value: - tt = TreeType.Metadata - self._info.setEntityInfo(None) - data_model = TreeModel(self._file_handler, tt) - self._data_tree.setModel(data_model) - selection_model = QItemSelectionModel(data_model) - self._data_tree.setSelectionModel(selection_model) - selection_model.currentChanged.connect(self.dataTreeSelection) - for i in range(data_model.columnCount(None)): - self._data_tree.resizeColumnToContents(i) - self._info.setFileInfo(self._file_handler.file_descriptor) - - def reset(self): - pass - - -class EntityInfo(QWidget): - - def __init__(self, parent): - super().__init__(parent=parent) - self._file_handler = FileHandler() - self.setLayout(QHBoxLayout()) - - self._metadata_tree = NixTreeView() - - mdata_grp = QGroupBox("Metadata") - mdata_grp.setLayout(QVBoxLayout()) - mdata_grp.layout().setContentsMargins(1, 10, 1, 1) - mdata_grp.layout().addWidget(self._metadata_tree) - - file_info_grp = QGroupBox("File info") - file_info_grp.setLayout(QVBoxLayout()) - file_info_grp.layout().setContentsMargins(1, 10, 1, 1) - self._file_info = QTextEdit("File information") - self._file_info.setEnabled(True) - self._file_info.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse) - self._file_info.setFrameShape(QFrame.NoFrame) - self._file_info.setLineWrapMode(QTextEdit.WidgetWidth) - file_info_grp.layout().addWidget(self._file_info) - - entity_info_grp = QGroupBox("Entity info") - entity_info_grp.setLayout(QVBoxLayout()) - entity_info_grp.layout().setContentsMargins(1, 10, 1, 1) - self._entity_info = QTextEdit("Entity information") - self._file_info.setEnabled(True) - self._file_info.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse) - self._file_info.setFrameShape(QFrame.NoFrame) - self._file_info.setLineWrapMode(QTextEdit.WidgetWidth) - entity_info_grp.layout().addWidget(self._entity_info) - - self._splitter = QSplitter(Qt.Horizontal) - self._splitter.addWidget(file_info_grp) - self._splitter.addWidget(entity_info_grp) - self._splitter.addWidget(mdata_grp) - self._splitter.setSizes([200, 400, 0]) - self._splitter.setStretchFactor(0, 0) - self._splitter.setStretchFactor(1, 1) - self._splitter.setStretchFactor(2, 1) - - self.layout().addWidget(self._splitter) - - - def setFileInfo(self, file_info): - if file_info is not None: - self._file_info.setText(file_info.toHtml()) - - def setEntityInfo(self, entity_info): - if entity_info is None or not isinstance(entity_info, ItemDescriptor): - self._splitter.setSizes([200, 400, 0]) - self._entity_info.setText("") - self._metadata_tree.setModel(None) - return - - if entity_info.metadata_id is not None: - self._splitter.setSizes([200, 400, 400]) - else: - self._splitter.setSizes([200, 400, 0]) - self._entity_info.setText(entity_info.to_html()) - metadata_model = TreeModel(self._file_handler, TreeType.Metadata, root_section_id=entity_info.metadata_id) - self._metadata_tree.setModel(metadata_model) - for i in range(metadata_model.columnCount(None)): - self._metadata_tree.resizeColumnToContents(i) - - -class SplashScreen(QWidget): - keyPressed = pyqtSignal(int) - - def __init__(self, parent=None) -> None: - super().__init__(parent=parent) - #self.setStyleSheet("background-color: white;") - - layout = QGridLayout() - layout.setColumnStretch(0, 2) - layout.setColumnStretch(1, 1) - layout.setColumnStretch(2, 1) - layout.setColumnStretch(3, 1) - layout.setColumnStretch(4, 2) - - layout.setRowStretch(0, 1) - layout.setRowStretch(1, 0) - layout.setRowStretch(2, 1) - layout.setRowStretch(3, 2) - self.setLayout(layout) - - label = QLabel() - label.setPixmap(QPixmap(os.path.join(cnst.ICONS_FOLDER, "nixview_transparent.png"))) - label.setMaximumWidth(300) - label.setAlignment(Qt.AlignCenter) - layout.addWidget(label, 1, 1, 1, 3, Qt.AlignCenter) - - frame = QFrame() - l = QVBoxLayout() - l.addWidget(QLabel("Recently opened files:")) - self._file_list = QListWidget(self) - self._file_list.setSelectionMode(QAbstractItemView.SingleSelection) - self._file_list.itemClicked.connect(self._on_file_clicked) - self._file_list.setFrameShape(QFrame.NoFrame) - self.keyPressed.connect(self._on_key_pressed) - l.addWidget(self._file_list) - frame.setLayout(l) - layout.addWidget(frame, 3, 1, 1, 3) - self._file_map = {} - self._read_recent_files() - - def keyPressEvent(self, event): - super(SplashScreen, self).keyPressEvent(event) - if event.key() == Qt.Key_Return: - self.keyPressed.emit(event.key()) - - def _create_short_filename(self, original, index, max_len=40): - short = original - parts = original.split(os.sep) - if len(parts) == 1: - short = "%i: %s" % (index, short[:max_len]) - else: - post = parts[-1] - if len(post) > max_len - 4: - post = post[:max_len - 4] - short = str("%i: " % index) + "... " + post - else: - short = str("%i: " % index) + " ... ".join([original[:max_len - len(post) - 4], post]) - return short - - def _read_recent_files(self): - settings = QSettings(cnst.organization, cnst.application) - filenames = settings.value(cnst.settings_recent_files_key, []) - del settings - - for i, f in enumerate(filenames): - shortname = self._create_short_filename(f, i + 1, max_len=38) - self._file_map[shortname] = f - item = QListWidgetItem(shortname) - item.setToolTip(f) - self._file_list.addItem(item) - - def reset(self): - self._file_list.clear() - self._read_recent_files() - - def _on_file_clicked(self, item): - comm.communicator.open_recent.emit(self._file_map[item.text()]) - - def _on_key_pressed(self, key): - item = self._file_list.currentItem() - if item is not None and key == Qt.Key_Return: - comm.communicator.open_recent.emit(self._file_map[item.text()]) diff --git a/nixview/ui/filescreen.py b/nixview/ui/filescreen.py new file mode 100644 index 0000000..440c616 --- /dev/null +++ b/nixview/ui/filescreen.py @@ -0,0 +1,141 @@ +from PyQt5.QtWidgets import QComboBox, QFrame, QGroupBox, QHBoxLayout, QLabel, QSplitter, QTextEdit, QVBoxLayout, QWidget +from PyQt5.QtCore import QItemSelectionModel, Qt + +from nixview.file_handler import FileHandler, ItemDescriptor +import nixview.communicator as comm +import nixview.constants as cnst +from nixview.data_models.tree_model import NixTreeView, TreeModel, TreeType + + +class FileScreen(QWidget): + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + self._file_handler = FileHandler() + + vbox = QVBoxLayout() + self.setLayout(vbox) + + main_splitter = QSplitter(Qt.Vertical) + self.layout().addWidget(main_splitter) + + self._info = EntityInfo(self) + main_splitter.addWidget(self._info) + + self._data_tree = NixTreeView(self) + + self._tree_type_combo = QComboBox() + self._tree_type_combo.adjustSize() + self._tree_type_combo.addItems([TreeType.Data.value, TreeType.Full.value, TreeType.Metadata.value]) + self._tree_type_combo.currentTextChanged.connect(self.update) + + hbox = QHBoxLayout() + hbox.addWidget(QLabel("Tree type:")) + hbox.addWidget(self._tree_type_combo) + hbox.addStretch() + data_group = QGroupBox("Data") + data_vbox = QVBoxLayout() + data_vbox.setContentsMargins(1, 10, 1, 1) + + data_vbox.addLayout(hbox) + data_vbox.addWidget(self._data_tree) + data_group.setLayout(data_vbox) + + main_splitter.addWidget(data_group) + main_splitter.setSizes([200, 600]) + vbox.addWidget(main_splitter) + + def dataTreeSelection(self, current_index, last_index): + if not current_index.isValid(): + return + item = current_index.internalPointer() + comm.communicator.item_selected.emit(item) + self._info.setEntityInfo(item.node_descriptor) + + def update(self): + tt = TreeType.Data + if self._tree_type_combo.currentText() == TreeType.Data.value: + tt = TreeType.Data + elif self._tree_type_combo.currentText() == TreeType.Full.value: + tt = TreeType.Full + elif self._tree_type_combo.currentText() == TreeType.Metadata.value: + tt = TreeType.Metadata + self._info.setEntityInfo(None) + data_model = TreeModel(self._file_handler, tt) + self._data_tree.setModel(data_model) + selection_model = QItemSelectionModel(data_model) + self._data_tree.setSelectionModel(selection_model) + selection_model.currentChanged.connect(self.dataTreeSelection) + for i in range(data_model.columnCount(None)): + self._data_tree.resizeColumnToContents(i) + self._info.setFileInfo(self._file_handler.file_descriptor) + + def reset(self): + pass + + +class EntityInfo(QWidget): + + def __init__(self, parent): + super().__init__(parent=parent) + self._file_handler = FileHandler() + self.setLayout(QHBoxLayout()) + + self._metadata_tree = NixTreeView() + + mdata_grp = QGroupBox("Metadata") + mdata_grp.setLayout(QVBoxLayout()) + mdata_grp.layout().setContentsMargins(1, 10, 1, 1) + mdata_grp.layout().addWidget(self._metadata_tree) + + file_info_grp = QGroupBox("File info") + file_info_grp.setLayout(QVBoxLayout()) + file_info_grp.layout().setContentsMargins(1, 10, 1, 1) + self._file_info = QTextEdit("File information") + self._file_info.setEnabled(True) + self._file_info.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse) + self._file_info.setFrameShape(QFrame.NoFrame) + self._file_info.setLineWrapMode(QTextEdit.WidgetWidth) + file_info_grp.layout().addWidget(self._file_info) + + entity_info_grp = QGroupBox("Entity info") + entity_info_grp.setLayout(QVBoxLayout()) + entity_info_grp.layout().setContentsMargins(1, 10, 1, 1) + self._entity_info = QTextEdit("Entity information") + self._file_info.setEnabled(True) + self._file_info.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse) + self._file_info.setFrameShape(QFrame.NoFrame) + self._file_info.setLineWrapMode(QTextEdit.WidgetWidth) + entity_info_grp.layout().addWidget(self._entity_info) + + self._splitter = QSplitter(Qt.Horizontal) + self._splitter.addWidget(file_info_grp) + self._splitter.addWidget(entity_info_grp) + self._splitter.addWidget(mdata_grp) + self._splitter.setSizes([200, 400, 0]) + self._splitter.setStretchFactor(0, 0) + self._splitter.setStretchFactor(1, 1) + self._splitter.setStretchFactor(2, 1) + + self.layout().addWidget(self._splitter) + + + def setFileInfo(self, file_info): + if file_info is not None: + self._file_info.setText(file_info.toHtml()) + + def setEntityInfo(self, entity_info): + if entity_info is None or not isinstance(entity_info, ItemDescriptor): + self._splitter.setSizes([200, 400, 0]) + self._entity_info.setText("") + self._metadata_tree.setModel(None) + return + + if entity_info.metadata_id is not None: + self._splitter.setSizes([200, 400, 400]) + else: + self._splitter.setSizes([200, 400, 0]) + self._entity_info.setText(entity_info.to_html()) + metadata_model = TreeModel(self._file_handler, TreeType.Metadata, root_section_id=entity_info.metadata_id) + self._metadata_tree.setModel(metadata_model) + for i in range(metadata_model.columnCount(None)): + self._metadata_tree.resizeColumnToContents(i) diff --git a/nixview/ui/plot_screen.py b/nixview/ui/plot_screen.py deleted file mode 100644 index 8c5a76b..0000000 --- a/nixview/ui/plot_screen.py +++ /dev/null @@ -1,36 +0,0 @@ -from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QWidget -from PyQt5.QtCore import QObject, pyqtSignal, Qt -import matplotlib -matplotlib.use('Qt5Agg') -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg -from matplotlib.figure import Figure - -class MplCanvas(FigureCanvasQTAgg): - - def __init__(self, parent=None, width=5, height=4, dpi=100): - fig = Figure(figsize=(width, height), dpi=dpi) - self.axes = fig.add_subplot(111) - super(MplCanvas, self).__init__(fig) - - -class PlotScreen(QWidget): - close_signal = pyqtSignal() - - def __init__(self, parent) -> None: - super().__init__(parent=parent) - sc = MplCanvas(self, width=5, height=4, dpi=100) - sc.axes.plot([0,1,2,3,4], [10,1,20,3,40]) - - self.setLayout(QVBoxLayout()) - self.layout().addWidget(sc) - - close_btn = QPushButton("close") - close_btn.clicked.connect(self.on_close) - - self.layout().addWidget(close_btn) - - def on_close(self): - self.close_signal.emit() - - def plot(self, item): - print("plot!", item) \ No newline at end of file diff --git a/nixview/ui/plotscreen.py b/nixview/ui/plotscreen.py new file mode 100644 index 0000000..0bc9197 --- /dev/null +++ b/nixview/ui/plotscreen.py @@ -0,0 +1,343 @@ +from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QWidget +from PyQt5.QtCore import QObject, pyqtSignal, Qt +import matplotlib +matplotlib.use('Qt5Agg') +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg +from matplotlib.figure import Figure + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.widgets import Slider +import nixio as nix + + + + +def create_label(entity): + label = "" + if hasattr(entity, "label"): + label += (entity.label if entity.label is not None else "") + if len(label) == 0 and hasattr(entity, "name"): + label += entity.name + if hasattr(entity, "unit") and entity.unit is not None: + label += " [%s]" % entity.unit + return label + + +class Plotter(object): + + def show(self): + plt.show() + + +class EventPlotter(Plotter): + + def __init__(self, data_array, xdim=-1): + self.array = data_array + self.sc = None + self.dim_count = len(data_array.dimensions) + if xdim == -1: + self.xdim = guess_best_xdim(self.array) + elif xdim > 1: + raise ValueError("EventPlotter: xdim is larger than 2! " + "Cannot plot that kind of data") + else: + self.xdim = xdim + + def plot(self, axis=None): + if axis is None: + self.fig = plt.figure(figsize=[5.5, 2.]) + self.axis = self.fig.add_axes([0.15, .2, 0.8, 0.75]) + self.axis.set_title(self.array.name) + else: + self.fig = axis.figure + self.axis = axis + if len(self.array.dimensions) == 1: + return self.plot_1d() + else: + return None + + def plot_1d(self): + data = self.array[:] + xlabel = create_label(self.array.dimensions[self.xdim]) + dim = self.array.dimensions[self.xdim] + if dim.dimension_type == nix.DimensionType.Range and not dim.is_alias: + ylabel = create_label(self.array) + else: + ylabel = "" + self.sc = self.axis.scatter(data, np.ones(data.shape)) + self.axis.set_ylim([0.5, 1.5]) + self.axis.set_yticks([1.]) + self.axis.set_yticklabels([]) + self.axis.set_xlabel(xlabel) + self.axis.set_ylabel(ylabel) + return self.axis + + +class CategoryPlotter(Plotter): + + def __init__(self, data_array, xdim=-1): + self.array = data_array + self.bars = [] + if xdim == -1: + self.xdim = guess_best_xdim(self.array) + elif xdim > 2: + raise ValueError("CategoryPlotter: xdim is larger than 2! " + "Cannot plot that kind of data") + else: + self.xdim = xdim + + def plot(self, axis=None): + if axis is None: + self.fig = plt.figure() + self.axis = self.fig.add_axes([0.15, .2, 0.8, 0.75]) + self.axis.set_title(self.array.name) + else: + self.fig = axis.figure + self.axis = axis + if len(self.array.dimensions) == 1: + return self.plot_1d() + elif len(self.array.dimensions) == 2: + return self.plot_2d() + else: + return None + + def plot_1d(self): + data = self.array[:] + dim = self.array.dimensions[self.xdim] + if dim.dimension_type == nix.DimensionType.Set: + categories = list(dim.labels) + else: + return None + if categories is None: + categories = ["Cat-%i" % i for i in range(len(data))] + ylabel = create_label(self.array) + if len(categories) == 0: + raise ValueError("Cannot plot a bar chart without any labels") + self.bars.append(self.axis.bar(range(1, len(categories)+1), data, + tick_label=categories)) + self.axis.set_ylabel(ylabel) + return self.axis + + def plot_2d(self): + data = self.array[:] + if self.xdim == 1: + data = data.T + + dim = self.array.dimensions[self.xdim] + if dim.dimension_type == nix.DimensionType.Set: + categories = list(dim.labels) + if len(categories) == 0: + categories = ["Cat-%i" % i for i in range(data.shape[self.xdim])] + + dim = self.array.dimensions[1-self.xdim] + if dim.dimension_type == nix.DimensionType.Set: + series_names = list(dim.labels) + if len(series_names) == 0: + series_names = ["Series-%i" % i + for i in range(data.shape[1-self.xdim])] + + bar_width = 1/data.shape[1] * 0.75 + for i in range(data.shape[1]): + x_values = np.arange(data.shape[0]) + i * bar_width + self.bars.append(self.axis.bar(x_values, data[:, i], + width=bar_width, + align="center")[0]) + self.axis.set_xticks(np.arange(data.shape[0]) + + data.shape[1] * bar_width/2) + self.axis.set_xticklabels(categories) + self.axis.legend(self.bars, series_names, loc=1) + return self.axis + + +class ImagePlotter(Plotter): + + def __init__(self, data_array, xdim=-1): + self.array = data_array + self.image = None + + def plot(self, axis=None): + dim_count = len(self.array.dimensions) + if axis is None: + self.fig = plt.figure() + self.axis = self.fig.add_axes([0.15, .2, 0.8, 0.75]) + self.axis.set_title(self.array.name) + else: + self.fig = axis.figure + self.axis = axis + if dim_count == 2: + return self.plot_2d() + elif dim_count == 3: + return self.plot_3d() + else: + return None + + def plot_2d(self): + data = self.array[:] + x = self.array.dimensions[0].axis(data.shape[0]) + y = self.array.dimensions[1].axis(data.shape[1]) + xlabel = create_label(self.array.dimensions[0]) + ylabel = create_label(self.array.dimensions[1]) + self.image = self.axis.imshow(data, extent=[x[0], x[-1], y[0], y[-1]]) + self.axis.set_xlabel(xlabel) + self.axis.set_ylabel(ylabel) + self.axis.set + return self.axis + + def plot_3d(self): + if self.array.shape[2] > 3: + print("cannot plot 3d data with more than 3 channels " + "in the third dim") + return None + return self.plot_2d() + + +class LinePlotter(Plotter): + + def __init__(self, data_array, xdim=-1): + self.array = data_array + self.lines = [] + self.dim_count = len(data_array.dimensions) + if xdim == -1: + self.xdim = guess_best_xdim(self.array) + elif xdim > 2: + raise ValueError("LinePlotter: xdim is larger than 2! " + "Cannot plot that kind of data") + else: + self.xdim = xdim + self.fig = None + self.axis = None + + def plot(self, axis=None, maxpoints=100000): + self.maxpoints = maxpoints + if axis is None: + self.fig = plt.figure() + self.axis = self.fig.add_axes([0.15, .2, 0.8, 0.75]) + self.axis.set_title(self.array.name) + self.__add_slider() + else: + self.axis = axis + + dim_count = len(self.array.dimensions) + if dim_count > 2: + return + if dim_count == 1: + return self.plot_array_1d() + else: + return self.plot_array_2d() + + def __add_slider(self): + steps = self.array.shape[self.xdim] / self.maxpoints + slider_ax = self.fig.add_axes([0.15, 0.025, 0.8, 0.025]) + self.slider = Slider(slider_ax, 'Slider', 1., steps, valinit=1., + valstep=0.25) + self.slider.on_changed(self.__update) + + def __update(self, val): + if len(self.lines) > 0: + minimum = val * self.maxpoints - self.maxpoints + start = minimum if minimum > 0 else 0 + end = val * self.maxpoints + self.__draw(start, end) + self.fig.canvas.draw_idle() + + def __draw(self, start, end): + if self.dim_count == 1: + self.__draw_1d(start, end) + else: + self.__draw_2d(start, end) + + def __draw_1d(self, start, end): + if start < 0: + start = 0 + if end > self.array.shape[self.xdim]: + end = self.array.shape[self.xdim] + + y = self.array[int(start):int(end)] + dim = self.array.dimensions[self.xdim] + x = np.asarray(dim.axis(len(y), int(start))) + + if len(self.lines) == 0: + l, = self.axis.plot(x, y, label=self.array.name) + self.lines.append(l) + else: + self.lines[0].set_ydata(y) + self.lines[0].set_xdata(x) + + self.axis.set_xlim([x[0], x[-1]]) + + def __draw_2d(self, start, end): + if start < 0: + start = 0 + if end > self.array.shape[self.xdim]: + end = self.array.shape[self.xdim] + + x_dimension = self.array.dimensions[self.xdim] + x = np.asarray(x_dimension.axis(int(end-start), start)) + y_dimension = self.array.dimensions[1-self.xdim] + labels = y_dimension.labels + if len(labels) == 0: + labels = list(map(str, range(self.array.shape[1-self.xdim]))) + + for i, l in enumerate(labels): + if (self.xdim == 0): + y = self.array[int(start):int(end), i] + else: + y = self.array[i, int(start):int(end)] + + if len(self.lines) <= i: + ll, = self.axis.plot(x, y, label=l) + self.lines.append(ll) + else: + self.lines[i].set_ydata(y) + self.lines[i].set_xdata(x) + + self.axis.set_xlim([x[0], x[-1]]) + + def plot_array_1d(self): + self.__draw_1d(0, self.maxpoints) + xlabel = create_label(self.array.dimensions[self.xdim]) + ylabel = create_label(self.array) + self.axis.set_xlabel(xlabel) + self.axis.set_ylabel(ylabel) + return self.axis + + def plot_array_2d(self): + self.__draw_2d(0, self.maxpoints) + xlabel = create_label(self.array.dimensions[self.xdim]) + ylabel = create_label(self.array) + self.axis.set_xlabel(xlabel) + self.axis.set_ylabel(ylabel) + self.axis.legend(loc=1) + return self.axis + + +class MplCanvas(FigureCanvasQTAgg): + + def __init__(self, parent=None, width=5, height=4, dpi=100): + fig = Figure(figsize=(width, height), dpi=dpi) + self.axes = fig.add_subplot(111) + super(MplCanvas, self).__init__(fig) + + +class PlotScreen(QWidget): + close_signal = pyqtSignal() + + def __init__(self, parent) -> None: + super().__init__(parent=parent) + sc = MplCanvas(self, width=5, height=4, dpi=100) + sc.axes.plot([0,1,2,3,4], [10,1,20,3,40]) + + self.setLayout(QVBoxLayout()) + self.layout().addWidget(sc) + + close_btn = QPushButton("close") + close_btn.clicked.connect(self.on_close) + + self.layout().addWidget(close_btn) + + def on_close(self): + self.close_signal.emit() + + def plot(self, item): + print("plot!", item) \ No newline at end of file diff --git a/nixview/ui/splashscreen.py b/nixview/ui/splashscreen.py new file mode 100644 index 0000000..f639f73 --- /dev/null +++ b/nixview/ui/splashscreen.py @@ -0,0 +1,91 @@ +import os +from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel, QFrame, QVBoxLayout, QListWidget, QAbstractItemView, QListWidgetItem +from PyQt5.QtGui import QPixmap +from PyQt5.QtCore import Qt, QSettings, pyqtSignal + +import nixview.communicator as comm +import nixview.constants as cnst + + +class SplashScreen(QWidget): + keyPressed = pyqtSignal(int) + + def __init__(self, parent=None) -> None: + super().__init__(parent=parent) + #self.setStyleSheet("background-color: white;") + + layout = QGridLayout() + layout.setColumnStretch(0, 2) + layout.setColumnStretch(1, 1) + layout.setColumnStretch(2, 1) + layout.setColumnStretch(3, 1) + layout.setColumnStretch(4, 2) + + layout.setRowStretch(0, 1) + layout.setRowStretch(1, 0) + layout.setRowStretch(2, 1) + layout.setRowStretch(3, 2) + self.setLayout(layout) + + label = QLabel() + label.setPixmap(QPixmap(os.path.join(cnst.ICONS_FOLDER, "nixview_transparent.png"))) + label.setMaximumWidth(300) + label.setAlignment(Qt.AlignCenter) + layout.addWidget(label, 1, 1, 1, 3, Qt.AlignCenter) + + frame = QFrame() + l = QVBoxLayout() + l.addWidget(QLabel("Recently opened files:")) + self._file_list = QListWidget(self) + self._file_list.setSelectionMode(QAbstractItemView.SingleSelection) + self._file_list.itemClicked.connect(self._on_file_clicked) + self._file_list.setFrameShape(QFrame.NoFrame) + self.keyPressed.connect(self._on_key_pressed) + l.addWidget(self._file_list) + frame.setLayout(l) + layout.addWidget(frame, 3, 1, 1, 3) + self._file_map = {} + self._read_recent_files() + + def keyPressEvent(self, event): + super(SplashScreen, self).keyPressEvent(event) + if event.key() == Qt.Key_Return: + self.keyPressed.emit(event.key()) + + def _create_short_filename(self, original, index, max_len=40): + short = original + parts = original.split(os.sep) + if len(parts) == 1: + short = "%i: %s" % (index, short[:max_len]) + else: + post = parts[-1] + if len(post) > max_len - 4: + post = post[:max_len - 4] + short = str("%i: " % index) + "... " + post + else: + short = str("%i: " % index) + " ... ".join([original[:max_len - len(post) - 4], post]) + return short + + def _read_recent_files(self): + settings = QSettings(cnst.organization, cnst.application) + filenames = settings.value(cnst.settings_recent_files_key, []) + del settings + + for i, f in enumerate(filenames): + shortname = self._create_short_filename(f, i + 1, max_len=38) + self._file_map[shortname] = f + item = QListWidgetItem(shortname) + item.setToolTip(f) + self._file_list.addItem(item) + + def reset(self): + self._file_list.clear() + self._read_recent_files() + + def _on_file_clicked(self, item): + comm.communicator.open_recent.emit(self._file_map[item.text()]) + + def _on_key_pressed(self, key): + item = self._file_list.currentItem() + if item is not None and key == Qt.Key_Return: + comm.communicator.open_recent.emit(self._file_map[item.text()])